zenon: C build tool of the 21st century#
An experimental build system and script runner for C and other languages/compilers supporting separate compilation.
Out of the box, zenon supports clang, clang++, flang,
ispc, ocaml, ghc, mlton and ats2
and additional compilers can be specified in the zenon.yaml file.
zenon is particularly useful when you have some files in a directory:
$ ls
a.c b.c c.c d.hs
you can compile and link these files into an executable by running:
$ zenon build -o example
create an executable and link libgit2 using pkg-config and run it:
$ zenon build -o example --pkg libgit2 --run
Installation#
opam:
$ opam install -y git+https://codeberg.org/zshipko/zenon-build.git
nix flakes:
Install zenon:
$ nix profile install "git+ssh://git@codeberg.org/zshipko/zenon-build"
$ zenon
Or just run it:
$ nix run "git+ssh://git@codeberg.org/zshipko/zenon-build"
dune:
$ git clone https://codeberg.org/zshipko/zenon-build.git
$ cd zenon-build
$ dune pkg lock && dune build
$ dune install
$ zenon
Running#
$ zenon build
will build all targets in your zenon.yaml file
and
$ zenon build example
will build just the example target.
For full command line options, see the output of zenon --help
When run without a zenon.yaml file, zenon will:
- automatically discover source files in the current directory
- detect the appropriate compiler and linker based on file extensions
You can also specify source files directly via the command line using the -f flag:
$ zenon build -f main.c -f utils.c
Glob patterns are also supported:
$ zenon build -f 'src/*.c'
Configuration#
To setup a new configuration file you can use zenon init or write it by hand.
For the full experience you will need to setup a zenon.yaml file:
build:
- target: example
files:
- 'src/*.c'
flags:
- lang: c
compile:
- "-std=c23"
The above configuration will compile an executable named example from all the C source files in src
Building a static library#
build:
- target: libutils.a
files:
- 'src/*.c'
Using dependencies#
build:
- target: libutils.a
files:
- 'utils/*.c'
- target: myapp
files:
- 'src/*.c'
depends-on: [libutils.a]
Sharing files and flags#
files:
- 'common/*.c'
flags:
- lang: c
compile: ["-Wall", "-O2"]
build:
- target: app1
files:
- 'app1/*.c'
- target: app2
files:
- 'app2/*.c'
Top-level files and flags apply to all targets.
Using pkg-config#
build:
- target: myapp
files:
- 'src/*.c'
pkg: [sdl2, opengl]
Running scripts#
build:
- name: codegen
script: python generate.py
- target: myapp
depends-on: [codegen]
files:
- 'src/*.c'
Field Reference#
Top-level fields#
build- List of build targets (required)files- Source files to include in all targetsflags- Compiler/linker flags for all targetstools- Custom compiler and linker configurationsignore- File patterns to exclude from all targetspkg- pkg-config packages for all targets
Build target fields#
target- Output file name (e.g.myapp,libfoo.a)name- Target name forzenon build <name>(defaults to target)files- Source files for this target (supports globs like*.c,**/*.c)root- Root directory for source files (defaults to.)depends-on- List of targets that must build firstlinker- Linker to use (auto-detected from target name and source files if not specified)script- Shell script to run instead of compilingafter- Shell script to run after buildingif- Only build if this shell command succeedspkg- pkg-config packages for this targetignore- File patterns to exclude from this targethidden- Don't build unless explicitly nameddisable_cache- Rebuild every timeflags- Compiler/linker flags for this targetcompilers- List of compiler names to use (e.g.,[clang, clang++])
Note: For some languages (like OCaml and Haskell) the files will need to be manually ordered for dependency
resolution if you're using multiple files.
Flag configuration#
flags:
- lang: c
compile: ["-std=c23", "-Wall"]
link: ["-lm"]
lang- Language (allor file extension without dot:c,cpp,hs, etc.)compile- Flags passed to compilerlink- Flags passed to linker
Custom compilers/linkers#
It is possible to define custom compilers under the tools section, then reference by name in build targets:
tools:
compilers:
- name: mycc
ext: [myc]
command: ["mycc", "#flags", "-o", "#output"]
linkers:
- name: mycc
link-type: exe
command: ["mycc", "--link", "#flags", "-o", "#output", "#objs"]
build:
- target: example
compilers: [mycc]
In the example above, #objs, #flags and #output are template arguments and will be replaced with the object paths, flags and output path.
name- Compiler nameext- File extensions this compiler handlescommand- Command template (#flags,#output,#objsare substituted)link-type- Specifies the type of linker (should only be set when defining linkers)has-runtime- For linkers, specifies if the language links a runtime. If it does then this linker must be used if any matching source files are present. Compilers withhas-runtimeset to true (ocamlopt,ghc,mlton, ...) can't be mixed without custom linking rules. However, all the available languages can be mixed with C/Fortran code.parallel- For compilers, specified whether parallel builds are allowedcompile-flag-prefix- Prefix for wrapping C compile flags (e.g.,-ccoptfor OCaml)link-flag-prefix- Prefix for wrapping C link flags (e.g.,-ccoptfor OCaml)
Using C libraries with other languages#
C flags (including those from pkg-config) are automatically passed to non-C compilers with appropriate wrapping. For example:
flags:
- lang: c
compile: ["-I/usr/local/include"]
link: ["-L/usr/local/lib", "-lsqlite3"]
build:
- target: myapp
compilers: [ocamlfind]
files: ['src/*.ml']
pkg: [libpq] # PostgreSQL C library
The C flags will be automatically wrapped for OCaml as -ccopt -I/usr/local/include -ccopt -L/usr/local/lib -ccopt -lsqlite3, and pkg-config flags from libpq will also be wrapped appropriately.
For custom compilers, use compile-flag-prefix and link-flag-prefix:
tools:
compilers:
- name: my-language
ext: [myl]
command: ["mylang", "#flags", "-o", "#output"]
compile-flag-prefix: "-ccopt"
link-flag-prefix: "-cclib"