👨‍💻 about me home CV/Resume News 🖊️ Contact Github LinkedIn I’m a Haskeller 🏆 Best of LuaX hey bang ypp panda lsvg Fizzbuzz Calculadoira TPG todo pwd rrpi

Bang

Bang

Bang is a Ninja file generator scriptable in LuaX.

Installation

Bang is written in LuaX. It can be compiled with Ninja and LuaX.

LuaX

$ git clone https://github.com/CDSoft/luax
$ ninja -C luax install  # this should install LuaX to ~/.local/bin

Bang

$ git clone https://github.com/CDSoft/bang
$ ninja -C bang install  # this should compile bang with Ninja and install it to ~/.local/bin

or set $PREFIX to install bang to a custom directory ($PREFIX/bin):

$ PREFIX=/path ninja install # installs bang to /path/bin

Pure Lua implementation

Bang also comes with a pure Lua implementation for environments where LuaX can not be executed. In this case $PREFIX/bin/bang.lua can be executed with any standard Lua 5.4 interpreter.

Warning: bang.lua is insanely slower than bang, especially when dealing with a large amount of source files.

Precompiled binaries

In case precompiled binaries are needed (GNU/Linux, MacOS, Windows), some can be found at cdelord.fr/hey. These archives contain bang as well as some other softwares more or less related to LuaX.

Warning: There are Linux binaries linked with musl and glibc. The musl binaries are platform independent but can not load shared libraries. The glibc binaries can load shared libraries but may depend on some specific glibc versions on the host.

Usage

$ bang -h
Usage: bang [-h] [-v] [-q] [-g cmd] [-o output] [<input>]

Ninja file generator

Arguments after "--" are given to the input script

Arguments:
   input                 Lua script (default: build.lua)

Options:
   -h, --help            Show this help message and exit.
   -v                    Print Bang version
   -q                    Quiet mode (no output on stdout)
   -g cmd                Set a custom command for the generator rule
   -o output             Output file (default: build.ninja)

For more information, see https://github.com/CDSoft/bang

Ninja functions

Comments

bang can add comments to the Ninja file:

comment "This is a comment added to the Ninja file"

section adds comments separated by horizontal lines:

section [[
A large title
that can run on several lines
]]

Variables

var adds a new variable definition:

var "varname" "string value"
var "varname" (number)
var "varname" {"word_1", "word_2", ...} -- will produce `varname = word_1 word_2 ...`

var returns the name of the variable (prefixed with "$").

The global variable vars is a table containing a copy of all the Ninja variables defined by the var function. vars has a function vars.expand that takes a string and expands all Ninja variables (i.e. prefixed with "$").

var "foo" "xyz"
...
vars.foo    -- "xyz"
vars["foo"] -- same as vars.foo
vars.expand "$foo/bar" -- "xyz/bar"

Ninja required version

The special variable ninja_required_version shall be set by the ninja_required_version function. ninja_required_version will change the default required version only if the script requires a higher version.

ninja_required_version "1.42"

Rules

rule adds a new rule definition:

rule "rule_name" {
    description = "...",
    command = "...",
    -- ...
}

Variable values can be strings or lists of strings. Lists of strings are flattened and concatenated (separated with spaces).

Rules can defined variables (see Rule variables).

Bang allows some build statement variables to be defined at the rule level:

These variables are added at the beginning of the corresponding variables in the build statements that use this rule.

The rule function returns the name of the rule ("rule_name").

Build statements

build adds a new build statement:

build "outputs" { "rule_name", "inputs" }

generates the build statement build outputs: rule_name inputs. The first word of the input list (rule_name) shall be the rule name applied by the build statement.

The build statement can be added some variable definitions in the inputs table:

build "outputs" { "rule_name", "inputs",
    varname = "value",
    -- ...
}

There are reserved variable names for bang to specify implicit inputs and outputs, dependency orders and validation statements:

build "outputs" { "rule_name", "inputs",
    implicit_out = "implicit outputs",
    implicit_in = "implicit inputs",
    order_only_deps = "order-only dependencies",
    validations = "build statements used as validations",
    -- ...
}

The build function returns the outputs ("outputs"), as a string if outputs contains a single output or a list of string otherwise.

Rules embedded in build statements

Some rules are specific to a single output and are used once. This leads to write pairs of rules and build statements.

Bang can merge rules and build statements into a single build statement containing the definition of the associated rule.

A build statement with a command variable is split into two parts:

  1. a rule with all rule variables found in the build statement definition
  2. a build statement with the remaining variables

In this case, the build statement definition does not contain any rule name.

E.g.:

build "output" { "inputs",
    command = "...",
}

is internally translated into:

rule "output" {
    command = "...",
}

build "output" { "output", "inputs" }

Note: the rule name is the output name where special characters are replaced with underscores.

Pools

pool adds a pool definition:

pool "name" {
    depth = pool_depth
}

The pool function returns the name of the pool ("pool_name").

Default targets

default adds targets to the default target:

default "target1"
default {"target2", "target3"}

Note: if no custom target is defined and if there are help, install or clean targets, bang will generate an explicit default target with all targets, except from help, install and clean targets.

Phony targets

phony is a shortcut to build that uses the phony rule:

phony "all" {"target1", "target2"}
-- same as
build "all" {"phony", "target1", "target2"}

Bang variables

Bang arguments

The command line arguments of bang are stored in a global table named bang. This table contains:

Build script arguments

Arguments after “–” are given to the input script in the global arg table.

Bang functions

Accumulations

Bang can accumulate names (rules, targets, …) in a list that can later be used to define other rules or build statements.

A standard way to do this in Lua would use a Lua table and table.concat or the list[#list+1] pattern. Bang provides a simple function to simplify this usage:

my_list = {}
-- ...
acc(my_list) "item1"
acc(my_list) {"item2", "item3"}
--...
my_list -- contains {"item1", "item2", "item3"}

Case expressions

The case function provides a switch-like structure to simplify conditional expressions. case is a curried function that takes a value (generally a string) and a table. It searches for the value in the keys of the table and returns the associated value. If the key is not found it returns the value associated to the F.Nil key or nil.

E.g.:

local cflags = {
    case(mode) {
        debug   = "-g -Og",
        release = "-s -O3",
        [F.Nil] = {},
    }
}

File listing

The ls function lists files in a directory. It returns a list of filenames, with the metatable of LuaX F lists.

E.g.:

ls "doc/*.md"
: foreach(function(doc)
    build (doc:splitext()..".pdf") { "md_to_pdf", doc }
end)
-- where md_to_pdf is a rule to convert Markdown files to PDF

Dynamic file creation

The file function creates new files. It returns a callable object to add text to a file (note that the write method is deprecated). The file is actually written when bang exits successfully.

file "name" "content"

The file can be generated incrementally by calling the file object several times:

f = file "name"
-- ...
f "Line 1\n"
-- ...
f "Line 2\n"
-- ...

Pipes

It is common in Makefiles to write commands with pipes. But pipes can be error prone since only the failure of the last process is captured by default. A simple solution (for Makefiles or Ninja files) is to chain several rules.

The pipe function takes a list of rules and returns a function that applies all the rules, in the order of the list. This function takes two parameters: the output and the inputs of the pipe.

Intermediate outputs are stored in $builddir/pipe. If a rule name contains a dot, its « extension » is used to name intermediate outputs.

E.g.:

rule "ypp.md"     { command = "ypp $in -o $out" }
rule "panda.html" { command = "panda $in -o $out", implicit_in = "foo.css" }

local ypp_then_panda = pipe { "ypp.md", "panda.md" }

ypp_then_panda "$builddir/doc/mydoc.html" "doc/mydoc.md"

is equivalent to:

build "$builddir/pipe/doc/mydoc.md" { "ypp.md", "doc/mydoc.md" }
build "$builddir/doc/mydoc.html"    { "panda.html", "$builddir/pipe/doc/mydoc.md" }

Since rule returns the name of the rule, this can also be written as:

local ypp_then_panda = pipe {
    rule "ypp.md"     { command = "ypp $in -o $out" },
    rule "panda.html" { command = "panda $in -o $out", implicit_in = "foo.css" },
}

The input list can contain variable definitions. These variables are added to all build statements, except for implicit_in and implicit_out that are added respectively to the first and last build statements only.

e.g.:

ypp_then_panda "out.html" { "in.md",
    implicit_in = "foo.in",
    implicit_out = "foo.out",
    other_var = "42",
}

is equivalent to:

build "$builddir/pipe/doc/out-1.md" { "ypp.md", "doc/in.md",
    implicit_in = "foo.in",
    other_var = "42",
}
build "out.html" { "panda.html", "$builddir/pipe/doc/out-1.md",
    implicit_out = "foo.out",
    other_var = "42",
}

Clean

Bang can generate targets to clean the generated files. The clean function takes a directory name that shall be deleted by ninja clean.

clean "$builddir"   -- `ninja clean` cleans $builddir
clean "tmp/foo"     -- `ninja clean` cleans /tmp/foo

clean defines the target clean (run by ninja clean) and a line in the help message (see ninja help).

In the same vein, clean.mrproper takes directories to clean with ninja mrproper.

Install

Bang can generate targets to install files outside the build directories. The install function adds targets to be installed with ninja install

The default installation prefix can be set by install.prefix:

install.prefix "$$HOME/foo/bar"     -- `ninja install` installs to ~/foo/bar

The default prefix in ~/.local.

It can be overridden by the PREFIX environment variable when calling Ninja. E.g.:

$ PREFIX=~/bar/foo ninja install

Artifacts are added to the list of files to be installed by the function install. This function takes the name of the destination directory, relative to the prefix and the file to be installed.

install "bin" "$builddir/bang" -- installs bang to $prefix/bin/

install defines the target install (run by ninja install) and a line in the help message (see ninja help).

Help

Bang can generate an help message (stored in a file next to the Ninja file) displayed by ninja help.

The help message is composed of three parts:

The description and epilog are defined by the help.description and help.epilog functions. Targets can be added by the help function. It takes the name of a target and its description.

help.description "A super useful Ninja file"
help.epilog "See https://cdelord.fr/bang"
-- ...
help "compile" "Compile every thing"
-- ...

Note: the clean and install target are automatically documented by the clean and install functions.

Generator

Bang generates a generator rule to update the Ninja file when the build description changes. This behaviour can be customized or disabled with the generator function:

The generator rule runs bang with the same options than the initial bang command.

E.g.:

generator {
    implicit_in = { "foo", "bar" },
}

In this example, the generator statement will be executed if the Lua script has changed as well as foo and bar.

Target

The target function is a simple helper function to select a luaxc compilation target from the command line. It takes a target name or a list of parameters potentially containing target names and returns the target description (an entry in sys.targets) and the remaining arguments.

E.g.:

local target, args = target(arg)
if #args > 0 then
    error(args:unwords()..": unexpected arguments")
end

--[[
if arg contains "linux-x86_64"
then target is {
     name="linux-x86_64",
     os="linux",
     arch="x86_64",
     libc="gnu",
     exe="",     -- executable extension (.exe on Windows)
     so=".so",   -- shared library extension (.dylib on MacOS, .dll on Windows)
}
--]]

-- e.g. to build a LuaX application in a specific directory:

var "builddir" (".build"/(target and target.name))

Examples

The Ninja file of bang (build.ninja) is generated by bang from build.lua.

The example directory contains a larger example:

License

This file is part of bang.

bang is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

bang is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with bang.  If not, see <https://www.gnu.org/licenses/>.

For further information about bang you can visit
https://cdelord.fr/bang