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:
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:
-
“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”.
-
“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.
-
“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.
-
“conflicts” -> the modules cannot be used together in the same build.
-
“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.
-
“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.
-
“if this than that” -> if “this” is part of the dependency tree, “depend” on “that”
-
“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.
namecontexthelpsourcesenvdependsselectsusesconflictsprovidesprovides_uniquerequiresbuilddownloadtasksnotify_allsrcdirmeta
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.
nameparenthelpenvselectsdisablesprovidesprovides_uniquerequiresrulestasksvar_optionsmeta
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
namebuildableparenthelpenvselectsdisablesprovidesprovides_uniquerequiresrulestasksvar_optionsmeta
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
namedescriptioncmdinoutoptionsgcc_depsrspfilerspfile_contentpoolalwaysshareableexportmeta
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:
LINKis the rule used to combine compiled source files for a given application.GIT_DOWNLOADwill be used for downloading source filesGIT_PATCHwill 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
| Option | Type | Default | Description |
|---|---|---|---|
from | string | - | Read values from another variable instead of this one |
joiner | string | " " | String used to join list elements |
prefix | string | - | String prepended before each element |
suffix | string | - | String appended after each element |
start | string | - | String prepended once before the entire result |
end | string | - | 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
namecontexthelpsourcesenvdependsselectsusesconflictsprovidesprovides_uniquerequiresbuilddownloadtasksnotify_allsrcdiris_build_depis_global_build_depmeta
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 withurland one ofcommit,branch, ortag.patches: Optional. A list of patch files to apply after download.dldir: Optional. A string overriding the download directory. Defaults tobuild/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:
| Name | Purpose |
|---|---|
defaults | a 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