Files
fds-os/docs/developer/build-host.md
T

15 KiB

M0: Arch Linux x86_64 build host

Development reference and historical context. For current operating instructions, use the user manual. Acceptance applies only to the source and artifacts identified in each record.

Documentation index · First build · Troubleshooting

This is the technical reference for the build foundation used by later milestones. The rootfs guide adds assembly, Python and isolated ARM execution requirements. If you are setting up the repository for the first time, follow Your first build for an ordered walkthrough with expected output. For editing, rebuilding, and configuration changes after setup, use Development.

Scope and pinned inputs

M0 validates the cross-build environment. The scoped Dasung integration adds a controller package and service definitions without implementing a full OS. M1 adds the configured FDS rootfs; M2 adds native init and the generic ARM VM. M3 adds static FDS tools. M4 cross-builds the Pi kernel and creates initramfs, SYSTEM GPT/EROFS and a FAT32 boot partition; see Boot images. M6 adds cartridge management and M7 adds writable DATA. M8 adds the optional WindowMaker stack and on-demand Ethernet. M12 adds the complete internal disk, independent recovery and persistent machine settings. The signature tools have passed acceptance; the frozen-input workflow and signed local 0.1.0 release have passed two complete offline builds and artifact/archive verification. The desktop guide explains the new dependencies and the development image used for virtual display tests. See Native init for the additional test dependencies. An ARM host and a connected Pi are not required.

Input Pin
void-packages Git submodule and VOID_PACKAGES_COMMIT: 02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1
Static XBPS host tools Versioned 0.60.4_1.x86_64-musl URL and a SHA-256 digest stored in this repository
Rust rust-toolchain.toml: 1.98.0, minimal profile, rustfmt
Rust target aarch64-unknown-linux-musl
Rust crate dependencies Smoketest has none; all workspace dependencies, including shared Clap CLI parsing, are pinned in Cargo.lock, fetched at bootstrap, and built with --locked --offline
XBPS test package srcpkgs/hello/template in the pinned Void tree: GNU hello 2.12.3_1
Pi kernel packages/fds-kernel/template: 6.12.87, pinned Raspberry Pi source archive and FDS config delta
Pi firmware config/pi-firmware.conf: signed 20260715_1 package with a pinned SHA-256

The musl ABI of the static host tools does not determine the target ABI. Scripts explicitly use XBPS_ARCH=x86_64 for the glibc build container, then run ./xbps-src -a aarch64 pkg hello to build the ARM glibc package. Local ARM package indexing also explicitly sets XBPS_ARCH=aarch64.

Generic Linux workstation tools and desktop additions

make workstation builds the Arch fds-tools package; make workstation-install installs it with pacman. make workstation-binaries builds native fds-cartridge and fds-emulator on generic Linux. Python 3 and Git resolve build versions; Dasung builds install these tools in the project-local Void container as well. Runtime requirements are QEMU, erofs-utils, bubblewrap and native XBPS with a prepared Void source checkout for software creation. fds-cartridge invokes xbps-src, installs runtime dependencies and creates direct EROFS trees. The Rust tar reader and xz utility remain for legacy cartridges and package/source archives. See the current workstation guide.

The target fds-control panel uses x11rb 0.13.2's pure-Rust core X11 connection. It adds no shared GUI toolkit or background service. Its protocol, hostname and OS-access dependencies are locked and included in Rust license notices. Both the panel and foreground launcher are packaged in fds-cli as static ARM binaries. See the revision rationale.

Host dependencies

Run as an ordinary user on x86_64 Arch Linux with unprivileged user namespaces. Rootfs construction now requires Linux 6.7+ with namespaced binfmt_misc support. The M5 build uses a private handler and static QEMU for child execution; it does not register an ARM interpreter in the workstation's global registry. The checkout path must not contain whitespace, a limitation of upstream xbps-src. Allow several GB of disk space and provide a network connection.

sudo pacman -S --needed bash coreutils binutils git curl make file tar xz gzip zstd \
  bubblewrap rustup ca-certificates findutils diffutils grep sed gawk util-linux

Alternatively, run ./tools/bootstrap-host --install-deps. Only this explicit option invokes sudo/pacman. Default make bootstrap checks dependencies and exits nonzero when one is missing, without installing host system packages.

Dependency rationale:

  • bash, coreutils, findutils, diffutils, grep, sed, gawk, and util-linux provide the build scripts and upstream build system with their required shell tools.
  • git pins upstream source; curl and CA certificates download HTTPS inputs.
  • tar, xz, gzip, and zstd extract archives and XBPS packages.
  • file and binutils inspect ELF artifacts; make provides the build entry points.
  • bubblewrap runs the build container as an ordinary user.
  • rustup installs the pinned Rust toolchain and target. rustfmt checks source formatting and is not a runtime dependency.

Rust's bundled rust-lld and target-provided static musl/CRT are sufficient for the current pure-Rust smoketest. No Zig, Docker, OpenSSL crate, or additional musl runtime is required. The imported Dasung controller bundles libusb and uses a Void musl C cross compiler inside the container; see its dependency rationale. Glibc C libraries cannot simply be reused in static-musl binaries.

Exact commands

Run sequentially from the repository root:

make bootstrap
make smoke-test
make check

Bootstrap initializes the pinned submodule, verifies and extracts XBPS, checks the tools' x86_64 static ELF properties, installs the pinned Rust toolchain and target, fetches the locked workspace crates, copies config/xbps-src.conf into the local Void checkout, then runs the equivalent of:

export PATH="$PWD/.host/xbps/usr/bin:$PATH"
export XBPS_ARCH=x86_64
cd .host/void-packages
./xbps-src -A x86_64 binary-bootstrap

binary-bootstrap installs upstream base-chroot as a compilation environment. It is separate from the FDS fds-base target rootfs built in M1. Builds run through bubblewrap without starting Void services on Arch. Repeated bootstrap reuses the masterdir and verified downloads. The script checks the container architecture, installed base-chroot package, and execution of GCC inside the container.

To reproduce the two builds individually from the repository root:

./tools/cargo-build --locked --offline --release \
  --target aarch64-unknown-linux-musl -p fds-smoketest
./tools/build-package hello

The latter command sets the local XBPS PATH, checks the Void pin, and executes this command within the writable build checkout:

./xbps-src -a aarch64 pkg hello

xbps-src installs the aarch64 GNU cross toolchain that runs on x86_64, together with the target glibc sysroot. No Pi compilation is needed. The pinned template's checksum verifies the package source archive.

Validation

file target/aarch64-unknown-linux-musl/release/fds-smoketest
readelf -hW target/aarch64-unknown-linux-musl/release/fds-smoketest
readelf -lW target/aarch64-unknown-linux-musl/release/fds-smoketest
readelf -dW target/aarch64-unknown-linux-musl/release/fds-smoketest
readelf -VW target/aarch64-unknown-linux-musl/release/fds-smoketest
ldd target/aarch64-unknown-linux-musl/release/fds-smoketest

Rust acceptance requires ELF64, little endian, AArch64, executable type EXEC/DYN, static linkage reported by file, no PT_INTERP, no DT_NEEDED, and no GLIBC_* version requirements. Static PIE is also acceptable. The pinned Rust target, self-contained linking configuration, and compile-time target checks establish musl provenance; an ELF's static label alone does not identify its libc.

ldd is supplemental diagnostic output. A static executable normally produces not a dynamic executable with exit status 1. An x86_64 host's ldd alone cannot prove that an ARM executable is static.

XBPS validation checks architecture=aarch64 in the local package index, then extracts usr/bin/hello from the generated package. It requires AArch64, /lib/ld-linux-aarch64.so.1, and libc.so.6. An architecture suffix in a package filename is not sufficient evidence.

make check verifies rejection of the wrong architecture, dynamic executables passed as static, a static-musl executable passed as a glibc package, non-ELF input, missing artifacts, invalid arguments, Void pin drift, and modified upstream files. It also tests overlay protection and runs rustfmt. Pipelines enable pipefail so tee cannot hide a failed build.

Optionally install sudo pacman -S --needed qemu-user to execute the ARM Rust binary during smoke-test and compare its output. When qemu-aarch64 is absent, the script explicitly reports SKIP. M0's required acceptance is actual cross-compilation plus ELF/package inspection. Static inspection is not an ARM runtime or Pi boot test.

Inputs, caches, and overlays

Use make clean-preview followed by make clean when repeated builds and VM tests accumulate. Cleanup explains the retained current images, release/input archives and caches. Full image builds and VM suites need tens of GiB per working tree, beyond the initial M0 smoke test's requirements.

  • vendor/void-packages/: clean, pinned upstream submodule; no generated overlays.
  • .host/void-packages/: independent local build checkout at the same pin.
  • .host/xbps/: project-local host tools, not installed into /usr.
  • .host/void-packages/masterdir-x86_64/: glibc build container.
  • .host/void-packages/hostdir/: download caches, cross packages, and build output.
  • out/packages/: exported aarch64 test package and its repository index.
  • out/manifests/void-build-packages.txt: packages remaining installed after the build. Cross toolchains and temporary dependencies removed by xbps-src remain traceable through cache digests and build logs.
  • out/manifests/void-package-inputs.sha256: digests of cached .xbps inputs.
  • out/manifests/artifacts.sha256: validation artifact digests.
  • out/logs/: actual command logs, including logs from failed attempts.

Place FDS package overlays at packages/fds-*/template. tools/prepare-void-workspace clones the pinned upstream locally without shared Git objects or network access. tools/prepare-void copies directories containing a template into that writable checkout and refuses to replace tracked upstream packages. These copies are visible inside the build container. Base, base-files, init, CLI, kernel and Dasung overlays are active; future directories without templates remain inactive.

Bootstrap migrates matching old generated copies, configuration, hostdir and masterdir-* out of the submodule using renames, preserving cached downloads and container files. Unknown files, staged edits, changed copies or destination conflicts stop migration for explicit reconciliation. Python 3 is used for checked migration and locking; it is already a documented FDS host prerequisite. The workstation builder also rejects submodules as writable build targets.

If an overlay changes and preparation reports a stale copy, inspect and preserve any unique edits before moving the specific generated copy out of srcpkgs/ and retrying. Modified upstream tracked files cause immediate failure. Conflicting local build configuration also causes failure and must be reconciled explicitly. See configuration and overlay troubleshooting for the procedure.

Updating Void requires an explicit change to both the submodule gitlink and VOID_PACKAGES_COMMIT, followed by validation and a commit containing both. Do not fetch master and silently change the pin. Replacing the static host XBPS archive also requires updating both its versioned URL and checksum.

Known limitations

  1. Source templates, Rust, and XBPS tools are pinned; the complete Void binary repository is not snapshotted. Initial bootstrap, xbps-src build-container updates, and missing build dependencies still use signed rolling repositories. Cached inputs, version inventories, and digests support auditing but cannot guarantee future upstream retention. M12 preserves the exact selected packages, sources and build tools through the accepted snapshot and offline workflow. Its two fresh builds produced 27 matching artifacts. This freezes the release inputs, not the entire rolling Void repository; the original M0 check alone does not establish reproducibility.
  2. Bootstrap supports x86_64 Arch Linux. Other host distributions and ARM hosts have not been validated. Unprivileged user namespaces must be available.
  3. M4 tests the actual Pi kernel and initramfs-to-EROFS handoff in a generic ARM VM. This does not emulate Pi firmware, RP1, physical bays or the display. Media burning and physical boot-performance measurements remain separate work.
  4. opt-level=z is the plan's initial setting, not a proven fastest setting. Compare optimization levels later using real programs and hardware.
  5. During the initial upstream bootstrap, bubblewrap's mounted /tmp produced rm: cannot remove '//tmp': Device or resource busy in the cleanup phase. Upstream returned success; the subsequent container execution, cross build, and public bootstrap command passed. The upstream source was left unchanged.

Upstream references