diff --git a/first/ackbuilder.md b/first/ackbuilder.md new file mode 100644 index 000000000..1b211a49e --- /dev/null +++ b/first/ackbuilder.md @@ -0,0 +1,199 @@ +ackbuilder +========== + +# ackbuilder + +## What is it? + +ackbuilder is a very small build tool inspired by [bazel](https://bazel.io/) +which uses either make or [ninja](https://ninja-build.org/) as a backend. + +It supports fully parallelisable builds (in both make and ninja), as well as +hopefully-robust support for rules which generate more than one output file, +which is something make is very, very bad at. + +It was written because the ACK is a really horribly complex thing to build and +there wasn't anything else. + +## Basic concepts + +Complete example, using the built-in C rules. This should be saved in a file +called `build.lua`: + + cprogram { + name = 'prog', + srcs = { "./*.c" }, + } + +This defines a rule `prog` which, when built, compiles all the source files in +the same directory as the `build.lua` file into an executable. + +Slightly more complex example: + + clibrary { + name = "library", + srcs = { "./library.c" }, + hdrs = { "./library.h" }, + } + + cprogram { + name = 'prog2', + srcs = { "./prog2.c" }, + deps = { "+library" } + } + +If we move the library into another directory, we can invoke it like this: + + cprogram { + name = 'prog3', + srcs = { "./prog3.c" }, + deps = { "path/to/library+library" } + } + +* Targets starting with `./` are relative to **the current directory** (i.e. + the one the build file is in). + +* Targets starting with a path are relative to the top directory of the + project. + +* Targets containing a `+` refer to a named target in another build file. So, + on encountering the library in `prog3` above, ackbuilder will look for + `path/to/library/build.lua`, load it, and then try to find a target in it + called `library`. + +**Warning**: files are interpreted from top to bottom; every time a target +referring to another build file is seen for the first time, that file is +interpreted then and there. You can't have circular dependencies (these are +caught and an error is generated). You can't refer to a target defined below +you in the same source file (these are not caught, and just won't be found). + +The `cprogram` and `clibrary` rules, by the way, are sophisticated enough to +automatically handle library and header paths. The exported headers by the +library are automatically imported into the program. + +## `simplerule` and `normalrule` + +These are the building blocks out of which all other rules are made. If you +want to run your own programs, you will be using these. + +`simplerule` is the simplest. You give it inputs, and outputs, and commands, +and it does it. + + simplerule { + name = 'sorted-input', + ins = { './input.txt' }, + outs = { './output.txt' }, + commands = { + "sort < %{ins} > %{outs}" + } + } + +In a command block, `%{...}` will evaluate the Lua expression between the +braces; various useful things are in scope, including the list of inputs and +outputs. + +However, this ends up leaving the output file lying around in the project +directory, which we don't want, so we usually use `normalrule` instead. +(`normalrule` is not strictly part of the ackbuilder core; it's in the standard +library along with `cprogram` and `clibrary`.) + + normalrule { + name = 'sorted-input', + ins = { './input.txt' }, + outleaves = { 'output.txt' }, + commands = { + "sort < %{ins} > %{outs}" + } + } + +Note `outleaves`; there is no `./`. This is a list of leaf filenames. The rule +will create a directory in the object tree and put the files specified in it, +somewhere; you don't care where. You can refer to the output file via the +target name, so: + + normalrule { + name = 'reversed', + ins = { '+sorted-input' }, + outleaves = { 'reversed.txt' }, + commands = { + "rev < %{ins} > %{outs}" + } + } + +One common use for this is to generate C header or source files. + + normalrule { + name = 'reversed_h', + ins = { '+reversed' }, + outleaves = { 'reversed.h' }, + commands = { + 'xxd -i %{ins} > %{outs}' + } + } + + cprogram { + name = 'prog', + srcs = { './*.c' }, + deps = { '+reversed_h' } + } + +Now you can refer to `reversed.h` in one of your C files and it'll just work +(`+reversed_h`'s output directory gets added to the include path +automatically). + +## Defining your own rules + +Like this: + + definerule("sort", + { + srcs = { type="targets" }, + }, + function(e) + return normalrule { + name = e.name, + ins = e.srcs, + outleaves = { 'sorted.txt' }, + commands = { + "sort < %{ins} > %{outs}" + } + } + } + ) + + sort { + name = 'sorted', + srcs = { './input.txt' } + } + +You give `definerule()` the name of the rule you want to define, a description +of the properties the rule will take, and a callback that does the work. + +You can do anything you like in the callback, including defining as many +targets as you like; but remember that all targets must have unique names, so +for any temporary files you probably want something like `name = +e.name.."/intermediate"` to ensure uniqueness. + +The callback should end by returning an invocation of another rule, with `name += e.name` as above. + +Rules are defined whenever a build file containing them is seen. Letting this +happen automatically doesn't always work so you probably want to explicitly +include it: + + include("foo/bar/baz/build.lua") + +Rule properties are typed and can be specified to be required or optional (or have a default value). + + definerule("sort", + { + srcs = { type="targets" }, + numeric = { type="boolean", default=false } + } + ...omitted... + +The most common one is `targets`. When the rule is invoked, ackbuilder will +resolve these for you so that when your callback fires, the property is a +flattened list of target objects. + +