Artifacts, Modules, and Generated Files
The build graph tracks three kinds of compilable or producible outputs: artifacts, modules, and generated files.
Artifacts
Artifacts are the primary compiled outputs of a package.
| Kind | Method | Output |
|---|---|---|
| Executable | graph.add_exe | Binary |
| Static library | graph.add_static_lib | .a / .lib |
| Shared library | graph.add_shared_lib | .so / .dylib |
| Test bundle | graph.add_test | Runnable test binary |
All artifact constructors accept the same base config record:
var app = graph.add_exe({
name = "app", // required: output name
root = "src/main.fol", // required: entry-point source file
target = target, // optional: Target handle
optimize = optimize, // optional: Optimize handle
});
name must be lowercase. Allowed characters: a-z, 0-9, -, _, ..
Artifact Name Validation
The build system validates artifact names at evaluation time. Invalid names (uppercase, spaces, special characters) produce a build error.
Linking
Static and shared libraries can be linked into executables using
artifact.link(dep):
var core = graph.add_static_lib({ name = "core", root = "src/core/lib.fol" });
var app = graph.add_exe({ name = "app", root = "src/main.fol" });
app.link(core);
Typed system-library requests can be linked the same way:
var ssl = graph.add_system_lib({ name = "ssl", mode = "dynamic" });
app.link(ssl);
Framework-style requests use the same surface:
var metal = graph.add_system_lib({
name = "Metal",
framework = true,
});
app.link(metal);
Linking is transitive through the graph. If core itself links utils, app
will also see utils.
Installation
Any artifact can be marked for installation:
graph.install(app);
graph.install(core);
Files and directories can also be installed directly:
var defaults = graph.file_from_root("config/defaults.toml");
var assets = graph.dir_from_root("assets");
graph.install_file({ name = "defaults", source = defaults });
graph.install_dir({ name = "assets", source = assets });
Modules
Modules are named source units that can be shared across artifacts without being standalone binaries.
var utils = graph.add_module({ name = "utils", root = "src/utils.fol" });
var app = graph.add_exe({ name = "app", root = "src/main.fol" });
app.import(utils);
artifact.import(module) makes the module visible in the importing artifact’s
source scope. Equivalent to Zig’s artifact.root_module.addImport(name, dep).
Modules from dependencies are accessed via dep.module(name):
var build = .build();
var dep = build.add_dep({ alias = "mylib", source = "loc", target = "../mylib" });
var logger = dep.module("logger");
app.import(logger);
dep.module(name) resolves only explicitly exported build modules. It does not
change the ordinary package import rules used in source files.
Generated Files
Generated files are outputs produced before compilation. They must be declared in the graph so the build system knows to produce them and what depends on them.
Kinds
| Kind | Method | Description |
|---|---|---|
| Write | graph.write_file | Written with literal string contents |
| Copy | graph.copy_file | Copied from a source path |
| Tool output | graph.add_system_tool | Produced by an external tool |
| Tool dir | graph.add_system_tool_dir | Produced as a generated directory |
| Codegen | graph.add_codegen | Produced by the FOL codegen pipeline |
| Codegen dir | graph.add_codegen_dir | Produced as a generated directory |
| Captured run | run.capture_stdout() | Stdout of a run step |
Connecting Generated Files
A generated file must be connected to the graph entity that depends on it.
Attach to a step (the step triggers its production):
var schema = graph.file_from_root("schema/api.yaml");
var defaults = graph.write_file({
name = "defaults",
path = "gen/defaults.txt",
contents = "strict",
});
var gen = graph.add_system_tool({
tool = "flatc",
args = { "--fol" },
file_args = { schema, defaults },
env = { MODE = "strict" },
output = "gen/types.fol",
});
var step = graph.step("proto");
step.attach(gen);
Add to an artifact (artifact cannot compile without it):
var schema = graph.add_codegen({
kind = "schema",
input = "schema/api.yaml",
output = "gen/api.fol",
});
var app = graph.add_exe({ name = "app", root = "src/main.fol" });
app.add_generated(schema);
Capture stdout and feed it as an arg to another run:
var gen_tool = graph.add_exe({ name = "gen", root = "tools/gen.fol" });
var gen_run = graph.add_run(gen_tool);
var gen_output = gen_run.capture_stdout();
var app_run = graph.add_run(app);
app_run.add_file_arg(gen_output);
Generated directories can be installed or exported as dirs:
var assets = graph.add_system_tool_dir({
tool = "assetpack",
output_dir = "gen/assets",
});
build.export_dir({ name = "assets", dir = assets });
graph.install_dir({ name = "assets", source = assets });
Steps
Steps are named build phases. The build system has several implicit steps
(build, run, test, install). Custom steps are declared with
graph.step.
var docs = graph.step("docs", "Generate documentation");
Steps are executed in dependency order. A step depends on another step via
step.depend_on:
var compile = graph.step("compile");
var docs = graph.step("docs");
docs.depend_on(compile);
Default Step Bindings
When only one executable exists in a package, the build system automatically
binds it to the default build and run steps. When multiple executables
exist, explicit step bindings are required via graph.add_run(artifact).
Selecting Steps at the Command Line
fol code build # run the install steps
fol code build docs # run the "docs" step
fol code run # run the default run step
fol code run --step serve # run the "serve" step
fol code test # run test steps
Graph Validation
After the build program executes, the graph is validated:
- No step dependency cycles
- All artifact inputs (modules, generated files) are resolvable
- Install targets point to declared artifacts
Validation errors are reported as build evaluation errors before any compilation begins.