Introduction

Fabricate is a Lua-configured build system that produces Ninja build files. The fabricate CLI evaluates your fab.lua configuration, downloads declared git dependencies, records build graph information, and writes an output/build.ninja file (and optionally a compile_commands.json). After fabricate setup runs you invoke Ninja to actually build your project.

This guide documents how to use Fabricate: the CLI options, how to structure a fab.lua, and the helper functions and object types that Fabricate makes available to Lua. Development internals, contributing guidelines, and other non-user topics are intentionally omitted.

Getting Started

  1. Install the fabricate binary (Cargo cargo install fabricate2 if you are building from source, or copy the compiled binary into your $PATH).
  2. Create a working directory that contains a fab.lua configuration file. All paths referenced by the configuration are interpreted relative to this directory.
  3. Run fabricate setup to generate the build directory. This will create the Ninja build file, compile commands, a fabricate cache file, ...
  4. Build either with Ninja directly or using Fabricates wrapper. The default build directory is build.
    4.1. with Ninja: ninja -C <build dir>.
    4.2. with Fabricate: fabricate --build-dir <build dir>.
  5. Finally install artifacts with fabricate install.

Command-Line Interface

The fabricate binary exposes three subcommands: setup, build, and install. All commands share the --build-dir (-b) flag that chooses which build directory to operate on. If omitted, the build directory defaults to build.

fabricate [GLOBAL OPTIONS] <SUBCOMMAND> [OPTIONS]
Global flagDefaultDescription
-b, --build-dir <path>buildDirectory that stores build.ninja, cached metadata, and intermediate outputs.
-h, --helpShow help for the selected command.
-V, --versionShow the Fabricate version.

setup

Evaluates the Lua configuration and writes/updates build.ninja.

FlagDefault (per code)Description
--config <path>fab.luaLua configuration file to execute.
--prefix <path>fab.luaInstallation prefix recorded in fabricate_cache.toml. (The help text mentions /usr, but the current build sets the default to fab.lua.)
-o, --option key=valueCollects user-defined options that Lua can read via fab.option. Repeat the flag for each key/value pair.
--dependency-override name=pathOverrides the git dependency declared via fab.git(name, …) to use an existing checkout at path instead of cloning into the build directory. Repeat as needed.

Example:

fabricate setup \
    --config fab.lua \
    --prefix /usr \
    --build-dir build \
    --option toolchain=clang \
    --option enable-tests=yes

Dependency overrides let you substitute local checkouts for remote git dependencies during setup. Each override uses the dependency name (the first argument passed to fab.git) and either an absolute path or a path relative to the directory that contains fab.lua. When present, Fabricate records the dependency metadata but returns the provided path to Lua, so rules can consume your locally modified sources without triggering network fetches.

If Ninja is installed, setup also invokes ninja -t cleandead inside the existing build directory before rewriting the graph.

build

Runs Ninja in the selected build directory. This is identical to running ninja -C <build-dir>.

install

Copies all artifacts listed in the install map. This subcommand fails if the cache is missing, or if any artifact is absent. Note that Fabricate does not allow for installation of directory artifacts.

FlagDefaultDescription
--dest-dir <path>(empty string)Optional DESTDIR-style prefix prepended to each install destination.

Example:

fabricate --build-dir build install --dest-dir /tmp/sysroot

Install computes each destination as DESTDIR + prefix + dest path where prefix comes from the last setup invocation and dest path is the install map key (such as bin/foo). Before copying a file Fabricate creates the parent directories.

Configuration File

Fabricate evaluates a single Lua file (by default fab.lua). The configuration is written in Lua (Lua 5.4). Many of the standard lua functions and libraries are available. Note that the lua environment is NOT sandboxed meaning a build script can run arbitrary commands even at setup time.

Install Artifacts

Anywhere in the root scope of fab.lua return a table. Fabricate currently reads the optional install field to discover which artifacts should be copied during an installation step. Destination paths are interpreted relative to the prefix. The field must be a table mapping destination paths to the Artifact objects produced earlier:

Example:

return {
    install = {
        ["bin/fabricate-example"] = app_artifact,
        ["lib/libexample.a"] = static_lib,
    }
}

If you do not want Fabricate to manage installation simply return a table without the install key or omit the return entirely.

Fab Library

The global fab table exposes functions implemented by Fabricate. These functions are the authorative way of interacting with Fabricate but are also very crude. This is why Fabricate provides many helpers written in Lua for a more user friendly interface.

fab.glob(..., opts?)

Runs a set of globs relative to a given directory (project root by default) and returns a list of matches. All globs given must match. Options can be given by passing a table as the last argument, valid options are:

FieldTypeDescription
case_sensitivebooleanOverride the default case-sensitive behavior.
require_literal_separatorbooleanIf true, * and ? will never match s/. This is false by default.
relative_tostringThe directory relative to which globs will run. Project root by default.
-- collect all C sources outside the tests directory
local c_files = fab.glob("src/**/*.c", {
    case_sensitive = false,
    excludes = { "src/tests/**" }
})

fab.project_dir()

Returns an absolute path to the project root.

fab.build_dir()

Returns an absolute path to the build directory.

fab.path_join(...)

Joins the provided path fragments using the host platform’s separator and returns the joined string. If a component is absolute, it replaces the entire path.

fab.path_rel(path)

Resolves a build directory relative path from an absolute path or a project root relative path.

fab.which(name)

Find an executable binary’s path by name. Returns an absolute path when the binary is found or nil otherwise.

  • If given an absolute path, returns it if the file exists and is executable.
  • If given a relative path, returns an absolute path to the file if it exists and is executable.
  • If given a string without path separators, looks for a file named binary_name at each directory in $PATH and if it finds an executable file there, returns it.

fab.option(name, type, required)

Declares a user option that can be provided on the CLI via --option name=value. The type argument controls validation:

  • "string", "number", or "boolean" expect the corresponding type and transform the CLI string automatically.
  • A table of allowed strings works as an enum (Fabricate checks that the CLI value matches one of the table entries and returns the matching value).

If required is false or omitted the option may be omitted and nil is returned. Otherwise Fabricate raises a setup-time error if the option is missing.

local selected_cc = fab.option("toolchain", { "gcc", "clang" })

fab.git(name, url, revision)

Clones (or reuses) a git repository into the build directory and returns an Artifact pointing at the repository directory. The clone is skipped when the cache already contains a matching URL and revision.

fab.def_source(path)

Declares a source file relative to the project root, returning a Source. Fabricate validates that the path stays inside the source tree. Note that Sources must exist at setup-time whereas Artifacts might not.

fab.def_rule(name, command, description?, depstyle?, build_compdb?)

Creates a rule object. Arguments:

  • name: must be unique, contain only alphanumeric characters plus _ or -, and must not start with fab_.
  • command: shell command template. Allows for "embed variables", the embeds take the following form: @EMBED@. The names of the embeds are case-insensitive. They are replaced by values passed at each invocation of a rule build. Fabricate supports a few special embeds:
    NameDescription
    @IN@Source file path(s)
    @OUT@Output file path
    @DEPFILE@Dependency file path
  • description: optional description displayed by Ninja at build time.
  • depstyle: one of "normal", "gcc", "clang", or "msvc" and controls how dependency files are interpreted. If unsure, set to "normal".
  • build_compdb: set to true to include builds using this rule when generating compile_commands.json.

The returned Rule object exposes the rule:build(...) method documented in the Rules chapter.

fab.typeof(userdata)

A helper that inspects an arbitrary userdata value and returns "source", "rule", "artifact", or "unknown".

Object Reference

Fabricate injects several userdata types into Lua. These behave like opaque objects with fields/methods. Fabricate uses them to collect build graph information.

Source

FieldTypeDescription
pathstringPath relative to the build directory.

Represents an input file inside the project root. Must exist at setup time.

Artifact

FieldTypeDescription
pathstringPath relative to the build directory.

Represents a build artifact produced during at build time.

Rule

FieldTypeDescription
namestringName of the rule.

A rule defines how to run a command. Besides the name field, the important API is rule:build(output, inputs, variables, implicit_inputs?):

  • output: Unique name of the artifact output by this build rule. This ends up as the special @OUT@ variable.
  • inputs: Array of Source or Artifact objects that the build depends on. These also end up in the special @IN@ variable.
  • variables: Table containing custom @VAR@ values declared when the rule was defined.
  • implicit_inputs: Optional additional sources/artifacts that should be wired as implicit dependencies (dependend on but not directly used).

The method returns an Artifact describing the produced file.