Skip to content

Core reference

The target types, configuration properties, and flags that ship in fabr’s core (STD.fabr) and are always available, independent of any language plugin. Ecosystem-specific definitions (like js_package) come from plugins — see the JavaScript reference.

This page is generated from fabr list-all, so it never drifts from the code.

Default: build

The current operation. Standard values are

  • build = standard compile/link/package
  • files = return the raw contents of the built target
  • run = create a direct executable
  • test = execute tests

Default: debug

Build type: standard values are one of

  • debug = no optimization, full debugging info (default)
  • relwithdebinfo = full optimization but still debuggable
  • release = full optimization, all debug info stripped

Default: ${HOST}

The platform we are building for, as a clang/LLVM triple (e.g. arm64-apple-macosx15.0, x86_64-linux-gnu). Defaults to HOST — the machine fabr is running on — and is what dependency/native selection gates on; override it (-D TARGET=… or ref<TARGET=…>) to cross-compile.

Default: check

What a test run does when its actual output diverges from an expectation it has on record (a snapshot entry, a golden expected-file):

  • check = fail, reporting the difference (default)
  • update = rewrite the record, and offer the new content back to the source tree. fabr test -u is sugar for setting this. Updates are offered, never applied by the build: an action is hermetic and cannot write to your tree, so the driver applies them, only on request, only after an otherwise-green run.
targetdef catalog {
deps = FILES;
}

A catalog pins a fixed set of external requirements in one place. Its deps are resolved jointly and once (a single version selection over the whole set), and each resolved package is exposed by its package name: reference it as <catalog>:<package> wherever a dependency is wanted, with the version fixed here rather than repeated at each use. Because the whole set resolves together, every consumer sees one consistent version of everything the catalog covers, and does no resolution of its own.

Operations: build

targetdef generate {
srcs = FILES;
run = REQUIRED COMMAND;
output = STRING;
}

Run a command pipeline and collect its output — the codegen/genrule builder (a build step, distinct from the interactive fabr run verb). run is a command line: cmd args... [redirs] | cmd args... [redirs]. Each command is a fabr runnable (a target/tool, mounted in its own sandbox dir); args are literal or a glob expanded over srcs. | pipes stdout to the next stage’s stdin; > name / 2> name / &> name capture stdout / stderr / both as content named name; < source streams a single-file reference to the first stage’s stdin. srcs are input files staged at the sandbox root; output (a dir:glob) collects files the tools wrote, unioned with the redirect captures (omit it for a pure redirect genrule). Example:

generate docs { run = list_targetdefs --json | gendoc > reference.md; }

Operations: run

targetdef script {
deps = FILES;
entry = REQUIRED FILES;
args = STRING;
}

Define a runnable plain shell script (run it with fabr run, collect its output with the generic generate target, or check it with a golden test). entry is the script FILE itself — it contributes the file to the install (at its resolved name) and launches it with the shell; deps stage any further support files (their filesets union flat — just files, no package mounting); args are fixed leading arguments. It yields a runnable, it does not itself execute.

Operations: run

targetdef serve {
tool = REQUIRED FILES;
deps = FILES;
args = STRING;
files = FILES;
}

Define a served program: a long-lived server plus the content it serves. tool names the server — any runnable (a script/js_script target, or an external package run via its bin); deps union additional support files into the install; files are the served content, staged at the install root; args are fixed leading arguments. The program launches with its working directory at the staged install (its content is its world — unlike a plain script under fabr run, which runs in the caller’s directory), so e.g. a stock static server serves files with no path argument. Under fabr run -w, a change to files is synced into the running server’s staging area in place; only a change to the program itself (tool, deps, args) restarts it. It yields a runnable, it does not itself execute.

Operations: build

targetdef sync {
* = FILES;
}

A sync publishes built content to one or more destination coordinates. Its body is a set of members written as <destination>:<address> = content (the coordinate is the key, the identityless built content the value); the address’s shape is the destination’s — an npm registry’s is <name>:<version>, e.g.

sync release { @npm:@fabr/core:0.1.0 = core; @npm:@fabr/cli:0.1.0 = cli; }

Each destination packages its members jointly with the whole release as context (npm rewrites a member’s manifest dependency on another member to that member’s assigned version). Building the sync target produces the wire artifacts (a dry-run); fabr sync uploads them, deps-first. The members are reference-keyed bindings rather than fixed schema properties; * = FILES types them — any coordinate key maps to a FILES (content) value.

targetdef fetch {
* = STRING;
}

A fetch repository is a declared table of downloads: each member names a URL and the integrity digest its content must match (<algorithm>-<base64>, told apart from the URL by shape, so the two may be written in either order).

fetch @dl { package.tgz = "https://…/<sha>" "sha256-…"; }

The digest is required: a URL promises nothing about staying the same, so the declaration is where immutability is claimed and it is checked on every fetch, before anything is cached. A member is downloaded only when something names it. @dl denotes the whole set and each member is a file in it, so @dl:name is the downloaded file and @dl:name:… projects into it — including into an archive — exactly as a local file of that name would.

targetdef repository_group {
* = FILES;
}

A repository_group serves one package namespace from several registries: every member is a route — a literal package-name prefix mapped to a declared registry — and references written against the group resolve together, in one joint version selection, with each package fetched from the registry its name routes to. This is how a private registry serving one scope coexists with a public registry serving everything else:

repository_group @deps {
@fortawesome/* = @fa;
* = @npm;
}

A key is a literal name (lodash), a literal with one trailing * (@fortawesome/*, acme-*), or the bare * — the empty prefix, which is not a special entry: the longest matching key always wins (an exact name beating its own name* twin), so exceptions are written as longer prefixes and declaration order carries no meaning. A name matching no route is simply not served (there is deliberately no scan or fall-through across registries — what resolves must never depend on what a registry happens to be missing). All routed registries must speak the same package ecosystem.

A route’s value may instead be a file set, which is interpreted as a package of the same kind as the rest of the repository group. In this case, the key must be the full name of the package.

Operations: *

targetdef flag {
provides = FILES;
}

A flag yields file content directly, with no build step. Its main use is as a named switch that a target lists in its deps to opt into some behaviour (the ts/* source-mode flags are instances of this type); provides may also expose a computed set of files as the flag’s result.