# Working on FDS/OS [Documentation index](README.md) · [First build](getting-started.md) · [Troubleshooting](troubleshooting.md) This guide is for someone who has completed the first build and wants to change or inspect the working code. Run commands from the repository root, as a normal user. M0–M12 software acceptance is complete; consult [AGENTS.md](../AGENTS.md) and the [roadmap](roadmap.md) before adding components. ## Choose the smallest useful build | You changed or want to do | Run | | --- | --- | | See the available entry points | `make help` | | Prepare a new checkout or restore missing host tools | `make bootstrap` | | Edit the Rust smoketest | The Rust build and validation sequence below | | Build/test the base display controller | `make dasung`, then `make dasung-test`; see [Dasung](dasung.md) | | Change the CLI, shared contracts or stage0 | `make tooling`, then `make tooling-test`; rebuild initramfs and run `make boot-test` for boot-path changes | | Change kernel configuration | `make kernel`, rebuild rootfs and boot images, then `make boot-test`; see [Boot images](boot.md) | | Change USB discovery, mapping or media writing | Build CLI images, then `make stress-test`; see [Stress tests](stress-testing.md) | | Change shutdown or DATA safety | Rebuild both profiles; run `make power-test` against development, plus `make data-test` and `make init-test`; see [Shutdown](power.md) | | Change init or boot services | `make rootfs PROFILE=cli`, `make rootfs-test`, then `make init-test`; see [Native init](init.md) | | Change base packages or rootfs assembly | `make rootfs PROFILE=cli`, `make rootfs-test`, then `make init-test`; see [Rootfs](rootfs.md) | | Build the native development toolset | `make rootfs PROFILE=development`, `make rootfs-test`, `make system-card PROFILE=development`, then `make development-test` | | Change internal storage or machine settings | Rebuild CLI and recovery, then `make internal-test`; see [Internal storage](internal-storage.md) | | Change release signing or verification | `make signing-test`; see [Release signatures](releases.md) | | Rebuild the test XBPS package | `./tools/build-package hello` | | Verify an M0 build-system change | `make bootstrap`, then `make smoke-test`, then `make check` | | Check Rust formatting and validation protections | `make check`, after building the Rust program | | Read or edit documentation | Check examples, links, and claims against the implementation | Run builds sequentially. The scripts share one Void masterdir and package cache; starting bootstrap and package builds concurrently is not a supported workflow. `make packages` builds every FDS base package. `make all` builds the complete artifact set; two independent frozen offline builds passed with matching output. See [Frozen inputs and offline rebuilds](reproducible-builds.md). There is no destructive `make clean` or generic `make test` target. ## Edit and rebuild the Rust program The active program is [rust/fds-smoketest/src/main.rs](../rust/fds-smoketest/src/main.rs). It is deliberately tiny: it establishes that the intended target and static linking work. `rust/dasungd/`, `rust/fds-common/`, `rust/fds-cli/`, and `rust/fds-stage0/`, `rust/fds-boottrace/`, and `rust/fds-cartridged/` are also active. Directories absent from the workspace member list remain reserved. Use the [tooling guide](tooling.md) for the FDS programs, [Cartridges](cartridges.md) for bay and metadata changes, and [DATA](data.md) for writable storage and managed jobs. Use the shared `clap` workspace dependency for every Rust command-line interface. Declare commands and options with `Parser`, `Subcommand`, `Args`, and typed value parsers; do not match or consume raw argument vectors. Shared media syntax lives in `rust/fds-burn/src/cli.rs`; installed FDS aliases use Clap's multicall support in `rust/fds-cli/src/cli.rs`. Keep authorization and filesystem validation in the operation layer after parsing. Add parser tests when changing required options, conflicts, private s6 flags, aliases, or child-argument forwarding. The disposable Rust VM workloads in `tests/helpers/` are a Cargo workspace member and use the same locked Clap dependency. They are never installed in the base packages. Their stress workload uses release optimization level 2; production executables retain the size-oriented release settings. After editing the program: ```sh cargo fmt --all cargo build --locked --offline --release \ --target aarch64-unknown-linux-musl -p fds-smoketest ./tools/verify-elf target/aarch64-unknown-linux-musl/release/fds-smoketest aarch64 static make check ``` Cargo writes to `target/aarch64-unknown-linux-musl/release/`. It does **not** refresh `out/fds-smoketest` or its manifest. Run `make smoke-test` when you need a freshly exported artifact and matching checksums. If you installed optional QEMU, execute the freshly built file with: ```sh qemu-aarch64 target/aarch64-unknown-linux-musl/release/fds-smoketest ``` The smoke-test contract expects exactly `FDS/OS M0: aarch64 static-musl OK`. Changing that text requires updating the runtime assertion and documentation as part of the same change. The crate also rejects the wrong architecture, libc, or missing static CRT at compile time. Do not bypass those checks to make an x86_64 build appear to pass. The release profile and target configuration live in [Cargo.toml](../Cargo.toml) and [.cargo/config.toml](../.cargo/config.toml). Rust and target versions are selected through [rust-toolchain.toml](../rust-toolchain.toml). This M0 crate has no external crate dependencies. `--offline` works after bootstrap has installed the toolchain; it is not a promise that the complete build pipeline is offline. ## Build and inspect a package ```sh ./tools/build-package hello ``` This checks the Void source pin, prepares configuration and overlays, invokes xbps-src's aarch64 cross build, and copies the resulting main package into `out/packages/`. Its log is `out/logs/xbps-hello.log`. Follow [Packages](packages.md) for indexing, querying, and extracting the output. `hello` is the verified M0 package. The helper accepts other template names, but its exporter currently looks only for that main package in the top-level `hostdir/binpkgs/`. Packages emitted into subrepositories and separately named subpackages need additional handling; the helper is not yet a universal package export interface. For diagnosis, this is the underlying command in its correct host environment: ```sh ( export PATH="$PWD/.host/xbps/usr/bin:$PATH" export XBPS_ARCH=x86_64 cd vendor/void-packages ./xbps-src -a aarch64 pkg hello ) ``` The subshell keeps the directory and environment changes local. Prefer the FDS helper for normal work: it also checks the pin and exports the output. The raw command does neither. Do not run `xbps-install` against the Arch host or install an ARM package into the x86_64 build container. ## Change build configuration deliberately The source of truth is [config/xbps-src.conf](../config/xbps-src.conf). `XBPS_MAKEJOBS` controls package build parallelism; the current value is 4. `XBPS_CHROOT_CMD=bwrap` selects the supported container backend. `tools/prepare-void` copies this file to the ignored `vendor/void-packages/etc/conf`. If the two differ, it stops instead of silently overwriting local settings. After intentionally editing the checked-in file, review the difference: ```sh diff -u vendor/void-packages/etc/conf config/xbps-src.conf ``` A difference makes `diff` exit 1; that is expected. Once the checked-in version contains every setting you intend to keep, synchronize and prepare: ```sh cp config/xbps-src.conf vendor/void-packages/etc/conf ./tools/prepare-void ``` This overwrites the local generated configuration, so reconcile any intentional local edits before copying. Re-run the three M0 validation commands after a build configuration change. A mirror change does not lock the packages served by that mirror; see [input limitations](build-host.md#known-limitations). ## How future package overlays fit FDS-owned package sources belong in `packages/fds-*/`, outside the upstream submodule. An overlay becomes active only when its directory contains a `template`. `fds-base`, `fds-base-files`, `fds-init`, `fds-dasungd`, `fds-cli`, `fds-kernel`, `fds-cartridged`, `fds-eink`, and `fds-dhcpcd` are active. For active overlays and future templates, `tools/prepare-void` copies each active directory into `vendor/void-packages/srcpkgs/`. It allows an identical repeat copy, rejects a name that collides with a tracked upstream package, and stops if a previous generated copy differs. Edit the source under `packages/`, then reconcile the specific stale generated copy. Never put the only copy of an FDS change inside `vendor/`. The [troubleshooting guide](troubleshooting.md#configuration-or-overlay-conflict) explains how to preserve and inspect a conflicting copy. Empty future directories are intentional; adding a fake template to silence a missing-package error does not implement that milestone. ## Preserve logs and identify inputs The normal log filenames are reused. Before reproducing a failure that you need to retain, save the existing logs: ```sh log_archive=$(mktemp -d "$PWD/out/saved-logs.XXXXXX") cp -a out/logs/. "$log_archive/" printf 'Saved logs to %s\n' "$log_archive" ``` After a successful smoke-test: ```sh cat VOID_PACKAGES_COMMIT rustc --version cargo --version cat out/manifests/void-build-packages.txt sha256sum -c out/manifests/artifacts.sha256 ``` The package inventory lists what remains installed in the build container after xbps-src cleans up temporary dependencies. Cached package digests and build logs provide additional input records. These records do not constitute a complete, immutable upstream package repository snapshot. ## Source updates and changes for review Keep upstream tracked files unchanged. Updating Void is a deliberate change to both the submodule gitlink and `VOID_PACKAGES_COMMIT`, followed by the full M0 checks. Updating host XBPS requires a matching archive URL and checksum. Updating Rust requires considering the pinned toolchain, target, and generated artifacts together. Do not use an unreviewed `git pull` inside the submodule as a repair. For a change report, state what changed, which commands actually passed, and what was skipped. Include the failing command and relevant log when reporting a bug. Do not describe ELF inspection as execution, QEMU userspace execution as a Pi boot, or a target timing as a measured result. Use English for documentation, comments, diagnostics, and project discussions. ## Desktop verification Build `make rootfs PROFILE=development`, then run `make desktop-test`. This executes the actual ARM WindowMaker, terminal, cartridge daemon, and DHCP client in disposable VMs. Its Xvfb backend and root test console exist only in generated fixtures. The normal SYSTEM configuration uses Xorg and the ordinary FDS user. VM helpers place QEMU in a private network namespace. The test's virtual DHCP server can advertise DNS while remaining unable to reach host interfaces or the Internet. QEMU diagnostics have separate `*.stderr.log` files, keeping host emulator messages out of guest serial/JSON responses. Screenshots and measured activation timestamps are retained under `out/m8-vm.*`. To return to a console image after desktop development, rebuild `make rootfs PROFILE=cli` and `make system-card PROFILE=cli`. A SYSTEM image cannot be relabeled as another profile without rebuilding the matching filesystem. ## Use the development SYSTEM `profiles/development.list` extends the same immutable base with native aarch64 **glibc** development tools. FDS control programs retain their static-musl build. Build and verify the profile on the x86_64 workstation: ```sh make rootfs PROFILE=development make rootfs-test make system-card PROFILE=development make development-test ``` These commands do not install compilers on the Pi's read-only SYSTEM at runtime. The profile must be built into a new SYSTEM image. Store source code and build outputs on an active DATA cartridge, or under `/tmp` for disposable experiments. The ordinary user's home is temporary in this profile too. At native startup, FDS advances an older wall clock to the image's fixed source timestamp in `/usr/share/fds/build-epoch`. A newer kernel/RTC clock is retained. This prevents immutable compiler and CMake inputs from appearing to be in the future when a machine boots without a usable RTC. It requires no network, time server or waiting. `/run/fds/clock.json` records which case occurred. The floor is an approximate lower bound, **not the current date**. Set the date explicitly from a trusted time source when accurate timestamps or certificate validity matter. Physical Pi RTC behavior remains to be tested. Boot-duration measurements continue to use `CLOCK_BOOTTIME`, independent of wall-clock changes. `make development-test` checks the older-clock case while compiling real projects; `make clock-test` checks preservation of a newer clock in a disposable VM. | Selected package(s) | Purpose | | --- | --- | | `gcc`, `glibc-devel` | Native C/C++ compilation, libc headers and startup files; GCC selects its matching support libraries and C++ headers | | `make`, `cmake`, `meson`, `ninja`, `pkg-config` | Common project build systems and dependency discovery; Meson brings its Python runtime | | `rust`, `cargo` | Native Rust development and package builds; Rust selects its standard library and LLVM runtime dependencies | | `git` | Local source history and optional remote checkout when networking is explicitly enabled | | `gdb`, `strace` | Interactive debugging and syscall inspection | | `vim` | Terminal editing, with no graphical session required | | Xvfb and the existing X11 diagnostic tools | Virtual desktop integration tests; they remain opt-in | No development package is added to the boot service bundle. The profile uses GCC for C/C++ and Rust for Rust; additional compilers and physical performance profilers can be considered separately when there is a concrete need. For a disposable first C program at `FDS>`: ```sh mkdir -p /tmp/hello cd /tmp/hello printf '#include \nint main(void) { puts("Hello from FDS"); }\n' >hello.c cc -g -o hello hello.c ./hello gdb ./hello ``` Within GDB, use `break main`, `run`, `next`, and `quit`. For persistent work, use `/data/projects` after selecting a healthy DATA cartridge. Stop programs and leave that directory before `fds eject BAY`. For a dependency-free Rust example: ```sh cargo new --vcs none /tmp/hello-rust cd /tmp/hello-rust cargo run --offline ``` `--offline` avoids contacting a registry. Projects with third-party crates need those sources prepared beforehand or an explicit network connection. To preserve Cargo downloads, put `CARGO_HOME` on DATA and stop related builds before ejecting. An ordinary development program uses the target's glibc environment; rebuilding the FDS distribution and its static control plane still follows the documented x86_64 cross-build workflow. `make development-test` runs native C, C++ and Rust compiler/linker executions, CMake/Meson/Ninja/Make builds, GDB and strace, a local Git commit, and a headless Vim edit in a disposable ARM VM. It verifies read-only SYSTEM, absent optional services and native shutdown. Test results are recorded in [M12 validation](m12-validation.md); they do not establish Pi compiler throughput.