C build tool of the 21st century
OCaml 94.5%
C 1.4%
Nix 0.9%
C++ 0.6%
Dune 0.5%
Haskell 0.4%
ATS 0.3%
Assembly 0.2%
Standard ML 0.2%
Other 1.0%
178 1 1

Clone this repository

https://tangled.org/zachshipko.com/zenon-build https://tangled.org/did:plc:gvjre7emtreh5bdqtfwle6j2/zenon-build
git@knot.tangled.wizardry.systems:zachshipko.com/zenon-build git@knot.tangled.wizardry.systems:did:plc:gvjre7emtreh5bdqtfwle6j2/zenon-build

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

zenon: C build tool of the 21st century#

screenshot of a build using zenon

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 targets
  • flags - Compiler/linker flags for all targets
  • tools - Custom compiler and linker configurations
  • ignore - File patterns to exclude from all targets
  • pkg - pkg-config packages for all targets

Build target fields#

  • target - Output file name (e.g. myapp, libfoo.a)
  • name - Target name for zenon 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 first
  • linker - Linker to use (auto-detected from target name and source files if not specified)
  • script - Shell script to run instead of compiling
  • after - Shell script to run after building
  • if - Only build if this shell command succeeds
  • pkg - pkg-config packages for this target
  • ignore - File patterns to exclude from this target
  • hidden - Don't build unless explicitly named
  • disable_cache - Rebuild every time
  • flags - Compiler/linker flags for this target
  • compilers - 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 (all or file extension without dot: c, cpp, hs, etc.)
  • compile - Flags passed to compiler
  • link - 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 name
  • ext - File extensions this compiler handles
  • command - Command template (#flags, #output, #objs are 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 with has-runtime set 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 allowed
  • compile-flag-prefix - Prefix for wrapping C compile flags (e.g., -ccopt for OCaml)
  • link-flag-prefix - Prefix for wrapping C link flags (e.g., -ccopt for 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"