Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

laze is a build system for C/C++ projects. It can be used in many cases instead of Make, CMake or Meson.

It’s main differentiators are:

  • declarative, easy to use yaml-based build descriptions
  • designed to handle massively modular projects
    • main driver: RIOT OS, which builds > 100k configurations as part of its CI testing
    • powerful module system
    • first-class cross compilation
    • first-class multi-repository support
  • optimized for fast build configuration
    • written in Rust
    • extensive caching
    • multithreaded build configuration
  • optimized for fast building
    • automatically re-uses objects that are built identically for different build configurations
  • actual building is done transparently by Ninja

This book contains a guide on how to install and use laze, and a reference for its build description format.

How does it look

Consider a simple application consisting of a single hello.c source file.

This laze file, saved to laze-project.yml next to hello.c, would build it:

# import default context and builder
imports:
  - laze: defaults

# define an application named "hello"
apps:
  - name: hello
    sources:
        - hello.c

The application can now be built:

laze build

The resulting executable ends up in build/out/host/hello/hello.elf.

Alternatively,

laze build run

Would run the executable, (re-)building it if necessary.

Contributing

laze is free and open source. You can find the source code on GitHub and issues and feature requests can be posted on the GitHub issue tracker. laze relies on the community to fix bugs and add features: if you’d like to contribute, please read the CONTRIBUTING guide and consider opening a pull request.

License

laze source code is released under the Apache 2.0 license.

This book has been heavily inspired by and is based on the mdBook user guide, and is thus licenced under the Mozilla Public License v2.0.

Installation

There are multiple ways to install the laze CLI tool. Choose any one of the methods below that best suit your needs. If you are installing laze for automatic deployment, check out the continuous integration chapter for more examples on how to install.

Distribution packages

The easiest way to install laze is probably by using a package for your distribution.

Available packages:

DistributionPackage name
Arch Linux AURlaze, laze-bin

Dependencies

laze requires Ninja. You can download the Ninja binary or find it in your system’s package manager.

Pre-compiled binaries

Executable binaries are available for download on the GitHub Releases page. Download the binary for your platform (Windows, macOS, or Linux) and extract the archive. The archive contains laze executable.

To make it easier to run, put the path to the binary into your PATH.

Build from source using Rust

To build the laze executable from source, you will first need to install Rust and Cargo. Follow the instructions on the Rust installation page. laze currently requires at least Rust version 1.54.

Once you have installed Rust, the following command can be used to build and install laze:

cargo install laze

This will automatically download laze from crates.io, build it, and install it in Cargo’s global binary directory (~/.cargo/bin/ by default).

Installing the latest master version

The version published to crates.io will ever so slightly be behind the version hosted on GitHub. If you need the latest version you can build the git version of laze yourself. Cargo makes this super easy!

cargo install --git https://github.com/kaspar030.git laze

Again, make sure to add the Cargo bin directory to your PATH.

Configuration

Currently, laze has no configuration itself. Only its <TAB> (shell-) completions need some set-up.

Shell completions

laze provides dynamic shell completions (TAB completions in the shell). In order to use them, they need to be set up.

If you’ve installed laze using your package manager, chances are that this is already working out of the box.

Use the snippet that corresponds to your shell to automatically load the correct completions on shell start (if in doubt, you’re probably running bash):

Bash

echo "source <(COMPLETE=bash laze)" >> ~/.bashrc

Elvish

echo "eval (E:COMPLETE=elvish laze | slurp)" >> ~/.elvish/rc.elv

Fish

echo "COMPLETE=fish laze | source" >> ~/.config/fish/config.fish

Powershell

echo "COMPLETE=powershell laze | Invoke-Expression" >> $PROFILE

Zsh

echo "source <(COMPLETE=zsh laze)" >> ~/.zshrc

The shell will need to be restarted for this to take effect. In order to just set up completions for the currently running shell session, issue just the source ... command, e.g., source <(COMPLETE=bash laze).

Getting Started

Running Laze

To build an application for a builder, run

laze build -b <builder> -a <application>

Contributing

If you are interested in making modifications to laze itself, check out the Contributing Guide for more information.

Build file structure

laze uses yaml files to describe a project.

Each laze project consists of a file named laze-project.yml in the project’s root directory, and any number of laze.yml files in subdirectories.

Please see the reference for a detailed description of the file format.

laze will always read laze-project.yml and all referenced laze.yml files of a project. Don’t worry, that’s fairly fast, e.g., reading and parsing ~650 build files of RIOT takes ~35ms on a Thinkpad T480s. And it’s cached, if no buildfile has been changed since it was last read, loading the cache takes less than 10ms.

Once laze has read the build files, it will configure builds as requested when executing it. This will resolve all dependencies for the requested builds, configure the environments and write a Ninja build file.

Once done configuring, laze will automatically call Ninja with the changed build configuration. Ninja will then do the actual building.

Apps, Builders, Contexts, Modules, Tasks

Builds, Apps, Builders, Contexts, Modules, Tasks

Conceptionally laze builds apps for builders. Each app/builder combination that laze configures is called a build.

An app represents a binary, and a builder would be e.g., the toolchain configuration for the host system.

Builders are contexts. All contexts (and thus builders) have a parent, up to the root context which is called “default”. A builder is just a context that apps can be built for. Any context with buildable: true is a builder.

A context could be “linux”, or “ubuntu” with parent context “linux”. A builder could then be “amd64” with parent context “ubuntu”. Builders can have other builders as parent context.

Contexts (and thus builders) inherit modules and variables from their parents.

Apps can depend on modules, which can have dependencies themselves. Technically, an app is just a special kind of module that can be built for a builder.

Apps and modules consist of zero or more source files.

Tasks are operations that can be run on binaries of a build configuration. They can be used to execute a compiled binary, run it through a debugger, install some files, update/reset an embedded target, …

Tasks are executed by laze (not ninja), after building possible dependencies.

Module Dependencies

Dependency types

Apps/modules can depend on other modules. There are multiple dependency types:

  1. “depends” -> hard depend on another module, import env

    This module won’t work unless the dependency is usable (not disabled, not conflicted by another module and its own dependencies can be resolved). It is pulled in and the dependency’s exported environment will be imported.

    “depends” is equivalent to specifying both “selects” “uses”.

  2. “selects” -> hard depend on another module, don’t import env

    like 1.), the dependency will be pulled in, but the dependency’s exported environment will not be imported.

  3. “uses” -> don’t depend on module, but import env if module is part of build

    The dependency will not be pulled in by this module, but if it is part of the build through other means (e.g., another module depends on it), this module will import the dependency’s exported environment.

  4. “conflicts” -> the modules cannot be used together in the same build.

  5. “optionally depends” -> soft depend on another module

    If the dependency is usable (not disabled, not conflicted by another module and its own dependencies can be resolved), it will be pulled in and its exported environment will be imported. If the dependency is not usable, the build continues as if the dependency was not specified at all.

  6. “optionally select” -> soft depend on another module (without importing env)

    If the dependency is usable (not disabled, not conflicted by another module and its own dependencies can be resolved), it will be pulled in, but its exported environment will not be imported.

  7. “if this than that” -> if “this” is part of the dependency tree, “depend” on “that”

  8. “optional if this than that” -> same as 6.) but as soft dependency.

Object Sharing

Object Sharing in laze

Object Sharing is a useful feature of laze that enables the sharing of identical compiled objects between builds. This means that if two different builds are using the same module and are compiled for the same builder, laze will avoid compiling the module multiple times.

When generating a build plan, laze would normally place the objects of a module in a directory like build/out/<builder>/<app>/src/<module>. With Object Sharing enabled, laze will instead place objects in build/objects/<source_path>/filename.<hash>.o, where hash represents the hash of the ninja build statement used to build the file. In addition, laze makes sure that it doesn’t include duplicate build statements.

This scheme effectively and automatically makes laze apps share common objects, which eliminates the need for users to take any action to achieve this. In testing with the RIOT OS, this feature has reduced the number of built objects by approximately 30-40%.

In summary, Object Sharing in laze is a powerful feature that streamlines the build process by avoiding the redundant compilation of identical objects. This can lead to significant time and resource savings for users, particularly in large-scale projects.

Variables and Environment

Variables and Environments

Apps/modules and contexts/builders can have variables. Variables are combined in environments.

Laze will collect all variables of a build’s builder and its ancestor contexts into that build’s global environment.

Each app/module can have local, global and exported variables. All global app/module variables get merged into a builds global environment.

An app/module’s local environment consist of the build’s global environment, the app/module’s local variables and its own and all it’s dependencies’ exported environments, transitively.

An app/module’s exported environment consists of its own exported variables and all it’s dependencies’ exported environments, transitively.

Confusing? Maybe. It is totally possible to only use global variables, and for smaller projects, that won’t hurt too much.

But imagine some module needs to be compiled with some CFLAGS define that no other module cares about. If set globally, all files would need to be recompiled whenever that define changes. If set as a local variable, only that module’s files need recompilation. But what if that define is evaluated in an API defining header that might be included by another module (a dependee of our first module)? In that case, the define must also be exported, so all dependees that make use of the header get the define.

Laze File Reference

This chapter contains the reference of laze’s YAML build file format.

laze_required_version

Expects a semver version string (a.b.c). Laze will refuse to read the file if its own version is smaller.

Example:

laze_required_version: 1.0.0

app

apps contains a list of app entries.

An app represents a binary that can be built for builders.

Example:

apps:
 - name: hello-world
   # ... possible other fields
 - name: foobar
   # ... possible other fields

app fields

As an app is just a special kind of module, they share all fields.

builders

builders contains a list of builder entries.

A builder represents a configuration and set of available modules that an app can be built for.

Example:

builders:
 - name: name_of_this_builder.
   # ... possible other fields
 - name: name_of_other_builder
   # ... possible other fields
contexts:
 - name: name_of_another_builder
   buildable: true
   # ... possible other fields

builder fields

As a builder is just a context that has buildable: true set, they share all fields.

contexts

contexts contains a list of context entries.

Example:

contexts:
 - name: name_of_this_context.
   # ... possible other fields
 - name: name_of_other_context
   # ... possible other fields

context fields

name

name

Name of this context. Any UTF8 string is valid. This will be used as part of file- and directory names, so better keep it simple.

Context names must be unique.

Each context must have a name field, all other fields are optional.

Example:

contexts:
 - name: name_of_this_context.
   # ... possible other fields
 - name: name_of_other_context
   # ... possible other fields

buildable

buildable

Set if this context if apps can be built for this context. Possible values: [true, false]. Defaults to false.

Example:

context:
 - name: kids_birthday_party
   parent: birthday_party
   buildable: true

parent

parent

The parent of this context. If unset, defaults to default.

Example:

context:
 - name: some_name
   parent: other_context_name

help

This field contains an optional string with help text for the context.

Example:

contexts:
  - name: nrf52840
    parent: cortex-m4
    help: "Nordic nRF52840 SoC with BLE support"

env

env

A map of variables. Each map key correspondents to the variable name. Each value must be either a single value or a list of values.

Example:

context:
 - name: some_name
   env:
     CFLAGS:
       - -D_this_is_a_list
       - -D_of_multiple_values
     LINKFLAGS: -Ithis_is_a_single_value

selects

selects

List of modules that are always selected for builds in this context or any of its children.

Example:

context:
 - name: birthday_party
   selects:
     - cake
     - music

disables

List of modules that are disabled for builds in this context or any of its children.

Example:

context:
 - name: kids_birthday_party
   parent: birthday_party
   disables:
     - beer

provides

This field contains an optional list of virtual module names that this context provides.

This works the same as provides on modules. The listed names are set on the context’s associated module, making the context satisfy dependencies on those names.

Example:

contexts:
  - name: context_with_hw_support
    parent: default
    provides:
      - hardware_abstraction

provides_unique

This field contains an optional list of virtual module names that this context uniquely provides.

Like provides, but also adds the listed names to conflicts. This means only one context (or module) that provides the same name via provides_unique can be active in a build.

Example:

contexts:
  - name: context_uart
    parent: default
    provides_unique:
      - serial_backend

  - name: context_usb
    parent: default
    provides_unique:
      - serial_backend

In this example, a build cannot use both context_uart and context_usb since they both uniquely provide serial_backend.

requires

This field contains an optional list of module names that must be present when this context is used.

Unlike selects, requires does not automatically select the listed modules. The build fails if a required module is not selected by something else.

Example:

contexts:
  - name: secure_context
    parent: default
    requires:
      - crypto_support

rules

This fiels contains a list of laze build rules.

A laze build rule mostly correspondents to Ninja rules.

Example:

contexts:
  - name: default
    rules:
      - name: CC
        in: c
        out: o
        cmd: ${CC} ${in} -o ${out}

rule fields

name

This field contains the name for this rule.

It will be used as rule name (or rule name prefix) in the generated Ninja build file, so use short, capital names/numbers/underscores like “CC”, “CXX”, …

Some names have special meaning in laze:

  • LINK is the rule used to combine compiled source files for a given application.
  • GIT_DOWNLOAD will be used for downloading source files
  • GIT_PATCH will be used for applying patches on git repositories.

Example:

context:
 - # ...
   rules:
   - name: CC
   # ... possible other fields

cmd

This field contains the command to be run when the output needs to be rebuilt. Will end up in Ninja’s “command” field for this rule.

Example:

    rules:
      - name: CC
        cmd: "${CC} -c ${in} -o ${out}""
        # ... other fields ...

description

Human readable description, will end up in Ninja’s “description” field for this rule.

Example:

    rules:
      - name: CC
        description: "Compiling ${in}..."
        # ... other fields ...

in

in is used to specify the extension of input files for this rule.

laze will look up a rule for each source file depending on this field.

Example:

    rules:
      - name: CC
        description: CC ${out}
        in: "c"
        # ... other fields ...

out

out is used to specify the extension of output files for this rule.

laze will use this to generate output file names.

Example:

    rules:
      - name: CC
        description: CC ${out}
        in: "c"
        out: "o"
        # ... other fields ...

options

Currently unused.

gcc_deps

This field is used to enable Ninja’s automatic header dependency tracking.

It takes a filename (usually containing $out) and will make Ninja read that file as Makefile expecting to contain extra dependencies of the source file.

laze will use this to set depfile = ... combined with deps = gcc in the generated Ninja build file.

See the Ninja Manual for more information.

Example:

    rules:
      - name: CC
        description: CC ${out}
        in: "c"
        out: "o"
        gcc_deps: "$out.d"
        cmd: "${CC} -MD -MF $out.d ${CFLAGS} -c ${in} -o ${out}"

rspfile

Use this to specify a file containing additional command line arguments. Supposed to be used in combination with rspfile_content.

This can be used if resulting command lines get too long, and the used tool supports reading arguments from file.

Example:

    rules:
      - name: LINK
        in: 'o'
        rspfile: $out.rsp
        rspfile_content: $in
        cmd: ${LINK} @${out}.rsp -o ${out}

rspfile_content

Use this to specify the contents of an rspfile.

Example:

    rules:
      - name: LINK
        in: 'o'
        rspfile: $out.rsp
        rspfile_content: $in
        cmd: ${LINK} @${out}.rsp -o ${out}

pool

This allows limiting execution of this rule’s to named concurrency pools. See the Ninja Manual for more information.

Currently, the only supported pool is Ninja’s predefined console pool.

From the Ninja manual:

It has the special property that any task in the pool has direct access to the
standard input, output and error streams provided to Ninja, which are normally
connected to the user’s console (hence the name) but could be redirected. This
can be useful for interactive tasks or long-running tasks which produce status
updates on the console (such as test suites).

While a task in the console pool is running, Ninja’s regular output (such as
progress status and output from concurrent tasks) is buffered until it
completes.

Example:

    rules:
      - name: FOO
        pool: console
        cmd: some_command_that_needs_console_input

always

This boolean flag makes the rule always run. It basically makes the resulting ninja build entry “phony”.

Currently, this only has any effect for the special LINK rule.

shareable

This boolean flag controls output sharing. It defaults to true. See object sharing for details.

When enabled for a rule, this rule’s output files will end up in a location that is context-independent:

${build-dir}/objects/<filestem>.<hash>.<ext>

E.g., a source file some-module/foo.c would end up in ${build-dir}/objects/some-module/foo.<hash>.o.

When disabled, the output will end up in a builder and app specific location:

${bindir}/<file-path>/foo.o

The variable ${bindir} defaults to ${build-dir}/out/${builder}/${app}, so e.g., a source file some-module/foo.c built for builder host and app bar would end up in ${bindir}/out/host/bar/some-module/foo.o.

Example:

context:
 - # ...
   rules:
   - name: MYRULE
     shareable: false # disable object sharing for this rule
   # ... possible other fields

export

This field contains an optional list of variables to export to the rule’s build environment.

Each entry can be either a bare variable name or a key-value map. A bare variable name exports the corresponding laze variable’s value. A key-value map exports the given value under the given name.

Example:

    rules:
      - name: CC
        cmd: "${CC} -c ${in} -o ${out}"
        export:
          - CFLAGS
          - CC_VERSION: 12
        # ... other fields ...

var_options

This field contains a map that controls how variables are flattened into strings during the build process.

Each map key is a variable name. The value is a set of formatting options that are applied when the variable’s list of values is joined into a single string.

options

OptionTypeDefaultDescription
fromstring-Read values from another variable instead of this one
joinerstring" "String used to join list elements
prefixstring-String prepended before each element
suffixstring-String appended after each element
startstring-String prepended once before the entire result
endstring-String appended once after the entire result

Using from creates a new variable from another variable’s values. A variable cannot have both its own values and a from option.

Examples

Adding a prefix to each element:

builders:
  - name: my_builder
    var_options:
      notify:
        prefix: -DMODULE_
    env:
      notify:
        - apple
        - banana

This flattens notify to -DMODULE_apple -DMODULE_banana.

Using a custom joiner and wrapping the result:

contexts:
  - name: default
    var_options:
      includes:
        joiner: ","
        prefix: "-I"
        start: "("
        end: ")"
    env:
      includes:
        - src
        - lib

This flattens includes to (-Isrc,-Ilib).

Creating a new variable from an existing one:

contexts:
  - name: default
    var_options:
      module_defines:
        from: modules_used
        prefix: -DMODULE_

This creates module_defines by reading the values of modules_used and prefixing each element with -DMODULE_.

tasks

This field contains a map of named task entries.

Tasks are operations that are executed by laze (not ninja), after optionally building the app binary. They can be used to execute a compiled binary, run it through a debugger, flash an embedded target, and more.

Each map key is the task name, which is used to invoke the task from the command line (e.g., laze run, laze size).

Tasks can be defined on contexts, builders, and modules. Tasks defined in parent contexts are inherited by child contexts and builders. Module tasks can override context tasks of the same name. If two modules define a task with the same name, they are treated as conflicting and cannot both be selected in the same build.

Example:

contexts:
  - name: default
    tasks:
      info:
        cmd:
          - "echo binary: ${out}"
      size:
        cmd:
          - "${SIZE} ${out}"

builders:
  - name: host
    tasks:
      run:
        cmd:
          - ${out}

Tasks on modules:

modules:
  - name: my_module
    tasks:
      test:
        cmd:
          - ${out} --run-tests

task fields

cmd

This field contains a list of shell commands to execute sequentially. Each entry is run as a shell command (sh -c on Unix, cmd /C on Windows).

Commands prefixed with : are treated as calls to other tasks. For example, :echo hello calls the echo task with hello as argument.

Task commands support argument substitution: $1..$N for positional arguments, $* for all arguments as a single string, and $@ for all arguments as individual arguments.

Laze variables (e.g., ${out}, ${builder}) are expanded in commands.

Example:

    tasks:
      info:
        cmd:
          - "echo binary: ${out}"
          - "echo builder: ${builder}"
      echo-all:
        cmd:
          - :echo first=$1
          - :echo all=$*

build

This field controls whether the app binary is built (via ninja) before executing the task.

Possible values: [true, false]. Defaults to true.

Set to false for tasks that don’t need a compiled binary, such as cleaning up or printing configuration information.

Example:

    tasks:
      clean:
        build: false
        cmd:
          - rm -rf ${bindir}

help

This field contains an optional string with help text for the task. It is used for CLI shell completion.

Example:

    tasks:
      flash:
        help: "flash the compiled binary to the target device"
        cmd:
          - flash-tool ${out}

export

This field contains an optional list of variables to export to the task’s shell environment.

Each entry can be either a bare variable name or a key-value map. A bare variable name exports the corresponding laze variable’s value. A key-value map exports the given value under the given name.

Exported variables are also available to subtasks called via the : prefix.

Example:

    tasks:
      deploy:
        export:
          - CFLAGS
          - TARGET_ADDR: 192.168.1.42
        cmd:
          - deploy-tool --addr $TARGET_ADDR ${out}

ignore_ctrl_c

This field controls whether Ctrl+C (SIGINT) signals are ignored during task execution.

Possible values: [true, false]. Defaults to false.

This is useful for tasks that run interactive programs which handle Ctrl+C themselves, such as debuggers or serial monitors.

Example:

    tasks:
      debug:
        ignore_ctrl_c: true
        cmd:
          - gdb ${out}

required_vars

This field contains an optional list of variable names that must be set for the task to be available in a build configuration.

Example:

    tasks:
      flash:
        required_vars:
          - FLASH_TOOL
          - TARGET_PORT
        cmd:
          - ${FLASH_TOOL} -p ${TARGET_PORT} ${out}

required_modules

This field contains an optional list of module names that must be selected for the task to be available in a build configuration.

Example:

    tasks:
      flash:
        required_modules:
          - flasher_support
        cmd:
          - flash-tool ${out}

workdir

This field contains an optional string specifying the working directory for the task’s commands. If not set, commands run in the application’s build directory.

A relative path is resolved relative to the application’s build directory. An absolute path is used as-is.

Example:

    tasks:
      test:
        workdir: ../test-fixtures
        cmd:
          - ./run-tests.sh ${out}

modules

‘modules’ contains a list of module entries.

Example:

modules:
 - name: name_of_this_module.
   # ... possible other fields
 - name: name_of_other_context
   # ... possible other fields

module fields

name

Name of this module. Any UTF8 string is valid. This will be used as part of file- and directory names, so better keep it simple.

Within each context, module names must be unique.

Each module must have a name. If the field is ommitted, ${relpath} is used.

Example:

modules:
 - name: name_of_this_module.
   # ... possible other fields
 - name: name_of_other_module
   # ... possible other fields

context

This field specifies which context(s) a module belongs to.

Can be a single string or a list of strings. If omitted, the module belongs to the "default" context.

When a list is given, the module is available in each of the specified contexts (and their children).

Example:

modules:
  - name: platform_support
    context: linux

  - name: shared_module
    context:
      - builder_arm
      - builder_riscv

help

This field contains an optional string with help text for the module.

Example:

modules:
  - name: crypto_support
    help: "provides AES and SHA256 implementations"

sources

List of source files used to build this module.

This field is optional.

The file path is relative to the directory of the yaml file this module is described in.

Example:

modules:
 - name: cake
   sources:
     - sugar.c
     - flour.c
     - dressings/cream.c
   # ... possible other fields

env

This field contains a map of environment variable scopes for the module.

Three scopes are available:

  • local: Variables only visible to this module’s own build commands. Applied after exported variables from dependencies, so it can override imported values.
  • export: Variables visible to this module and transitively to all modules that depend on or use this module.
  • global: Variables merged into the build’s global environment, visible to all modules in the build.

Each scope is a map where keys are variable names and values are either a single value or a list of values.

Example:

modules:
  - name: my_module
    env:
      local:
        CFLAGS:
          - -DLOCAL_ONLY
      export:
        CFLAGS:
          - -I${relpath}/include
      global:
        GLOBAL_VAR: my_module_was_here

depends

List of modules this module depends on.

If a module depends on another module, it will pull that module into the build, and import that module’s exported environment.

Note: depending a module is equivalent to both selecting and using it.

If a dependency name is prefixed with “?”, the dependency turns into an optional dependency. That means, if the dependency is available, it will be depended on, otherwise it will be ignored.

Example:

modules:
 - name: datetime
   depends:
     - date
     - time
   # ... possible other fields
 - name: party
   depends:
     - people
     - ?music
     - ?alcohol
   # ... possible other fields

selects

List of modules this module selects.

If a module depends on another module, it will pull that module into the build.

If a dependency name is prefixed with “?”, the dependency turns into an optional dependency. That means, if the dependency is available, it will be selected, otherwise it will be ignored.

Example:

modules:
 - name: release_build
   selects:
     - optimize_speed
     - disable_debug
   # ... possible other fields

uses

List of modules this module uses.

If a module uses another module, it will import that module’s exported environment, if that module is part of the build.

Example:

modules:
 - name: datetime
   uses:
     - timezone_support
   # ... possible other fields

conflicts

List of modules that this module conflicts. Two conflicting modules cannot both be part of a build.

A conflicting B implies B conflicting A.

It is possible to conflict a provided feature to ensure a module is the only selected module providing a feature, but provides_unique should be used for this.

Example:

modules:
 - name: gnu_libc
   conflicts:
     - musl_libc
   # ... possible other fields

provides

List of features that this module provides.

If a module depends on or selects something provided by another module, it works like an alias.

A feature can be provided by multiple modules. In that case, all providing modules will be considered. Unless the dependency is optional, it fails if not at least one module can be resolved. All modules that resolve will be used.

See also provides_unique

Example:

modules:
 - name: amazon_s3
   provides:
     - s3_api
   # ... possible other fields
 - name: backblaze_s3
   provides:
     - s3_api
   # ... possible other fields

 - name: s3_storage
   depends:
     # both "amazon_s3" and "backblaze_s3" will be added to dependencies
     - s3_api

provides_unique

List of features that this module provides, but needs to be the only provider.

provides_unique can be used for this.

Adding to provides_unique is equivalent to adding to both provides and conflicts.

Example:

modules:
 - name: gnu_libc
   # ensure only one "libc" will be chosen
   provides_unique:
     - libc
 - name: musl_libc
   provides_unique:
     - libc
   # ... possible other fields

requires

This field contains an optional list of module names that must be present in the build for this module to be valid.

Unlike depends, requires does not automatically select the listed modules. The build fails if a required module is not selected by something else.

Example:

modules:
  - name: network_driver
    requires:
      - hardware_abstraction

build

This field defines a custom build step for the module.

It is used for code generation or pre-processing steps where a module needs to produce source files or other artifacts before normal compilation. The build commands have access to the module’s environment variables.

fields

  • cmd: Required. A list of shell commands. They are joined with && to form a single ninja build command.
  • out: Optional. A list of output files produced by the custom build.
  • gcc_deps: Optional. A string specifying a file for gcc-style dependency tracking.

Example:

modules:
  - name: generated_code
    is_build_dep: true
    depends:
      - config_module
    build:
      cmd:
        - echo ${VARIABLE} > build/${builder}/generated.c
      out:
        - build/${builder}/generated.c
    sources:
      - build/${builder}/generated.c

download

This field specifies a git repository to download the module’s source files from.

Modules with download automatically have is_build_dep set to true, and their srcdir points to the download directory.

The context must define GIT_DOWNLOAD and (if using patches) GIT_PATCH rules.

fields

  • git: Required. A map with url and one of commit, branch, or tag.
  • patches: Optional. A list of patch files to apply after download.
  • dldir: Optional. A string overriding the download directory. Defaults to build/dl/<relpath>/<module_name>.

Example:

modules:
  - name: external_lib
    download:
      git:
        url: https://github.com/example/lib.git
        commit: abc123
    sources:
      - lib.c

  - name: patched_lib
    download:
      git:
        url: https://github.com/example/lib.git
        commit: main
      patches:
        - 0001-fix-build.patch
    sources:
      - lib.c

notify_all

This field controls the content of the ${notify} variable for this module.

Possible values: [true, false]. Defaults to false.

When false, ${notify} contains only the names of this module’s recursive dependencies. When true, ${notify} contains the names of ALL modules in the build.

Module names in ${notify} are converted to uppercase with -, /, ., and : replaced by _.

The ${notify} variable is typically used with var_options to generate preprocessor defines.

Example:

builders:
  - name: my_builder
    var_options:
      notify:
        prefix: -DMODULE_

modules:
  - name: auto_init
    notify_all: true
    sources:
      - auto_init.c

With modules auto_init and my_driver in the build, ${notify} for the auto_init module would contain MY_DRIVER AUTO_INIT, and with the var_options above, it flattens to -DMODULE_MY_DRIVER -DMODULE_AUTO_INIT.

srcdir

This field contains an optional string that overrides the base path for the module’s source files.

By default, srcdir is the module’s relpath (the directory containing the laze YAML file), or the download directory for modules with download.

The value is also available as the ${srcdir} variable in the module’s environment.

Example:

modules:
  - name: vendor_lib
    srcdir: vendor/lib/src
    sources:
      - lib.c
      - util.c

is_build_dep

This field controls whether modules that depend on or use this module wait for its build outputs before compiling.

Possible values: [true, false]. Defaults to false.

This is useful for modules with a custom build step that generates headers or source files needed by other modules.

Modules with download automatically have is_build_dep set to true.

Example:

modules:
  - name: code_generator
    is_build_dep: true
    build:
      cmd:
        - generate-headers --out build/${builder}/generated.h
      out:
        - build/${builder}/generated.h

is_global_build_dep

This field controls whether ALL modules in the build wait for this module’s build outputs before compiling.

Possible values: [true, false]. Defaults to false.

This is a stronger variant of is_build_dep. While is_build_dep only affects direct dependees, is_global_build_dep affects every module in the entire build.

Example:

modules:
  - name: global_config_generator
    is_global_build_dep: true
    build:
      cmd:
        - generate-config > build/${builder}/config.h
      out:
        - build/${builder}/config.h

subdirs

subdirs contains a list of directory names. Laze will parse <directory name>/laze.yml.

Example:

subdirs:
  - drivers
  - utils

includes

This field contains a list of YAML file paths to include.

Paths are relative to the directory of the current file. Each included file is loaded and processed as if its content were part of the including file. Included files share the same relpath context as the including file.

Unlike subdirs, which looks for laze.yml in subdirectories and adjusts relpath, includes loads files directly without changing the path context.

Example:

# in laze.yml
includes:
  - extra-modules.yml
  - generated.yml

apps:
  - name: my_app

imports

imports contains a list of import entries, each representing a local or remote source for additional laze projects.

Example:

imports:
 - git:
    - url: https://github.com/kaspar030/laze
      tag: 0.1.17

import types

git

A git import makes laze clone additional laze files from a git repository.

git requires a url. Additionally, commit, tag or branch can be set. If neither is specified, the default branch will be checked out.

Example:

imports:
 - git:
    url: https://example.com/foo
    commit: 890c1e8e02b0a872d73b1e49b3da45a5c8170016
    # or
    # tag: v1.2.3
    # or
    # branch: main

laze

A laze import allows using laze files that are bundled inside the laze binary.

laze needs the name of the desired file.

Currently, the following are defined:

NamePurpose
defaultsa default context and host builder for simple C projects

Example:

imports:
  - laze: defaults

path

A path import allows using a local directory as import source.

If path is not absolute, it is considered relative to the project root.

Optionally, path can be symlinked into the imports directory inside the laze build directory by setting symlink: true in the import. The symlink name within $build_dir/imports defaults to the last path component of path. This can be changed by setting name. Using a symlink helps turning absolute pathnames into relative ones. This might be desirable for privacy reasons, or help with reproducible builds.

Example:

imports:
  - path: /path/to/local/directory
  - path: /path/to/another/local/directory
    symlink: true
  - path: /path/to/a/third/local/directory
    symlink: true
    name: directory3

defaults

This field contains a map that defines default properties for modules and/or apps defined in the same file and its subdirectories.

The map keys are "module" and/or "app". Each value is a module entry whose fields are inherited by all modules (or apps) in the same file and any files included via subdirs. Defaults propagate down through subdirectories and can be overridden at each level.

Any module field can be set in defaults, including context, sources, env, depends, and others.

Example:

defaults:
  module:
    context: my_builder
    env:
      export:
        CFLAGS:
          - -Wall

apps:
  - name: my_app
    # inherits context: my_builder and CFLAGS from defaults
    sources:
      - main.c

Variables defined by laze

appdir

This variable contains the path in which the app of a build was defined.

Useful mostly for tasks, as ${relpath} would evaluate to the directory in which the task is defined.

Examples:

# in apps/foo/laze.yml
apps:
  - name: foo_app


# in modules/foo/laze.yml
modules:
  - name: foo
    env:
      export:
        CFLAGS:
          - -DAPPDIR=\"${appdir}\"

builder

This variable contains the name of the builder.

Examples:

modules:
  - name: foo
    env:
      export:
        CFLAGS:
          - -DBUILDER=\"${builder}\"

modules

This variable evaluates to a list of all modules used in a build.

outfile

This variable contains the full path to the linked output binary of a build. Defaults to ${bindir}/${app}.elf.

Can be overridden in env: to change the output filename or extension. The final resolved value becomes available as ${out} in tasks.

If a POST_LINK rule is defined, its out extension replaces the original extension (e.g., .elf becomes .bin).

Example:

builders:
  - name: my_builder
    env:
      outfile: "${bindir}/${app}.bin"

relpath

This variable is evaluated early and will be replaced with the relative (to the project root) path of the laze yaml file.

Example:

modules:
  - name: foo
    env:
      export:
        CFLAGS:
          - -I${relpath}/include

root

This variable will be replaced with the relative path to the root of the main project. This can be used for specifying root-relative path names. Usually (for laze projects that were not imported), this contains . If a laze file is part of an import of another laze project, ${root} contains the relative path to the location where the import has been downloaded.

Example:

# in some project:
imports:
  - git:
     url: .../foo.git
     commit: ...


# in .../foo.git/some/subdir/laze.yml:

modules:
  - name: foo
    env:
      export:
        CFLAGS:
          - -I${root}/include
          # this will evaluate to `-Ibuild/imports/foo-<hash>/include`

srcdir

Contains the relative (to project root) base path of a module’s source files. If a module has not been downloaded, this is usually identical to ${relpath}. For downloaded modules, it points to the module’s download directory.

app

This variable contains the name of the app being built.

Example:

modules:
  - name: foo
    env:
      export:
        CFLAGS:
          - -DAPP_NAME=\"${app}\"

build-dir

This variable contains the top-level build output directory. Defaults to build, and can be changed with the --build-dir / -B CLI flag.

contexts

This variable evaluates to a list of all context names in the current builder’s context chain, from root to builder.

host::arch

This variable contains the CPU architecture of the host machine running laze (e.g., x86_64, aarch64).

host::family

This variable contains the OS family of the host machine running laze (e.g., unix, windows).

host::os

This variable contains the operating system of the host machine running laze (e.g., linux, macos, windows).

host::tuple

This variable contains the full target triple of the host machine running laze (e.g., x86_64-unknown-linux-gnu).

LAZE_BIN

This variable contains the absolute path to the currently running laze binary. Used internally by download rules.

notify

This variable contains a list of module names relevant to the current module. By default, it contains only the names of recursive dependencies. If notify_all is set to true, it contains all modules in the build.

Module names are converted to uppercase with -, /, ., and : replaced by _.

Typically used with var_options to generate preprocessor defines.

out

In build rules, this variable passes through to ninja’s $out variable.

In tasks, this variable contains the final output binary path (the resolved value of outfile, possibly with a changed extension from a POST_LINK rule).

project-root

This variable contains the path to the project root directory.

relroot

This variable contains the relative path from the current module’s location back to the project root. For example, a module at src/drivers/ would have relroot set to ../...

meta

This field is ignored by laze and can contain arbitrary data.

It is available on the top level, on contexts, modules, rules, and tasks, and can be used by external tools to attach metadata to laze build files.

Example:

modules:
  - name: my_module
    meta:
      maintainer: alice
      license: MIT

Expression evaluation

Laze supports inline expression evaluation using the $(...) syntax.

Expressions are evaluated after variable expansion (${...}), so variables are resolved first, then expressions are evaluated.

Expression evaluation is powered by the evalexpr crate.

Syntax

  • $(expression) evaluates the expression and substitutes the result.
  • Nesting is supported: inner expressions are evaluated first.
  • $$(...) escapes the dollar sign and is not evaluated.

Examples

Arithmetic:

env:
  RESULT: "$(1 + 1)"
  # evaluates to "2"

Nested expressions:

env:
  RESULT: "$(1 + $(1 + 1))"
  # evaluates to "3"

Functions:

env:
  MAX_VAL: "$(max(1, 2, 3, 4))"
  # evaluates to "4"

String functions:

env:
  UPPER: '$(str::to_uppercase "foobar")'
  # evaluates to "FOOBAR"

Combined with variable expansion:

env:
  BASE: "10"
  OFFSET: "$(${BASE} + 5)"
  # evaluates to "15"

Supported operations

  • Arithmetic: +, -, *, /, %, ^
  • Comparison: ==, !=, <, <=, >, >=
  • Boolean: &&, ||, !
  • String concatenation: + (on strings)
  • Conditional: if(condition, then, else)

Supported functions

Math: min, max, floor, ceil, round, abs, sqrt, exp, ln, log

String: str::to_uppercase, str::to_lowercase, str::trim, str::contains, str::starts_with, str::ends_with, str::len, str::from, str::substring, str::regex_matches, str::regex_replace