-
Notifications
You must be signed in to change notification settings - Fork 4
llar Build Matrix
One version of a library can be built in several ways. For example, the same source may produce these variants:
linux + amd64 + shared=OFF
linux + arm64 + shared=OFF
linux + amd64 + shared=ON
LLAR uses a build matrix to describe these differences. The selected matrix values tell a formula which dependencies, flags, commands, and output it should use. A different accepted selection may produce a different reusable artifact.
LLAR's build matrix is inspired by Go build constraints. For example, when
building for Linux, Go includes a file marked with //go:build linux and leaves
out files for other targets. The constraint selects source files for one build.
LLAR instead builds reusable artifacts across a dependency graph. A build such as:
libpng: os=linux, arch=arm64 | shared=ON
zlib: os=linux, arch=arm64
selects a libpng artifact and the matching zlib dependency artifact. Some values must reach dependencies, while others belong only to the current package. This leads to LLAR's matrix model.
LLAR separates matrix values into two groups:
| Group | Meaning | Examples |
|---|---|---|
Require |
Build environment that dependencies may also need to know. |
os, arch, libc, ABI |
Options |
Build choices for the current package. |
shared, debug, zlib, tests
|
The available os and arch values are not defined separately by each
formula. They are expected to come from LLAR's platform and toolchain support as
described by the Cross Compile design, which is still WIP.
For example, a future LLAR integration may use esp32-clang internally to make
arch=xtensa-esp32 available as a build target. The request selects the target
architecture; LLAR chooses and configures the compiler behind it. A formula only
decides whether its library supports that target.
The Go representation is:
type Matrix struct {
Require map[string][]string
Options map[string][]string
DefaultOptions map[string][]string
}A build request is flat. The caller does not need to decide whether a value is
Require or Options. A formula makes that decision by reading the value through
target.require or target.options.
Formula callbacks read the selected values through target:
import "slices"
onRequire (proj, deps) => {
if slices.contains(target.options["zlib"], "ON") {
deps.require "madler/zlib", "v1.3.1"
}
}
onBuild ctx => {
if slices.contains(target.require["os"], "linux") {
...
}
if slices.contains(target.options["shared"], "ON") {
...
}
}In this example:
-
osis required build environment and is passed through dependency resolution. -
zlibandsharedare choices used only by formulas that support them.
Only values read by a formula affect that module's build variant. An unrelated request value does not create another artifact for the module.
defaults supplies option values when the request does not provide them:
defaults {
"debug": "OFF",
"shared": "OFF",
}An explicitly requested value replaces the default for the same key. Defaults
apply only to Options; they do not set os, arch, or other Require values.
Suppose the request is:
os=linux, arch=arm64, zlib=ON
pnggroup/libpng reads the zlib option and depends on madler/zlib. Their
effective matrices are:
pnggroup/libpng: os=linux, arch=arm64 | zlib=ON
madler/zlib: os=linux, arch=arm64
The environment values reach the dependency. The zlib option stays with
libpng because zlib's own formula does not use that option.
When a matrix has several values, each choice can produce another build variant:
os: [linux, darwin]
arch: [amd64, arm64]
shared: [ON, OFF]
This gives up to eight combinations:
2 operating systems x 2 architectures x 2 shared modes = 8
The matrix describes what can be selected. LLAR may prebuild some combinations and build others only when requested.
Not every combination is supported by every project. A formula can reject an
unsupported selection with filter:
filter => {
if slices.contains(target.require["arch"], "xtensa-esp32") {
return false
}
return true
}LLAR sets target before running the filter. When the filter returns false,
LLAR stops before dependency discovery and building for that selection.
In this future example, LLAR may know how to build for xtensa-esp32, while the
current library does not support that architecture. Filter rejects the
library-specific limitation without removing the target from LLAR's platform
support.
Matrix keys should be understandable on their own. Choosing one key should not make another key appear, disappear, or change its meaning.
Dependent keys quickly create confusing combinations. For example:
tls: [native, openssl]
openssl-linkage: [static, shared]
What does this mean?
tls=native, openssl-linkage=static
The linkage value has no effect because OpenSSL is not selected. It still makes the matrix look larger and may create different cache names for builds that are actually the same.
Represent the real choices as one option instead:
tls: [native, openssl-static, openssl-shared]
Independent options make formulas easier to write because:
- every combination has a clear meaning;
- defaults cannot produce half-valid selections;
- cache and artifact names do not contain irrelevant values;
- dependencies can receive Require values without learning option rules from another package;
- LLAR can discover and test each matrix key without first satisfying another key.
Use Filter for genuine platform limitations. When two options describe one combined choice, merge them into one option instead of connecting them with Filter rules.