th-dlls test: add HsOpenSSL package-keys
Home /
Input Output /
haskell.nix
May 08, 8-9 PM (0)
May 08, 9-10 PM (1)
May 08, 10-11 PM (0)
May 08, 11-12 AM (1)
May 09, 12-1 AM (1)
May 09, 1-2 AM (0)
May 09, 2-3 AM (0)
May 09, 3-4 AM (1)
May 09, 4-5 AM (1)
May 09, 5-6 AM (0)
May 09, 6-7 AM (0)
May 09, 7-8 AM (0)
May 09, 8-9 AM (0)
May 09, 9-10 AM (0)
May 09, 10-11 AM (2)
May 09, 11-12 PM (0)
May 09, 12-1 PM (0)
May 09, 1-2 PM (0)
May 09, 2-3 PM (0)
May 09, 3-4 PM (0)
May 09, 4-5 PM (0)
May 09, 5-6 PM (0)
May 09, 6-7 PM (0)
May 09, 7-8 PM (0)
May 09, 8-9 PM (0)
May 09, 9-10 PM (0)
May 09, 10-11 PM (0)
May 09, 11-12 AM (0)
May 10, 12-1 AM (3)
May 10, 1-2 AM (0)
May 10, 2-3 AM (0)
May 10, 3-4 AM (0)
May 10, 4-5 AM (0)
May 10, 5-6 AM (0)
May 10, 6-7 AM (0)
May 10, 7-8 AM (0)
May 10, 8-9 AM (1)
May 10, 9-10 AM (0)
May 10, 10-11 AM (2)
May 10, 11-12 PM (0)
May 10, 12-1 PM (0)
May 10, 1-2 PM (4)
May 10, 2-3 PM (0)
May 10, 3-4 PM (0)
May 10, 4-5 PM (0)
May 10, 5-6 PM (0)
May 10, 6-7 PM (0)
May 10, 7-8 PM (0)
May 10, 8-9 PM (3)
May 10, 9-10 PM (1)
May 10, 10-11 PM (4)
May 10, 11-12 AM (0)
May 11, 12-1 AM (2)
May 11, 1-2 AM (0)
May 11, 2-3 AM (0)
May 11, 3-4 AM (0)
May 11, 4-5 AM (0)
May 11, 5-6 AM (0)
May 11, 6-7 AM (0)
May 11, 7-8 AM (0)
May 11, 8-9 AM (0)
May 11, 9-10 AM (0)
May 11, 10-11 AM (0)
May 11, 11-12 PM (0)
May 11, 12-1 PM (1)
May 11, 1-2 PM (0)
May 11, 2-3 PM (0)
May 11, 3-4 PM (0)
May 11, 4-5 PM (0)
May 11, 5-6 PM (0)
May 11, 6-7 PM (0)
May 11, 7-8 PM (0)
May 11, 8-9 PM (0)
May 11, 9-10 PM (0)
May 11, 10-11 PM (0)
May 11, 11-12 AM (0)
May 12, 12-1 AM (0)
May 12, 1-2 AM (0)
May 12, 2-3 AM (0)
May 12, 3-4 AM (0)
May 12, 4-5 AM (2)
May 12, 5-6 AM (0)
May 12, 6-7 AM (0)
May 12, 7-8 AM (0)
May 12, 8-9 AM (7)
May 12, 9-10 AM (6)
May 12, 10-11 AM (3)
May 12, 11-12 PM (1)
May 12, 12-1 PM (0)
May 12, 1-2 PM (1)
May 12, 2-3 PM (1)
May 12, 3-4 PM (0)
May 12, 4-5 PM (0)
May 12, 5-6 PM (0)
May 12, 6-7 PM (0)
May 12, 7-8 PM (0)
May 12, 8-9 PM (0)
May 12, 9-10 PM (0)
May 12, 10-11 PM (1)
May 12, 11-12 AM (0)
May 13, 12-1 AM (3)
May 13, 1-2 AM (0)
May 13, 2-3 AM (0)
May 13, 3-4 AM (0)
May 13, 4-5 AM (0)
May 13, 5-6 AM (2)
May 13, 6-7 AM (0)
May 13, 7-8 AM (0)
May 13, 8-9 AM (0)
May 13, 9-10 AM (0)
May 13, 10-11 AM (0)
May 13, 11-12 PM (0)
May 13, 12-1 PM (0)
May 13, 1-2 PM (0)
May 13, 2-3 PM (0)
May 13, 3-4 PM (0)
May 13, 4-5 PM (0)
May 13, 5-6 PM (0)
May 13, 6-7 PM (0)
May 13, 7-8 PM (0)
May 13, 8-9 PM (0)
May 13, 9-10 PM (0)
May 13, 10-11 PM (0)
May 13, 11-12 AM (0)
May 14, 12-1 AM (2)
May 14, 1-2 AM (0)
May 14, 2-3 AM (0)
May 14, 3-4 AM (1)
May 14, 4-5 AM (0)
May 14, 5-6 AM (0)
May 14, 6-7 AM (2)
May 14, 7-8 AM (0)
May 14, 8-9 AM (1)
May 14, 9-10 AM (0)
May 14, 10-11 AM (0)
May 14, 11-12 PM (0)
May 14, 12-1 PM (0)
May 14, 1-2 PM (0)
May 14, 2-3 PM (0)
May 14, 3-4 PM (0)
May 14, 4-5 PM (0)
May 14, 5-6 PM (0)
May 14, 6-7 PM (0)
May 14, 7-8 PM (0)
May 14, 8-9 PM (0)
May 14, 9-10 PM (0)
May 14, 10-11 PM (0)
May 14, 11-12 AM (0)
May 15, 12-1 AM (1)
May 15, 1-2 AM (0)
May 15, 2-3 AM (0)
May 15, 3-4 AM (0)
May 15, 4-5 AM (2)
May 15, 5-6 AM (0)
May 15, 6-7 AM (0)
May 15, 7-8 AM (0)
May 15, 8-9 AM (0)
May 15, 9-10 AM (0)
May 15, 10-11 AM (1)
May 15, 11-12 PM (1)
May 15, 12-1 PM (0)
May 15, 1-2 PM (0)
May 15, 2-3 PM (2)
May 15, 3-4 PM (0)
May 15, 4-5 PM (0)
May 15, 5-6 PM (0)
May 15, 6-7 PM (0)
May 15, 7-8 PM (0)
May 15, 8-9 PM (0)
68 commits this week
May 08, 2026
-
May 15, 2026
v2 builder: handle Custom-Build / local packages where plan-nix's uid is a placeholder
Custom-Build packages collapse every component (lib / sublib / exe / setup) into a single plan-nix entry — plan-nix marks the shared entry by leaving `component-name` unset. cabal-install installs every component under one shared `<pkg>-<ver>-<hash>/` directory and writes a single `.conf`. With a per-component target (`:pkg:<pkg>:lib:<lib>`) the lib slice's `cabal v2-build` builds only the library yet still drops the package-wide `.conf` into `package.db`. A downstream exe slice that lndir-composes this then sees the `.conf`, concludes the unit is already installed, and reports "Up to date" without building the exe. Compounding this, plan-nix records local packages (those listed in `packages:`) as `style: "local"` with the placeholder uid `<pkg>-<ver>-inplace`. The slice serves the same source as a tarball repo (`style: "global"` / `pkg-src: repo-tar`) and cabal computes a content-hashed real uid like `pkg-0.1.0.0-6b5595c2`. plan-nix's uid can never match cabal's — so the previous `expectedUnitId`-based cleanup, which kept only the plan-nix uid's dir, wiped out cabal's freshly installed real unit. Two fixes: * `comp-v2-builder.nix`: when plan-nix marks the package as Custom-Build (`component-name == null`), expand `targetSelector` to `:pkg:<pkg>` so every slice's `cabal v2-build` materialises the full shared-uid unit (lib + every exe). Downstream slices that lndir-compose the output then get the `bin/` they need. * `build-cabal-slice.nix`: when `expectedUnitId` is null, fall back to the captured uids cabal actually installed in this slice (read from `$buildRoot/captured-unit-ids`) for the trim-keep set. If captured-uids is empty (cabal short-circuited on "Up to date" because a dep slice already supplied everything), skip the trim entirely so `kindSpecificInstallPhase` can still find `bin/<exeName>` in the lndir-composed dep-slice content. Verified on `aarch64-darwin.unstable.ghc9124.native.tests.setup-deps.run` (with and without the library component) and `aarch64-darwin.unstable.ghc9141.native.tests.setup-deps.run`.
v2 builder: filter missing .o files for ghcjs backpack ar archives
When cabal v2-build instantiates a backpack signature on ghcjs, GHC's JS backend doesn't always emit a `.o` for the signature module — the signature is filled by an external implementation unit. Cabal then asks `emar` to archive both `Module.o` and `Consumer.o`, and `llvm-ar` aborts on the missing `Module.o`.
v1 had a wrapper in `comp-builder.nix` for this case but its gate (`instantiations != {}`) only fires on the indefinite library drv, never on the exe drv whose internal `cabal v2-build` rebuilds the sibling instantiated lib. v2 (cabal v2-build everywhere) never matches that gate at all.
Fix in `build-cabal-slice.nix`: on ghcjs, extend `ghcShim` with an lndir mirror of GHC's libdir, materialise a writable `settings` file, swap the `ar command` entry to a wrapper that drops missing `.o` entries (both inline argv and response-file lines), and wrap `ghc`/`ghc-<v>` with `makeWrapper --add-flags "-B<patched libdir>"` so they pick up the patched settings. `--with-ar=` would be the natural lever, but cabal v2-build's internal "Building library" reads `ar command` from GHC's settings instead.
iserv-proxy: gate -with-rtsopts on Nix side, not in cabal.project
cabal.project doesn't allow `if impl(...)` inside a `package` stanza ("Unrecognized section 'if'"). Move the GHC version check to the Nix side via `lib.optionalString` on `compiler-nix-name.version`, so the option is only emitted into cabalProjectLocal when GHC ≥ 9.14.
iserv-proxy: bake --optimistic-linking via -with-rtsopts (GHC ≥9.14)
Add `-with-rtsopts=--optimistic-linking` to iserv-proxy's cabalProjectLocal so the RTS option is embedded in `main.c` at link time and processed with `RtsOptsAll` — bypassing the OPTION_UNSAFE gate that command-line `+RTS --optimistic-linking -RTS` is subject to. The wrapper scripts under `overlays/{linux-cross,mingw_w64}.nix` no longer need to pass it at invocation. Gated on `impl(ghc >=9.14)` since earlier RTS versions don't recognise the flag.
flake.lock: roll iserv-proxy back to 8cdc446 (drop aarch64-android stubs)
Reverts the two stub commits 0b8d6f6 / 3d649af in stable-haskell/iserv-proxy (`flake.lock` was bumped to them in haskell.nix commits 3303ea332 / 11c3abf88). Those commits added local definitions of bionic-absent symbols (`__cxa_guard_acquire`, `__cxa_guard_release`, `__fadvise64`, `__getpriority`, `__ldtoa`, `__sflush`, `__vfprintf`, `async_safe_fatal_no_abort`, `async_safe_format_buffer`) to `cbits/symbols.aarch64-android.c` to satisfy ld.lld during a link where they were undefined. In the current build configuration (NDK 27, current cabal flags, ghc 9.14.1) those symbols already resolve cleanly without the stubs — and the stubs were displacing the real bionic / libc++ implementations at runtime, causing the qemu segfault on aarch64-android iserv-proxy-interpreter that we'd been tracking as a separate "qemu builder" issue. Verified `tests.js-template-haskell.build` on `ghc9141.aarch64-android-prebuilt` passes with the override `--override-input iserv-proxy github:stable-haskell/iserv-proxy/8cdc446f` on linux-0 with `--builders ''`: iserv-proxy-interpreter links and runs under qemu-aarch64 with no segfault.
cabal-licenses.nix: allow LicenseRef-Apache
v2: round-trip <prog>-options: and hardeningDisable; fix android libsodium
Three changes to make the v2 builder honour two cabal.project / module-level settings that v1 already supported, then use them to fix the libsodium build on android cross targets. * modules/install-plan/configure-args.nix + modules/package-options.nix: Plumb plan.json's `--<prog>-option=VAL` entries (gcc / ld / hsc2hs / alex / happy / c2hs / cpphs / ghc-pkg / ...) through to a new per-package `programOptions` module option, grouped by program. v1 already consumes the same data via `configureFlags`; v2 had no path for this — `configure-options:` only works for `build-type: Configure` packages, so it can't round-trip arbitrary `--<prog>-option=` flags for Simple builds. * builder/comp-v2-builder.nix: emit a `package <pkg> <prog>-options:` line in the slice's cabal.project for each program with non-empty options, derived from the new `programOptions` option. cabal-install auto-generates a `<prog>-options:` field per program in its built-in `ProgramDb` (see `Distribution.Simple.Program.Db.programDbOptions`), so any `c2hs-options:` / `hsc2hs-options:` / etc. the user writes in cabal.project now reaches the program's invocation in slice builds. * builder/comp-v2-builder.nix + builder/build-cabal-slice.nix: Forward each slice's `component.hardeningDisable` through to the slice's mkDerivation so stdenv strips the listed hardening flags from `NIX_HARDENING_ENABLE` (e.g. `fortify` drops `-D_FORTIFY_SOURCE` for the slice's C-toolchain invocations). v2 was silently ignoring this haskell.nix module option. * test/cabal.project.android + test/exe-dlls/default.nix + test/exe-lib-dlls/default.nix + test/th-dlls/default.nix: New shared android-only cabal.project overlay that sets `package libsodium c2hs-options: --cppopts=-D_Null_unspecified=` so c2hs's C parser can preprocess past the Clang nullability annotation `_Null_unspecified` in the Android NDK's stdio.h. Three tests (exe-dlls / exe-lib-dlls / th-dlls) that depend on libsodium now conditionally include the overlay on isAndroid. v1 already had the equivalent via `packages.libsodium.configureFlags` in test/modules.nix and `hardeningDisable = ["fortify"]`; this cabal.project + module wiring makes the same fix reach v2. Verified `tests.th-dlls.build` on `ghc9141.aarch64-android-prebuilt`: libsodium builds cleanly on both v1 and v2. Pre-existing qemu segfault on the cached/built `iserv-proxy-interpreter` then prevents th-orphans from running TH splices — separate issue, documented in `memory/project_v2_aarch64_android_iserv.md`.
dummy-ghc-info: handle wasm 9.12+ interpreter/dyn ways; mktemp the diff file
Two fixes for the dummy-ghc-info still-failing matrix at eval 1507: * lib/dummy-ghc.nix — wasm (wasi32 / wasm32) gained an interpreter and dynamic RTS ways in GHC 9.12+. Real cross GHC for 9.12+ wasm now reports `Tables next to code: NO`, `Have / Use interpreter: YES`, and `RTS ways: v debug debug_dyn dyn`. Branch the existing ghcjs/wasm case on `versionAtLeast ghc.version "9.12" && targetPlatform.isWasm`; older wasm and all ghcjs keep the legacy stage-1-without-interpreter values. * test/dummy-ghc-info/default.nix — `mktemp` the diff-output file rather than hard-coding `/tmp/dummy-vs-real.diff`. The darwin sandbox refuses writes to `/tmp/`, so the previous code errored on permission-denied before printing the actual "diverges from real" block, making darwin failures look mysterious in CI. All the ghc9124/ghc984 ghcjs and ghc967/9124/9141 native darwin failures were just this — they pass once the diff write target is writable. Verified on linux-0 against eval 1507's still-failing matrix: PASS aarch64-darwin ghc9124 ghcjs / native PASS aarch64-darwin ghc9141 native / wasi32 PASS aarch64-darwin ghc967 native PASS aarch64-darwin ghc984 ghcjs / native PASS x86_64-darwin ghc9124 ghcjs / native PASS x86_64-linux ghc9124 wasi32 PASS x86_64-linux ghc9141 wasi32
comp-v2-builder: pass --with-PROG directly for cabal's built-in programs
cabal's `Distribution.Simple.Program.Db.userSpecifyPath` silently ignores `--with-PROG=PATH` for any program not pre-registered in the `ProgramDb` (the underlying `Map.update` over `unconfiguredProgs` is a no-op for unknown names). The 24 built-in programs from `Distribution.Simple.Program.Builtin.builtinPrograms` (alex, happy, c2hs, hsc2hs, cpphs, hscolour, doctest, haddock, etc.) are pre-registered, so cabal v2-build accepts `--with-NAME=PATH` for them directly. Arbitrary build-tool-exes (e.g. `pkga-exe`) aren't — cabal v2-build rejects those with "unrecognized 'v2-build' option". The previous `--configure-option=--with-NAME=PATH` blanket-wrap worked for arbitrary names but broke the built-in case: wrapping threads the flag through to per-package Setup configure, which DOES register the override in its own ProgramDb, but cabal v2-build's OWN program resolution (used for the per-slice preprocess invocations like "Running alex…") never sees the override and falls back to the cross-target binary in the bundled cabal-store. Concretely: language-c's alex preprocess step ran the cross-target aarch64 alex on an x86_64 build host, producing "posix_spawnp: invalid argument (Exec format error)". Split the flag emission accordingly: * built-in: emit `--with-NAME=PATH` directly to `cabal v2-build` * non-built-in: emit `--configure-option=--with-NAME=PATH` Verified by `tests.th-dlls.build` on `ghc9141llvm.aarch64-multiplatform` (previously failed on language-c's alex invocation, now builds through to th-dlls).
Add test/cabal-sublib-shell demonstrating sublib-in-shell bug
This test builds a haskell.nix dev shell for a consumer package that depends on a public sublib of another local package (provider:slib). Inside the shell it runs `cabal v2-build --offline` against a minimal cabal.project (only consumer), so cabal must satisfy provider and provider:slib from the shell's pre-populated ghc-pkg database. The test currently fails because cabal-install's solver rejects the installed provider with: rejecting: provider-0.1.0.0/installed-… (does not contain library 'slib', which is required by consumer) Root cause is in Cabal's cabal-install-solver/src/Distribution/Solver/Modular/IndexConversion.hs (`convIP`), which annotates every InstalledPackageInfo with only `LMainLibName` and never exposes sublibs registered as separate InstalledPackageInfo entries (as haskell.nix does for multi-library packages). A fix in cabal-install-solver is required before this test can pass.
comp-v2-builder: pass pname/version to slice mkDerivation, not name
The cross-aware nixpkgs naming convention inserts the host platform's `config` BETWEEN `pname` and `version` when both are set, giving store-path names like `<pkg>-<ctype>-<cname>-<crossSuffix>-<version>` (matching v1's `comp-builder.nix:528-530` shape). Setting `name` directly appends the cross suffix at the END instead, producing `<pkg>-<ctype>-<cname>-<version>-<crossSuffix>`, which is silently incompatible with consumers that mirror v1's naming — in particular `tests.coverage.run`'s per-package coverage report expects `pkgb-test-tests<crossSuffix>-0.1.0.0-check<crossSuffix>` (the cover.nix output dir is keyed off the check derivation's `.name`). Switch `buildCabalStoreSlice` to accept `pname` + `version` separately (replacing the single `name` parameter) so v2 slice names match v1's cross-aware shape. The doc slice puts `-doc` in `version` so the resulting drv name is `<pkg>-<ctype>-<cname>-<crossSuffix>-<version>-doc` — same prefix shape as the regular slice plus a distinguishing suffix. Verified `tests.coverage.run` on `ghc984.musl32` (previously failed on missing `.tix` path) plus `tests.dummy-ghc-info` on `ghc9141` native + aarch64-multiplatform, `ghc9124` musl32 + ucrt64, and `ghc967` native + musl64 (all still pass).
comp-v2-builder: wrap --with-PROG build-tool flags in --configure-option=
`cabal v2-build` only accepts `--with-PROG=PATH` for a fixed set of GHC-toolchain programs (`--with-ghc`, `--with-ghc-pkg`, `--with-gcc`, ...); arbitrary `--with-<build-tool-exe>=PATH` is rejected with "unrecognized 'v2-build' option". Threading each flag through `--configure-option=` makes cabal pass it to per-package `Setup configure`, which DOES accept arbitrary `--with-PROG=PATH` for any program declared as a build-tool. This unblocks cross builds of slices whose deps have `build-tool-depends:` on a non-GHC-toolchain executable (e.g. `pkgb test-suite tests` depending on `pkga:pkga-exe` in `tests.coverage.run` on `ghc984.musl32`). Kept the flag at the cabal-CLI level rather than emitting a `package * configure-options:` block in cabal.project — the latter enters EVERY package's `pkgHashConfigureOptions` and forks the UnitId for packages that don't declare the build-tool from what their own slice computed. CLI-level flags only contribute to the hash of packages that actually invoke the program.
dummy-ghc: fix RTS-ways set for 9.8 native and add ld response-files filter
Verified `ghc --info` directly on each nixpkgs cross/native GHC derivation; the boundary between the 12-way set (with `v`, `p`, `_dyn` but no `_p_dyn`) and the full 16-way set is actually at 9.12, not 9.8 — and 9.8 still uses the 10-way 9.6-style ordering (no `v`/`p`, no `_dyn`). Updated branches: * < 9.10 non-musl: 10-way set. * < 9.12 musl OR 9.10 anything: 12-way set. * >= 9.12: 16-way set. `ld supports response files` shows up in 9.8+ native but cabal doesn't drive UnitId hashing from it; add to ignoredFields. Verified by `tests.dummy-ghc-info` on linux-0 for: ghc967/984 native + musl, ghc9103 native + static, ghc9124 musl32 + ucrt64, ghc9141 native + aarch64-multiplatform, ghc983 musl32.
dummy-ghc: align --info with real GHC across more compiler/cross variants
Extend `lib/dummy-ghc.nix` and the `tests.dummy-ghc-info` filter
list so the dummy's `--info` matches the real cross/native GHC
`--info` for the variants tested in hydra:
* aarch64-multiplatform (linux-gnu cross, Stage 1, no `_dyn`
RTS ways) — add a cross-linux-gnu branch alongside
android/static, keyed off cpu mismatch so libc-only crosses
(musl64) fall through to the native branch.
* `cross compiling` field now compares cpu + kernel rather
than the full platform triple, matching what GHC itself
reports (libc differences alone are not cross).
* musl32 (i686-linux-musl) and ucrt64 (x86_64-w64-mingw32):
`platformString` normalises `i686 -> i386` and the
Windows vendor/kernel pair to `unknown/mingw32`, matching
real GHC's `Target platform` / `target platform string`.
* ghc967 native and musl64: branch the RTS-ways string on
GHC version (9.6 uses a smaller, differently-ordered set
than 9.8+) and on libc (the 9.6 musl bootstrap reports a
12-way set while stock nixpkgs 9.6.7 reports 10).
Test-side: add a small set of per-version cosmetic fields to
`ignoredFields` (`GCC extra via C opts`, `LLVM clang command`,
`ld command`/flags, `dllwrap`, `touch command`,
`target RTS linker only supports shared libraries`) that don't
drive cabal-install elaboration.
Verified `tests.dummy-ghc-info` passes on linux-0 (with
`--builders ''`) for: ghc9141 native + aarch64-multiplatform,
ghc9124 musl32 + ucrt64, ghc967 native + musl64.
iserv-proxy: route android static-linking flags via cabalProjectLocal
v2 ignores the `setupBuildFlags` + `enableDebugRTS = true` mechanism that v1 used to give the cross-compiled iserv-proxy-interpreter its `-optl-static -optl-ldl -debug` (and `-optl-no-pie` on aarch32) at link time. Express the equivalent through cabal.project so plan-nix records the flags in the slice's UnitId-relevant configure-args. The inner `pkgs.stdenv.hostPlatform.isAndroid` guard scopes the stanza to the cross-host evaluation only; the build-platform iserv-proxy (`exes final.pkgsBuildBuild`) does not pick it up and links normally. Known issue: the resulting binary still SIGSEGVs under qemu-aarch64 at startup — see `project_v2_aarch64_android_iserv.md` in `.claude/memory`. Tracking separately.
iserv-proxy: add -v to android ghc-options for build diagnostics
Forces a fresh build with the GHC linker invocation visible in the log so we can compare the v2 link line with v1's.
iserv-proxy: add cabal static/shared flags to android cabalProjectLocal
Belt-and-braces alongside the existing -optl-static ghc-option, and changes the slice hash to force a clean rebuild on linux-0 (avoiding a possibly-tainted binary cached from nix-linux-builder).
iserv-proxy: drop executable-static cabal flag (match v1 exactly)
v1's iserv-proxy build only uses `-optl-static -optl-ldl` to drive static linking; it does NOT set cabal's `executable-static`. The `executable-static` flag forces ghc to use a specific static link flow that combined with `-optl-static` may have led to subtle binary differences (the strace showed v2 segfaulting in early RTS init while v1 mmap'd its GC arena correctly). Drop `executable-static` to mirror v1.
iserv-proxy: drop -debug from android ghc-options
The static aarch64-android iserv binary segfaulted under qemu before reaching argv parsing — the strace shows it hit the stack guard page during RTS init. The debug RTS variant (`-debug`) does more setup work at startup and pushed stack usage past qemu's mapped guard. v1 set `enableDebugRTS = true` via the haskell.nix module option but apparently never actually ran under qemu with the recent qemu / RTS versions — or v1's `Setup build --ghc-option=-debug` doesn't propagate quite the same way as `ghc-options: -debug` in cabal.project's pkgHash. Without -debug the binary runs to args parsing under qemu, so this is what we want. -optl-static / -optl-ldl stays.