-
Notifications
You must be signed in to change notification settings - Fork 4
Cross compile Design
LLAR provides non-invasive C-family cross compilation. Existing Formulas keep
using their current CMake, configure, pkg-config, and direct compiler
commands. They do not add a second cross-compilation recipe or manually map the
selected OS and architecture to compiler flags.
LLAR performs the cross-compilation work around the Formula:
-
cmd/llarreads the selected target fromformula.Matrix. - It selects and prepares the target sysroot as an ordinary Formula.
-
llvm.Newprepares target-specific compiler and linker commands. -
c.NewTargetprojects those commands into the C build mechanisms used by the Formula. -
build.Builderapplies the target while the existing Formula hooks run.
The core rule is:
Formula code describes how to build the project. LLAR supplies where the result runs and the compiler, linker, SDK, and build-system settings required to build for that target.
Formula configuration remains authoritative. LLAR fills missing target settings but does not replace an explicit CMake toolchain, configure option, configure environment value, or explicit tool path.
The generic build module remains language-neutral. It receives a
build.Target, applies its command patches, and does not know that the target
represents C, LLVM, a sysroot, or cross compilation.
The design was derived from existing package and build systems, then checked with Linux ELF, Darwin Mach-O, Autoconf, and real zlib experiments.
The investigated systems place the same concepts at different boundaries:
| System | Model | Relevant conclusion for LLAR |
|---|---|---|
| Conan 2 | Build and host profiles are separate. A profile or generator can provide compiler executables and a separate tools.build:sysroot. Official examples usually consume complete external SDKs or cross toolchains. |
Compiler and sysroot can be separate prepared inputs, but the sysroot must be a complete target environment rather than an arbitrary libc file. |
| Xmake | A standalone toolchain is normally a complete SDK containing tools, headers, and libraries. | A build must receive a coherent target environment, even if LLAR stores its components separately. |
| Bazel | Execution and target platforms select a language-specific toolchain. A C++ toolchain owns its tools, sysroot, flags, and runtimes. | Generic build orchestration should depend on a target contract; C-specific facts belong to a C implementation. |
| Nixpkgs | Compiler, libc, and other components are separate derivations, then bound by stdenv, package sets, and wrappers. |
Physical separation is viable, but LLAR does not adopt Nix's wrapper and runtime-store linkage model. |
References:
- Conan cross building
- Conan toolchain packages
- Xmake toolchain configuration
- Nixpkgs cross compilation
- Bazel toolchains
- Bazel C++ toolchain configuration
This comparison led to two design choices.
First, LLAR manages a complete sysroot: target headers, startup objects,
link-time libraries, loader conventions, and related SDK content. It does not
treat libc.so as an ordinary library that can be exchanged independently of
the target ABI.
Second, LLAR uses LLVM's per-invocation target model. Clang accepts a target and sysroot as command arguments, while a GCC cross compiler normally has its target encoded in the compiler instance. LLVM can therefore prepare one explicit command for each selected target without changing Formula code.
The Linux experiment used:
build platform: Linux amd64, Ubuntu 18.04
target platform: Linux arm64
cross packages: glibc 2.27 development packages
runtime: Linux arm64, Ubuntu 22.04, glibc 2.35
An AArch64 shared library using malloc and free was built under /work,
moved to /artifact, linked to a consumer, and run on the target system. The
consumer printed:
probe=42
The generated library and consumer recorded:
DT_NEEDED: libc.so.6
VERNEED: GLIBC_2.17
loader: /lib/ld-linux-aarch64.so.1
It did not contain /work, the cross sysroot path, LLAR workspace paths,
RPATH, or RUNPATH. The target loader bound malloc@GLIBC_2.17 and
free@GLIBC_2.17 to the target machine's glibc 2.35.
This establishes the Linux runtime rule used by the proposal:
A sysroot supplies the build-time ABI baseline. It does not replace the target operating system's runtime libc or change the standard dynamic-linking convention.
The experiment used glibc 2.27 packages. The observed symbols required only
GLIBC_2.17, but that does not make the experiment equivalent to building
against a complete glibc 2.17 sysroot.
The Darwin experiment used:
build platform: Linux arm64
compiler: Clang 14
linker: ld64.lld
SDK: macOS 14.4 SDK
target: arm64-apple-macos11.0
runtime: macOS arm64
The generated Mach-O library and consumer were moved away from their build directory and executed on macOS. The consumer printed:
probe=42
The artifacts contained @rpath/libprobe.dylib and
/usr/lib/libSystem.B.dylib, but no /work, /sdk, or LLAR workspace path.
The ad-hoc signature passed validation, and dyld bound _malloc and _free to
the target system's libSystem.
This establishes the corresponding Darwin rule:
The SDK supplies build-time target declarations and link stubs. The installed artifact continues to use the target system's normal dyld and
libSystemconventions.
The experiment validates command and runtime behavior. It does not define how a macOS SDK is acquired or distributed.
Configure scripts do not share one universal target interface.
A GNU Autoconf probe built with a cross compiler but without --host failed
with status 77:
checking whether we are cross compiling...
configure: error: cannot run C compiled programs.
If you meant to cross compile, use `--host'.
The same probe succeeded after receiving the target host tuple and reported cross compilation:
./configure --host=aarch64-apple-darwin
checking whether we are cross compiling... yes
However, zlib 1.3.1 uses a custom script named configure. It contains
CHOST, does not declare --host, and rejects an injected --host argument.
The resulting rule is based on the actual script rather than its filename:
- every configure script receives missing compiler and binary-tool commands;
- LLAR appends
--hostonly when the script contains that literal option; -
CHOSTalone does not match; - a Formula-provided
--hostis preserved.
The complete LLAR path was exercised with an unchanged zlib 1.3.1 source tree:
| Build platform | Target platform | Target environment | Result |
|---|---|---|---|
| Linux arm64 | Darwin arm64 | Xcode 14.5 SDK fixture | macOS consumer printed zlib=1.3.1 compressed=27
|
| Linux amd64 | Linux arm64 | Bootlin AArch64 glibc 2.24 sysroot | Linux arm64 consumer printed zlib=1.3.1 compressed=27
|
These experiments validate target injection, source build, archive creation, artifact relocation, target linking, and target execution. The Bootlin fixture was glibc 2.24 and therefore does not validate the fixed glibc 2.17 Formula.
The experiments in this section exercise C libraries. The C++ command path is part of the same target contract, but these results are not presented as C++ runtime compatibility evidence.
Cross compilation is activated by the selected matrix, not by Formula code. For example, an existing CMake Formula continues to run:
cmake configure
cmake build
cmake install
When the selected OS or architecture differs from the host, LLAR supplies the target toolchain and sysroot to those same commands. The Formula does not add a cross-specific branch.
The same rule applies to configure scripts, pkg-config, and direct C tools.
LLAR observes commands at the existing execbroker boundary and adds only the
missing target defaults.
This rule assumes that the upstream build system has a cross-compilation path. LLAR supplies target facts but does not invent project-specific results for a configure-time test that requires executing a target binary.
Cross compilation is active when the selected target OS or architecture differs from the host. Built-in C targets select these default sysroot Formulas:
| Target OS | Target architecture | Default sysroot Formula |
|---|---|---|
linux |
amd64 |
bminor/glibc@glibc-2.17 |
linux |
arm64 |
bminor/glibc@glibc-2.17 |
darwin |
amd64 |
joseluisq/macosx-sdks@14.5 |
darwin |
arm64 |
joseluisq/macosx-sdks@14.5 |
A sysroot is an ordinary Formula and module.Version. cmd/llar passes it as
a generic modules.Options.Roots entry so it participates in the same MVS
resolution as the requested Formula. The module loader does not know that the
extra root is a sysroot.
Linux reserves the presence of Matrix.Require["libc"] as the convention for
a Formula-owned libc selection. If the key exists, cmd/llar does not add the
fixed glibc Formula. LLAR does not normalize or interpret the value.
A sysroot Formula publishes its actual directory through existing C metadata:
--sysroot=<absolute output path>
The path may be the Formula output root or a directory below it. The CLI reads
the path through x/metadata/cc; it does not infer the path from the module
installation directory.
The sysroot path exists only after the sysroot Formula has been built or
restored. Preparation therefore uses the same Builder.Build entrypoint twice:
- Prepare an LLVM toolchain for the target OS and architecture without a sysroot.
- Create a bootstrap C target from that toolchain.
- Build or restore the selected sysroot Formula.
- Parse the exact sysroot path from its C metadata.
- Prepare the final LLVM toolchain with the parsed sysroot.
- Create the final C target and build the resolved Formula graph.
The second build reuses the sysroot through the normal build cache. There is no
separate cross-compilation builder and no sysroot knowledge inside build.
sequenceDiagram
participant CLI as "cmd/llar"
participant C as "internal/build/c"
participant Modules as "internal/modules"
participant LLVM as "internal/build/c/llvm"
participant Build as "internal/build"
participant Metadata as "x/metadata/cc"
CLI->>C: "Sysroot(target OS, target arch)"
C-->>CLI: "module.Version"
CLI->>Modules: "Load(root, Matrix, Roots: [sysroot])"
Modules-->>CLI: "one MVS-resolved graph"
CLI->>LLVM: "New(OS, Arch)"
LLVM-->>CLI: "bootstrap c.Toolchain"
CLI->>C: "NewTarget(bootstrap Toolchain)"
C-->>CLI: "bootstrap build.Target"
CLI->>Build: "Build(sysroot)"
Build-->>CLI: "sysroot Result"
CLI->>Metadata: "Parse(Result.Metadata)"
Metadata-->>CLI: "sysroot path"
CLI->>LLVM: "New(OS, Arch, Sysroot)"
LLVM-->>CLI: "final c.Toolchain"
CLI->>C: "NewTarget(final Toolchain, Sysroot)"
C-->>CLI: "final build.Target"
CLI->>Build: "Build(resolved graph)"
Build-->>CLI: "target Results"
llvm.New owns LLVM-specific target spelling, linker selection, sysroot flag
syntax, and executable lookup:
| OS/architecture | Clang target | Raw linker | Sysroot argument |
|---|---|---|---|
linux/amd64 |
x86_64-linux-gnu |
ld.lld |
--sysroot=<path> |
linux/arm64 |
aarch64-linux-gnu |
ld.lld |
--sysroot=<path> |
darwin/amd64 |
x86_64-apple-macos10.13 |
ld64.lld |
-isysroot<path> |
darwin/arm64 |
arm64-apple-macos11.0 |
ld64.lld |
-isysroot<path> |
Compiler and linker commands are slices because mandatory arguments are part of the prepared command:
CC = ["/usr/bin/clang", "--target=aarch64-linux-gnu", "-fuse-ld=lld", "--sysroot=/sdk"]
The C module consumes these commands without deriving LLVM triples, choosing LLD, or translating sysroot syntax.
build.Builder converts each brokered command into build.Command, calls
Target.Use, and applies the returned build.Patch within the build's existing
execbroker.Scope.
This reaches commands launched through CMake helpers, Autotools helpers, gsh, and direct brokered execution. Child build tools receive target compiler commands through the CMake or configure projection rather than through a process-global environment.
On the first CMake configure command, c.Target lazily creates a toolchain file
and appends it unless the Formula already supplied CMAKE_TOOLCHAIN_FILE or
--toolchain. CMake build and install commands do not create or receive the
file.
The toolchain file sets missing values for:
CMAKE_SYSTEM_NAME
CMAKE_SYSTEM_PROCESSOR
CMAKE_C_COMPILER
CMAKE_CXX_COMPILER
CMAKE_LINKER
CMAKE_AR
CMAKE_RANLIB
CMAKE_NM
CMAKE_STRIP
Linux uses CMAKE_SYSROOT. Darwin uses CMAKE_OSX_SYSROOT and
CMAKE_OSX_ARCHITECTURES.
When a sysroot is active, CMake target lookup uses:
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY
Dependency roots remain owned by x/cmake.Use. It accumulates the current
CMAKE_FIND_ROOT_PATH, and x/cmake.Configure passes that value to CMake. The
generated target file does not copy or cache the dependency list.
For a command whose basename is configure, c.Target supplies missing
environment values:
CC CXX LD AR RANLIB NM STRIP
CC, CXX, and LD contain shell-quoted prepared command slices. Existing
Formula values remain unchanged.
Before appending --host, the target reads the actual configure script:
- If the Formula already passed
--host, preserve it and append nothing. - If the script contains the literal
--host, append the target Autoconf host tuple. - If the script does not contain the option, run it with only the tool environment.
The target does not inject --build. The Autoconf host tuple is build-system
input and is not forwarded to Clang as its target.
When a sysroot is active, pkg-config receives missing values for:
PKG_CONFIG_SYSROOT_DIR=<sysroot>
PKG_CONFIG_LIBDIR=<dependency paths plus target sysroot paths>
Dependency paths come from the current PKG_CONFIG_PATH, populated by
x/pkgconfig.Use and existing build helpers. The C target adds:
<sysroot>/usr/lib64/pkgconfig
<sysroot>/usr/lib/pkgconfig
<sysroot>/usr/share/pkgconfig
An explicit PKG_CONFIG_LIBDIR remains unchanged. This prevents target builds
from falling back to host .pc files.
Bare generic tool names are projected to prepared target commands:
| Formula command | Prepared command |
|---|---|
cc, gcc, clang
|
Toolchain.CC() |
c++, g++, clang++
|
Toolchain.CXX() |
ld, ld.lld, ld64.lld
|
Toolchain.Linker() |
ar, llvm-ar
|
Toolchain.Archiver() |
ranlib, llvm-ranlib
|
Toolchain.Ranlib() |
nm, llvm-nm
|
Toolchain.NM() |
strip, llvm-strip
|
Toolchain.Strip() |
The executable replaces the generic name, and mandatory target arguments are prepended before Formula arguments. A command containing an explicit tool path is not rewritten. Configure scripts are the exception because the resolved path is required for option inspection.
| Condition | Behavior |
|---|---|
| Native target | Use the existing native build path without target injection |
| Unsupported cross OS/architecture | Leave the selected matrix to the Formula; do not create a built-in C target |
Linux matrix contains libc
|
Do not add the default glibc Formula |
| Sysroot Formula cannot be loaded or built | Return the existing load or build error |
| Sysroot metadata has no sysroot | Fail before building consumers |
| Required LLVM command is absent | Fail while preparing the LLVM toolchain |
| Formula supplies a CMake toolchain | Preserve it and do not append LLAR's toolchain file |
| Formula supplies a configure environment value | Preserve the Formula value |
Formula supplies --host
|
Preserve it and do not append another value |
Configure script does not declare --host
|
Do not append --host
|
| Formula uses an explicit C tool path | Do not rewrite that command |
The sysroot is a build input, not a runtime dependency that LLAR distributes with every library.
For GNU/Linux, the sysroot constrains available headers, startup objects,
libraries, loader conventions, and glibc symbol versions. The resulting ELF
continues to name the standard loader and libc.so.6; the target system
provides their runtime implementations.
For Darwin, the SDK supplies headers, frameworks, target declarations, and
link stubs. The resulting Mach-O continues to bind through dyld and target
system install names such as /usr/lib/libSystem.B.dylib.
LLAR does not inject a sysroot workspace path as RPATH or RUNPATH.
Formula compatibility is tested on the corresponding target machine. The llarhub GitHub Actions matrix runs:
llar test <formula> --os <runner-os> --arch <runner-arch>
on Linux and Darwin amd64 and arm64 runners. Each runner builds the Formula for
its own platform and executes its onTest consumer there.
Each job is native to its runner; no artifact is transferred between
platforms. Target-machine llar test establishes that the Formula and its
consumer are valid for that platform. Cross compilation is expected to produce
equivalent target semantics because LLAR projects the selected OS,
architecture, ABI, SDK/sysroot, and build-system facts through its injection
path. Representative cross-build experiments validate that projection.
The two test responsibilities remain separate:
| Test | Responsibility |
|---|---|
Target-machine llar test
|
Verify the Formula builds and its installed result is usable on that platform |
| Cross-compilation tests and experiments | Verify matrix selection, sysroot preparation, LLVM commands, command projection, and absence of host-path leakage |
Target projection is checked independently for each supported command shape:
- CMake configure receives a lazily generated toolchain file, while build and install commands do not.
- GNU Autoconf-compatible scripts receive
--host. - custom configure scripts such as zlib do not receive unsupported options.
-
pkg-configsearches target dependency and sysroot paths rather than host defaults. - direct C compiler, linker, archiver, and inspection commands resolve to the prepared target tools.
- explicit Formula configuration remains unchanged in every path.
The runtime experiments additionally inspect ELF or Mach-O architecture, dynamic dependencies, symbol bindings, runtime search paths, and build workspace path leakage before running a target consumer.
The build module exposes a language-neutral command transformation contract:
package build
type Command struct {
Name string
Args []string
Env []string
Dir string
}
type Patch struct {
Name string
PrependArg []string
AppendArg []string
Env []string
}
type Target interface {
Use(Command) Patch
}
type Options struct {
// existing build options
Target Target
}Boundary:
- Owns applying one target patch to one brokered command and scoping that
target to a
Builder.Buildcall. - Does not select a target, know C, select a sysroot, parse C metadata, or construct LLVM commands.
- Does not accept an external command middleware; callers provide only the target contract.
The C module defines prepared C-family commands and implements build.Target:
package c
type Toolchain struct {
// prepared command data
}
func NewToolchain(
cc, cxx, linker []string,
archiver, ranlib, nm, strip string,
) Toolchain
func (t Toolchain) CC() []string
func (t Toolchain) CXX() []string
func (t Toolchain) Linker() []string
func (t Toolchain) Archiver() string
func (t Toolchain) Ranlib() string
func (t Toolchain) NM() string
func (t Toolchain) Strip() string
type Config struct {
Matrix string
Toolchain Toolchain
Sysroot string
}
func Sysroot(targetOS, targetArch string) (module.Version, bool)
func NewTarget(config Config) (*Target, error)
func (t *Target) Use(cmd build.Command) build.PatchBoundary:
-
Toolchainowns only prepared C-family command data. -
Targetowns CMake, configure,pkg-config, and direct C-tool projection. -
Sysrootowns the fixed built-in C sysroot selection table. - The module does not load or build Formulas, resolve LLVM executables, derive LLVM target arguments, execute commands, or install middleware.
The LLVM module constructs a C toolchain from target facts:
package llvm
type Config struct {
OS string
Arch string
Sysroot string
}
type Toolchain struct {
c.Toolchain
}
func New(config Config) (*Toolchain, error)Boundary:
- Owns LLVM target triples, target compiler arguments, linker selection, sysroot argument syntax, and LLVM executable lookup.
- Returns prepared C commands consumed by
c.NewTarget. - Does not select or build a sysroot Formula, interpret matrix conventions,
understand CMake or configure scripts, or depend on
build.Builder.
The module loader accepts generic additional root requirements:
package modules
type Options struct {
FormulaStore repo.Store
Matrix formula.Matrix
Roots []module.Version
}
func Load(
ctx context.Context,
main module.Version,
opts Options,
) ([]*Module, error)Boundary:
- Owns Formula loading, matrix injection, dependency resolution, and version conflict resolution.
- Treats
Rootsas ordinary requirements in the same MVS graph. - Does not know which root is a libc or sysroot, parse build metadata, or import the C module.
The command entrypoint remains the composition root:
func buildModule(
ctx context.Context,
store repo.Store,
modPath, version string,
matrix formula.Matrix,
runTest bool,
) errorBoundary:
- Owns native-versus-cross selection, the
libcmatrix convention, default sysroot composition, sysroot metadata parsing, LLVM and C target creation, and the order of calls tomodules.LoadandBuilder.Build. - Passes only generic roots to
modulesand onlybuild.Targettobuild. - Does not implement CMake, configure,
pkg-config, compiler, or linker rewriting itself.
The user runs llar make for Linux arm64 on another platform. The Formula uses
its existing x/cmake build path and contains no cross-compilation branch.
sequenceDiagram
participant User
participant CLI as "cmd/llar"
participant C as "internal/build/c"
participant Modules as "internal/modules"
participant LLVM as "internal/build/c/llvm"
participant Build as "internal/build"
participant Metadata as "x/metadata/cc"
participant CMake as "x/cmake"
participant Broker as "execbroker"
User->>CLI: "llar make <module>@<version> --os linux --arch arm64"
CLI->>C: "Sysroot(linux, arm64)"
C-->>CLI: "bminor/glibc@glibc-2.17"
CLI->>Modules: "Load Formula with sysroot Root"
Modules-->>CLI: "one resolved module graph"
CLI->>LLVM: "New(linux, arm64, no sysroot)"
LLVM-->>CLI: "bootstrap compiler and linker commands"
CLI->>C: "NewTarget(bootstrap Toolchain)"
CLI->>Build: "Build(selected glibc Formula)"
Build-->>CLI: "sysroot Result"
CLI->>Metadata: "Parse(Result.Metadata)"
Metadata-->>CLI: "sysroot path"
CLI->>LLVM: "New(linux, arm64, sysroot path)"
LLVM-->>CLI: "final compiler and linker commands"
CLI->>C: "NewTarget(final Toolchain, sysroot path)"
CLI->>Build: "Build(resolved graph with C Target)"
Build->>Broker: "open scoped target middleware"
Build->>CMake: "run existing Formula onBuild"
CMake->>Broker: "cmake configure"
Broker->>C: "Target.Use(cmake command)"
C-->>Broker: "append generated toolchain file"
Broker->>CMake: "execute target-configured CMake"
CMake->>Broker: "cmake build and install"
C-->>Broker: "no configure patch"
Build-->>CLI: "target build Results"
CLI-->>User: "module result"
Dependency roots accumulated by x/cmake.Use remain in the configure
environment. The C target contributes target compilers, the sysroot, and find
root modes without replacing those dependency roots.
The Formula calls its existing configure helper. The actual source script
declares --host, so LLAR supplies both the prepared LLVM commands and the
Autoconf target tuple.
sequenceDiagram
participant User
participant CLI as "cmd/llar"
participant Modules as "internal/modules"
participant LLVM as "internal/build/c/llvm"
participant C as "internal/build/c"
participant Build as "internal/build"
participant Auto as "x/autotools"
participant Broker as "execbroker"
User->>CLI: "llar make <module>@<version> --os darwin --arch arm64"
CLI->>C: "Sysroot(darwin, arm64)"
C-->>CLI: "joseluisq/macosx-sdks@14.5"
CLI->>Modules: "resolve Formula and SDK in one graph"
CLI->>LLVM: "prepare bootstrap commands"
CLI->>C: "prepare bootstrap C Target"
CLI->>Build: "build or restore SDK Formula"
Build-->>CLI: "SDK sysroot metadata"
CLI->>LLVM: "New(darwin, arm64, SDK path)"
LLVM-->>CLI: "Clang and ld64.lld commands"
CLI->>C: "NewTarget(final Toolchain, SDK path)"
CLI->>Build: "Build(graph with C Target)"
Build->>Broker: "open scoped target middleware"
Auto->>Broker: "run resolved source configure script"
Broker->>C: "Target.Use(Command with Name, Args, Env, Dir)"
C->>C: "preserve explicit values and inspect script"
C-->>Broker: "set missing tools and append --host"
Broker->>Auto: "execute configure in cross mode"
Auto->>Broker: "build and install"
Build-->>CLI: "Darwin arm64 result"
CLI-->>User: "module result"
The Autoconf host tuple makes the script enter cross mode. Clang receives its
separate target and SDK arguments from llvm.Toolchain; --host is not used
as a compiler argument.
zlib uses a custom script that is named configure but does not accept the
Autoconf --host option.
sequenceDiagram
participant Formula
participant Auto as "x/autotools"
participant Broker as "execbroker"
participant C as "c.Target"
participant Script as "custom configure"
Formula->>Auto: "run existing Configure call"
Auto->>Broker: "execute source/configure"
Broker->>C: "Target.Use(Command)"
C->>C: "add missing CC, CXX, LD, AR, RANLIB, NM, STRIP"
C->>C: "read script: CHOST exists, literal --host does not"
C-->>Broker: "Patch with tool environment only"
Broker->>Script: "execute without --host"
The Formula remains unchanged. The prepared compiler command still selects the target, while LLAR avoids imposing an unsupported build-system option.
A Linux Formula that declares a libc requirement owns its libc variants.
LLAR preserves the matrix and does not add its fixed glibc Formula.
sequenceDiagram
participant User
participant CLI as "cmd/llar"
participant Modules as "internal/modules"
participant LLVM as "internal/build/c/llvm"
participant C as "internal/build/c"
participant Build as "internal/build"
User->>CLI: "llar make <module>@<version> --os linux --arch arm64 --require libc=<value>"
CLI->>CLI: "observe Matrix.Require contains libc"
CLI->>CLI: "skip the fixed glibc Root"
CLI->>Modules: "Load Formula with unchanged Matrix"
Modules->>Modules: "resolve Formula-declared dependencies"
Modules-->>CLI: "resolved graph"
CLI->>LLVM: "prepare Linux arm64 commands"
CLI->>C: "NewTarget(prepared Toolchain)"
CLI->>Build: "Build Formula graph"
Build-->>CLI: "target result"
CLI-->>User: "module result"
LLAR does not interpret <value> or replace the Formula's selected libc with
the built-in default.
GitHub Actions verifies each Formula on the corresponding native runner. This is the compatibility test for the Formula and its installed consumer; it is not a transfer test for an artifact produced by another runner.
sequenceDiagram
participant CI as "GitHub Actions target runner"
participant CLI as "llar test"
participant Modules as "internal/modules"
participant Build as "internal/build"
participant Formula
CI->>CLI: "llar test <formula> --os <runner OS> --arch <runner arch>"
CLI->>Modules: "load Formula and dependencies"
Modules-->>CLI: "resolved native graph"
CLI->>Build: "Build(graph, RunTest: true)"
Build->>Formula: "run onBuild on cache miss"
Build->>Formula: "run root onTest"
Formula-->>Build: "consumer result"
Build-->>CLI: "success or test error"
CLI-->>CI: "job result"
The target runner proves that the Formula is valid for that OS and architecture. LLAR's cross-compilation tests separately prove that the same target facts are injected when the build host differs.