C code generation example
This tour builds a small native artifact from a message schema. The
example is intentionally compact: one schema file, one hand-written
main.c, generated C sources, a static library, and a CLI that proves the
generated library links and runs.
The point is to show how a native builder can expose generated files, toolchain steps, descriptors, outputs, and the materialization plan as views of one typed program.
Start from the artifact
The source fixture is c-codegen/messages.def, a tiny key/value
schema. The builder generates:
include/config.hfrom typed configuration defines.include/generated_messages.handgenerated/generated_messages.cfrom the schema.lib/libmessages-demo.afrom the generated object.bin/messages-demo, linked against the generated library.lib/pkgconfig/messages-demo.pcas metadata for downstream users.
Source files
These pages are the complete source for the worked example: the builder module plus the files it consumes.
- C codegen builder module - Source for the C codegen builder constructor and worked native demo program.
- messages.def - Schema fixture consumed by the C code generator in the native demo.
- main.c - Small CLI source linked against the generated static library.
Builder vocabulary
CCodegenBuilder ornaments BuilderSpec with the native-builder
fields that matter here: compiler identity, schema input, config
defines, and artifact kind. The public constructor keeps the domain
surface focused on the source inputs and leaves the internal program
to spell out the build mechanics.
cCodegen {
name = "messages-demo";
schema = ./c-codegen/messages.def;
main = ./c-codegen/main.c;
}
Program walkthrough
The constructor lowers the spec into a sequence of operations:
read the schema and main.c, declare bash, gcc, and ar, write
the config header, generate C sources, compile the generated source,
archive the static library, link the demo CLI, run a smoke test, emit
pkg-config metadata, and declare each output.
Because those steps are data, metaBuilder can interpret them without rebuilding the builder by hand. Dependency analysis sees the tool-use edges, dry-run shows the build actions, plan-view exposes the shell plan, and materialization turns the same plan into a derivation.
One build, many views
The section below is generated from mb.program.introspect.run
program. It is the internalized view of the native build: validation,
dependency graph, dry-run, plan-view, descriptors, outputs, and
materialization.
The same internalized program is interpreted several ways.
Validation
- result:
ok
Dependency Graph
- nodes:
8 - edges:
5 - services:
0 - node
tool:bash(tool) packagebash-interactive - node
tool:gcc(tool) packagegcc-wrapper - node
tool:ar(tool) packagebinutils-wrapper - node
operation:generate-c-sources(operation) viabash - node
operation:compile-generated-object(operation) viagcc - node
operation:archive-library(operation) viaar - node
operation:link-demo-cli(operation) viagcc - node
operation:smoke-test(operation) viabash - edge
tool:bash->operation:generate-c-sources(uses-tool) - edge
tool:gcc->operation:compile-generated-object(uses-tool) - edge
tool:ar->operation:archive-library(uses-tool) - edge
tool:gcc->operation:link-demo-cli(uses-tool) - edge
tool:bash->operation:smoke-test(uses-tool)
Dry Run - Builder
- source
messages.def - source
main.c - tool
bash - tool
gcc - tool
ar - write
include/config.h - run
generate-c-sourceswithbash - run
compile-generated-objectwithgcc - run
archive-librarywithar - run
link-demo-cliwithgcc - run
smoke-testwithbash - write
lib/pkgconfig/messages-demo.pc - descriptor
messages-demo-metadata - transform
cli(elf) - transform
static-library(archive) - transform
headers(directory) - transform
pkg-config(text) - materialize
messages-demo-artifactwithrunCommand - evidence
smoke-test
Dry Run - Runtime
None.
Plan View
- write
write:include/config.h - run
generate-c-sources - run
compile-generated-object - run
archive-library - run
link-demo-cli - run
smoke-test - write
write:lib/pkgconfig/messages-demo.pc write:include/config.h:mkdir -p "$(dirname "$out/include/config.h")"generate-c-sources:bash -c 'set -eucompile-generated-object:gcc -I"$out"/include -c ''"$out"/generated/generated_messages.c -o ''"$out"/generated/generated_messages.oarchive-library:ar rcs ''"$out"/lib/libmessages-demo.a ''"$out"/generated/generated_messages.olink-demo-cli:gcc -I"$out"/include "$pathMap_45f70a987ef70685ad13cc6283095c8504a3d4d95f23567a890bb52427d3338d" ''"$out"/lib/libmessages-demo.a -o ''"$out"/bin/messages-demosmoke-test:bash -c 'set -euwrite:lib/pkgconfig/messages-demo.pc:mkdir -p "$(dirname "$out/lib/pkgconfig/messages-demo.pc")"
Plan Shell Excerpt
pathMap_301f316e1baa45459961d7b5c3f4a9881e17adfe67b148a8295231c6d567d78f=<source:messages.def>
pathMap_45f70a987ef70685ad13cc6283095c8504a3d4d95f23567a890bb52427d3338d=<source:main.c>
mkdir -p "$(dirname "$out/include/config.h")"
cat > "$out/include/config.h" <<'METABUILDER_HEREDOC_856398558058b5929d6076680e5c3cd22f31adde4a8117afd770ec6dc55cfd7d'
#define DEMO_FEATURE 1
#define MESSAGE_LIMIT 8
METABUILDER_HEREDOC_856398558058b5929d6076680e5c3cd22f31adde4a8117afd770ec6dc55cfd7d
bash -c 'set -eu
schema="$1"
Runtime Services
None.
Descriptors
- descriptor
messages-demo-metadata(payload keysartifactKind,compiler,configDefines,generated,kind)
Builder Self-Documentation
- documented operations:
19 - runtime services:
0
Materialized Outputs
- cli -> $out/bin/messages-demo (elf)
- static-library -> $out/lib/libmessages-demo.a (archive)
- headers -> $out/include (directory)
- pkg-config -> lib/pkgconfig/messages-demo.pc (text)
Materialization
- derivation:
messages-demo-artifact - outputs:
4 - runtime artifacts:
0
Substrates
- dockerfile:
supported - shell:
supported