Merge branch 'main' into develop
May 21, 3-4 AM (7)
May 21, 4-5 AM (4)
May 21, 5-6 AM (27)
May 21, 6-7 AM (14)
May 21, 7-8 AM (22)
May 21, 8-9 AM (34)
May 21, 9-10 AM (45)
May 21, 10-11 AM (37)
May 21, 11-12 PM (27)
May 21, 12-1 PM (63)
May 21, 1-2 PM (68)
May 21, 2-3 PM (60)
May 21, 3-4 PM (53)
May 21, 4-5 PM (20)
May 21, 5-6 PM (27)
May 21, 6-7 PM (27)
May 21, 7-8 PM (25)
May 21, 8-9 PM (23)
May 21, 9-10 PM (3)
May 21, 10-11 PM (29)
May 21, 11-12 AM (10)
May 22, 12-1 AM (16)
May 22, 1-2 AM (6)
May 22, 2-3 AM (8)
May 22, 3-4 AM (4)
May 22, 4-5 AM (11)
May 22, 5-6 AM (10)
May 22, 6-7 AM (21)
May 22, 7-8 AM (13)
May 22, 8-9 AM (38)
May 22, 9-10 AM (12)
May 22, 10-11 AM (18)
May 22, 11-12 PM (25)
May 22, 12-1 PM (24)
May 22, 1-2 PM (34)
May 22, 2-3 PM (56)
May 22, 3-4 PM (13)
May 22, 4-5 PM (29)
May 22, 5-6 PM (13)
May 22, 6-7 PM (19)
May 22, 7-8 PM (20)
May 22, 8-9 PM (12)
May 22, 9-10 PM (12)
May 22, 10-11 PM (41)
May 22, 11-12 AM (12)
May 23, 12-1 AM (9)
May 23, 1-2 AM (0)
May 23, 2-3 AM (3)
May 23, 3-4 AM (1)
May 23, 4-5 AM (1)
May 23, 5-6 AM (4)
May 23, 6-7 AM (12)
May 23, 7-8 AM (1)
May 23, 8-9 AM (3)
May 23, 9-10 AM (1)
May 23, 10-11 AM (1)
May 23, 11-12 PM (5)
May 23, 12-1 PM (1)
May 23, 1-2 PM (6)
May 23, 2-3 PM (5)
May 23, 3-4 PM (5)
May 23, 4-5 PM (4)
May 23, 5-6 PM (0)
May 23, 6-7 PM (3)
May 23, 7-8 PM (23)
May 23, 8-9 PM (1)
May 23, 9-10 PM (9)
May 23, 10-11 PM (21)
May 23, 11-12 AM (27)
May 24, 12-1 AM (9)
May 24, 1-2 AM (0)
May 24, 2-3 AM (1)
May 24, 3-4 AM (1)
May 24, 4-5 AM (0)
May 24, 5-6 AM (3)
May 24, 6-7 AM (1)
May 24, 7-8 AM (2)
May 24, 8-9 AM (2)
May 24, 9-10 AM (4)
May 24, 10-11 AM (4)
May 24, 11-12 PM (1)
May 24, 12-1 PM (7)
May 24, 1-2 PM (46)
May 24, 2-3 PM (5)
May 24, 3-4 PM (3)
May 24, 4-5 PM (18)
May 24, 5-6 PM (2)
May 24, 6-7 PM (4)
May 24, 7-8 PM (13)
May 24, 8-9 PM (10)
May 24, 9-10 PM (15)
May 24, 10-11 PM (34)
May 24, 11-12 AM (42)
May 25, 12-1 AM (9)
May 25, 1-2 AM (5)
May 25, 2-3 AM (6)
May 25, 3-4 AM (1)
May 25, 4-5 AM (6)
May 25, 5-6 AM (14)
May 25, 6-7 AM (17)
May 25, 7-8 AM (17)
May 25, 8-9 AM (32)
May 25, 9-10 AM (43)
May 25, 10-11 AM (64)
May 25, 11-12 PM (33)
May 25, 12-1 PM (43)
May 25, 1-2 PM (40)
May 25, 2-3 PM (20)
May 25, 3-4 PM (27)
May 25, 4-5 PM (16)
May 25, 5-6 PM (6)
May 25, 6-7 PM (7)
May 25, 7-8 PM (11)
May 25, 8-9 PM (12)
May 25, 9-10 PM (16)
May 25, 10-11 PM (44)
May 25, 11-12 AM (26)
May 26, 12-1 AM (12)
May 26, 1-2 AM (11)
May 26, 2-3 AM (8)
May 26, 3-4 AM (11)
May 26, 4-5 AM (6)
May 26, 5-6 AM (9)
May 26, 6-7 AM (26)
May 26, 7-8 AM (43)
May 26, 8-9 AM (39)
May 26, 9-10 AM (42)
May 26, 10-11 AM (45)
May 26, 11-12 PM (59)
May 26, 12-1 PM (34)
May 26, 1-2 PM (50)
May 26, 2-3 PM (50)
May 26, 3-4 PM (18)
May 26, 4-5 PM (20)
May 26, 5-6 PM (13)
May 26, 6-7 PM (20)
May 26, 7-8 PM (12)
May 26, 8-9 PM (15)
May 26, 9-10 PM (15)
May 26, 10-11 PM (35)
May 26, 11-12 AM (30)
May 27, 12-1 AM (16)
May 27, 1-2 AM (8)
May 27, 2-3 AM (9)
May 27, 3-4 AM (5)
May 27, 4-5 AM (32)
May 27, 5-6 AM (9)
May 27, 6-7 AM (49)
May 27, 7-8 AM (63)
May 27, 8-9 AM (37)
May 27, 9-10 AM (74)
May 27, 10-11 AM (83)
May 27, 11-12 PM (30)
May 27, 12-1 PM (50)
May 27, 1-2 PM (38)
May 27, 2-3 PM (53)
May 27, 3-4 PM (37)
May 27, 4-5 PM (6)
May 27, 5-6 PM (17)
May 27, 6-7 PM (18)
May 27, 7-8 PM (24)
May 27, 8-9 PM (13)
May 27, 9-10 PM (13)
May 27, 10-11 PM (29)
May 27, 11-12 AM (27)
May 28, 12-1 AM (9)
May 28, 1-2 AM (3)
May 28, 2-3 AM (4)
May 28, 3-4 AM (1)
3,353 commits this week
May 21, 2026
-
May 28, 2026
add ghcXX-minimal-ghc-web flavor: wasm + JS backend dev tooling
The wasm and JavaScript backends need a small set of dev-time tools
that aren't part of a stock Haskell shell:
* nodejs_22 — required for utils/jsffi/post-link.mjs (uses
import.meta.filename added in Node 20.11; Ubuntu's
apt nodejs is 18.x and silently breaks the post-link
step) and for JSFFI host execution at test time
* wabt — wasm-objdump for inspecting custom sections (e.g.
detecting ghc_wasm_jsffi imports) in wasm modules
* wasmtime — pure-WASI runtime, when a wasm module has no JSFFI
imports and can run standalone
* emscripten — the JavaScript backend's C toolchain (emcc / em++ /
emar / emnm / emranlib / emstrip)
Currently downstream consumers (e.g. stable-haskell/ghc's wasm
cross-compiler CI) bootstrap these per-platform via apt + NodeSource +
nix-env + curl installers, plus PATH workarounds for devx scrubbing
/usr/bin. Shipping them in the flavor collapses ~70 lines of
platform-shell to one `shell:` line in user workflows.
Deliberately NOT bundled: wasi-sdk. Its version needs to match the
wasm32-wasi-ghc cross-compiler bundle that ghc-wasm-meta
(https://gitlab.haskell.org/ghc/ghc-wasm-meta) owns, so keeping that
pin in ghc-wasm-meta avoids version drift across two trees. Users
still bootstrap wasi-sdk via ghc-wasm-meta for wasm32-wasi builds.
Closure cost on aarch64-darwin (paired with the prior trim commit):
ghc98-minimal-ghc (current master) : 6.12 GB
ghc98-minimal-ghc (after trim PR) : 4.00 GB (-2.12 GB)
ghc98-minimal-ghc-web (this commit) : 5.99 GB (+1.99 GB)
Net: the web flavor with the full toolchain ends up SMALLER than the
current untrimmed minimal-ghc, because the trim commit removed
ghc-9.10.3 and emscripten's LLVM/apple-sdk now dedupes against the
shell's base nixpkgs pin (no version fragmentation).
Comfortably under the 10 GB GitHub Actions per-repo cache cap on
both Darwin and Linux (Linux delta is similar magnitude — emscripten
+ closure-compiler are the heavy hitters on both).
Verified inside the patched shell:
$ ghc --version # 9.8.4
$ cabal --version # 3.17.0.0
$ happy --version # 2.1.7
$ alex --version # 3.5.4.0
$ git --version # 2.51.2 (gitMinimal)
$ node --version # v22.21.1
$ wasm-objdump --version # 1.0.37
$ wasmtime --version # 38.0.3
$ emcc --version # 4.0.12-git
All on the expected store paths.
chore(deps): bump tmp in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [tmp](https://github.com/raszi/node-tmp). Updates `tmp` from 0.2.5 to 0.2.7 - [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md) - [Commits](https://github.com/raszi/node-tmp/compare/v0.2.5...v0.2.7) --- updated-dependencies: - dependency-name: tmp dependency-version: 0.2.7 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <[email protected]>
trim -minimal-ghc closure: build happy/alex with the shell's GHC
The `withGHCTooling` block in dynamic.nix sourced `happy` and `alex` from nixpkgs's `pkgs.haskellPackages`, which builds them with a different GHC than the shell's `compiler` (currently ghc-9.10.3 in nixpkgs vs ghc-9.8.4 in our shell). The Haskell library outputs of both packages live under `lib/ghc-<other-ver>/lib/*.dylib`; their .dylib path strings anchor the foreign GHC in the closure via Nix's reference scanner. On aarch64-darwin this dragged in ghc-9.10.3 (1.40 GB) and ghc-9.10.3-doc (753 MB) — ~2.15 GB of essentially-unused payload. Switch to haskell.nix's `tool` builder (same pattern as cross-js.nix and cross-windows.nix), which builds happy/alex with the shell's `compiler-nix-name`. The resulting library outputs reference the GHC that's already in the closure rather than dragging in a second one. While here: * swap `git` → `gitMinimal` — drops the heavyweight perl-modules and git-doc that aren't useful inside the dev shell (~150 MB cascade). * tool-map.nix: drop `inherit cabalProjectLocal` from happy/alex. They're standard mainline packages and build cleanly from regular hackage; the inherited cabalProjectLocal pinned a head.hackage SHA that was stale and broke fresh evaluations of `(tool "happy")` / `(tool "alex")`. While there, bump happy 1.20.1.1 → 2.1.7 and alex 3.2.7.3 → 3.5.4.0 to match what nixpkgs.haskellPackages was shipping previously, so users see no behavioural change. Measured on aarch64-darwin (ghc98-minimal-ghc): before: 6.12 GB / 228 paths after : 4.00 GB / 196 paths saved : 2.12 GB (34.7% smaller) Top removed paths: ghc-9.10.3 1399 MB ghc-9.10.3-doc 753 MB git-2.51.2 49 MB git-2.51.2-doc 15 MB perl5.40.0-SSLeay/Mozilla/IO-Socket-SSL (gitMinimal cascade) Verified inside the patched shell: $ ghc --version # 9.8.4 $ cabal --version # 3.17.0.0 $ happy --version # 2.1.7 (same as before) $ alex --version # 3.5.4.0 (same as before) $ git --version # 2.51.2
Cosmetic commits, docs and tracing::info optimization
adopt Ledger.Prelude helper lemmas from earlier PR
Make sizes of off-diagonal inputs similar to on-diagonal ones
Add cabal v2-build-based component builder ("v2 builder") (#2513)
* v2 builder: cabal v2-build slicing, dev shell, plan-nix matching
Adds the v2 component builder alongside v1 and a project-level
`builderVersion` switch that picks between them. Where v1 runs
`Setup.hs configure / build` per component, v2 runs `cabal
v2-build` against a per-slice slicing repo and pre-composed
starting store, registers each new unit under
`$out/store/ghc-<ver>[-inplace]/...`, and emits a plan-entry
diff against plan-nix when something diverges.
Highlights:
* `builder/comp-v2-builder.nix` — per-component slice builder.
Drives a per-slice cabal.project (constraints pinned from
plan-nix's `depends`, `extra-packages:` listing every
transitive dep, per-pkg `flags:` / `ghc-options:` /
`configure-options:` blocks) and a per-slice slicing repo
of source tarballs and a `00-index.tar.gz` so cabal's solver
hits a closed package set. Hands the cabal.project plus
the slice's direct dep slices off to `build-cabal-slice.nix`
to actually run cabal.
* `builder/build-cabal-slice.nix` — generic runner the per-
component builder calls into. Composes the starting store
via `lndir` from every dep slice (walks `pkgsHostTarget`
for direct deps and follows each entry's
`$out/nix-support/transitive-deps` for the rest of the
closure, so transitive dep slices stay out of nix-side
`buildInputs`), runs `cabal v2-build`, captures every newly
installed unit back into `$out/store`, writes the slice's
own `nix-support/transitive-deps` for downstream consumers,
and on `dryRunOnly` emits a per-entry diff between cabal's
planned `plan.json` and plan-nix's expectations.
* `builder/compose-store.nix` — `lndir`-based store
composition with the same direct + transitive-deps walk.
Used by `comp-v2-builder` for the `passthru.store` exposed
on each slice and by `shell-for-v2.nix` for the dev shell's
composed dep store.
* `builder/cabal-install-patches/{prune-unreachable-sublibs*,
skip-installed-revdeps-in-completed}.patch` — three
cabal-install patches the v2 slice solver needs to keep its
plan in sync with plan-nix.
* `builder/shell-for-v2.nix` — v2 dev shell. Resolves each
selected component's direct external `component.depends` to
its library (via `haskellLib.dependToLib`, so sublib
references resolve to their sublib slices), composes those
into a starting cabal store via `composeStore` (the
transitive closure is materialised at build time from each
slice's `nix-support/transitive-deps`), exposes the result
via `~/.cabal/store/<ghc>-inplace/` (the default
`exposePackagesVia = "cabal-store"`) or via a
`GHC_ENVIRONMENT`-wrapped ghc (`exposePackagesVia =
"ghc-pkg"`), and merges `passthru.depSlices` from
`inputsFrom` so cross + native shells both see each other's
slices.
* `builder/{default,hspkg-builder}.nix` — dispatch on
`builderVersion`: the per-component derivation at
`hsPkgs.<pkg>.components.<kind>.<n>` is whichever builder
the project selects (no per-component opt-in).
`hspkg-builder.nix` also resolves `homeDependIds` and the
`packageRefersOwnExe` flag from `config.plan-json-by-id`
(the project-level index) so these per-component lookups
are O(log N) attrset hits instead of linear scans over the
install-plan.
* `modules/{component-driver,project-common,shell}.nix` —
`builderVersion`, `crossTemplateHaskellSupport`,
`exposePackagesVia` options.
* `modules/install-plan/configure-args.nix` — filter
`--ghc-option=-hide-all-packages` out of `ghcOptions` for
the v2 builder (cabal injects it on every Setup configure
call, and round-tripping it through cabal.project would
duplicate it in `pkgHashProgramArgs`); extract
`--configure-option=...` entries into a new dedicated
`package.configureOptions` option (declared in
`modules/package-options.nix`) so the v2 builder can emit
them back into its own cabal.project.
* `modules/install-plan/non-reinstallable.nix` — gate the
ghcjs/wasm `ghci`-related `non-reinstallable` set on
`builderVersion != 2`; under v2 the slice solver follows
cabal's install plan literally.
* `lib/check.nix` — `passthru.isSlice` branch that runs the
slice-built exe directly instead of going through v1's
`setup test` pipeline.
* `lib/default.nix` — `uniqueWithNameKey` prefers
`identifier.unit-id` when present so two `foo-1.2.3` slices
with different inputs partition into different buckets.
* `lib/call-cabal-project-to-nix.nix` — set
`CABAL_INSTALLED_PACKAGE_ID_OS = pkgs.stdenv.buildPlatform.parsed.kernel.name;`
on the plan-to-nix derivation so the patched cabal-install
(#2501) actually fires; without the env var the patch is a
no-op.
* `compiler/ghc/default.nix`, `overlays/{bootstrap,tools}.nix`
— pin GHC, `hscolour`, and the boot tools to
`builderVersion = 1` so v2 churn doesn't trigger GHC
rebuilds while v2 is still settling.
* `overlays/{windows,mingw_w64}.nix` — v2-builder-aware
Windows-cross TH wrapper: `mingw_w64.nix` exposes
`wrapGhc :: ghc -> ghc` and `iservRuntimeLibs`;
`overlays/windows.nix` plumbs the build-platform
`runCommand` / `makeWrapper` / `libffi` through to that
module.
* `overlays/haskell.nix` — project-level shell dispatch
(`shellForV1` / `shellForV2`), `crossTemplateHaskellSupport`
plumbing, and `cabalProjectLocal` refactored so the
Windows-host iserv-proxy linker flags can be additive.
* `test/cabal-sublib-shell/` — new test exercising a sublib
consumer in the v2 dev shell. Other test wiring tweaks
(`test/{th-dlls,th-dlls-minimal,cabal-sublib}/default.nix`,
`test/cabal.project.local`, `test/{setup-deps,
shell-for-setup-deps}/pkg/pkg.cabal`, `test/default.nix`)
to keep things passing under v2.
* `ci.nix` — pin `evalSystem` default to `x86_64-linux` so
cross derivations evaluate consistently.
* `docs/dev/builder-v{1,2}.md` (+ `docs/SUMMARY.md` entry) —
design notes for the two builders.
Performance notes:
* `transitiveTarballs` is exposed as the deduped values of
`depTarballsDeduped`. The un-deduped concat (which sticks
every direct dep's full transitive list onto each consumer
unfiltered) grows exponentially with depth in a typical
Haskell graph (every package shares `base` / `bytestring` /
`text` / ...) and materialises multi-GiB blocks in the
evaluator on cardano-wallet-sized projects.
* `homeDepSliceOf` / `depTransitiveTarballsOf` fall through
to any sublib's slice when a dep package defines only
`library <name>` stanzas (no main library — e.g.
`cardano-wallet-ui:{common,shelley}`).
* `propagated` keeps `libs` Windows-only (matching v1):
cross-target slices land in the v2 dev shell's
`inputsFrom`, and unconditionally propagating `libs` would
drag e.g. `pkgs.liburing` from a Linux-target slice into a
Darwin consumer's `pkgsHostTarget`. Each slice gathers its
own transitive sysLibs (deduped) into `extraBuildInputs`
via `transitiveDepLibs` instead.
* v2 builder: open the cardano-wallet dev shell end-to-end
A cluster of fixes that together let the v2 dev shell evaluate, build,
and reuse prebuilt slices for cardano-wallet on aarch64-darwin. Most
fixes are independent in spirit but interact at the slice level — they
are bundled here to keep the cardano-wallet shell green in a single
commit.
- Sublib reachability env var (`HASKELLNIX_EXTRA_SUBLIB_SEEDS`)
threaded through both the solver-side and install-plan cabal-install
patches. Slices that target a sublib (or transitively depend on
one) seed the patched reachability walk from `pkgLibDepClosure` so
unrelated sublibs (e.g. `lib:testlib`'s deps) stay pruned.
- Source-repository-package handling in slices. Each
`pkg-src.type = "source-repo"` package gets a per-slice minimal git
repo (`git init -b minimal && git add . && git commit`) wrapped
around `${src}` and emitted as a `source-repository-package` block
in the slice's `cabal.project` — the same shape
`lib/call-cabal-project-to-nix.nix` produces at project level — so
cabal hashes the same source bytes plan-nix saw and the slice's
UnitId matches. Build-tools (`alex`, `happy`, `hsc2hs`, ...)
composed via `transitiveBuildToolSlices` so consumers find them in
the cabal-store instead of rebuilding.
- Plan-nix flag honouring for source-repo packages. Module-level
flag overrides (e.g. cardano-wallet's `flags.release = true`) don't
reach cabal at plan time, so plan-nix records cabal-file defaults.
`flagBlockFor` now reads from plan-json for source-repo pkgs so the
slice's `pkgHashFlagAssignment` matches plan-nix.
- Custom-build packages skip the UnitId check. Plan-nix carries a
single shared `id` per Custom-build package; cabal-install splits
them into per-component UnitIds that can never match. Detected via
missing `component-name` on the plan entry.
- `propagatedBuildInputs` for `depSlices` (was `buildInputs`) so
pkg-config deps and other propagated inputs chain transitively
via stdenv. Splice handles the cross-compilation case for normal
nixpkgs deps; slice $outs themselves don't auto-swap but cardano-
wallet's native shell doesn't trip that.
- `cp -rL` for local-package tarballs to inline out-of-bounds
symlinks like cardano-wallet's `lib/wallet/specifications ->
../../specifications` that cabal-install 3.16+ rejects as
[Cabal-7125] "Unsafe link target in tar archive".
- `$out/store` cleanup at end of installPhase. When the expected
UnitId is known, keep only `<uid>/`, `package.db/<uid>.conf` and
`lib/libHS<uid>-*` — `rm -rf $ghcDir` drops everything else
(including the lndir-composed dep symlink tree, which on a deep
graph was 10k+ entries that fixupPhase + NAR serialisation walked
for nothing). Falls back to `find -type l -delete` when the UnitId
isn't known (source-repo or `style: "local"`). Cache files
(`package.cache{,.lock}`) dropped from every slice — stale on
arrival downstream and a major source of "Keeping existing link"
spam at lndir time.
- `find -type d` instead of `ls -d */` for the unitdirs-before
snapshot. bash expands `*/` into args including OS-prefix
unit-ids (`-clsss-1.8.0.1-...`), and `ls` interprets the leading
`-` as flags. Same root cause as the earlier `grep -qx --` fix.
- v2 shell: walk through local packages transitively, collecting
external deps at every step. Local packages are excluded from the
composed cabal-store so cabal compiles them on demand from the
user's tree; their (transitive) external deps go in. Build-tool
exes that resolve to local packages are similarly excluded.
- v2 shell: `pkgs.gitMinimal` on PATH so `git --version` works
inside the shell. On Darwin the apple-sdk overlay sets
`DEVELOPER_DIR` to the SDK path (no tools), so `/usr/bin/git`'s
xcrun shim fails with "tool 'git' not found"; nix-managed git
bypasses the shim.
- Slice cabal: `--jobs=$NIX_BUILD_CORES` (capped at 4, mirrors v1)
on the `cabal v2-build` invocation, which passes through to
`Setup build -jN` for ghc per-module parallelism. Build-phase
flag, so it stays out of `pkgHashConfigureOptions` and UnitIds
remain stable.
* v2 builder: pass `-jN` through to `Setup build` via env var
cabal-install 3.16's `setupHsBuildFlags` deliberately leaves
`buildNumJobs = mempty` (TODO: [nice to have] upstream), so neither
`cabal v2-build --jobs=N` nor `--ghc-options=-jN` actually parallelise
per-module compilation:
* `--jobs=N` is package-level (one slice = one package) and is
*not* threaded through to Setup build.
* `--ghc-options=-jN` would land in `pkgHashConfigureOptions` and
fork the slice's UnitId from plan-nix.
`setup-build-num-jobs-env.patch` adds a small read of
`HASKELLNIX_BUILD_NUM_JOBS` at the call site that constructs
BuildFlags, feeding it into `buildNumJobs`. Build-phase flag, so
UnitIds stay stable. build-cabal-slice.nix exports the env var
capped at 4 (mirrors v1's `-j$(($NIX_BUILD_CORES > 4 ? 4 : ...))`),
so `Setup build` invokes `ghc --make -j4` and per-module compilation
parallelises again.
Verified end-to-end: io-classes slice's GHC response file now shows
`-j4` while the captured UnitId still matches plan-nix's
`-clsss-1.8.0.1-14f3d66e`.
* v2 dev shell: pre-build SRPs, filter project pkgs, dedup by unit-id
Three fixes to `builder/shell-for-v2.nix` so the shell's cabal
store matches the user's intuition (project packages built from
the working tree, everything else pre-built):
1. Switch the default `packages` from `selectLocalPackages` to
`selectProjectPackages`, and rename `pkgIsLocal` →
`pkgIsProject` to use `isProject` instead of `isLocal`.
`isLocal` covers source-repository-packages too, but SRPs are
pinned upstream code the user doesn't iterate on — they belong
in the shell's pre-built store, not excluded from it.
2. Filter project-package exes out of `buildToolDrvs`. Without
this, any project exe listed as `build-tool-depends` of another
project package gets pre-built and dragged into the shell
closure, along with all of its lib slices' deps (~25 stray
slices in the cardano-wallet shell). Mirrors v1's
`removeSelectedInputs` and the existing `ownBuildToolSlices`
filter.
3. Dedup the `projectPkgClosure` walk by `identifier.id` (cabal
unit-id), not `identifier.name`. v2 plan-nix splits each
unit-id component into its own `hsPkgs` entry — components are
spread across siblings — so deduping by package name dropped
every sibling after the first and lost their deps (e.g. the
`unit` test's `x509`/`pem` deps when `test-common` was visited
first).
* test/cabal.project.local: bump head.hackage SHA
The `--sha256` pin for the head.hackage tar no longer matched
upstream — CI was failing with `hash mismatch in fixed-output
derivation`. Update to the current sha256 reported by nix.
* v2 dev shell: exclude only `selectedPackages`, not all project pkgs
The previous commit treated every project package as "iterated on
from the working tree" and excluded them all from the shell's
cabal store. That broke `tests.cabal-sublib-shell.run`, which
intentionally selects just `consumer` and expects `provider` (a
sibling project package) to be pre-built — so an unpatched cabal
inside the shell can reuse provider's lib/sublib instead of
rebuilding from source.
Mirror v1's `removeSelectedInputs` semantics: only exclude packages
in `selectedPackages` itself. With the default
`packages = selectProjectPackages` every project pkg is selected
(so the cardano-wallet UX is unchanged); with an explicit
`packages = ps: [ ps.foo ]`, only `foo` is excluded and sibling
project pkgs land in the store.
Also drops the project-pkg closure walk — `selectedPackages`
already enumerates every unit-id fragment, so a straight
`concatMap getAllComponents` covers all components without the
walk.
* test/shell-for-setup-deps: use `ghc-pkg` exposure mode
The test runs `runghc conduit-test.hs` inside the v2 shell; that
needs `Conduit` visible to plain `ghc`/`runghc`. Default
`exposePackagesVia = "cabal-store"` only seeds `~/.cabal/store`
(which `cabal v2-build` reads), so plain ghc still misses the
setup-deps. Switch to `"ghc-pkg"` so the wrapped ghc stacks the
composed package.db via `GHC_ENVIRONMENT`.
* test/cabal-simple-prof: enable profiling via cabalProjectLocal
The test sets `enableProfiling = true` / `enableLibraryProfiling
= true` via haskell.nix modules. Under v1 those translate
directly to per-component `--enable-profiling` configure flags,
but under v2 (now the default) the slice reads its toggles from
plan.json's recorded `configure-args` — and plan.json was
generated from cabal.project alone, where neither flag is set.
The slice therefore built without `-prof`, and `+RTS -p` failed
with "the flag -p requires the program to be built with -prof".
Inject `profiling: True` / `library-profiling: True` at project
level via `cabalProjectLocal` so plan-nix records
`--enable-profiling` / `--enable-library-profiling`, matching the
slice's actual build and keeping the slice's UnitId
reproducible. The module-level overrides are kept so v1 still
builds with profiling too.
* v2 builder: support per-package configure-args
Previously `projectConfigPragmas` read configure-args off the
first configured plan entry and emitted them under a single
`package *` block, on the assumption that "the same flags appear
in every configured plan entry's configure-args". That holds for
project-wide toggles (--enable-optimization, --disable-shared,
...) but breaks the moment a user sets something per-package —
e.g. `package cabal-simple\n profiling: True`. The setting only
landed on cabal-simple's plan entry; if the first configured
entry was a transitive dep, the slice's cabal.project never saw
`profiling: True` and the exe built without `-prof`.
Group plan entries by `pkg-name`, take the union of pragmas
across each pkg's units (cabal.project only supports per-package
granularity), then split into:
* `package *` — pragmas common to *every* pkg-name. The slice's
`cabal v2-build` resolves transitive hackage deps fresh, so
these have to land project-wide for the deps' UnitIds to line
up with plan-nix.
* `package <name>` — for each pkg-name with pragmas extending
the baseline (e.g. `package cabal-simple` getting
`profiling: True` from
`packages.cabal-simple.enableProfiling = true` mirrored into
cabal.project).
`tests.cabal-simple-prof.run` now uses the per-package shape that
mirrors haskell.nix's modules:
package *
library-profiling: True
package cabal-simple
profiling: True
* v2 builder: run hpack on local hpack-using packages
When a project has `supportHpack = true` and a local package
ships only `package.yaml` (no `.cabal`), v1 ran hpack at the
component-build phase to generate the `.cabal`. v2 never ran
it, so the v2 source tarball had no `.cabal` and the
`v2-exe-repo` pre-build step failed with `tar:
<pkg>-<ver>/<pkg>.cabal: Not found in archive`.
Thread `cabal-generator` through `hspkg-builder.nix` to
`comp-v2-builder.nix` and run hpack inside the staged source
dir before tarballing, mirroring v1's
`comp-builder.nix:485` invocation. Skipped when the package
isn't local (hackage tarballs already contain a generated
`.cabal`) or when `cabalFile` is set (e.g. an X-revision from
`package-description-override`, which provides its own `.cabal`).
Verified against `tests.cabal-hpack.run`.
* test/cabal-sublib: restore ghcjs `broken` marker
Re-enables the `broken = isGhcjs && ghc >= 9.6.1` guard that
commit 19b9a5110 commented out while bringing up v2. The
underlying failure is in ghcjs/emscripten wasm-ld ("section too
large" linking the `-O`-optimized C-backend objects), which
predates v2 and isn't a slicer issue. Skip the test on ghcjs
again so CI doesn't fail on a pre-existing upstream limitation.
* test: move HsOpenSSL pointer-types workaround to cabal.project.local
`packages.HsOpenSSL.ghcOptions` previously held a
`-optc=-Wno-incompatible-pointer-types` workaround. v2's slices
read configure-args from plan.json; module-level `ghcOptions`
never reach plan-nix's plan.json, so under v2 the slice picked up
the flag (via comp-v2-builder's per-package ghcOptions block) but
plan-nix did not — and the resulting UnitIds diverged
(`HsOpnSSL-0.11.7.10-9ebdbccd` expected vs `-ecc34d19` produced).
Move the workaround into `test/cabal.project.local` under
`package HsOpenSSL ghc-options:` so plan-nix and the slice agree.
Drop the now-unneeded `HsOpenSSL` entry from `package-keys` in
`test/modules.nix`.
Verified against `tests.exe-dlls.build`.
* test/shell-for: use `ghc-pkg` exposure for runghc
The test runs `runghc conduit-test.hs` against `env.ghc` /
`envDefault.ghc`. With v2's default
`exposePackagesVia = "cabal-store"` only `~/.cabal/store` is
seeded; plain `runghc` doesn't read the cabal store, so it failed
with "Could not find module 'Conduit'". Switch the three shells
to `"ghc-pkg"` so `env.ghc` is wrapped to stack the composed
package.db via `GHC_ENVIRONMENT`. Mirrors the earlier fix in
`tests.shell-for-setup-deps`.
* v2 dev shell: speed up haskell-nix-cabal-store-sync
The script ran a per-conf `diff -q` and a per-unit `diff -qr` on
every shell entry — for cardano-wallet's ~240 slices that meant
forking hundreds of `diff` invocations and walking each unit's
file tree. Two short-circuits:
* Per-composedStore marker file at
`~/.cabal/store/.haskell-nix-shell-markers/<basename-of-src>`,
written after a successful run. On re-entry the marker is
present and the script exits before the scan. Different
shells have distinct basenames, so alternating shells don't
invalidate each other's markers.
* Readlink fast-path in the scan: confs and lib files are
installed as symlinks to `$src/...`, and unit dirs are lndir
trees of symlinks under `$src/$ghcName/$unitId/`. When the
target is already in the right shape, skip the per-file `diff`
fork. Only when the cheap check fails does the script fall
back to `diff -q` / `diff -qr` to detect genuine conflicts.
Measured on cardano-wallet's shell (~240 slices, fresh CABAL_DIR):
first run 24.5s, second run 0.012s.
* v2 builder: surface HPC artefacts and src dir for cover.nix
Three incremental improvements toward making `lib/cover.nix` work
against v2 lib/test slices. The end-to-end coverage test still
fails because v2's lib slices use a hashed UnitId
(`pkgb-0.1.0.0-<hash>`) while a sibling test slice rebuilds the
lib inplace and emits `.tix` referencing `pkgb-0.1.0.0-inplace`,
so HPC can't match them up. That alignment is a deeper change;
land the supporting plumbing first:
* Add `srcSubDir` / `srcSubDirPath` to v2 slice passthru so
`lib/cover.nix:19`'s `map (l: l.srcSubDirPath) mixLibraries`
no longer hits "attribute missing".
* Library slices now copy each `extra-compilation-artifacts/hpc/<way>/{mix,tix}`
subdir up to `$out/share/hpc/<way>/{mix,tix}/<pkg>-<ver>/`
(the predictable name `lib/cover.nix` and the cover-report
tests expect — v1 does this too in `comp-builder.nix:831`).
* v2's `lib/check.nix` branch copies `<exeName>.tix` from the
test run to `$out/share/hpc/vanilla/tix/<exeName>/`, mirroring
v1's `lib/check.nix:119`.
* v2 builder: error on `.profiled` with migration hint
v1 slices provided `.profiled` as an overlay rebuild with
`enableLibraryProfiling = true`. v2 reads configure-args from
plan.json, so an overlay would emit `--enable-profiling` toggles
that plan-nix doesn't know about and the slice's UnitId would
diverge.
Replace the (missing) attribute with a `throw` that points users
at `cabal.project` / `cabalProjectLocal`:
package <pkgname>
profiling: True
library-profiling: True
so plan-nix records the toggles and the slice's UnitId stays
aligned. Set the `throw` at both the top level and inside
`passthru` because mkDerivation only lifts passthru → top level at
derivation creation time; post-hoc `// { passthru = ... }` doesn't
re-lift.
Adds `docs/dev/profiling.md` with the migration recipe.
* configure-args: pick up profiling/coverage from plan.json
Plan-to-nix's `modules/install-plan/configure-args.nix` already
translates `--ghc-option=` and `--configure-option=` entries from
plan.json's per-pkg `configure-args` into haskell.nix module
options. Extend it to also pick up the `--enable-{profiling,
library-profiling, coverage}` toggles so v1's `comp-builder`
(which reads `enableProfiling`, `enableLibraryProfiling`,
`doCoverage` straight off the component) honours
`package <pkg>\n profiling: True` set in cabal.project — without
needing module-level overrides.
v2 already reads these from plan.json directly via its own
`projectConfigPragmas`; the picked-up values are merely
consistent there.
Migrate the `tests.{exe,exe-lib,th}-dlls.{check-,build-}profiled`
variants to a sibling project whose `cabalProjectLocal` enables
profiling — the v2 builder no longer ships `.profiled` as an
overlay rebuild (see `docs/dev/profiling.md`).
* v2 builder: align slice/check derivation names with v1
Drops the `cabal-slice-` prefix and reorders the slice's `name`
attribute from `<pkg>-<ver>-<ctype>-<cname>` to v1's shape
`<pkg>-<ctype>-<cname>-<ver>` (mirrors `comp-builder.nix:268`).
Same for the auxiliary `check-*` and `store-*` derivations.
Callers that hardcoded v1 derivation names — most prominently
`tests.coverage.run`, which checks for
`tix/pkgb-test-tests-0.1.0.0-check/tests.tix` — keep working
without per-test edits when projects flip
`builderVersion = 1 → 2`.
Also surface the lib slice's `.mix` files under
`<pkg>-<ver>-inplace/<Module>.mix` so they line up with the
inplace UnitId that test-slice-built `tests.tix` files reference
(`Tix [ TixModule "<pkg>-<ver>-inplace/<Module>" … ]`); the .mix
content itself is byte-identical between the lib slice's
hash-named UnitId build and the test slice's inplace rebuild.
Move the coverage-test's `doCoverage` modules into
`cabalProjectLocal` (`package <pkg>\n coverage: True`) so v2's
plan-nix records `--enable-coverage` and the slice actually
produces .mix/.tix output. The module-level overrides are kept
for the v1 builder.
Verified against `tests.coverage.run`.
* v2 builder: drop \`.jsexe/all.js\` exeExt under cabal v2-build
v1 (Setup.hs install) for ghcjs preserves the
\`bin/<exe>.jsexe/all.js\` directory layout, which is why
\`comp-builder.nix:450\` appends \`.jsexe/all.js\` to the exe name
for \`isGhcjs && ghc < 9.8\`. v2 builds via \`cabal v2-build\`,
whose install step bundles the \`.jsexe/\` contents into a single
self-contained \`#!/usr/bin/env node\` script at \`bin/<cname>\` —
there is no \`.jsexe/\` directory in the slice's output. Use
the bundled-file path so v2's \`exePath\` / \`find\` lookup land
on the file cabal actually produced.
Verified against \`aarch64-darwin.unstable.ghc967.ghcjs.hello\`
(slice surfaced \`bin/hello\` cleanly, no placeholder fallback).
* v2 builder: trim dist-newstyle/ from slice $out
The slice's `$out/dist-newstyle/` carried cabal's source tarballs
and the build tree — hundreds of MB to GB per slice for nontrivial
projects. Nothing downstream of a successfully-built slice reads
it: subsequent slices only pull from `$out/store/`, and the
diagnostic `checkAgainstPlan` is a separate derivation with its
own dist-newstyle.
Trim it at the end of `installPhase`, after
`comp-v2-builder.nix`'s test/bench install step (which does need
to find the unpinged binary in `dist-newstyle/build/.../<exe>`).
Lift `cache/plan.json` to `$out/plan.json` so it stays available
for human debugging — the rest of `cache/` is internal.
Verified on the ghc967.ghcjs.hello slice: $out shrinks from
~7.9GB to ~1MB, with `bin/`, `store/`, `plan.json`, `unit-ids`,
and `nix-support/` retained.
* test/with-packages: use \`project.shellFor\` instead of \`comp.shell\`/\`comp.env\`
v2 slices don't expose v1's per-component \`.shell\` / \`.env\`
attributes — \`shell\` is provided at the project level via
\`project.shellFor\`, and that mode covers what this test needs
(a wrapped \`ghc\` / \`runghc\` that can see the package's deps).
\`exposePackagesVia = "ghc-pkg"\` makes \`shell.ghc\` a wrapped
GHC stacking the composed package.db via \`GHC_ENVIRONMENT\`, so
\`runghc ./Point.hs\` and \`ghc Point.hs\` resolve the deps the
same way v1's \`library.env\` did.
Works under both v1 and v2 — no per-component shell required.
* test: migrate `.profiled` callers to sibling cabal.project
v2 slices no longer ship `.profiled` as an overlay rebuild — see
`docs/dev/profiling.md`. Migrate the remaining tests that
referenced `…components.<kind>.<name>.profiled`
(`js-template-haskell`, `th-dlls-minimal`, `gi-gtk`) to a sibling
project whose `cabalProjectLocal` enables profiling, matching the
shape already used by `exe-dlls` / `exe-lib-dlls` / `th-dlls`.
Expose each new project's `plan-nix` under a distinct
`ifdInputs` key so the materialised plan-nix expressions are
covered by CI:
plan-nix — non-profiled (default)
plan-nix-ei — externalInterpreter variant (th-dlls /
th-dlls-minimal)
plan-nix-profiled — profiled
plan-nix-profiled-ei — profiled + externalInterpreter
* plan-nix: gate `-inplace` on GHC ≥ 9.8
GHC 9.8 added an `-inplace` suffix to its boot-package UnitIds
(`base-4.19.2.0-inplace` vs `base-4.18.3.0` on 9.6) and started
emitting a `Project Unit Id` field in `ghc --info`. The dummy
ghc/ghc-pkg in `lib/call-cabal-project-to-nix.nix` was hardcoding
both, so cabal computed UnitIds against the dummy with
`-inplace` even on GHC 9.6. When the slice's real ghc 9.6 then
returned ids without `-inplace`, the slice's UnitId for any
package depending on a boot package (`colour` ↔ `base`) diverged
from plan-nix and the slice failed its UnitId check.
Verified empirically that `-inplace` appears starting at GHC 9.8:
ghc967 → id: base-4.18.3.0
ghc984 → id: base-4.19.2.0-inplace
ghc9103 → id: base-4.20.2.0-inplace
ghc9124 → id: base-4.21.2.0-inplace
ghc9141 → id: base-4.22.0.0-inplace
Make both the `Project Unit Id` field and the `-inplace` suffix
conditional on `versionAtLeast ghc.version "9.8"`.
Verified against `tests.shell-for.env` on ghc967 (which exercises
the colour slice that was failing in CI).
* v2 builder: expose `.doc` via sibling `cabal v2-haddock` slice
Under v1 every component's `.doc` was a sibling Setup haddock
derivation that shared the lib's UnitId. Under cabal v2-build
this no longer holds: `cabal v2-haddock` flips
`elabBuildHaddocks` (and the haddock-html / haddock-hscolour /
... family) on every unit's `ElaboratedConfiguredPackage`, which
all land in `pkgHashConfigInputs`. Calling `cabal v2-haddock`
against a plan that didn't already have `documentation: True`
forks every UnitId in the closure and triggers a from-source
rebuild.
Round-tripping `documentation: True` through plan.json's
`--ghc-option=-haddock` / `configure-args.nix` /
`ghc-options: -haddock` is NOT equivalent: the ghc-option keeps
haddock comments in `.hi` files but doesn't set the haddock-config
booleans, so the slice's `pkgHashConfigInputs` diverges from
plan-nix's and the dep closure's UnitIds fork (observed on
OneTuple in `tests.sublib-docs`: plan-nix `9a847723` vs slice
`f546bd36`).
Approach:
* `comp-v2-builder.nix` detects per-package `--ghc-option=-haddock`
in plan.json (the `documentation: True` signal cabal-install
surfaces) and emits `package <pkg>\n documentation: True\n`
in the slice's cabal.project for every documented package.
`-haddock` is filtered out of the per-pkg `ghc-options:` block
so cabal doesn't see it twice in `pkgHashGhcOptions`.
* Each library slice exposes `.doc`, a sibling derivation that
runs `cabal v2-haddock` against the already-built unit (no
closure rebuild). `.doc` throws with a migration hint when
documentation isn't in the project's plan-json — that's the
only shape where the UnitIds align.
* Doc slices propagate `(map d: d.doc)` for `docEnabled` deps
only, so cross-package hyperlinks resolve. Mixed projects
(some packages docs, others not) keep working because non-doc
deps come in as plain slices.
* `build-cabal-slice.nix` keeps cabal's native unit-dir layout
(`$out/store/<ghc>/<unit-id>/share/doc/html/`) so doc slices
`lndir` into `~/.cabal/store/` as drop-in replacements, and
cross-package hyperlinks (absolute
`file:///nix/store/<doc-slice>/store/<ghc>/<dep-uid>/...`)
resolve back into the slice's own tree. Non-target unit
haddocks are stripped from `$out` to keep each slice lean —
deps' html lives in the deps' own `.doc` slices.
`docs/dev/haddock.md` documents the v2 semantics, the
`documentation: True` requirement, and why there's no
`slice.haddockDir` (local plan-nix UnitIds use `<pkg>-<ver>-inplace`
form while the slice's cabal-store uses cabal's mangled hash form,
so there's no eval-time-stable html path — callers `find` it
under the doc slice).
Verified `tests.sublib-docs.run` builds Lib.html and Slib.html
under `slice.doc`, and OneTuple's UnitId in plan.json matches
plan-nix's recorded id.
* v2 builder: fix `ghcShim` collision when cross GHC ships unprefixed tools
GHC 9.14.1's `armv7a-android` cross GHC ships *both*
`<prefix>deriveConstants` and an unprefixed `deriveConstants` in
its `bin/` (the latter for build-host use). `ghcShim`'s
single-pass loop iterated `${ghc}/bin/*` alphabetically, hit
`armv7a-...-deriveConstants` first, and the case branch
synthesised an unprefixed-fallback alias
`$out/bin/deriveConstants -> <prefix>deriveConstants` (the
`[ -e ]` guard was for that fallback, not the raw link). When
the loop later reached the real `deriveConstants`, the raw
`ln -s "$f" "$out/bin/$base"` crashed with "File exists".
Earlier cross GHCs only shipped `<prefix>deriveConstants`, so
the fallback was the sole producer of `$out/bin/deriveConstants`
and never collided. GHC 9.14.1 added the unprefixed sibling and
exposed the race.
Switch to a two-pass shape: pass 1 links every source bin/ entry
under its own name, pass 2 synthesises unprefixed aliases only
for prefixed names that have no real unprefixed sibling. Real
files always win; aliases only fill genuine gaps.
Same fix in both `builder/build-cabal-slice.nix` and
`builder/shell-for-v2.nix` (which carries an identical shim for
the dev shell's cross-cabal wrapper).
* Replace post-plan ghc.src override with project-level useLocalGhcLib
`modules/configuration-nix.nix` used to unconditionally `mkForce`
`packages.ghc.src` to a symlinkJoin of `(configured-src + generated)`
under `compiler/`, redirecting `lib:ghc`'s source to the local GHC
tree *after* plan-nix had been computed against hackage's
`ghc-X.Y.Z.tar.gz`. v1 (Setup.hs) didn't care — it builds whatever
`src` it's given. v2 forks the slice's UnitId from plan-nix's
recorded one (the slice rebuilds from a different source than the
planner saw) and the slice fails its UnitId check.
Replace with an opt-in project-level flag, `useLocalGhcLib`, that
exposes the GHC compiler tree to the planner up-front. Mechanism
differs by project type:
* Cabal projects (modules/cabal-project.nix) inject a
`source-repository-package` block + `allow-boot-library-installs:
True` into `cabalProjectLocal`, with an `inputMap` entry keyed
by `<url>/<ref>` (string-context-stripped) so haskell.nix
short-circuits the `builtins.fetchGit` path. Plan-to-nix and
the v2 slice both see the same wrapped repo, cabal hashes the
same content into `pkg-src-sha256`, and UnitIds align.
* Stack projects (modules/stack-project.nix) re-add the
`packages.ghc.src` override as a contributed module, gated on
`useLocalGhcLib`. Stack-to-nix's input shape doesn't fit the
cabal source-repo path; instead we lean on stack only supporting
v1 for now (`builderVersion = mkForce 1` in stack-project.nix's
config) — v1 doesn't enforce UnitId alignment, so the post-plan
swap is fine.
The option itself lives in `modules/project-common.nix` so both
project types see it without duplication.
`builder/comp-v2-builder.nix` emits `allow-boot-library-installs:
True` in the slice's cabal.project whenever the target or any non-
pre-existing lib dep is on cabal-install's hard-coded
non-reinstallable list (`ghc`, `template-haskell`, `Cabal`,
`Cabal-syntax`, `ghc-prim`, `ghc-bignum`, `ghc-boot`,
`ghc-boot-th`, `ghc-heap`, `base`, `ghci`, `ghc-internal`, `rts`).
Without it the slice's solver rejects the source instance with
"constraint from non-reinstallable package requires installed
instance" once `ghc` is being source-built.
`test/ghc-lib-reinstallable/cabal.nix` and
`test/ghc-lib-reinstallable/stack.nix` opt in via
`useLocalGhcLib = true`. The stack test's resolver is bumped to
`nightly-2026-05-03` (latest known to the pinned stackage.nix at
the time of this commit).
Verified end-to-end:
* `tests.ghc-lib-reinstallable-cabal.run` (v2, ghc9141) — passes
* `tests.ghc-lib-reinstallable-cabal.run` (v1, ghc9141) — passes
* `tests.ghc-lib-reinstallable-stack.run` (v1, ghc9124) — passes
* cabal-project: inject `executable-static: True` for musl hosts
Plan-to-nix's cabal-install records `--disable-executable-static`
in `configure-args` for every component when the project hasn't
explicitly enabled it. v1's `builder/comp-builder.nix:384`
papered over this by injecting `--ghc-option=-optl=-static` (and
`-optl=-pthread`) at comp-builder time — outside plan.nix's view.
v1 doesn't enforce UnitId alignment with plan.json, so the
post-plan flag insertion is harmless there.
Under v2 the slice mirrors plan.json's `configure-args` exactly,
so without something at the project level the slice links
dynamically and the `tests.c-ffi.run` musl assertion (`grep "not
a"` on glibc-ldd output) fails.
Inject `executable-static: True` into `cabalProjectLocal` when
`stdenv.hostPlatform.isMusl`. Plan-to-nix then records
`--enable-executable-static`, v2's
`comp-v2-builder.nix:projectConfigPragmas` round-trips it through,
and cabal links the executable statically. v1 is unaffected — its
post-plan `-optl=-static` injection still fires, redundant but
harmless.
Verified by running `tests.c-ffi.run` on the
`x86_64-linux.unstable.ghc9141.musl64` jobset
(commit `d027fbe4e` + this patch in a fresh worktree).
* dummy-ghc: add Android branch matching real cross-ghc capabilities
`dummy-ghc` is the eval-time stand-in cabal-install runs against
during plan-to-nix. Its `--info` output shapes plan.json's
per-unit `configure-args` (which feed `pkgHashConfigInputs` and
therefore the UnitId). When the dummy reports different
capabilities than the real cross-ghc the slice rebuilds with,
plan-nix and slice compute different UnitIds and the slice's
expected-package check fails.
The "otherwise" branch (Linux / Darwin / native) reports
`Support shared libraries: YES`, `GHC Dynamic: YES`, RTS ways
including the full `_dyn` family, and `Stage: 2`. Real Android
cross-ghc (`aarch64-unknown-linux-android-ghc 9.14.1`) is a
stage-1 NDK build with no `_dyn` ways and no `Support shared
libraries` field at all (its `GHC Dynamic` is `NO`). Falling
through to "otherwise" made plan-nix record `--enable-shared`
for Android packages while the slice's real ghc silently flipped
to `--disable-shared`, forking the UnitId on
`pkgHashSharedLib`.
Add an explicit Android branch. Reference values cross-checked
against `aarch64-unknown-linux-android-ghc --info` on the live
9.14.1 cross GHC derivation; key fields:
* Support dynamic-too: YES (GHC's flag, kept like real ghc)
* GHC Dynamic: NO (was YES — drove --enable-shared)
* RTS ways: no `_dyn` family
* Stage: 1
* No `Support shared libraries` field (cabal interprets
absence as no-shared-libs, matching real ghc)
Verified `tests.extra-hackage` slices for
`x86_64-linux.unstable.ghc9141.aarch64-android-prebuilt` no
longer fail the slice's UnitId-alignment check. (The `.run`
step still fails because qemu-aarch64 can't load Android's
`/system/bin/linker64` to actually exec the binary — that's a
pre-existing runtime issue, not a v2 alignment problem.)
For musl, real and dummy ghc already agree
(`Support dynamic-too: YES, GHC Dynamic: YES`); the
`executable-static: True` injection in
`modules/cabal-project.nix` stays — it's a user-opt-in flag,
not capability-derived from `ghc --info`.
* shellFor: route through buildPackages so cross targets evaluate
`shellFor` (both v1 and v2) ran into "no C compiler provided for
this platform" under cross targets where the host stdenv has no
cc — most visibly ghcjs, also wasm and android-prebuilt. Three
distinct issues, all fixed here:
1. `mkShell` itself was `pkgs.mkShell` (host-stdenv-bound).
`pkgs.bashInteractive`'s eval reads `stdenv.cc.isClang`, and
under ghcjs cross there is no cc → throw. Pass
`mkShell = pkgs.buildPackages.mkShell` to both shellForV1 and
shellForV2 so the shell drv lives on the build platform
regardless of the host (the user opens the shell on their
build host anyway).
2. `pkgs.runCommand` was building `packageEnv` and `wrappedGhc`
(the wrapped GHC the shell exports as `shell.ghc`) against
the cross stdenv. Once these landed in the shell's
`nativeBuildInputs`, mkShell's dep-splicing eventually
re-evaluated the cross stdenv's bash via setup hooks, hitting
the same cc.isClang. Switch both to
`pkgs.pkgsBuildBuild.runCommand` and `pkgs.pkgsBuildBuild.makeWrapper`
so the wrappers themselves are build-platform drvs. (Native
builds are unaffected — `pkgsBuildBuild == pkgs` there.)
3. `store = composedStore` was a top-level attr to mkShell.
Recent nixpkgs's `mkDerivation` folds unknown top-level attrs
into `env` and type-checks each value, and `isDerivation
composedStore` forces evaluation of the cross stdenv's bash
the same way. Move `store` into `passthru.store` — mkDerivation
lifts passthru into the result drv anyway, so `shell.store`
continues to resolve.
Verified `tests.with-packages.test-shell` now evaluates under both
the `aarch64-darwin.unstable.ghc9141.ghcjs` (cross) and `.native`
jobsets, and `shell.store` still resolves on both.
* dummy-ghc: extend Android branch to cover pkgsStatic
The Android dummy-ghc branch added in 491a91d13 reports a stage-1
GHC built without dynamic support: no `_dyn` RTS ways,
`GHC Dynamic: NO`, no `Support shared libraries` field. The same
shape applies to nixpkgs's `pkgsStatic` (the haskell.nix `static`
jobset) — its real GHC is also built without dynamic support.
The only difference is `Stage`: Android cross is stage-1, the
static GHC is stage-2.
Without this branch, plan-to-nix records `--enable-shared` for
static-target packages while the slice's real ghc silently flips
to `--disable-shared`, forking the UnitId on `pkgHashSharedLib`
and breaking the slice's expected-package check.
Extend the Android branch's predicate to
`isAndroid || isStatic` and emit the right `Stage` per platform.
Reference: `pkgsStatic.haskell-nix.compiler.ghc9141 --info` on
the live `x86_64-unknown-linux-musl` derivation.
Verified: `tests.js-template-haskell.check` on
`x86_64-linux.unstable.ghc9141.static` builds end-to-end (slices
align, the test exe links statically as expected, the `.check`
derivation completes).
* dummy-ghc: extract to its own file + add cross-compiling field + test
Move the eval-time `dummy-ghc` script construction from
`lib/call-cabal-project-to-nix.nix` to a standalone
`lib/dummy-ghc.nix` so it can be tested independently.
Add a new test `tests.dummy-ghc-info` that diffs `dummy-ghc --info`
against the real GHC's `--info` (after stripping nix-store paths
and a hand-curated list of fields cabal-install does not consult
for elaboration / unit-id hashing). The test fails on any
remaining divergence, surfacing where `dummy-ghc.nix` needs to
mirror real GHC more closely.
Two divergences caught and fixed by the new test on the native
ghc9141 jobset:
* Real GHC always emits `("cross compiling","YES"/"NO")`; dummy
didn't. Cabal-install reads this field to decide whether to
apply cross-toolchain detection. Add it, gated on
`buildPlatform.config != targetPlatform.config`.
* Real GHC 9.14.1 omits the legacy `Support shared libraries`
field on native targets — cabal infers shared-lib support from
`Support dynamic-too` + `RTS ways`. Dummy was emitting
`("Support shared libraries","YES")` in its "otherwise" branch,
creating a phantom field real GHC doesn't have. Drop it.
Plus minor cosmetic: dummy emitted the alist's first entry as
`("target os", "OSDarwin")` (with a space after the comma); real
GHC omits the space. Tighten dummy to match.
`tests.dummy-ghc-info` is registered alongside other tests in
`test/default.nix` so it runs across every cross-target jobset
(armv7a-android, x86_64-musl, ghcjs, wasi, mingwW64, …). Future
divergences land as test failures rather than silent UnitId forks
between plan-nix and the v2 slice.
* dummy-ghc: report Host=buildPlatform; fix ghcjs/wasm interpreter
Driven by `tests.dummy-ghc-info` failures. Two fixes:
* Real cross-GHC reports `Host platform = buildPlatform` (where
ghc itself runs), NOT the cross-target platform. `pkgs.stdenv.hostPlatform`
in a cross-pkgs context IS the cross target, so the previous
`Host platform = pkgs.stdenv.hostPlatform` was wrong. Use
`buildPlatform` for both `Build platform` and `Host platform` —
`Target platform` carries the cross target.
* ghcjs / wasm cross-GHCs report `Have interpreter: NO`,
`Use interpreter: NO` (the JS / WASM backends don't have a
GHCi-style bytecode interpreter). Dummy was emitting `YES`
for both. Flip them to match.
Verified `tests.dummy-ghc-info` passes on the
`aarch64-darwin.unstable.ghc9141.{native,ghcjs}` jobsets. The
test will now catch any future divergence between dummy-ghc and
real GHC's `--info` output.
* v2 builder: route exe-depends closure walk through pkgsBuildBuild's plan
Build-tool exes (hsc2hs / alex / happy / ...) run on the build
platform; their unit-ids and lib-dep closures belong to the
build-build project's plan, not the cross plan. Previously
\`mkClosureFrom\` resolved every \`exe-depends\` id against the cross
plan-json — pulling in cross-target unit-ids and (transitively) the
wrong versions of process / directory / etc. for the slice's
\`libConstraintPins\` and \`sourceRepoEntries\`.
Tag every closure node with its plan ("cross" or "bb"), and have
\`exe-depends\` ids cross over to the bb plan via direct id lookup
(rare hit) or pkg-name fallback through \`package-ids-by-name\`.
Lib-deps of bb-tagged nodes stay in bb-plan; lib-deps of cross-tagged
nodes stay in cross. \`sourceRepoEntries\` now dispatches on
\`e.plan\` for the \`hsPkgs\` lookup.
\`libConstraintPins\` reverts to \`allDepClosure\` so cabal in the
slice pins the bb plan's versions for an exe's lib closure (matching
what the bb slice was built against).
Plumbed via \`hsPkgs.{plan-json-by-id, package-ids-by-name}\` exposed
in component-driver — \`hsPkgs.pkgsBuildBuild.<idx>\` reaches the
bb project's indexes naturally.
* v2 builder: skip exe entries when forming libConstraintPins
The bb-plan walk added by the previous commit pulled exe entries
(hsc2hs / alex / happy / ...) into `allDepClosure`. Pinning them
in `libConstraintPins` lands them in `extra-packages:`, which
makes cabal's solver in the slice plan the build-tool from source
instead of using the pre-installed exe slice. Skip closure
entries whose `component-name` starts with `exe:` — their lib
closures (process, directory, ...) still get pinned via the
`libDepsOf` walk in bb-plan.
* v2 builder: drop build-tool source tarballs from the slicing repo
Build-tool exes (hsc2hs / alex / happy / ...) reach the slice's
build environment via `extraNativeBuildInputs` (PATH). Their
source tarballs were also being added to the slicing repo's index
via `++ lib.concatMap (s: s.passthru.transitiveTarballs) buildToolSlices`,
which let cabal's solver see hsc2hs in the index and plan a
from-source rebuild for `build-tool-depends: hsc2hs:hsc2hs` —
forking the tool's unit-id (cross GHC info ≠ build-platform GHC
info) and failing the expected-package check.
Drop the line and rely on cabal's legacy PATH fallback for build-tool
resolution. The tool's lib closure (process, directory, ...) still
reaches the slicing repo when those libs are also lib-deps of the
target package; otherwise they're not needed (the slice doesn't
re-solve the tool).
* v2 builder: split sibling exe-deps from lib-deps
The v2 slice handles sibling `depends` and `exe-depends` from
plan-json differently:
* lib-deps land in `externalDepIds`, drive the slicing repo
(via `depTransitiveTarballsOf`), and get pinned in
`libConstraintPins`.
* exe-deps (build-tools) need their exe on PATH but their
source MUST stay out of the slicing repo's index — otherwise
cabal's solver in the slice plans the tool from source for
`build-tool-depends: foo:foo`-style deps and forks the
tool's unit-id (cross GHC info ≠ build-platform GHC info).
Split the plan-json walk in hspkg-builder into `homeDependIds`
(lib) and `homeBuildToolIds` (exe). The v2 builder's
`homeDepExeSlices` now feeds from the latter; `externalDepIds`
naturally drops exe-only entries.
* hspkg-builder: hoist homeIds out of comp-v2-builder attr-set
Pure restructure: `inherit (homeIds) ...` only works when `homeIds`
is in scope, not as a sibling field of the same attr-set. Move
the let-binding above the `comp-v2-builder { ... }` call so the
inherit references resolve.
* v2 builder: cross builds steer cabal off build-tool goals via --with-PROG
On native, build-tool source tarballs go in the slicing repo (as
before) so cabal recognises the pre-installed bb slice's unit-id
and skips re-building. Restored to old behaviour, including
merging `homeBuildToolIds` into `externalDepIds`.
On cross the unit-ids legitimately diverge (cross GHC info ≠
build-platform GHC info), so the bb hsc2hs slice would never
match what cabal-in-slice computes; cabal would plan it from
source and fork. Instead:
* keep the build-tool's source OUT of the slicing repo
(`externalDepIds` skips `homeBuildToolIds`; `depTransitives`
skips the `buildToolSlices` tarballs);
* emit `--with-<exe>=<bb-slice>/bin/<exe>` for every transitive
build-tool reached in `allDepClosure` — cabal skips planning
the tool from source and uses the explicit path;
* skip the unit-id MATCH check (`expectedUnitId = null`), but
`build-cabal-slice` now verifies exactly ONE unit-id is
captured so the slice still produces a single, well-defined
`$out/store` content.
* v2 builder: collapse duplicate isCross definition
The hoisted-up isCross collides with the existing one further
down. Drop the lower-down one.
* v2 builder: keep build-tool tarballs in the slicing repo on cross too
The cabal solver in the slice always plans every transitive
`build-tool-depends:` package, even when `--with-PROG=PATH` is
set — `--with-PROG` only short-circuits at build time, after the
solver has succeeded. Leaving the build-tool's source out of the
index made the solver fail with
`unknown package: <pkg>:<exe>:exe.<exe> (dependency of ...)`.
Restore the build-tool's transitive tarballs (the tool itself
plus its lib closure) for every build. On native cabal recognises
the pre-installed bb slice's unit-id and skips re-building; on
cross `withProgFlags` provides `--with-<exe>=<bb-slice>/bin/<exe>`
so cabal short-circuits the build despite the divergent unit-id.
The dry-run `expectedPackage` check is skipped on cross (cabal
*plans* every build-tool there, by design), leaving the
post-install captured-unit-ids count==1 check as the guarantee
that cabal did short-circuit the tool builds.
* v2 builder: compose cross build-tool slice (matching uid) on cross
Build-tool slices are the unit cabal needs to find in the
cabal-store to skip re-building. Previously we composed the
build-build slice (e.g. native hsc2hs), whose plan-nix-computed
uid Y doesn't match what the cross slice's cabal computes for the
tool (uses cross dummy-ghc info, matching real cross-ghc info per
tests.dummy-ghc-info). cabal didn't recognise the composed slice
and rebuilt the tool from source.
Compose the CROSS hsPkgs's tool slice instead (`targetSlice` in
the new `transitiveBuildToolEntries`). Its uid matches what the
slice's cabal computes, so cabal recognises the tool as already
installed and skips re-building. The cross binary isn't runnable
on the build host, but cabal only needs the unit-id-keyed dir
present in the cabal-store; actual invocation goes through
`--with-PROG=<buildSlice>/bin/<exe>` which points at the
build-build binary.
Skip the expected-uid check on cross (`expectedUnitId = null`):
the slice's network unit-id picks up `--with-PROG=PATH` into
pkgHashProgramArgs, which plan-nix doesn't have, so the two uids
legitimately diverge. The post-install captured-set check now
verifies the slice produced exactly one unit for the target
package plus zero-or-more units for the allowed transitive
build-tools (a safety net for cases where cabal didn't recognise
a composed targetSlice's uid). The dry-run plan check similarly
tolerates build-tool packages — anything else is still a fail.
* v2 builder: tighten unit-id pkg-name parser to a strict version pattern
`iserv-proxy-9.3-011cc258...`'s hash starts with `0`, which the
old `^[0-9]` regex matched, leaving the parser thinking the hash
was the version and producing `iserv-proxy-9.3` as pkg-name.
Use `^[0-9]+(\.[0-9]+)*$` so only true version tokens match
(the hash, being all-hex with mixed letters, never matches).
* flake.lock: bump iserv-proxy to a31c4c4 (armv7a NDK stubs)
Picks up `stable-haskell/iserv-proxy@a31c4c4`:
armv7a-android: stub symbols missing from current NDK
so the v2 cross builder's `iserv-proxy-interpreter` exe slice
gets past lld's undefined-symbol errors on armv7a-android.
* flake.lock: bump iserv-proxy to 8cdc446 (posix_spawnp stub)
* v2 builder: inject component.libs paths into post-register .conf
GHC's runtime linker (the one TH eval uses, including under
`-fexternal-interpreter` / `ghc-iserv`) dlopens each loaded
package's `extra-libraries:` entries by bare name (e.g.
HsOpenSSL's `extra-libraries: ssl crypto` → `libssl.so`).
Cabal records empty `library-dirs:` etc. in the per-unit
`.conf` because we deliberately don't push these paths into
the slice's configure args (that would land in
`pkgHashExtraLibDirs` and fork the slice's unit-id from
plan-nix).
v1 baked these into the .conf via `make-config-files.nix`'s
`extra-lib-dirs` flag fed to `Setup configure` (v1 used its
own unit-id scheme so the hash didn't matter). In v2 we
post-process: after `cabal v2-build` registers the unit, append
the lib paths to the `library-dirs:` / `library-dirs-static:` /
`dynamic-library-dirs:` fields, then `ghc-pkg recache`.
Downstream consumers' unit-ids are unaffected — their config hash
records each dep's unit-id, not the dep's .conf content — so the
chain stays plan-nix-consistent end to end.
* v2 builder: use .conf 'name:' field over uid prefix for pkg matching
cabal's OS-prefix patch shortens unit-id prefixes (e.g.
`bytrdr-1.0.4-...` for the `byteorder` package), so parsing the
pkg-name from the uid alone misclassified lib units and failed the
post-install captured-unit check with
`slice captured 0 units for the target package 'byteorder'` even
when the captured uid WAS the right one.
Read the authoritative pkg-name from each unit's `.conf` (which
records the real package name) and fall back to uid parsing only
for bin-only units that don't have a .conf.
* v2 builder: handle sublib pkg-name + drop backpack nix-tools override
Three related cleanups:
* `build-cabal-slice.nix`: prefer `package-name:` over `name:` in
the captured-unit `.conf` parser. For sublibs cabal records
`name: z-<pkg>-z-<sublib>` (z-encoded) but also
`package-name: <pkg>` — the latter is what the slice's expected
package check should match against.
* `test/backpack/default.nix`: drop
`inherit (evalPackages.haskell-nix) nix-tools;`. The line was
needed when backpack PR (#2467, 2026-03-17) added a new
`make-install-plan` flag that wasn't yet in the prebuilt
`nix-tools-static` tarball. The tarball has been updated since
(febad32e4 on 2026-03-24, and subsequently), so the default
`nix-tools-unchecked` picks up backpack support; the override
forces an unnecessary from-source build that on aarch64-darwin
cascades into a v2 slice mismatch on `cabal-install`'s
`pkg-src.repo.type` (`secure-repo` vs `remote-repo`).
* `test/cabal.project.local`: bump the pinned
`head.hackage.ghc.haskell.org` `--sha256` to match the current
index tarball — the previous hash was stale and would fail
the fixed-output hash check during plan-to-nix.
* v2 builder: accept backpack instantiations in the captured-unit set
The exe slice that consumes an indefinite backpack library ends up
materialising the instantiated form (a `+`-suffixed unit-id, e.g.
`bckpck-0.1.0.0-55b00c8f+Dwq5ijz...`) alongside the exe itself,
because the indefinite consumer's own slice produced no compiled
artifacts to compose in (cabal v2-build for an indefinite library
is "Up to date" and writes no `.conf`). v1 handles this with a
per-instantiation derivation (`lib/default.nix:369`'s
`components.library.override { inherit instantiations; }`); v2
doesn't yet have a slice-per-instantiation, so the instantiated
unit rides along inside the consuming slice's $out.
Three captured-unit changes:
* Read pkg-name from cabal's `dist-newstyle/cache/plan.json`
for bin-only units (no `.conf`). Falling back to uid parsing
misclassified OS-prefix-shortened names like `bckpck-...` as
a package called `bckpck` instead of `backpack`.
* Allow 0 target-package units (indefinite libraries register
nothing).
* Skip `+`-suffixed unit-ids when counting target-package units
— they're cabal-emitted instantiation byproducts, not the
slice's own target. The "no foreign package" invariant
(everything outside `expectedPackage` ∪ `allowedBuildToolPackages`
is rejected) still applies and is what catches an accidentally
rebuilt lib dep.
* v2 builder: error on \`.dwarf\` with migration hint; route DWARF via cabal.project
Mirrors the existing \`.profiled\` treatment. v1's \`.dwarf\` was an
overlay rebuild with \`enableDWARF = true\`, which would diverge
from plan-nix's UnitId in v2 (the overlay toggles aren't in
plan-nix's hash). Express DWARF in cabal.project's \`debug-info:\`
instead — the slice itself carries DWARF when that's set, and
\`<slice>.exePath\` gives the debug-info exe directly.
Updated test/cabal-simple-debug to add \`debug-info: 2\` to
cabalProjectLocal and use \`.exePath\` instead of \`.dwarf.exePath\`.
* docs+v2: route DWARF via compilerSelection and debug-info, document the migration
v2 splits v1's monolithic `.dwarf` overlay into two pieces:
* `debug-info:` in cabal.project for DWARF in the user's source
(plan-nix records it → matching UnitIds).
* `compilerSelection` swap to `c.dwarf` for DWARF in the RTS /
`ghc-internal` (every plan/slice/dummy-ghc derives from the
same GHC, so UnitIds stay consistent).
* `docs/dev/debug-info.md` — parallel to docs/dev/profiling.md.
* `builder/comp-v2-builder.nix` — `.dwarf` now throws with both
knobs in the migration hint and a pointer to the new doc.
* `test/cabal-simple-debug/default.nix` — uses both knobs and
queries `.exePath` instead of `.dwarf.exePath`.
* linux-cross: provide `wrapGhc` for v2 cross-TH
v2 builder's slice cabal v2-build doesn't consume v1's
`setupBuildFlags`, so the cross-TH iserv ghc-options
(`-fexternal-interpreter -pgmi <qemu-wrapper>` etc.) never reached
the cross GHC and slices for packages with TH (e.g. th-orphans,
th-lift) failed with "Couldn't find a target code interpreter.
Try with -fexternal-interpreter" on aarch64-android-prebuilt,
aarch64-multiplatform, and other linux-cross targets.
Mirror `overlays/mingw_w64.nix`'s `wrapGhc`: a derivation that
`--add-flags`-wraps every ghc / ghci binary with the linux-cross
`ghcOptions` (already used by v1 via `setupBuildFlags`). Cabal
still sees the unwrapped `ghc --info` output (compiler-id, target
platform, capabilities) so UnitId hash inputs are unchanged and
plan-nix's recorded UnitIds keep matching the slice's
computation. Other GHC binaries (`ghc-pkg`, `hsc2hs`, `runghc`,
`haddock`, …) are symlinked through unchanged.
The v2 comp-v2-builder already consumes
`templateHaskell.wrapGhc` when set (see `sliceGhc`), so wiring
this through is purely additive — windows already had it.
* flake.lock: bump iserv-proxy to 0b8d6f6 (aarch64-android stubs)
* flake.lock: bump iserv-proxy to 3d649af (more aarch64-android stubs)
* iserv-proxy: link statically on Android in cabal.project for v2
v1's iserv-proxy-interpreter.override added `--ghc-option=-optl-static
--ghc-option=-optl-ldl` (and `-optl-no-pie` on aarch32) via
`setupBuildFlags` so the cross-compiled binary is self-contained
— necessary because qemu-user-mode on the build host can't
satisfy Android's dynamic loader (`/system/bin/linker64` /
`/system/bin/linker`). v2 ignores `setupBuildFlags`, so the
slice's binary came out dynamically linked and qemu refused to
launch it with
`qemu-aarch64: Could not open '/system/bin/linker64'`, breaking
cross-TH for any package that touched iserv on android.
Express the same flags in the iserv-proxy project's
cabalProjectLocal under `if os(android)`. plan-nix now records
them in the slice's UnitId-relevant configure-args, so the
slice's cabal v2-build picks them up — same outcome as v1.
* iserv-proxy: drop `if os(android)` guard (isAndroid Nix check is enough)
The `final.lib.optionalString final.stdenv.hostPlatform.isAndroid`
already keeps the flags out of the build-platform iserv-proxy
build (different cabalProject' instantiation under pkgsBuildBuild).
Drop the redundant `if os(android)` so the block parses cleanly
— cabal's project-file conditional may not have matched the
android os name we use here, leaving the ghc-options block
inert.
* iserv-proxy: key Android static-link guard off the INNER pkgs
The cabalProjectLocal closure is shared between two
`cabalProject'` instantiations of the iserv-proxy project — one
under `pkgsBuildBuild` (build host iserv-proxy) and one under
`final` (cross-target iserv-proxy-interpreter). Guarding on
`final.stdenv.hostPlatform.isAndroid` matched both because
`final` is the OUTER cross context (android). The build-host
iserv-proxy then got `-optl-static` and the link of its shared
library failed with
`requires dynamic R_X86_64_32 reloc against '__TMC_END__' which
may overflow at runtime`.
Use the INNER `pkgs.stdenv.hostPlatform.isAndroid` (the lambda's
own `pkgs` arg) so each instantiation sees its own platform.
The Windows case is fine as-is because it has a cabal-level
`if os(mingw32)` guard inside the project file.
* iserv-proxy: add -debug to Android ghc-options (matches enableDebugRTS)
v1's android override included `enableDebugRTS = true` alongside
`-optl-static -optl-ldl` — the haskell.nix module translates
that to `--ghc-option=-debug`, picking the debug RTS variant.
Without it the statically-linked iserv-proxy-interpreter
segfaulted under qemu-aarch64. Add `-debug` to the cabal.project
ghc-options block so v2 picks up the same combination.
* iserv-proxy: also pass -optc-fPIC on aarch64 Android (matches v1)
v1's armv6l-linux.nix `addPackageKeys` injects
`--gcc-option=-fPIC` into `setupBuildFlags` for every package on
aarch64 cross builds (work-around for GHC #15275). For
iserv-proxy specifically this means the cbits get compiled
with -fPIC, which the qemu-emulated static aarch64-android
binary needs at runtime — without it the binary segfaults
inside qemu before iserv even initialises. v2 ignores
`setupBuildFlags`, so route -fPIC through cabal.project as
`-optc-fPIC` (passes -fPIC to gcc via ghc).
* iserv-proxy: force static/non-shared on Android (match v1 cabal flags)
v1 invokes Setup configure with
`--disable-shared --enable-static --disable-executable-dynamic`
for iserv-proxy on android (via the project's default cabal-pkg
flags), so all iserv artifacts are static — necessary for qemu
to run the binary on the build host without Android's dynamic
loader. v2's default cabal.project has `shared: True`,
`static: False`, `executable-dynamic: False`; adding the
explicit override for the iserv-proxy package on android gets
the same effect: static lib + static exe. Avoids the segfault
under qemu that was hitting after `-optl-static` alone left the
linker fighting between a shared lib and static exe.
* iserv-proxy: use cabal executable-static, drop redundant -optl-static
`executable-static: True` is cabal's first-class way to ask for a
static executable; it tells ghc to pass `-static -optl-static` at
the right points, gets dependency static-libs right, and doesn't
interact badly with `-fPIC` the way the standalone `-optl-static`
ghc-option did (which left us with a successfully-linked but
segfaulting binary under qemu-aarch64).
* linux-cross: add -optc-fPIC project-wide on aarch64
v1's armv6l-linux.nix injects `--gcc-option=-fPIC` into every
package's setupBuildFlags on aarch64 (work-around for GHC #15275).
The wrapGhc consumes `linux-cross.ghcOptions` so adding
`-optc-fPIC` there makes the C bits of all cross-compiled
packages get -fPIC just like v1. Without it the static
aarch64-android iserv binary segfaulted under qemu even with all
the cabal flags matching v1. Also clean up the per-package
iserv-proxy block (executable-static: True is enough; static/
shared/executable-dynamic flow from there).
* 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 …
chore(mithril-stm): bump version and update changelog
Fix fourmolu formatting in Encrypted.hs
Correct indentation of do-block inside else branch of withEncryptedKeyOutput.
:arrow_up: Update @upptime to v1.41.9
:card_file_box: Update status summary [skip ci] [upptime]
:pencil: Update summary in README [skip ci] [upptime]
docs: update leios spin up for respin prep
:pencil: Update summary in README [skip ci] [upptime]
:card_file_box: Update status summary [skip ci] [upptime]
recipes: make leios start-node use the flake locked pin
bump: leios and leios bench pins
:arrow_up: Update @upptime to v1.41.9
:card_file_box: Update status summary [skip ci] [upptime]