This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
SquidDev's profile picture

Howl - Lua build system

Started by SquidDev, 13 December 2014 - 11:37 AM
SquidDev #1
Posted 13 December 2014 - 12:37 PM
9 months ago I wrote a program called PPI, which gave the ability to combine multiple files into one file. I've been using this for a while, and hand massive issues with it. Recently I've been rethinking this, and how it could be improved.

The result of this is Howl, a Lua build system. Howl is based mostly on Grunt and Rake. It uses 'tasks' , which can depend on other tasks.

FeaturesPlanned
I'm not sure how realistic these are, do say what else you would like below (or on Github).Usage
Firstly you need to download Howl:

pastebin run LYAxmSby get 703e2f46ce68c2ca158673ff0ec4208c/Howl.min.lua Howl

Then you need to define a Howlfile, more on that later.
Then all you need to run is /Howl [task name]. Every directory from the current directory to the root will be checked for the presence of a Howlfile (Howlfile or Howlfile.lua). This will be executed.

Why?
SpoilerI love to write in a modular format. Sometimes you get massive programs (*cough* Firewolf, *cough*) which are almost impossible to edit. The purpose of Howl started off as a way of me combining these files into one. The code runs just as quickly, and it is much easier to write and distribute.

Obviously though, computer space is limited on computers. So minifying sources is key. Howl's minifyer reduces Howl's own filesize by half, which is clearly optimal. I haven't had a chance to run it on OneOS, but I'm sure the results not be dissimilar.

However combining, then minifying files every time is a very slow way of working. Howl does everything with one command instead, streamlining your workflow.

For my AES implementation, Howl automatically runs tests as well, ensuring that everything compiles with the official FIPS test vectors.

Basically, I'm awful at sales spiels: download this. It saves you time and effort (and it would make me happy if someone uses it).

Howlfiles
SpoilerHowlfiles store all tasks and configuration about a project. The main documentation is on Howl's wiki, but I'll give an example here.


-- Define a list of dependencies
local sources = Dependencies(CurrentDirectory)
sources:Main "Awesome.lua"
:Depends "Utils"
:Depends "Submodule"
sources:Main "Utils.lua"
:Name "Utils"
sources:Main "Submodule.lua"
:Name "Submodule"
		:Depends "SubmoduleUtils"
sources:Main "SubmoduleUtils.lua"
:Name "SubmoduleUtils"
-- Define a task to clean up
Tasks:Clean("clean", File "build")
-- Use the build in combiner task
Tasks:Combine("combine", sources, File "build/Result.lua", {"clean"})
-- Define a minifier task, this automatically runs "combine" to generate the "build/Results.lua" file
Tasks:Minify("minify", File "build/Result.lua", "build/Result.min.lua")
Tasks:Task "test"({"minify"}, function()
  print("running some tests")
end):Description("Run some tests")

Tasks:Default("test") -- Set the default task.

You can read more on defining tasks on the Wiki.

Screenshots
SpoilerThe list of tasks

Running a task

Contributing
I'd love ideas, suggestions, feedback, or code! Howl is on GitHub, so do send me a pull request, or start an issue.
Edited on 12 April 2016 - 07:07 PM
SquidDev #2
Posted 20 December 2014 - 07:36 PM
Just pushed an update. This adds support for:
  • Oeed's Compilr. This packages all files into one with an emulated file system, perfect for Bedrock apps.
  • Bootstrap script. Instead of rebuilding your file every time, you can create a bootstrap script. This runs loadfiles in the right order to resolve dependencies.
  • Runs in normal Lua. It will now run in any normal installation of Lua with Penlight installed. This is pretty much standard on most installations of Lua I've found, but you can get it from LuaRocks. Colo(u)rs won't work in DOS terminals, sorry.
ardera #3
Posted 07 January 2015 - 05:17 PM
Great, works perfectly!
SquidDev #4
Posted 12 January 2015 - 07:14 PM
ardera and I have been working hard (thanks a bunch). Version 0.1 has been released! This adds support for:
  • Source verification on combination
  • Traceback and line mapping support
  • Finalizer for errors
  • Busted test framework support
You can see Howl in action on my AES project. I use Howl to both build and test the project, which works wonderfully.
micmou #5
Posted 13 January 2015 - 07:06 AM
Would it be at all possible to build from networked compuetrs? To somehow bypass the file size limit on computers? I am not too familiar with CC just got back into it after a long time so don't be mad if this is a face palm question.
Edited on 13 January 2015 - 06:13 AM
SquidDev #6
Posted 13 January 2015 - 07:44 AM
Would it be at all possible to build from networked compuetrs? To somehow bypass the file size limit on computers? I am not too familiar with CC just got back into it after a long time so don't be mad if this is a face palm question.

I guess it would be possible. This is mostly designed for development, so working in an environment where you can control the max computer storage (emulator or in Minecraft via config). I'm not sure how it would work though, I'll have a think about it though.
micmou #7
Posted 13 January 2015 - 08:23 PM
It could also be used to increase security and realism as well many cool possibilities for servers with A CC economy and security I know is non existent with CC just a cooler and more challenging system
Edited on 13 January 2015 - 07:41 PM
SquidDev #8
Posted 14 January 2015 - 09:30 AM
Would it be at all possible to build from networked compuetrs? To somehow bypass the file size limit on computers? I am not too familiar with CC just got back into it after a long time so don't be mad if this is a face palm question.

Been thinking about this. Building on a networked computer could mean two things:

Deployment
Build on one computer, then automatically send a file to several computers. For instance if you have several computers that run the same program, you could send the file over rednet. This could be defined with something like:

Tasks:Task "deploy"(function()
  -- Read a file and build an object
  local handle = fs.open(File "mycode.lua", "r")
  local file = textutils.serialize({path = "mycode.lua" contents = handle.readAll()})
  handle.close()
  -- For every computer under protocol "mysystem"
  for _, id in pairs({rednet.lookup("mysystem")}) do
    -- Send the file
    rednet.send(id, file)
  end
end)
  :Requires "mycode.lua"

Then you could have on every computer a process that hosts itself on "mysystem" and listens for messages like that. This functionality could be built into the script itself, though I don't recommend that for deployment.

Running Howl deploy would build "mycode.lua" and send it to all computers.

Multiple "build servers"
I'm still not sure of the purpose of this.

It could also be used to increase security and realism as well many cool possibilities for servers with A CC economy and security I know is non existent with CC just a cooler and more challenging system
Realism yes, I don't understand what this has to do with security though. Sorry, slightly confused at the moment.
micmou #9
Posted 15 January 2015 - 04:26 AM
Would it be at all possible to build from networked compuetrs? To somehow bypass the file size limit on computers? I am not too familiar with CC just got back into it after a long time so don't be mad if this is a face palm question.

Been thinking about this. Building on a networked computer could mean two things:

Deployment
Build on one computer, then automatically send a file to several computers. For instance if you have several computers that run the same program, you could send the file over rednet. This could be defined with something like:

Tasks:Task "deploy"(function()
  -- Read a file and build an object
  local handle = fs.open(File "mycode.lua", "r")
  local file = textutils.serialize({path = "mycode.lua" contents = handle.readAll()})
  handle.close()
  -- For every computer under protocol "mysystem"
  for _, id in pairs({rednet.lookup("mysystem")}) do
	-- Send the file
	rednet.send(id, file)
  end
end)
  :Requires "mycode.lua"

Then you could have on every computer a process that hosts itself on "mysystem" and listens for messages like that. This functionality could be built into the script itself, though I don't recommend that for deployment.

Running Howl deploy would build "mycode.lua" and send it to all computers.

Multiple "build servers"
I'm still not sure of the purpose of this.

It could also be used to increase security and realism as well many cool possibilities for servers with A CC economy and security I know is non existent with CC just a cooler and more challenging system
Realism yes, I don't understand what this has to do with security though. Sorry, slightly confused at the moment.

Well many really good security systems for example the one I built at my school query a remote server for like the files to challenge but that is more like databases maybe a community built project as a training thing to demonstrates the concept of networking like in house software companies commonly uses modular building systems to develop software I build networking hooks into main project as a module not likely in CC but still cool to demonstrate the idea easily

EDIT: Sorry If I am hard to understand BTW it is an issue I am working on.
Edited on 15 January 2015 - 03:28 AM
ardera #10
Posted 16 January 2015 - 06:50 PM
I think he means that he wants to use multiple CCPC's as filesystem resources.
FUNCTION MAN! #11
Posted 16 January 2015 - 06:59 PM
I think he means that he wants to use multiple CCPC's as filesystem resources.

IMO that shouldn't be implemented in the core but as a task
SquidDev #12
Posted 16 January 2015 - 07:37 PM
I think he means that he wants to use multiple CCPC's as filesystem resources.

I think you are probably right. I'm guessing what micmou is describing is checking if a file has changed on a remote server and rebuilding it if so. File watching is hard enough without using networked computers (editing files outside CC means you can't even add hooks to the fs API).

IMO that shouldn't be implemented in the core but as a task

micmou, if you really need this I suggest writing something to do this, and executing it with dofile. Howl is currently 60kB, I don't really want to be adding elements to the core without justification. If you do end up implementing it, I would be very interested to see the result.
SquidDev #13
Posted 03 February 2016 - 09:16 PM
Ouch: I've left this for a long time. However it isn't dead - I'm working on it on and off. I've just pushed a new build which adds support for Lua style require statements.

You can add this to your Howlfile:

local files = Files()
  :Add "wild:*.lua"	 -- Use files, wildcards or patterns
  :Remove "test"	   -- Exclude files, some such as .git are removed by default
  :Startup "main.lua" -- Defaults to startup
Tasks:RequireAs(files, "build", "build/build.lua")
  :Link() -- Link to the files instead, similar to the bootstrap task

The produced script will allow individual files to require others, instead of manually defining dependencies.

Other changes include dependency tasks depending on produced files and Lua 5.2 support in the combiner.
Lupus590 #14
Posted 04 February 2016 - 11:52 AM
Any news on an LDoc clone/port? It will make my life a lot easier for developing Hive.
Edited on 04 February 2016 - 10:53 AM
SquidDev #15
Posted 04 February 2016 - 12:10 PM
Any news on an LDoc clone/port? It will make my life a lot easier for developing Hive.

I'm currently working on it. At the moment though I'd recommend running it from normal Lua instead.
Lupus590 #16
Posted 04 February 2016 - 12:18 PM
Got a format for comments so I don't have to add/modify them latter? or is that part not finalised yet?
SquidDev #17
Posted 04 February 2016 - 12:39 PM
Got a format for comments so I don't have to add/modify them latter? or is that part not finalised yet?

I'll just use LDoc's. Howl uses something pretty similar already so feel free to look around in the repo. The general format will be something like:


--- Dumps an object
-- @param object The object to dump
-- @tparam[opt=true] boolean meta Print metatables too
-- @tparam[opt=""] string indent Starting indent level
-- @treturn string The dumped string
local function dump(object, meta, indent)
end

Porting is a big task though due to the massive number of dependencies on Penlight.
SquidDev #18
Posted 11 April 2016 - 04:38 PM
Just released another update! Howl now supports uploading to Gists. This is really useful as it means you can upload your built code from the command line, making sharing your code even easier!


Tasks:gist "upload" (function(spec)
  spec:summary "My awesome project"
  spec:gist "703e2f46ce68c2ca158673ff0ec4208c"
  spec:from "build" {
    include = { "Howl.lua" }
  }
end) :Requires { "build/Howl.lua" }

I'd love to do this for pastebin, but sadly it isn't supported :(/>.
minizbot2012 #19
Posted 12 April 2016 - 02:04 AM
Awesome to see gist support in other programs :)/> (I know its not my API, but, still) I also noticed that you are using tokens, I probably should convert my API over to tokens as well (I don't think user / pass is appropriate for an API for these types of things).
Lupus590 #20
Posted 22 August 2016 - 06:06 PM
Bug report, check your github. Also, I posted you a suggestion regarding user experience.
Edited on 22 August 2016 - 04:07 PM
SquidDev #21
Posted 22 August 2016 - 07:02 PM
Bug report, check your github. Also, I posted you a suggestion regarding user experience.
Thanks! I've fixed/implemented them and updated the Gist: it should be all OK now.
Lupus590 #22
Posted 01 January 2017 - 02:13 AM
Does anyone know how well Howl will handle combining 3 things which all use the same file or the same group of files?

I'm thinking ahead of my project quite a bit here but I may have a dependency tree* like this:
*not sure about the actual name, but that will do

core -> server -> combined
core -> turtle -> combined
core -> client -> combined


edit:never mind, still learning how to use howl
Edited on 02 January 2017 - 12:57 PM
Lupus590 #23
Posted 06 March 2018 - 03:37 PM
I'm writing a program with Howl which contains another program which it needs to 'deploy' onto other computers, I'm thinking of just having Howl bundle the 'child' program into a file (like Howl does normally) and then embed that into the parent program as a string, how would I do this?

Also, I think that various parts of the wiki need updating.
Edited on 06 March 2018 - 04:04 PM
SquidDev #24
Posted 06 March 2018 - 05:04 PM
I'm writing a program with Howl which contains another program which it needs to 'deploy' onto other computers, I'm thinking of just having Howl bundle the 'child' program into a file (like Howl does normally) and then embed that into the parent program as a string, how would I do this?
Your best bet is probably to write a custom task. I think you can do something like this:

local platform = require "howl.platform"
Tasks:addTask "wrap", (function()
  local input = platform.fs.read(File "input.lua")
  local template = platform.fs.read(File "template.lua") -- Could just be a normal string

  platform.fs.write(File "output.lua", template:format(input))
end)
  :requires { "input.lua", "template.lua" }
  :Produces { "output.lua" }

Also, I think that parts of the wiki needs updating.
Oh I'm sure. Howl is (and has been for the last two years) mid-rewrite. As I've spent more time modding and less time actually writing CC programs, I've found myself having less time and inclination to keep Howl updated, meaning the documentation is pretty far behind. It's something I want to do at some point, it's just never been super urgent. Sorry.
Lupus590 #25
Posted 06 March 2018 - 05:31 PM

local platform = require "howl.platform"
Tasks:addTask "wrap", (function()
  local input = platform.fs.read(File "input.lua")
  local template = platform.fs.read(File "template.lua") -- Could just be a normal string

  platform.fs.write(File "output.lua", template:format(input))
end)
  :requires { "input.lua", "template.lua" }
  :Produces { "output.lua" }

I'm running on pre require CC
Edited on 06 March 2018 - 04:31 PM
SquidDev #26
Posted 06 March 2018 - 06:06 PM
I'm running on pre require CC
Howl bundles its own require, which loads from a custom table first, falling back to CC's require (if it exists). Most of this code targets 1.78/1.75 instead.
Lupus590 #27
Posted 06 March 2018 - 07:27 PM
Well i get "howlfile:1: attempt to call nil" when i do `require "local p = howl.platform"`.

running CC 1.79

Edit: tried print(require) and got nil when running howl

Are the install instuctions in OP up to date?

Edit 2: Just built and ran latest release on github still nothing

Going to try master branch

Edit 3: build from master works (with a bit of fiddling with Howls howlfile - upload task breaks without a github key)


Edit 4: so I say in the files now what it depends on (using require like normal Lua) and Howl just figures it out?
Howls howlfile looks alot less like dark magic now.
Edited on 06 March 2018 - 07:32 PM
Lupus590 #28
Posted 07 March 2018 - 10:48 PM
So my "super simple get to learn Howl" project is causing Howl to error.

howlfile.lua

Options:Default "trace"

Tasks:clean()

Tasks:minify "minify" {
	input = "build/out.lua",
	output = "build/out.min.lua",
}

Tasks:require "main" {
	include = "src/*.lua",
	startup = "src/main.lua",
	output = "build/out.lua",
	--api = true,
}

Tasks:Task "build" { "clean", "minify" } :Description "Main build task"

src/dep.lua

local str = "test"

src/main.lua

require "dep.lua"
print(str)
print(dep.str)

Using Howl built from the master branch (like in above post), I'm using the non minified version (for easier debugging)

When running any task it errors with "too long without yielding", lines blamed are 6289, 6282, 6291 with a seemingly random distibution.

Errors with trace:https://imgur.com/a/hRgmc

Edit: Since Howl'sbootstrap seems to work for Howl, I tried hacking that for my project (second image in imgur album)
Edited on 08 March 2018 - 09:15 AM
SquidDev #29
Posted 08 March 2018 - 10:34 AM
When running any task it errors with "too long without yielding", lines blamed are 6289, 6282, 6291 with a seemingly random distibution.
OK, that's really weird - I'm entirely unable to reproduce. I've updated the download with the latest version: can you try with that and confirm it still errors?

With respect to your programs, I'd do things a little differently:

--# Howlfile.lua
Tasks:require "main" (function(task)
  --# Configuring with a function allows us to do a little more, though is more verbose.
  task:output "build/out.lua"
  task:startup "src/main.lua"
  --# Doing `task:include "src/*.lua" will include files as src.dep. This includes them
  --# as dep
  task:from "src" { include = "*.lua" }
end)

--# src/dep.lua
local str = "test"
return { str = str } --# Note the explicit return of variables

--# src/main.lua
local dep = require "dep" --# require returns the above table, instead of injecting into the
print(dep.str)

Yes, I need to document this better.
Edited on 08 March 2018 - 09:34 AM
Lupus590 #30
Posted 08 March 2018 - 11:47 AM
main task works, clean (and thus build) still errors, minify works

Edit: You can see the Howl version I'm using here: https://github.com/lupus590/CC-My-Code-Pack/tree/master/assets/computercraft/lua/rom/Programs
Edited on 08 March 2018 - 10:54 AM
Lupus590 #31
Posted 08 March 2018 - 01:04 PM

local platform = require "howl.platform"
Tasks:addTask "wrap", (function()
  local input = platform.fs.read(File "input.lua")
  local template = platform.fs.read(File "template.lua") -- Could just be a normal string

  platform.fs.write(File "output.lua", template:format(input))
end)
  :requires { "input.lua", "template.lua" }
  :Produces { "output.lua" }

Going back to this, what would template.lua look like?
SquidDev #32
Posted 08 March 2018 - 01:16 PM
main task works, clean (and thus build) still errors, minify works
That's really weird. clean doesn't do anything that the main task doesn't: it just lists all files in the build directory and deletes each one.

Going back to this, what would template.lua look like?
That's up to you: I'm just using string.format in the example code, so you'd so something like:

print("Before the input")
%q --# Input will be embedded here
print("After the input")
However, you can always using string.gsub instead and add some fancy template string. Howl does something similar inside the vfs task (see usage, and template) to substitute some fields.
Lupus590 #33
Posted 08 March 2018 - 01:49 PM
https://imgur.com/SdC9b9Y


Options:Default "trace"
Tasks:clean()
Tasks:minify "minify" {
input = "build/out.lua",
output = "build/out.min.lua",
}
Tasks:require "main" (function(task)
  --# Configuring with a function allows us to do a little more, though is more verbose.
  task:output "build/out.lua"
  task:startup "src/main.lua"
  --# Doing `task:include "src/*.lua" will include files as src.dep. This includes them
  --# as dep
  task:from "src" { include = "*.lua" }
end)
:requires { "output.lua" }
Tasks:require "secondary" (function(task)
  --# Configuring with a function allows us to do a little more, though is more verbose.
  task:output "src/med.lua"
  task:startup "src/main.lua"
  --# Doing `task:include "src/*.lua" will include files as src.dep. This includes them
  --# as dep
  task:from "src" { include = "*.lua" }
end)
:Produces { "med.lua" }

local platform = require "howl.platform"
Tasks:addTask ("wrap", function()
  local input = platform.fs.read(File "med.lua")
  local template = platform.fs.read(File "template.lua") -- Could just be a normal string
  platform.fs.write(File "output.lua", template:format(input))
end)
  :requires { "med.lua" }
  :Produces { "output.lua" }
Tasks:Task "build" { "clean", "minify" } :Description "Main build task"

dep, main, template are as you said to make them

However, you can always using string.gsub instead and add some fancy template string. Howl does something similar inside the vfs task (see usage, and template) to substitute some fields.

I really need to dive into strings more, this is beyond me currently.
SquidDev #34
Posted 08 March 2018 - 04:26 PM
You're declaring the task as producing med.lua, but the actual configuration says task:output "src/med.lua". If you have to specify the :Produces on a built-in task then you're probably got something wrong. This was resulting in reading of med.lua failing (as it wasn't actually created). I've added a check in the file read functions which should ensure you get friendlier error messages should this happen again.

I really need to dive into strings more, this is beyond me currently.
You should! Lua's string processing facilities are pretty simple but still sufficiently powerful that I've rarely needed something else.

By the way, are you still on Gitter (or IRC or something)? I might be able to be more helpful there - I'm all too aware that Howl isn't the easiest thing to work with :(/>.
Lupus590 #35
Posted 08 March 2018 - 08:40 PM
You're declaring the task as producing med.lua, but the actual configuration says task:output "src/med.lua". If you have to specify the :Produces on a built-in task then you're probably got something wrong. This was resulting in reading of med.lua failing (as it wasn't actually created). I've added a check in the file read functions which should ensure you get friendlier error messages should this happen again.

Finally got round to this, worked nicely.
Lupus590 #36
Posted 08 March 2018 - 10:21 PM
FYI, clean task still gets too long without yielding

Edit: requested Howl clean -v https://imgur.com/GDr6lt5
Edited on 08 March 2018 - 09:24 PM
Lupus590 #37
Posted 10 March 2018 - 06:50 PM
This didn't work, what should I be doing instead?

  task:from ("src" = { include = "*.lua", exclude = "template.lua" },
  task:from "intermediary" { include = "*.lua" }

Also didn't work

  task:from {"src" = { include = "*.lua", exclude = "template.lua" }, "intermediary" = { include = "*.lua" }}

  task:from ("src" = { include = "*.lua", exclude = "template.lua" }, "intermediary" = { include = "*.lua" })

  task:from "src" { include = "*.lua", exclude = "template.lua" }
	"intermediary" { include = "*.lua" }

Edit: this worked

  task:from "." { include = {"src/*.lua", "intermediary/*.lua"}, exclude = "src/template.lua" }
Edited on 10 March 2018 - 05:53 PM
Lupus590 #38
Posted 13 March 2018 - 10:32 PM
So the wrap function seems to be adding characters which are breaking everything. I stuffed a print into the wrap task and it printed fine before being put into the file but is broken when reading it out again, this has lead me to belive that the issue is on this line: local output = platform.fs.read(File "intermediary/output.lua")


local platform = require "howl.platform"
Tasks:addTask ("wrap", function()
  local input = platform.fs.read(File "intermediary/med.lua")
  local template = platform.fs.read(File "src/template.lua") -- Could just be a normal string

  -- print(input)
 
  platform.fs.write(File "intermediary/output.lua", template:format(input))
  local output = platform.fs.read(File "intermediary/output.lua")
 
  -- print(output)
 
end)
  :requires { "intermediary/med.lua" }
  :Produces { "intermediary/output.lua" }

Full Code on my google Drive

(Offtopic: Mildly Better Shell - any idea if this will work in a resource pack? and how to do so?)
SquidDev #39
Posted 13 March 2018 - 11:00 PM
So the wrap function seems to be adding characters which are breaking everything. I stuffed a print into the wrap task and it printed fine before being put into the file but is broken when reading it out again
I'll have a chat with you on gitter about this -
(Offtopic: Mildly Better Shell - any idea if this will work in a resource pack? and how to do so?)
Hrmr, this isn't something I've really thought about. It might be possible to copy the .mbs directory into rom somewhere and then launch the startup script from the autorun directory. Alternatively, you could just copy the bin and lib folders to rom/bin and rom/apis respectively and hope everything works!