Configuration Reference¶
antel.json is generated by antel init and can also be written by hand. All fields are listed below:
Build artifacts, object files, logs, and reports for each config live under
.antel/build/<projectName>_<config>/ (for example, the default config writes to
.antel/build/demo_antel/). Antel preparation outputs belong in .antel/build/, beside
the ref checkouts in .antel/refs/.
| Field | Type | Required | Description |
|---|---|---|---|
| projectName | string | yes | Project name; also determines the output directory name and the generated target name. Must not be empty and may only contain letters, digits, underscores, dots and hyphens |
| source | string[] | yes | Paths of the c/c++ source files to compile, relative to the directory containing the config file; must not be empty |
| ref | object[] | no | Git projects to shallow-clone before compilation; each entry has url and optional branch and name |
| before_build | object[] | no | Commands run in order after refs are fetched and before change detection; each has argv command and optional outputs |
| after_build | object[] | no | Commands run in order after a successful antel build / antel rebuild; each has argv command |
| include_directories | string[] | no | Header search paths, passed to the compiler as -I; the files in them participate in change detection |
| target_type | string | yes | static, shared or exe (case-insensitive) |
| compiler | string | yes | msvc, gxx or llvm (case-insensitive) |
| compile_args | string[] | no | Compile arguments passed to the compiler |
| link_args | string[] | no | Link arguments passed to the linker, e.g. ["-ldl"]; must be a string array |
| analyze_files | string[] | no | Deprecated: the report automatically covers all source files; this field is no longer read |
| report | boolean | no | Generate the visual report report.html automatically after a successful build (default false); not part of the compilation |
| jobs | integer | no | Number of compile units built in parallel; defaults to min(8, CPU cores); set to 1 for serial builds |
| backend | string | no | Tool that executes the compilation: auto (default: use make if available, otherwise fall back to the built-in executor) / make / antel |
| compile_commands | boolean | no | Whether to write compile_commands.json (default true) |
| response_file | string | no | Whether to switch to a response file when the link command line gets too long: auto (default) / always / never |
| pkg_config | string[] | no | List of external libraries; injects pkg-config's --cflags/--libs output per package, e.g. ["libcurl"] |
| data_files | array | no | Runtime resources copied into the output directory; either a string (from==to) or {"from": source, "to": destination} |
| gresource | string | no | GLib resource description file (.gresource.xml); the resources are compiled into the executable (single-file distribution) |
| embed | string[] | no | Any binary embedded into the executable (single-file distribution), accessed in the program via _binary_ symbols |
| sanitize | string[] | no | List of sanitizers; each entry adds -fsanitize=<item> to both compilation and linking, e.g. ["address"] |
| coverage | boolean | no | Whether to instrument for coverage: adds -fprofile-arcs -ftest-coverage to compilation and --coverage to linking |
| version | string | no | Shared library version (e.g. "1.0.0"); produces libX.so.<version> plus symlinks |
| soname | string | no | Shared library soname (e.g. "libX.so.1"); defaults to the major version of version |
| rpath | string[] | no | Adds -Wl,-rpath,<path> at link time so shared libraries can be loaded by soname |
| exclude_source | string[] | no | Reserved field; currently not used in the build |
Invalid field values abort with an error
target_type and compiler only accept the values listed in the table above — a wrong value exits with a non-zero code and prints the allowed options. An empty source, an empty projectName or one with invalid characters, a non-array link_args, a jobs that is not a positive integer, or an invalid backend / response_file value all error out the same way. There is no "silently fall back to a default" behavior.
Field details¶
source¶
Paths are relative to the directory containing the config file; subdirectories use / separators:
Each source file compiles to one object file under obj/, named by replacing path separators with underscores: src/main.c → obj/src_main.o. That means src/main.c and src_main.c would collide on the same object file name, so keep them apart.
ref¶
ref shallow-clones Git projects into .antel/refs/ before antel build or antel rebuild compiles:
{
"ref": [
{
"url": "https://github.com/example/libfoo.git",
"branch": "stable",
"name": "libfoo"
},
{
"url": "https://github.com/example/libbar.git",
"branch": "main"
}
]
}
urlis required.branchis optional and defaults to the remote's default branch. If omitted,nameis derived from the repository URL.- The projects above are cloned to
.antel/refs/libfooand.antel/refs/libbar. Existing checkouts are not updated automatically; remove the corresponding directory before selecting another branch or fetching newer commits. refonly acquires source trees; it does not add files to the build automatically. List required.cfiles undersourceand header directories underinclude_directories.- Reference checkouts are separate from build artifacts and are preserved by
antel clean.
before_build¶
Runs after reference checkouts are prepared and before resource deployment and
incremental scanning. Commands are argv arrays, not shell strings. Optional
outputs are included in the hash baseline: changed generated headers trigger
recompilation, while changed files used only by the link step trigger relinking.
{
"before_build": [
{
"command": ["python3", "prepare.py", "--mode", "release"],
"outputs": [".antel/build/generated/config.h", ".antel/build/generated/exports.map"]
}
]
}
The build exits non-zero if a command fails or a declared output is not created.
after_build¶
Runs each command in array order after antel build or antel rebuild
completes successfully. Commands are argv arrays, not shell strings. This also
runs when an incremental build finds no changes. It is skipped after a compile or
link failure; if an after-build command fails, Antel exits non-zero.
{
"after_build": [
{"command": ["python3", "package.py"]},
{"command": ["python3", "notify.py", "--success"]}
]
}
include_directories¶
It serves two purposes: the paths are passed to the compiler as -I arguments, and the .h, .c, .cc and .cpp files in them participate in change detection, so headers placed here are picked up when modified.
Headers in the same directory as their source file don't need to be listed here — they are covered by the .d dependency files generated at compile time; see Incremental build.
compile_args¶
Inserted verbatim into the compile command; how you write them and their order is entirely up to you:
-fPICis required when building a shared library; theantel inittemplate already includes it and the tool will not add it automatically-std=,-O*,-w,-fno-rtti,-Dmacro definitions and the like all go here
link_args¶
Entries starting with -l are recognized as system libraries; the rest are appended verbatim to the end of the link command. Writing it as a string (e.g. "link_args": "-ldl") is treated as a configuration error and exits immediately.
analyze_files¶
Deprecated: early versions used it to specify which source files antel analyze should analyze. The analysis report now automatically covers every source file in the configuration, so this field is no longer read and can safely be deleted. The template has switched to report.
report¶
Generates the visual analysis report automatically after a successful build (<output directory>/report.html):
- Not part of the compilation: no compile flags are added, the artifacts are byte-identical to
report: false, and it does not enter the hash baseline — it only runs one extra analysis after a successful build - Use
antel analyzewhenever you want to generate it manually; the result is the same as a build withreport: true - See the analyze section of Commands for what the report contains
jobs¶
The number of compile units built in parallel; defaults to min(8, CPU cores), and 1 means serial. Parallelism only affects how fast the compile stage progresses — it never changes the artifacts: the same input produces byte-identical object files and executables whether built serially or in parallel (there are assertions for this in the tests). The trade-off is that multi-process output interleaves, so use jobs: 1 when you need to read diagnostics file by file.
backend¶
Selects who executes the compilation. Default auto: make is preferred, and when make is not available on the machine it automatically falls back to the built-in parallel executor (the config summary marks which executor is actually used, e.g. auto → make / auto → antel (make not found)). You can also set make or antel explicitly.
Either way, what to compile is always decided by antel (the hash baseline plus the -MMD dependencies); make only builds them in parallel:
- antel generates an internal rules file
<output directory>/log/antel.mk(rebuilt on every build, with a "generated — do not edit" header). It is not a deliverable: no Makefile appears in the project root, and you don't need to maintain it. - Before handing work to make, antel first deletes the object files judged stale this round. make decides by timestamps, and in practice handing it an "up-to-date" object causes it to be skipped; but by hash those objects really are stale, so deleting them guarantees a rebuild.
- make's full output is recorded in
<output directory>/log/<project name>.make. - Both executors produce identical artifacts (the tests byte-compare the executables).
Measured (150 compile units, 8 cores): both executors take roughly the same time (about 1.1–1.3 s). The benefit of using make is not speed but consistency with the make toolchain — you can reproduce the same compilation by hand with make -f <output directory>/log/antel.mk.
Calling antel from within make¶
If antel is invoked by make (the MAKEFLAGS environment variable is present), it won't layer on more parallelism internally: the built-in executor falls back to jobs: 1, and the make executor simply omits -j, leaving the degree of parallelism to the outer layer — otherwise the outer budget would be blown through.
Known limitation: make's jobserver pipe does not pass through antel to the sub-make it spawns (Python closes inherited file descriptors when launching subprocesses), so a nested make prints jobserver unavailable: using -j1 and runs serially. This degradation is in the safe direction (never over-parallelizes); the cost is that this layer loses its parallelism. If you need to share the jobserver, have the outer layer use the + prefix and wait for a later batch to add fd passthrough.
compile_commands¶
Default true; every build rewrites <output directory>/compile_commands.json, recording the full command line of each compile unit entry by entry (giving structured arguments rather than the unreliable command string for paths containing spaces). clangd only looks for this file within the source tree, so you need to point it at the output directory explicitly — one of two ways:
response_file¶
When the link command line gets too long (default threshold: 100,000 characters, e.g. with a huge number of object files), arguments are passed via a response file instead: auto enables it only above the threshold, always keeps it on, never turns it off. ar does not support response files, so static library archiving never takes this path.
pkg_config¶
A list of external libraries, e.g. ["libcurl", "sqlite3"]. At build time pkg-config --cflags/--libs is run for each package and its output is injected into the compile and link commands:
- The
-I/-Dand similar flags from--cflagsare appended to every compile command (after yourcompile_args, so your-Iflags take precedence) and also land incompile_commands.json, which clangd benefits from too - The
-L/-lflags from--libsare appended at the end of the link command (static library archiving withardoes not participate in linking and is skipped naturally)
A missing package or a machine without the pkg-config command fails immediately with an error — arguments are never silently dropped.
data_files¶
Runtime resources (images, config files, scripts, etc.) are copied into the output directory at build time and read by the program via relative paths. Suited to replaceable, large resources. Choose one of two forms:
{
"data_files": [
{"from": "assets", "to": "assets"},
{"from": "config/settings.ini", "to": "settings.ini"}
]
}
- String form:
fromandtoare the same name; a source directory is copied as a whole (internal structure preserved), a source file is copied by name - Object form:
fromis the source under the project directory,tois the destination under the output directory; whether the destination is a directory or a file follows the source - Source files enter the hash baseline: after modifying them,
antel buildre-syncs them antel cleanreclaims them along with the output directory
gresource¶
GLib resources: images, CSS, UI descriptions and more are compiled into C source and then into the executable — single-file distribution; copying one binary carries all the resources. Requires glib-compile-resources (usually shipped with GTK dev packages):
In gresource.gresource.xml, <gresource prefix="/io/github/luskyle/app"> sets the access prefix and <file> lists the resource paths. The gio bindings come from pkg_config, e.g. "pkg_config": ["libadwaita-1"]. The program reads them directly with the GResource API:
GBytes *bytes = g_resources_lookup_data("/io/github/luskyle/app/img/logo.png",
G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
The generated gresource.c participates in the build as an ordinary compile unit and its hash enters the baseline: change a resource file → it is regenerated, recompiled and relinked automatically. The generated code ships its own ELF constructor, so no manual resource registration is needed. The working directory during the build is the project root, and relative paths inside the xml are resolved against it; antel clean reclaims the generated artifacts.
embed¶
Any binary (images, models, key mappings, etc.) is embedded into the executable with ld -r -b binary — single-file distribution. Suited to generic C/C++ projects that don't use GLib:
In C code the data is accessed via auto-generated symbols: the symbol name is the path (every non-alphanumeric character replaced with an underscore) prefixed with _binary_ and suffixed with _start/_end/_size:
/* assets/logo.png → _binary_assets_logo_png_start/_end */
extern const unsigned char _binary_assets_logo_png_start[];
extern const unsigned char _binary_assets_logo_png_end[];
Embedded files enter the hash baseline: after a modification, antel build regenerates the .o and relinks (even when no source file changed). The artifact lives at <output directory>/obj/embed_<n>.o and is reclaimed by antel clean.
sanitize¶
Sanitizer switches that apply to both compilation and linking (the link stage must bring in the toolchain runtime, e.g. ASan's libasan):
Each entry adds one -fsanitize=<item> to every compile unit and the link command. They can be combined:
address: detects memory errors (out-of-bounds, use-after-free)undefined: detects undefined behaviorleak,threadand others are passed through per the-fsanitize=semantics; one the toolchain doesn't support fails immediately with an error
Disable optimization when enabling sanitizers
At optimization levels above -O1, GCC optimizes away accesses with undefined behavior and the ASan instrumentation disappears along with them, so nothing gets detected. When debugging memory issues, pair it with -O0 -g (see demos/antelstats/san.json for the full setup).
coverage¶
Coverage instrumentation:
Compilation gets -fprofile-arcs -ftest-coverage (producing .gcno), linking gets --coverage. After running the executable once, the matching .gcda files land in obj/ (in the same directory as the .gcno files), and you can then produce a report with gcov (or lcov):
Both .gcno and .gcda live inside the output directory and are reclaimed together by antel clean.
version / soname / rpath: versioned shared libraries¶
With target_type: shared, give the library a version and antel produces libX.so.<version> plus symlinks:
Artifacts and link parameters:
- The real file is
libX.so.1.0.0(the link command carries-Wl,-soname,libX.so.1; the soname is derived from the major version) - The symlinks
libX.so.1 -> libX.so.1.0.0andlibX.so -> libX.so.1serve compile-time-lXand runtime soname lookup - An explicit
sonametakes precedence over the derived one, e.g."soname": "libcustom.so.3" GENERATED_TARGETSalready covers*.so.*, soantel rebuild/antel cleanreclaim the versioned files and symlinks
Consumer side: when an executable depends on this library, use link_args to point at the library directory and rpath so it can be found at runtime (demos/antelstats/app.json is a complete example):
{
"link_args": ["-L.antel/build/antelstats_antel", "-lantelstats"],
"rpath": ["$ORIGIN/../antelstats_antel"]
}
At runtime $ORIGIN expands to the directory containing the executable, so rpath supports relative lookup and the whole output directory tree can be copied elsewhere and still run. Verification: if readelf -d shows the SONAME and ldd resolves against it, the versioning chain works.
A complete configuration example¶
Putting all the fields above together, here is a complete configuration for a "GUI executable + third-party library + three resource kinds" project (taken from demos/resdemo; see Examples for the runtime effect):
{
"projectName": "resdemo",
"target_type": "exe",
"compiler": "gxx",
"source": ["src/main.c"],
"exclude_source": [],
"include_directories": ["include"],
"compile_args": ["-O2", "-Wall"],
"link_args": ["-lm"],
"jobs": 8,
"backend": "auto",
"compile_commands": true,
"report": false,
"pkg_config": ["libadwaita-1"],
"data_files": ["assets"],
"gresource": "gresource.gresource.xml",
"embed": ["assets/payload.bin"]
}
Build directory layout¶
The output directory is ./.antel/build/<projectName>_<config file name>/: with the config
file named antel.json and the project named helloworld, it is
.antel/build/helloworld_antel/. Object files, logs, reports, and build targets live
there. Preparation outputs belong in .antel/build/, beside ref checkouts in
.antel/refs/; antel clean removes only the corresponding
.antel/build/<projectName>_<config file name>/.
| Path | Contents |
|---|---|
<output directory>/<project name> |
The executable target (exe) |
<output directory>/lib<project name>.a, lib<project name>.so |
Static library and shared library targets |
<output directory>/lib<project name>.so.<version>, lib<project name>.so.<major version>, lib<project name>.so |
Versioned shared library: real file plus two levels of symlinks (when version is set) |
<output directory>/obj/*.o |
Object files |
<output directory>/obj/*.o.d |
Dependency file per compile unit, generated by -MMD -MF |
<output directory>/obj/*.gcno, *.gcda |
Coverage instrumentation artifacts (coverage: true; reclaimed by clean) |
<output directory>/obj/embed_<n>.o |
Object of a binary embedded via embed (participates in linking; reclaimed by clean) |
<output directory>/gresource.c |
Resource source generated by gresource (participates in the build as a compile unit) |
<output directory>/compile_commands.json |
Compilation database for clangd and similar tools; can be turned off with compile_commands: false |
<output directory>/report.html |
Visual analysis report (auto-generated after builds with report: true, or by antel analyze); a self-contained single file |
<output directory>/log/hashes |
The hash baseline, recording every input file of the last successful build |
<output directory>/log/hashes_diff |
The files that changed relative to the baseline this run, with their before/after hashes |
<output directory>/log/stale_files |
The list of source files that actually need recompiling this run |
<output directory>/log/<project name>.<compiler> |
The full compile commands executed this run |
<output directory>/log/antel.mk |
Internal rules file (generated with backend: make, rebuilt every build; not a deliverable) |
<output directory>/log/<project name>.make |
make's full output when it executes the compilation |
<output directory>/log/<project name>_link.sh |
The link script executed this run; linking runs this script |
<output directory>/log/<project name>_link.rsp |
The response file used when the link command line is too long (controlled by response_file) |
<output directory>/log/linkInfor |
The full output of the linking process |
<output directory>/log/readelf_*, ldd_*, nm_*, symbol_*, archive_*, objdump_* |
Analysis results for the generated targets — symbol tables, dynamic dependencies, archive contents and more (also the data source of the "dynamic dependencies / artifact analysis" in the report) |
antel clean removes the entire output directory, including the generated targets and everything in the table above.