Build System
The Build System constructs immutable results from explicit declarations. A Build is one invocation: it resolves requested declarations, coordinates required work and publishes a verified result. This specification owns the relationships between the concepts below and the policy of their collaboration.
Source and public API: build.go. Resolution state and operations: Plan.
Concepts
Read a child specification for the meaning, properties and behavior of one concept. This index describes how the concepts work together without replacing those contracts.
| Concept | Responsibility |
|---|---|
| Recipe | Authored declarations, values, source provenance and invocation capabilities. |
| Instance | One configured selection and its dependency bindings. |
| Builder | Source detection and reusable default behavior. |
| Target | Destination context, permitted product selection and launch-command preparation. |
| Environment | Prepared immutable execution filesystem. |
| Toolchain | Validated tool descriptors and provider selection. |
| Executor | Images, isolated sessions and explicit process capabilities. |
| Operation | Owned execution work, filesystem capabilities and observation. |
| Package | Build and installation phases for one configured input. |
| Artifact | Immutable results, content validation and storage. |
| OS | Root policy, compatibility evidence and filesystem images. |
These public packages use the same vocabulary as their primary types. Language and execution adapters are nested beneath their owner. Transport formats, receipts and work directories remain implementation mechanisms.
Collaboration
Build uses Recipe to locate declarations, Target to select context and Builder to select default behavior. It records the configured graph as Instances in a Plan. Package executes each required phase through Operation. Environment supplies a prepared filesystem, Toolchain supplies validated descriptors and Executor runs the processes. Artifact stores verified results. OS constructs an image from composed installation contributions. Build publishes the final result.
flowchart TD
Build --> Recipe
Build --> Target
Build --> Builder
Recipe --> Instance
Target --> Instance
Builder --> Instance
Instance --> Package
Package --> Operation
Environment --> Operation
Toolchain --> Operation
Operation --> Executor
Package --> Artifact
Artifact --> OS
OS --> Publication["Build publication"]
Relationships and cross-concept rules are defined here. The packages may import each other’s public APIs; sibling specifications do not require each other’s definitions. Concrete API type references in source remain part of those public interfaces.
Invocation properties and API
Build contains a Plan, execution capabilities, storage coordinates, implementation identity, root policy, resource allocation and observation sinks. Build.Run coordinates the selected work and returns a Manifest containing product, destination, content identity, prepared image identity, machine properties and compatibility requirements.
Diagnostic writers retain phase and subject identity across concurrent package, environment, source and OS work. The invocation serializes writes and forwards the optional operation.PhaseWriter capability without choosing presentation. Domain owners bind their logs to their local execution phase; the application decides how those identities appear.
Build.PrepareRun performs the same image construction plus the Target support closure and returns a prepared host command. It requires an empty private directory supplied by the caller; the caller owns cleanup after preparation or execution fails and after the process ends. It never starts a host process itself.
Resolve constructs the Plan without executing construction handlers. Its options supply source exclusions, storage, machine validation and an optional baseline dependency. Distribution names, required initialization paths and supplied declarations are application policy, not hidden defaults in these APIs.
Publication writes immutable content before replacing the current result manifest. A required failure prevents publication and cancels dependent work. Already published independent results may remain cached.
Resolution and configuration
A project is an explicit collection of Recipe declarations and source inputs represented by a Catalog. Declaration identity, dependency address and resolved Instance identity are distinct. Loading a declaration does not select it for work.
Names are case-sensitive. Qualified names select the exact declaration. Unqualified names search the declaring collection, then the project, then the supplied collection. Duplicate declarations are errors. There are no dependency aliases, provider advertisements or solver backtracking. A failed version or machine constraint reports the selected provider and the failed constraint instead of trying unrelated alternatives.
Sources are acquired before source-based Builder detection. Detection is pure inspection; changed source or detector identity invalidates its cached choice. A Linux dependency names an actual authored source declaration, not a source-less implicit kernel. Declaration URLs never manufacture dependency aliases.
The selected product and Target are independent roots of an acyclic graph. A shared reference selects the same destination Instance in the implemented invocation. Distinct configurations within one product require distinct declarations; arbitrary multi-context variants are not implemented. Separate invocations can configure their graphs independently.
Configuration runs once per selected Instance, from consumers toward providers, after all upstream contributors have completed. The dependency graph is fixed before this phase. Requests retain contributor identity, file, line and ordinal and are presented in a stable order. Effective returned options are copied and frozen.
A configuration handler may submit values only to reachable declared dependencies. The product root may address the combined product and support closures; the Target root may address its support closure. Neither may configure an unrelated registered declaration. Handlers cannot add dependencies, change providers, acquire sources, start commands, read construction results or inspect mutable host state.
A missing handler accepts no incoming requests and yields the initial options. Nonempty requests without a handler fail. Explicit handlers own their vocabulary and rejection policy. Exact assignment accepts equal values, combines distinct keys and rejects conflicting values with both origins. Defaults fill absent keys; an explicit disabled value is not absence. Other merge policies require an explicit contract, never implicit last-writer precedence. Diagnostic provenance must not influence semantic output identity.
Execution context and tools
Execution and destination are separate machine roles. Tools run on the execution machine; produced software is intended for the destination. The controlling host may differ from both. Target supplies destination properties; application policy supplies supported execution choices. Host architecture and compiler defaults cannot silently override either role.
A tool dependency’s destination is its consumer’s execution machine. Its own requirements follow that context recursively. A compiler may also declare a code-generation destination as an ordinary explicit input. Emulation requires supported policy and must be reported. Runtime requirements must be checked against the actual execution kernel; an image or a newly compiled kernel cannot satisfy a missing syscall of the running executor.
Builder declares required tool contract names. Target supplies defaults, local bindings override them, Environment supplies prepared descriptors and Toolchain validates the selected contracts. Operation receives already allocated descriptors. Unknown schemas and unsupported machine combinations fail before commands run.
Source-built providers, SDK inputs, wrapper generation and staged self-hosting require explicit acyclic dependencies. Normalized descriptors, commands, wrappers, runtime requirements and their implementations participate in identity. Cross-language linking requires compatible ABI, linker and library policy; sharing architecture or compiler family is insufficient. These extensions remain outside the implemented bootstrap-only profile.
Upstream adapters must separate native build tools from destination libraries and compiler settings. Cargo procedural macros and build scripts need execution-compatible inputs; Linux native helpers need native compiler settings. A Makefile ignoring supplied compiler or staging settings requires an explicit adaptation. No implicit source patching or claim of cross-compatibility is permitted.
Native ecosystem resolvers may acquire inputs only during a declared preparation step whose manifests, locks, options and fetched bytes become explicit immutable inputs. Ordinary compilation must not acquire hidden network dependencies. Unaccounted acquisition fails or makes the result explicitly non-reusable. Native language dependencies do not silently add declarations after graph configuration.
Isolation and resource ownership
Operation gives Executor explicit read-only source and prior-result mounts and private writable work, output and installation paths. Persistent compiler caches are supplied separately and survive cancellation. Metadata must be preserved or rejected explicitly. Mutable scratch and cache contents are not successful results.
Commands inside a handler execute sequentially. Build schedules independent phases concurrently through a private invocation-local queue; this does not introduce another domain concept or public scheduling API.
The phase dependencies reflect consumed results: installation waits for its own package build, image construction waits for all installation contributions, and final publication waits for the image and all requested support results. A package shared by the product and support closures has one execution state and runs each required phase once. Support compilation can overlap userspace compilation and image construction. Declaration requirements still determine selection, configuration order, installation membership and compatibility obligations; a configuration-only dependency does not make its compiled output an input. General inter-package artifact consumption remains unsupported and must gain explicit phase edges when implemented.
Build.Jobs is one positive total tool-job budget. The coordinator divides currently free jobs among ready phases, starts only phases with at least one job and returns their allocations on completion. Each phase keeps its positive allocation until it finishes and exposes that value as ctx.jobs; running compilers are not resized. Released jobs can start newly ready work. This simple policy may leave capacity idle when only an already running phase remains. Jobs=1 serializes execution. Job allocation changes do not themselves change artifact identities.
Build serializes writes to its shared log and checks prepared-environment availability once per execution attempt. The image identity is immutable within an attempt. Recovery to a different image cancels and joins all started phases before resolving a fresh attempt; results from different execution identities cannot be combined. The first observed phase failure cancels active work, prevents scheduling more phases and prevents final publication. All started phases are joined before returning, including on caller cancellation. Completed independent cache results remain available.
Public callers keep invocations on the same Build or Plan serial. Supplied executors must support independent concurrent sessions, and supplied handler adapters must support independent invocations over immutable catalog data. Each Package and its Instance remain serial within an invocation.
Cancellation terminates owned execution and releases temporary resources while retaining diagnostics. Exact stdout and stderr bytes, including non-UTF-8 sequences, are preserved. Observation reports selections, configuration origins, reuse decisions, phase identities and outcomes; imperative handlers do not imply a completely known command graph before execution.
Installation and image construction
Package installation contributes a logical tree. The product’s ordinary requires closure participates in composition, without guessing a smaller runtime set from linker output. Missing installation behavior contributes nothing and does not force compilation. Build tools, execution images and SDK inputs are not automatically installed. Declared destination runtime requirements would participate as ordinary requirements.
The Target support closure is independent from the OS installation closure. Kernel or boot-loader construction may be semantically required without placing its outputs in the installed root. A product explicitly requesting an ordinary installed contribution receives it, subject to supplied root policy.
Artifact composes contributions without traversal-order precedence. Equal entries may be shared; conflicting contents or metadata fail with all contributors identified. OS validates the supplied root policy and constructs a deterministic EROFS image. The application decides the baseline dependency and initialization requirements.
root denotes the composed tree and os the exact filesystem image in the implemented OS profile. Publication metadata remains separate; it is not a wrapper around image bytes. A configuration request concerning a kernel does not make the kernel output a compiler input unless a handler actually consumes it.
Applying an installation tree to a host directory is a separate operation from constructing it. Changing the application destination must not rebuild software. Future application must preflight containment, links, metadata, privilege and collisions. Equal entries may be accepted; different existing entries fail. There is no implicit overwrite, removal, upgrade database or whole-filesystem transaction. An I/O failure must report partial application. Direct root application requires an explicitly compatible host and layout; cross-application requires an explicit destination. This application workflow is not implemented.
Identity, reuse and compatibility
Derivation identity covers captured declaration and handler code, source, normalized options, relevant machine roles, prepared execution identity, allocated tools, actual input contents and the output contract. Library callers may supply an explicit Implementation version for derivation behavior. The Axis CLI currently leaves it empty: rebuilding Axis or editing its Go implementation does not invalidate package or OS receipts. Diagnostic origins, unrelated declarations, storage coordinates and unused outputs are excluded. Different execution architectures remain different derivations even when equal final content can be deduplicated.
Configuration and compatibility checking still occur on reuse. A receipt is usable only after validating recorded inputs and complete output contents. Current phase caching may retain a whole handler result; that does not license hidden inputs. OS image reuse depends on composed content and construction inputs, not an unrelated destination name or unused boot output.
OS retains product-origin version and configuration requirements for dependencies outside the installed root. Target-only requests do not contaminate those obligations. Recording obligations is not proof that an unbuilt destination satisfies them. An imported bare image without trustworthy associated requirements cannot receive an automatic compatibility claim.
Before a runtime launcher starts, the selected support must satisfy these obligations. PrepareRun requires each recorded external obligation to have a selected support provider with a successful build handler. Configuration verification belongs to that provider: the shipped Linux handler checks the effective Kconfig output, including on the path that creates a reusable receipt. Verified reuse carries that same configured result; a missing or corrupt result is rebuilt. This profile reuses the configured graph and does not import or rebind an arbitrary image. Future distribution readiness has the same obligation. Evidence may require a kernel result even when image construction did not. Rebinding to another compatible destination repeats validation without rebuilding unchanged image bytes. Unknown compatibility conditions fail explicitly; matching architecture alone is insufficient.
Runtime launching and distribution extensions
Runtime launching is an optional Target behavior. Packing remains a future destination behavior. A future packer is a downstream consumer of explicit product and support outputs. Its inputs must not create a dependency cycle back to the product. It may construct a disk image, archive, directory or installer without modifying input bytes or redefining os.
A distribution can contain exact image bytes together with boot files, kernels, firmware, storage layout and support data. Padding and allocation surrounding an image belong to the distribution. Changing those components invalidates affected support or packing work, not unchanged userspace construction unless an actual input or compatibility requirement changes.
Each invocation binds its selected product explicitly. Multiple products retain separate configuration and compatibility records; there is no arbitrary global image getter. An input slot records producer identity as well as output name; a single-value accessor fails on zero or multiple matches.
The implemented launch flow keeps responsibilities explicit:
- Build resolves and incrementally constructs the OS and Target support as independent closures. Support is built without installing its outputs in the OS. Failure prevents publication of that invocation and command preparation.
- Artifact materializes named regular-file outputs as private read-only copies. Build binds the selected image to
osand restricts support lookup to dependencies reachable from the selected Target. Tree outputs are unsupported in this launch profile. - Target evaluates its optional
runbehavior with immutable machine information, configured options, the bound image, support lookup and private scratch coordinates. Recipe supplies the invocation adapter. The handler returns a native command; it cannot configure declarations, execute commands or mutate constructed results. - The application supplies stdin and streams and invokes the host
executor.Processcapability. It owns the project lock, terminal modes and temporary-directory lifetime. Executor owns the launched process and serializes output; cancellation terminates the launched command. The host profile requires attached processes, not daemonized services.
The first command argument selects an executable already installed on the controlling host. It is separate from the prepared build Environment and from Target package requirements; launch-time software installation and source-built host tools are not implemented. The shipped QEMU command explicitly chooses TCG emulation.
Device access, native accelerators and remote execution require explicit supported profiles. Compiling, command preparation or filesystem validation never proves that an image boots.
Hardware-specific provisioning, authorization, device firmware and physical deployment are external inputs or later operations. A hardware profile must state its required initial state, supplied and external components, storage layout and validation status. Missing firmware cannot be treated as an empty successful output.
Implemented profile and verification
The native API currently resolves declarations and constructs EROFS images for the application-supplied Linux/aarch64 profile. The supplied adapters support Make/Linux/Zig behavior, local and HTTP(S) TAR sources, native GCC/Zig bootstrap descriptors, Docker construction and host process launching. Resolution and configuration are sequential; independent construction phases run concurrently within the global job budget. Language-specific syntax and implemented field restrictions belong to the Recipe adapter subtree.
Not implemented: general product export, arbitrary multi-context variants, deterministic discovery handlers, Git/XZ acquisition, general build input-slot consumption, source-built/cross toolchains, packing, host installation, launch-time tool installation and runtime device provisioning. The extension requirements above constrain future implementations; they do not claim current support.
go test -race ./... and go vet ./... check the library. Optional DOCKER_TEST_IMAGE, AXIS_BUILD_TEST_IMAGE and AXIS_ZIG_TEST_IMAGE enable session, construction/EROFS and real compiler-cache integration evidence. These checks are distinct from OS boot or hardware acceptance.