Shell build cost — runtime/shell task group 2 ============================================= Host: solomon, x86_64-linux, 32 cores, 125 GiB RAM Nix: nix (Nix) 2.35.2 Date: 2026-09-22 nixpkgs: the flake.lock pin (20b1ddd1aa5ace70c9468305030aa4f9ef79671b), the same pin docs/display-environment-options.md measured against Config: nixosConfigurations.k230 with k230.shell = { enable = true; probes = true; debugLog = true; } -- nix/shell.nix, i.e. sway (Xwayland off) + foot + wvkbd + seatd, plus the bring-up probes cage, cage-rgb565, drm_info, libinput, wayland-utils, wlfps. Baseline: nixosConfigurations.k230-console, the same board with the shell off. Its toplevel derivation is byte-identical to main's (i724km86i4ysyq47zajinx4bs10d5hh5-nixos-system-nixos-26.11.20260919.20b1ddd.drv) and `nix build --dry-run .#toplevel-console` reports nothing to build and nothing to fetch, so every number below is a delta on a fully present baseline closure. Task 2.1 — the module evaluates both ways ----------------------------------------- $ nix eval --raw .#nixosConfigurations.k230-console.config.system.build.toplevel.drvPath /nix/store/i724km86i4ysyq47zajinx4bs10d5hh5-nixos-system-nixos-26.11.20260919.20b1ddd.drv $ nix eval --raw .#nixosConfigurations.k230.config.system.build.toplevel.drvPath /nix/store/xfcjmdifzqxi4n9sgdq68hx7cdirisk7-nixos-system-nixos-26.11.20260919.20b1ddd.drv (the second path is the pre-rename evaluation; the final one is recorded with the build below) Dry run, before building anything --------------------------------- $ nix build --dry-run .#shell-compositor these 139 derivations will be built these 388 paths will be fetched (1.3 GiB download, 3.8 GiB unpacked) $ nix build --dry-run .#nixosConfigurations.k230.config.system.build.toplevel these 237 derivations will be built these 555 paths will be fetched (1.8 GiB download, 5.7 GiB unpacked) Against the estimate of 89 derivations / 875 MiB in docs/display-environment-options.md these look far worse, and the difference has three parts, each checked rather than guessed: 1. The count includes non-compile derivations. The 139 for the compositor include ~45 that are patches, setup hooks, wrapper scripts and `python3-env` builders (absolute_gir_path.patch, pkg-config-role-hook, pythoncheck.sh, ...). They take seconds. The document's 89 was counted the same way, so this does not explain the gap on its own. 2. About 50 of the 237 are REBUILDS OF DERIVATIONS THE BASELINE ALREADY CONTAINS. Checked with nix-store -q --outputs: for systemd (hrg87xdmvmibsh6mwh9i65mhy03i8887, the very same derivation config.systemd.package names in both configurations) the `out` output is present and `dev`, `man` and `debug` are missing; util-linux has `bin`, `lib`, `mount`, `login`, `swap` and `lastlog` present and `out`, `dev`, `man`, `debug` missing; linux-pam and openssh likewise keep `out` and have lost `dev`/`man`/`doc`. Those outputs were garbage-collected on this host after the baseline was built, and sway's stack needs some of them (libsystemd headers for the tray, libpam for seatd's login checks, util-linux's dev output through libinput's udev rules). Nix cannot rebuild one output of a multi-output derivation, so the whole derivation -- and everything between it and the toplevel: cryptsetup, lvm2, iptables, iproute2, shadow, sudo, kbd, e2fsprogs, dbus, the system units -- is rebuilt to identical store paths. These cost wall-clock time on this host and add nothing to the closure. The full list is under "Rebuilt baseline derivations" below. 3. The genuinely new work is larger than 89 because of what the measured stack pulls in on this pin beyond sway/foot/wvkbd themselves, traced with `nix why-depends --derivation`: - libjxl, openexr, imath, libhwy, gperftools, gtest, dav1d, libwebp, libtiff, lerc, giflib: gdk-pixbuf's loader cache, reached through swaybg, which sway-unwrapped hard-depends on for `output bg`. - a CROSS-COMPILED python3 with fonttools, pyudev, libevdev, mako, markdown, pyyaml: gobject-introspection for glib, reached through wvkbd -> pango/cairo -> glib. glib's introspection data is built with the target's python under mesonEmulatorHook. - go: libcap's Go bindings (libcap -> system-path). - libice, libsm, libxcb-{errors,image,render-util,util,wm}, libxft: wlroots' X11 *backend* and sway's libxcb-wm, still built with Xwayland off. - the probes: cage x2, drm_info (pciutils, libdisplay-info), libinput's tools, wayland-utils, wlfps, dejavu fonts. None of these was in the document's dry run because that run was made against a store that still had the GC'd outputs and, judging by the count, before this pin's gobject-introspection required a target python. Build, measured --------------- Task 2.2 — the compositor alone $ time nix build --max-jobs 8 --cores 0 .#shell-compositor wall 722.140 s (12 min 2 s), 139 derivations built, 388 paths fetched -> /nix/store/0dw6pla4d4qqyndk77mhahijjzv4g81v-sway-1.12 (the wrapper; sway-unwrapped, wlroots 0.20.2 with Xwayland off, librsvg and the gdk-pixbuf loader tail are inside that 12 minutes) Every derivation cross-compiled. Nothing refused. The one build failure of the whole exercise was in this repository's own wlfps derivation, recorded under 2.4. Task 2.4 — derivations that refused to cross-compile ----------------------------------------------------- One, and it was not in the shell stack itself. execline-riscv64-unknown-linux-gnu-2.9.9.2 ./configure: line 270: pkg-config: command not found ./configure: fatal: pkg-config --atleast-version=2.15.1.0 --print-errors -- libskarnet failed Reached through: services.seatd (nixos/modules/services/desktops/seatd.nix) -> ExecStart wraps seatd in `s6-notify-socket-from-fd` -> pkgs.s6 -> execline. execline's configure shells out to pkg-config to find skalibs and nixpkgs' derivation for it puts no pkg-config in nativeBuildInputs, so the cross build dies in configurePhase before compiling anything. What was done: nix/shell.nix no longer uses services.seatd. It declares its own seatd unit (Type=simple, no notify wrapper, no s6) and the compositor's unit waits for /run/seatd.sock in an ExecStartPre loop instead of on a readiness notification. seatd itself cross-compiled without complaint. Cost: -3 derivations (s6, execline, skalibs) and one fewer supervisor in the closure. Two failures in this repository's own additions, for completeness, both fixed in the same commit and neither a nixpkgs refusal: - wlfps: `pkg-config` in a cross buildPhase resolves to the build platform's, which has no riscv64 wayland-client. It must be $PKG_CONFIG, the cross wrapper. - cage-rgb565: the probe patch included , which is in libdrm's include path and cage does not depend on libdrm. The fourcc is now spelled out in the patch. Task 2.3 — the whole closure with the shell on ----------------------------------------------- $ time nix build --max-jobs 8 --cores 0 .#nixosConfigurations.k230.config.system.build.toplevel (the closure as first evaluated: without evemu, and before the kernel gained INPUT_UINPUT; those ride the image build below) Stages, because the build restarted three times on the three failures recorded under 2.4 (each restart resumed from the store; nothing was rebuilt twice): compositor (task 2.2) 139 derivations 722 s toplevel, attempt 1 98 to build -> failed at wlfps, 10 s toplevel, attempt 2 76 to build -> failed at cage-rgb565, 37 s toplevel, attempt 3 73 to build -> failed at execline, 122 s toplevel, attempt 4 30 to build -> done, 1814 s ------- wall-clock, all stages 2705 s = 45 min derivations built, all stages about 235 (237 in the initial dry run, less s6/execline/skalibs, plus the regenerated unit and /etc derivations) of which rebuilds of baseline derivations for GC'd outputs about 50 (see above) -> /nix/store/l6p5v97r0wjxvjcakflyvm7h9vwn9dc0-nixos-system-nixos-26.11.20260919.20b1ddd Closure, nix path-info -S: baseline (shell off) 1109.9 MiB 481 store paths shell on 1220.1 MiB 560 store paths delta +110.2 MiB +95 paths added, 16 replaced (the 16 are /etc, units, fontconfig and the system-path, regenerated) The added 95 paths total 111.2 MiB. Largest: librsvg 16.1, glib 15.2, python3.14-setuptools 11.1 (dragged in as a runtime reference by gobject-introspection's glib build, not used on the board), xkeyboard-config 10.3, dejavu-fonts 9.0, sway-unwrapped 5.8, libjxl 3.7, harfbuzz 3.5, gdk-pixbuf 2.8, libglvnd 2.6 (wlroots links it for its GLES renderer even though Pixman is selected; no Mesa behind it), wlroots 1.9. Against the estimate: docs/display-environment-options.md said 89 derivations and 875 MiB of fetches. Measured, the real delta ON THE BOARD is +110 MiB of closure; the build-host cost was 45 minutes and ~235 derivations, of which ~50 were this host re-making outputs it had thrown away and ~45 are hooks and patches. The 875 MiB "to fetch" figure was build-time inputs, not closure, and is not comparable to +110 MiB. Mesa: `grep -iE 'mesa|llvm|libgbm'` over the shell closure's 560 paths matches exactly one, mesa-libgbm-26.1.3 -- Mesa's generic buffer manager split out as its own package, which wlroots links for the GBM allocator it does not use here (allocator.c only reaches GBM when the renderer has WLR_BUFFER_CAP_DMABUF; Pixman does not). No Mesa GL or Vulkan driver, no llvmpipe, no LLVM: none of those names appears. The baseline has none of the four. A first draft of this file said "absent" outright, which was wrong by one library and is corrected here. Task 2.5 — touch-menu and screenshot probes, final image ---------------------------------------------------------- The final combined image added the touch-menu's `htop`, `jq` and `gnused` dependencies, its sudo rule and the probe-only `grim` screenshot utility. The image build recorded 22 derivations (the two new target packages were `htop` and `grim`; the rest were regenerated NixOS configuration and image derivations) and six fetched paths, 8.0 MiB unpacked. Its complete, source stage-1 image is `/nix/store/bfjzna9z1b5n5h1kx2qm543q5hkaisip-k230-sd-image.img`. Immediately afterwards, with no other full closure/image build running: $ time nix build --option substituters https://cache.nixos.org/ \ --max-jobs 2 --cores 8 .#toplevel wall 0.052 s; 0 derivations built -> /nix/store/n8hhaw7ncrqpb3dic8kc2ini8wsl6kq9-nixos-system-nixos-26.11.20260919.20b1ddd `nix path-info --closure-size` reports 1,280,320,680 bytes (1221.0 MiB) for that final enabled closure. The final closure contains riscv64 `htop`, `jq`, `sudo`, `evemu` and `grim`; the menu invokes the installed `htop` and `jq` store paths and `/run/wrappers/bin/sudo`, rather than relying on a terminal's ambient PATH. No package refused to cross-compile in this final increment. Corrected final image — build-host evidence only ------------------------------------------------ On 2026-09-22, from source tip `ce2f37f`, the corrected image was built with the official cache only and `--max-jobs 2 --cores 8`. It completed with exit status 0 in 58.187 s and built 21 derivations. This is a build-host result; it does not claim that the resulting image booted or that its shell ran on the physical board. image: /nix/store/s50h4h5awhx9479nhflayif890z2r6c0-k230-sd-image.img size: 2,308,689,920 bytes toplevel: /nix/store/7py9rixdgi2z79p6njcv3rik7azsvfa9-nixos-system-nixos-26.11.20260919.20b1ddd closure: 1,280,679,016 bytes kernel: /nix/store/1kgxy1xsvmyb2y8gzjxz4j2asirgy5ap-linux-riscv64-unknown-linux-gnu-6.6.36-xuantie The built kernel's installed configuration, not the Nix source fragment, was read from `/nix/store/sidwsxh7gq6ih1ma81xmh2c1pa8xmmk1-linux-riscv64-unknown-linux-gnu-6.6.36-xuantie-dev/lib/modules/6.6.36/build/.config`: CONFIG_INPUT_MISC=y CONFIG_INPUT_UINPUT=y The image reports stage 1 as `built from source by this flake (nix/stage1.nix)`. Its raw slots byte-compare with that built stage-1 output: fn_u-boot-spl.bin fe3d537f62c34a3d01127c58fef4caec50f0e65b0192670712ecb1599584a954 fn_ug_u-boot.bin bd562f2bcff46ba9352061b08074f19df2f17dd0ca080293b47c20e3f5a6e50e env.env cf108755a3cf3a8070a2f0ded84af672108e2301dfd58e5c2aa6a7555dbbd352 Portrait monitor image — build-host evidence only -------------------------------------------------- On 2026-09-22, the portrait monitor configuration at source commit `bafb59e58f404912248538f8b5a4077b4fbe54e1` was evaluated and built from a clean worktree. The immutable evaluated image derivation is `/nix/store/vlmb1p30qxscg37lkrw43fii8b03jagf-k230-sd-image.img.drv`; it contains the evaluated toplevel, kernel, stage-1 and device-tree derivation inputs listed below. The build used only `https://cache.nixos.org`, with `--max-jobs 2 --cores 8`. It exited 0 after 61.303 seconds and built 12 derivations. This is build-host evidence only; it does not claim that this image has been flashed or booted. image: /nix/store/3ss2r7bwgvxy1yhspkz2g42w504wlkvw-k230-sd-image.img size: 2,308,689,920 bytes toplevel: /nix/store/07qjn49hhr8m57x4afiapaxdxngl44zp-nixos-system-nixos-26.11.20260919.20b1ddd closure: 1,280,680,104 bytes kernel: /nix/store/1kgxy1xsvmyb2y8gzjxz4j2asirgy5ap-linux-riscv64-unknown-linux-gnu-6.6.36-xuantie stage 1: /nix/store/7s8fkshr67s6impk0syb9y2dl7xsfm54-k230-stage1 The installed kernel configuration in its matching `dev` output still has `CONFIG_INPUT_MISC=y` and `CONFIG_INPUT_UINPUT=y`. The image identifies stage 1 as `built from source by this flake (nix/stage1.nix)`. Both SPL copies, U-Boot, and both environment copies byte-compare against that exact built stage-1 output at their raw offsets: offset 1 MiB: fn_u-boot-spl.bin PASS offset 1.5 MiB: fn_u-boot-spl.bin PASS offset 2 MiB: fn_ug_u-boot.bin PASS offset 3 MiB: env.env PASS offset 3.125 MiB: env.env PASS fn_u-boot-spl.bin fe3d537f62c34a3d01127c58fef4caec50f0e65b0192670712ecb1599584a954 fn_ug_u-boot.bin bd562f2bcff46ba9352061b08074f19df2f17dd0ca080293b47c20e3f5a6e50e env.env cf108755a3cf3a8070a2f0ded84af672108e2301dfd58e5c2aa6a7555dbbd352 2026-09-22 declarative-defaults/firewall checkpoint (source 7a83afa): system: /nix/store/n5lqa24jxmlw5sn1q89y58js3ag7p859-nixos-system-nixos-26.11.20260919.20b1ddd command: nix path-info -S system closure: 1,280,479,704 bytes (1221.2 MiB) image: /nix/store/mc8di5pz0miia3fs27bqn1l9f7xx0a3w-k230-sd-image.img image file: 2,308,689,920 bytes image SHA-256: d6dacd7d4bfcc9233b78fd3fead6ca80e54dc679b9b4365fc9a1b915cbb585eb final portrait-config rebuild: six derivations, all succeeded; no kernel rebuild preceding kernel/system build: 734.86 seconds including shared-kernel wait (docs/evidence/declarative-daily-build.json). The system closure excludes the SD image file. The image-output closure is a distinct larger host-side measurement and is not installed userspace size.