evidence · 15.4 KiB

shell-build.txt

Committed at docs/evidence/shell-build.txt. This is what grounds the requirements that cite it.

Open raw source

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 <drm_fourcc.h>, 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-path>
  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.