跳转至

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:

{
  "source": ["src/main.c", "src/util.c"]
}

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"
    }
  ]
}
  • url is required. branch is optional and defaults to the remote's default branch. If omitted, name is derived from the repository URL.
  • The projects above are cloned to .antel/refs/libfoo and .antel/refs/libbar. Existing checkouts are not updated automatically; remove the corresponding directory before selecting another branch or fetching newer commits.
  • ref only acquires source trees; it does not add files to the build automatically. List required .c files under source and header directories under include_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:

  • -fPIC is required when building a shared library; the antel init template already includes it and the tool will not add it automatically
  • -std=, -O*, -w, -fno-rtti, -D macro definitions and the like all go here
{
  "link_args": ["-lpthread", "-ldl"]
}

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):

{
    "report": true
}
  • 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 analyze whenever you want to generate it manually; the result is the same as a build with report: 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:

# Option 1: a startup flag
clangd --compile-commands-dir=<output directory>
# Option 2: a .clangd at the project root
CompileFlags:
  CompilationDatabase: <output directory>

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 / -D and similar flags from --cflags are appended to every compile command (after your compile_args, so your -I flags take precedence) and also land in compile_commands.json, which clangd benefits from too
  • The -L / -l flags from --libs are appended at the end of the link command (static library archiving with ar does 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": ["assets"]
}
{
  "data_files": [
    {"from": "assets", "to": "assets"},
    {"from": "config/settings.ini", "to": "settings.ini"}
  ]
}
  • String form: from and to are the same name; a source directory is copied as a whole (internal structure preserved), a source file is copied by name
  • Object form: from is the source under the project directory, to is 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 build re-syncs them
  • antel clean reclaims 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):

{
  "gresource": "gresource.gresource.xml"
}

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:

{
  "embed": ["assets/logo.png", "assets/firmware.bin"]
}

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):

{
  "sanitize": ["address", "undefined"]
}

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 behavior
  • leak, thread and 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:

{
  "coverage": true
}

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):

antel rebuild && antel run
cd <output directory>/obj && gcov <target>.gcda

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:

{
  "target_type": "shared",
  "version": "1.0.0"
}

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.0 and libX.so -> libX.so.1 serve compile-time -lX and runtime soname lookup
  • An explicit soname takes precedence over the derived one, e.g. "soname": "libcustom.so.3"
  • GENERATED_TARGETS already covers *.so.*, so antel rebuild / antel clean reclaim 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.