Files
fds-os/docs/build-host.md
T
2026-09-21 22:29:23 +08:00

237 lines
13 KiB
Markdown

# M0: Arch Linux x86_64 build host
[Documentation index](README.md) · [First build](getting-started.md) · [Troubleshooting](troubleshooting.md)
This is the technical reference for the build foundation used by later milestones.
The [rootfs guide](rootfs.md) adds assembly, Python and isolated ARM execution requirements. If you are
setting up the repository for the first time, follow [Your first build](getting-started.md)
for an ordered walkthrough with expected output. For editing, rebuilding, and
configuration changes after setup, use [Development](development.md).
## Scope and pinned inputs
M0 validates the cross-build environment. The scoped [Dasung integration](dasung.md)
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](boot.md).
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](internal-storage.md),
independent recovery and persistent machine settings. The
[signature tools](releases.md) have passed acceptance; the
[frozen-input workflow](reproducible-builds.md) and signed local 0.1.0 release
have passed two complete offline builds and artifact/archive verification. The [desktop guide](desktop.md) explains the
new dependencies and the development image used for virtual display tests.
See [Native init](init.md) 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`.
## Separate generic Linux workstation tools
`make workstation` builds native `fds-cartridge` and `fds-emulator` without the
Void/Arch bootstrap. Their runtime dependencies are QEMU (`qemu-system-aarch64`
and `qemu-img`), erofs-utils, xz and bubblewrap. A software recipe chooses its
cross compiler. The shared archive reader adds Rust `tar` 0.4.46 and its
`filetime` dependency; both are locked. No new daemon or host build tools are
installed in the Pi base image. Follow [the workstation guide](workstation.md).
The full OS build prerequisites below retain their existing scope.
## 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.
```sh
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](dasung.md#dependencies-and-source-provenance).
Glibc C libraries cannot simply be reused in static-musl binaries.
## Exact commands
Run sequentially from the repository root:
```sh
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:
```sh
export PATH="$PWD/.host/xbps/usr/bin:$PATH"
export XBPS_ARCH=x86_64
cd vendor/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:
```sh
./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 submodule:
```sh
./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
```sh
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
- `.host/xbps/`: project-local host tools, not installed into `/usr`.
- `vendor/void-packages/masterdir-x86_64/`: glibc build container.
- `vendor/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`
copies only directories containing a template 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.
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](troubleshooting.md#configuration-or-overlay-conflict)
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](reproducible-builds.md). 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
- [Pinned Void xbps-src usage and foreign host documentation](https://github.com/void-linux/void-packages/blob/02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1/README.md)
- [Pinned aarch64 cross profile](https://github.com/void-linux/void-packages/blob/02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1/common/cross-profiles/aarch64.sh)
- [Void static XBPS](https://docs.voidlinux.org/xbps/troubleshooting/static.html)
- [Official static tool SHA-256 checksums](https://repo-default.voidlinux.org/static/sha256sums.txt)
- [Rust aarch64-unknown-linux-musl](https://doc.rust-lang.org/rustc/platform-support/aarch64-unknown-linux-musl.html)
- [Rust self-contained linking](https://doc.rust-lang.org/rustc/codegen-options/index.html#link-self-contained)