-
Notifications
You must be signed in to change notification settings - Fork 4
llar Build Matrix
An LLAR build matrix describes the build variants of one module version. Build systems commonly vary by operating system, architecture, ABI, toolchain, linkage, and package features. LLAR represents these inputs through one matrix model and includes the effective selection in the artifact identity.
The design is inspired by Go build constraints. Go constraints select source files for one compilation; an LLAR matrix selects a complete build result. Two accepted selections may use different dependencies, flags, commands, or installed output, and therefore produce different reusable artifacts.
type Matrix struct {
Require map[string][]string
Options map[string][]string
DefaultOptions map[string][]string
}| Part | Purpose | Examples |
|---|---|---|
Require |
Build-environment dimensions that propagate through dependency resolution. Dependencies using the same dimension must select compatible values. |
os, arch, ABI, libc, toolchain |
Options |
Package-specific build choices. An option affects only formulae that read it. |
shared, debug, zlib, tests
|
DefaultOptions |
Option values used when the request omits those keys. |
debug=OFF, shared=OFF
|
A build request supplies a flat set of matrix values. Formula usage determines
whether a key is consumed through target.require or target.options; callers
do not classify request values themselves.
Formula callbacks read the active matrix selection through target:
import "slices"
onRequire (proj, deps) => {
if slices.contains(target.options["zlib"], "ON") {
deps.require "madler/zlib", "v1.3.1"
}
}
onBuild (ctx, proj, out) => {
if slices.contains(target.require["os"], "linux") {
...
}
if slices.contains(target.options["shared"], "ON") {
...
}
}Only keys read by the formula participate in that module's matrix semantics. Unused request keys do not change its artifact identity.
defaults supplies omitted option values:
defaults {
"debug": "OFF",
"shared": "OFF",
}Requested values override defaults with the same key. Defaults apply only to options; they do not set required dimensions or declare every legal value.
Given this request:
os=linux, arch=arm64, zlib=ON
if pnggroup/libpng reads zlib and depends on madler/zlib, their effective
matrices are:
pnggroup/libpng: os=linux, arch=arm64 | zlib=ON
madler/zlib: os=linux, arch=arm64
Required dimensions propagate across the dependency edge. Package options stay only where they affect formula behavior.
Each dimension contributes possible values. Their Cartesian product describes the possible artifact variants before unsupported selections are filtered:
os: [linux, darwin]
arch: [amd64, arm64]
shared: [ON, OFF]
2 x 2 x 2 = 8 possible variants
The matrix describes variant possibility. Build policy separately decides which accepted variants are prebuilt and which are built on demand.
filter rejects a selected combination that the formula or upstream project
cannot build:
filter => {
if slices.contains(target.require["os"], "darwin") &&
slices.contains(target.require["arch"], "mips") {
return false
}
return true
}LLAR injects the selected target before running filter. A false result means
the module version does not support that selection, so dependency discovery and
building do not continue for it.
Filter is appropriate for external compatibility limits, such as an upstream project not supporting a platform combination. It should not be the primary way to model options whose meanings depend on each other.
A matrix dimension should remain meaningful and independently selectable regardless of the values chosen for other dimensions. Orthogonal dimensions keep the matrix close to a Cartesian product instead of turning it into a graph of conditional keys and values.
This matters because orthogonality provides:
- Predictable combinations: LLAR and formula authors can enumerate variants without a separate cross-key constraint solver.
- Stable artifact identity: every selected value contributes meaningfully to the result, avoiding duplicate artifacts that differ only by irrelevant keys.
- Independent propagation: required dimensions can move across dependency edges without carrying package-specific conditional rules.
- Local formula logic: matrix discovery, defaults, overrides, tests, and maintenance can treat each key independently instead of reconstructing option prerequisites.
If one option exists only under a particular value of another option, merge the valid combinations into one dimension. Do not model an OpenSSL-only linkage option separately:
tls: [native, openssl]
openssl-linkage: [static, shared]
openssl-linkage has no meaning when tls=native, and the Cartesian product
creates meaningless selections. Represent the valid choices directly:
tls: [native, openssl-static, openssl-shared]
Use filter for genuine upstream compatibility restrictions, not to preserve a
dependent option model that can be expressed as one orthogonal dimension.