Home / Input Output / haskell.nix
May 20, 8-9 PM (0)
May 20, 9-10 PM (0)
May 20, 10-11 PM (0)
May 20, 11-12 AM (1)
May 21, 12-1 AM (1)
May 21, 1-2 AM (0)
May 21, 2-3 AM (1)
May 21, 3-4 AM (2)
May 21, 4-5 AM (0)
May 21, 5-6 AM (2)
May 21, 6-7 AM (1)
May 21, 7-8 AM (2)
May 21, 8-9 AM (2)
May 21, 9-10 AM (0)
May 21, 10-11 AM (0)
May 21, 11-12 PM (0)
May 21, 12-1 PM (0)
May 21, 1-2 PM (0)
May 21, 2-3 PM (0)
May 21, 3-4 PM (0)
May 21, 4-5 PM (0)
May 21, 5-6 PM (0)
May 21, 6-7 PM (0)
May 21, 7-8 PM (0)
May 21, 8-9 PM (0)
May 21, 9-10 PM (0)
May 21, 10-11 PM (0)
May 21, 11-12 AM (0)
May 22, 12-1 AM (1)
May 22, 1-2 AM (0)
May 22, 2-3 AM (0)
May 22, 3-4 AM (0)
May 22, 4-5 AM (1)
May 22, 5-6 AM (5)
May 22, 6-7 AM (0)
May 22, 7-8 AM (0)
May 22, 8-9 AM (0)
May 22, 9-10 AM (0)
May 22, 10-11 AM (1)
May 22, 11-12 PM (1)
May 22, 12-1 PM (0)
May 22, 1-2 PM (0)
May 22, 2-3 PM (0)
May 22, 3-4 PM (0)
May 22, 4-5 PM (0)
May 22, 5-6 PM (0)
May 22, 6-7 PM (0)
May 22, 7-8 PM (0)
May 22, 8-9 PM (0)
May 22, 9-10 PM (2)
May 22, 10-11 PM (0)
May 22, 11-12 AM (1)
May 23, 12-1 AM (1)
May 23, 1-2 AM (0)
May 23, 2-3 AM (2)
May 23, 3-4 AM (0)
May 23, 4-5 AM (1)
May 23, 5-6 AM (0)
May 23, 6-7 AM (0)
May 23, 7-8 AM (0)
May 23, 8-9 AM (2)
May 23, 9-10 AM (0)
May 23, 10-11 AM (1)
May 23, 11-12 PM (0)
May 23, 12-1 PM (0)
May 23, 1-2 PM (0)
May 23, 2-3 PM (0)
May 23, 3-4 PM (0)
May 23, 4-5 PM (0)
May 23, 5-6 PM (0)
May 23, 6-7 PM (0)
May 23, 7-8 PM (0)
May 23, 8-9 PM (0)
May 23, 9-10 PM (0)
May 23, 10-11 PM (2)
May 23, 11-12 AM (0)
May 24, 12-1 AM (1)
May 24, 1-2 AM (0)
May 24, 2-3 AM (0)
May 24, 3-4 AM (0)
May 24, 4-5 AM (0)
May 24, 5-6 AM (2)
May 24, 6-7 AM (0)
May 24, 7-8 AM (0)
May 24, 8-9 AM (0)
May 24, 9-10 AM (1)
May 24, 10-11 AM (0)
May 24, 11-12 PM (0)
May 24, 12-1 PM (0)
May 24, 1-2 PM (0)
May 24, 2-3 PM (0)
May 24, 3-4 PM (0)
May 24, 4-5 PM (0)
May 24, 5-6 PM (0)
May 24, 6-7 PM (0)
May 24, 7-8 PM (0)
May 24, 8-9 PM (0)
May 24, 9-10 PM (2)
May 24, 10-11 PM (0)
May 24, 11-12 AM (0)
May 25, 12-1 AM (2)
May 25, 1-2 AM (0)
May 25, 2-3 AM (0)
May 25, 3-4 AM (0)
May 25, 4-5 AM (0)
May 25, 5-6 AM (0)
May 25, 6-7 AM (0)
May 25, 7-8 AM (0)
May 25, 8-9 AM (0)
May 25, 9-10 AM (0)
May 25, 10-11 AM (0)
May 25, 11-12 PM (1)
May 25, 12-1 PM (0)
May 25, 1-2 PM (0)
May 25, 2-3 PM (0)
May 25, 3-4 PM (0)
May 25, 4-5 PM (0)
May 25, 5-6 PM (0)
May 25, 6-7 PM (0)
May 25, 7-8 PM (0)
May 25, 8-9 PM (0)
May 25, 9-10 PM (1)
May 25, 10-11 PM (0)
May 25, 11-12 AM (0)
May 26, 12-1 AM (1)
May 26, 1-2 AM (0)
May 26, 2-3 AM (0)
May 26, 3-4 AM (0)
May 26, 4-5 AM (0)
May 26, 5-6 AM (0)
May 26, 6-7 AM (0)
May 26, 7-8 AM (0)
May 26, 8-9 AM (0)
May 26, 9-10 AM (0)
May 26, 10-11 AM (0)
May 26, 11-12 PM (0)
May 26, 12-1 PM (0)
May 26, 1-2 PM (2)
May 26, 2-3 PM (1)
May 26, 3-4 PM (0)
May 26, 4-5 PM (0)
May 26, 5-6 PM (0)
May 26, 6-7 PM (0)
May 26, 7-8 PM (0)
May 26, 8-9 PM (0)
May 26, 9-10 PM (0)
May 26, 10-11 PM (0)
May 26, 11-12 AM (0)
May 27, 12-1 AM (1)
May 27, 1-2 AM (0)
May 27, 2-3 AM (1)
May 27, 3-4 AM (0)
May 27, 4-5 AM (0)
May 27, 5-6 AM (0)
May 27, 6-7 AM (0)
May 27, 7-8 AM (0)
May 27, 8-9 AM (0)
May 27, 9-10 AM (0)
May 27, 10-11 AM (0)
May 27, 11-12 PM (0)
May 27, 12-1 PM (0)
May 27, 1-2 PM (0)
May 27, 2-3 PM (0)
May 27, 3-4 PM (0)
May 27, 4-5 PM (0)
May 27, 5-6 PM (0)
May 27, 6-7 PM (0)
May 27, 7-8 PM (0)
May 27, 8-9 PM (0)
49 commits this week May 20, 2026 - May 27, 2026
ghc-shim: extend native-musl LD_LIBRARY_PATH wrap to all GHCs
The musl libgcc_s lookup workaround was gated on GHC ≥ 9.10, but
the underlying cause — musl's `libstdc++.so` `DT_RUNPATH` points at
musl's libc dir, not at the gcc lib dir where `libgcc_s.so.1`
lives — applies to every native-musl GHC.  GHC 9.6.7 on musl64
falls into the default "symlinks only" branch, so the
`extraLibraryPaths` prefix never gets applied and TH eval breaks
with:

    Error loading shared library libgcc_s.so.1: No such file or
    directory (needed by …/x86_64-unknown-linux-musl/lib/libstdc++.so)

The 9.10 gate was only correct for the unprefixed alias creation
(unlit / ghc-iserv* — earlier ghcs already ship those at the
unprefixed paths).  Split the two concerns:

* `needsLibShim`: any native-musl (or ghcjs) — lndir libdir +
  makeWrapper for `ghc` / `ghc-<version>` so the
  `--prefix LD_LIBRARY_PATH` from `extraLibraryPaths` reaches the
  iserv process.
* `nativeMuslNeedsAliases`: native-musl ≥ 9.10 — also create the
  unprefixed unlit / ghc-iserv aliases.

Also drop the matching 9.10 gate around `extraLibraryPaths` in
`build-cabal-slice.nix` so musl < 9.10 actually gets a populated
path list to wrap.

Verified `tests.th-dlls.build` on `x86_64-linux.unstable.ghc967.musl64`.
v2 doc slice: skip dep-slice confs when finding the target unit
The doc-slice install-phase strips `share/doc/` from every unit
dir except the target (so a doc slice only ships its own package's
haddocks, not transitively-haddock'd deps).  The target was being
identified by walking the slice's package.db and matching `name:`
on the first conf.

When the doc slice's `depSlices` compose in the corresponding
non-doc slice (it does, so v2-haddock can read `--read-interface`
without rebuilding), `package.db/` ends up with TWO confs for the
target package: the base slice's variant (lndir'd as a symlink)
AND this slice's own --cid (written as a real file).  Bash's
`for f in /path/*.conf` iterates lexicographically, so which conf
the loop matches first depends on alphabetic order of the two
unit-id hashes:

  - 6c5d51e6: this slice's `0ac3cc35` sorts before the base
    slice's `51429f12` → target_uid correctly = this slice's
    --cid → docs preserved.
  - HEAD:     this slice's `bb5ba586` sorts after the base
    slice's `5836f819` → target_uid wrongly = the base
    slice's unit-id → docs in `bb5ba586/share/doc/html` get
    stripped because that unit is treated as a non-target dep.

The transitive cabal-install closure difference between hackage
snapshots happens to flip the lexicographic order of those two
hashes; the underlying bug was always present.

Skip symlinked confs in the lookup, matching the same convention
already used by the `captured store unit` loop at line 1004.
This makes the strip loop key off this slice's own --cid unit
regardless of unit-id ordering.

Verified `tests.sublib-docs.run` on aarch64-darwin native ghc9141.
test/* + migration guide: v2-compatible test migrations
Migrates the in-tree test suite to work under `builderVersion = 2`.
Adds `docs/dev/migrating-to-v2.md` documenting the patterns we hit,
keyed off the symptoms users are likely to see when flipping the
flag on their own projects.

Patterns covered (with file-level examples):

* Library / executable profiling — `.profiled` overlay replaced by
  a sibling project whose `cabalProjectLocal` carries
  `library-profiling: True` / `profiling: True`
  (`test/exe-dlls`, `test/exe-lib-dlls`, `test/gi-gtk`,
  `test/th-dlls`, `test/th-dlls-minimal`, `test/js-template-haskell`,
  `test/cabal-simple-prof`).
* Coverage — module-level `doCoverage` mirrored into
  `cabalProjectLocal` `coverage: True` (`test/coverage`).
* DWARF — `.dwarf` overlay dropped, replaced by
  `compilerSelection = … c.dwarf …` + `debug-info: 2`
  (`test/cabal-simple-debug`).
* Sublib haddock — `documentation: True` in `cabalProjectLocal`;
  output path moved so `find -name <Module.html>` accepts both v1
  and v2 layouts (`test/sublib-docs`).
* Per-package `ghc-options` — `HsOpenSSL` workaround moved from
  `test/modules.nix` to `test/cabal.project.local` (`test/modules.nix`).
* Per-package `extra-lib-dirs` — Windows `test-clib` paths moved
  into `cabalProjectLocal` (`test/th-dlls-minimal`).
* `ghcOptions` scope — moved from per-component to per-package
  (`test/th-dlls`).
* `shellFor` migration — `component.shell` / `component.env` →
  `project.shellFor { exposePackagesVia = "ghc-pkg"; }`
  (`test/with-packages`, `test/shell-for`,
  `test/shell-for-setup-deps`).
* `withHoogle = false` on static — avoids `.dyn_hi` lookup failures
  in hoogle's haddock chain (`test/with-packages`).
* `origSrc` / `origSubDir` workaround — synthesised package-root
  drv with `.git` at the top, source-cleaning disabled via
  `outPath` + `filterPath` (`test/githash`).
* Custom-setup `base` / `directory` — added to explicit
  `setup-depends` (`test/setup-deps`, `test/shell-for-setup-deps`).
* Native-musl `git` — host vs build-build picker keyed on
  `haskellLib.isNativeMusl` to avoid glibc/musl libc mixing
  (`test/githash`).
* Android cross — `test/cabal.project.android` injected
  conditionally (`test/exe-dlls`, `test/exe-lib-dlls`,
  `test/th-dlls`).
* `useLocalGhcLib = true` — earlier change (PR #2510) covered
  separately; referenced in the guide for completeness.
project: make builderVersion = 2 the default
Flips the project-level default from `1` to `2` so the existing
test suite runs under the new builder by default.  Surfaces which
tests need migration via CI failures; the goal here is to harvest
that signal into a migration document, not to ship v2 as the
permanent default — expect a revert (or selective opt-out) once
the migration is mapped out.

Internal projects already pin v1 explicitly
(`compiler/ghc/default.nix`, `overlays/bootstrap.nix`,
`overlays/tools.nix`), so GHC builds and tool invocations are
unaffected by the flip.
js-template-haskell, th-dlls: disable on android-aarch32 for ghc9124, ghc9141
iserv-proxy-interpreter segfaults under qemu-arm during TH eval on
android-aarch32 with these GHCs:

  qemu: uncaught target signal 11 (Segmentation fault) - core dumped
  iserv-proxy: hFlush: resource vanished (Broken pipe)
  External interpreter terminated (1)

Same failure mode the existing `ghc9102 / ghc9103` exclusion catches.
Extend the exclusion to ghc9124 and ghc9141 (and their llvm
variants) so these TH-using tests don't fail on this fragile combo
while the underlying qemu / iserv interaction is investigated.

(`th-dlls-minimal` is gated to Windows only and is unaffected.)
test/annotations: disable on armv7a-android
`qemu-arm` segfaults running `iserv-proxy-interpreter` when ghc
spawns iserv for the annotations TH eval, so the build can't
proceed past `Compiling Lib`.  The same chain works on
`aarch64-android-prebuilt` (qemu-aarch64), so the issue is specific
to the 32-bit ARM qemu / iserv interaction rather than something
haskell.nix can sidestep.

Disable on `isAndroid && isAarch32` — same shape as the existing
`th-dlls` guard.
Add cabal v2-build-based component builder ("v2 builder")
Adds an alternative component builder that runs `cabal v2-build`
inside a slice derivation and emits a cabal-store layout rather
than going through Setup.hs, alongside the existing Setup-based
v1 builder.  Selected per-project via the new `builderVersion`
option (default `1` for now; flip to `2` to opt in on a
per-project basis).

What the v2 builder provides:

* Per-component cabal-store slices that compose into a single
  `~/.cabal/store/ghc-<version>/` layout via `composeStore`.  Each
  slice's UnitId matches what a downstream `cabal v2-build` would
  compute against the real compiler, so a v2 store can be reused
  by interactive cabal invocations without recomputation.
* Public sublibrary support (`library <n>` stanzas) end-to-end —
  not currently supported by v1's Setup-based path.
* A v2 dev shell (`shell-for-v2.nix`) wired through `project.shellFor`
  whenever `builderVersion = 2`.  Two exposure modes:
  `cabal-store` (default) pre-populates `~/.cabal/store` so plain
  `cabal v2-build` reuses the slice's units; `ghc-pkg` mode wraps
  `ghc` / `ghc-pkg` with `GHC_ENVIRONMENT` / `GHC_PACKAGE_PATH` so
  `ghc -e` and similar see the composed store directly.
* A shared ghc shim (`ghc-shim.nix`) used by both the slice's
  `cabal v2-build` and the v2 shell so the user-facing compiler
  matches the slice compiler.  Adds cabal near-compiler aliases,
  patches the ghcjs `ar command` setting (drops missing `.o`s for
  backpack-on-ghcjs), and adds native-musl `unlit` /
  `ghc-iserv[-dyn|-prof]` aliases.

Plumbing changes:

* `builder/default.nix` + `builder/hspkg-builder.nix` dispatch on
  `builderVersion`.  v2-specific helpers (`buildCabalStoreSlice`,
  `composeStore`, `makeGhcShim`, the patched `v2CabalInstall`)
  live in `builder/default.nix`.
* `modules/project-common.nix` adds `builderVersion`.
  `modules/component-driver.nix` propagates it (plus
  `crossTemplateHaskellSupport` and `cabalProjectLocal`) into the
  pkg-set config.  `modules/stack-project.nix` forces v1 for
  stack projects (stack-to-nix doesn't produce the plan-json
  shapes v2 consumes).
* `modules/package-options.nix` adds round-trip options
  (`configureOptions`, `extraLibDirs`, `extraIncludeDirs`,
  `programOptions`) that v2 emits back into each slice's
  cabal.project.
* `modules/install-plan/configure-args.nix` extracts those
  round-trip options from plan.json's configure-args, and
  filters `-hide-all-packages` for v2 (cabal injects it on every
  Setup configure call; round-tripping it would fork UnitId
  hashes between plan-nix and slice cabal).
* `modules/install-plan/non-reinstallable.nix` skips the
  ghcjs/wasm boot-package hiding on v2 (v2 follows cabal's plan
  literally rather than overlaying haskell.nix's exclusions).
* `modules/shell.nix` adds the `exposePackagesVia` option.
* `lib/check.nix` adds a v2-slice branch that runs the installed
  exe under the platform's testWrapper, mirroring the v1 check
  semantics for slice derivations.
* `overlays/haskell.nix` propagates `builderVersion` /
  `cabalProjectLocal` through `addProjectAndPackageAttrs`,
  dispatches `shellFor` on `builderVersion`, and rebuilds
  iserv-proxy as both a non-profiled and profiled variant — v2
  reads the prof variant from `package iserv-proxy profiling:
  True` in cabal.project rather than `enableProfiling = true` on
  the .override (which v2 doesn't honour).
* `overlays/linux-cross.nix` + `overlays/mingw_w64.nix` add a
  `wrapGhc` derivation that bakes `-fexternal-interpreter -pgmi
  <iserv-wrapper>` into ghc itself — v2 can't put those into
  per-package `ghc-options:` (UnitId divergence), so it wraps the
  compiler instead.  The `compilerSelection` chain picks the
  wrapped ghc on cross targets.

Internal projects pin v1:

* `compiler/ghc/default.nix` (hadrian) and
  `overlays/bootstrap.nix` (hscolour) set `builderVersion = 1`
  explicitly so v2 evolution doesn't churn GHC builds while v2 is
  still settling.  `overlays/tools.nix` defaults
  `haskell-nix.tool` invocations to v1 for the same reason.

Tests are NOT migrated in this commit — the existing tests run
under v1 (the default) and exercise master's behaviour as before.
A follow-up will migrate or duplicate them under v2 once we know
which expectations need adjusting.
Add cabal v2-build-based component builder ("v2 builder")
Adds an alternative component builder that runs `cabal v2-build`
inside a slice derivation and emits a cabal-store layout rather
than going through Setup.hs, alongside the existing Setup-based
v1 builder.  Selected per-project via the new `builderVersion`
option (default `1` for now; flip to `2` to opt in on a
per-project basis).

What the v2 builder provides:

* Per-component cabal-store slices that compose into a single
  `~/.cabal/store/ghc-<version>/` layout via `composeStore`.  Each
  slice's UnitId matches what a downstream `cabal v2-build` would
  compute against the real compiler, so a v2 store can be reused
  by interactive cabal invocations without recomputation.
* Public sublibrary support (`library <n>` stanzas) end-to-end —
  not currently supported by v1's Setup-based path.
* A v2 dev shell (`shell-for-v2.nix`) wired through `project.shellFor`
  whenever `builderVersion = 2`.  Two exposure modes:
  `cabal-store` (default) pre-populates `~/.cabal/store` so plain
  `cabal v2-build` reuses the slice's units; `ghc-pkg` mode wraps
  `ghc` / `ghc-pkg` with `GHC_ENVIRONMENT` / `GHC_PACKAGE_PATH` so
  `ghc -e` and similar see the composed store directly.
* A shared ghc shim (`ghc-shim.nix`) used by both the slice's
  `cabal v2-build` and the v2 shell so the user-facing compiler
  matches the slice compiler.  Adds cabal near-compiler aliases,
  patches the ghcjs `ar command` setting (drops missing `.o`s for
  backpack-on-ghcjs), and adds native-musl `unlit` /
  `ghc-iserv[-dyn|-prof]` aliases.

Plumbing changes:

* `builder/default.nix` + `builder/hspkg-builder.nix` dispatch on
  `builderVersion`.  v2-specific helpers (`buildCabalStoreSlice`,
  `composeStore`, `makeGhcShim`, the patched `v2CabalInstall`)
  live in `builder/default.nix`.
* `modules/project-common.nix` adds `builderVersion`.
  `modules/component-driver.nix` propagates it (plus
  `crossTemplateHaskellSupport` and `cabalProjectLocal`) into the
  pkg-set config.  `modules/stack-project.nix` forces v1 for
  stack projects (stack-to-nix doesn't produce the plan-json
  shapes v2 consumes).
* `modules/package-options.nix` adds round-trip options
  (`configureOptions`, `extraLibDirs`, `extraIncludeDirs`,
  `programOptions`) that v2 emits back into each slice's
  cabal.project.
* `modules/install-plan/configure-args.nix` extracts those
  round-trip options from plan.json's configure-args, and
  filters `-hide-all-packages` for v2 (cabal injects it on every
  Setup configure call; round-tripping it would fork UnitId
  hashes between plan-nix and slice cabal).
* `modules/install-plan/non-reinstallable.nix` skips the
  ghcjs/wasm boot-package hiding on v2 (v2 follows cabal's plan
  literally rather than overlaying haskell.nix's exclusions).
* `modules/shell.nix` adds the `exposePackagesVia` option.
* `lib/check.nix` adds a v2-slice branch that runs the installed
  exe under the platform's testWrapper, mirroring the v1 check
  semantics for slice derivations.
* `overlays/haskell.nix` propagates `builderVersion` /
  `cabalProjectLocal` through `addProjectAndPackageAttrs`,
  dispatches `shellFor` on `builderVersion`, and rebuilds
  iserv-proxy as both a non-profiled and profiled variant — v2
  reads the prof variant from `package iserv-proxy profiling:
  True` in cabal.project rather than `enableProfiling = true` on
  the .override (which v2 doesn't honour).
* `overlays/linux-cross.nix` + `overlays/mingw_w64.nix` add a
  `wrapGhc` derivation that bakes `-fexternal-interpreter -pgmi
  <iserv-wrapper>` into ghc itself — v2 can't put those into
  per-package `ghc-options:` (UnitId divergence), so it wraps the
  compiler instead.  The `compilerSelection` chain picks the
  wrapped ghc on cross targets.

Internal projects pin v1:

* `compiler/ghc/default.nix` (hadrian) and
  `overlays/bootstrap.nix` (hscolour) set `builderVersion = 1`
  explicitly so v2 evolution doesn't churn GHC builds while v2 is
  still settling.  `overlays/tools.nix` defaults
  `haskell-nix.tool` invocations to v1 for the same reason.

Tests are NOT migrated in this commit — the existing tests run
under v1 (the default) and exercise master's behaviour as before.
A follow-up will migrate or duplicate them under v2 once we know
which expectations need adjusting.
iserv-proxy: enable --optimistic-linking on GHC ≥ 9.14 (#2512)
Bake `--optimistic-linking` into iserv-proxy / iserv-proxy-interpreter
at link time via `-with-rtsopts`.  `GHC/Linker/Executable.hs` emits
this into the generated `main.c` as `__conf.rts_opts`, which
`setupRtsFlags` processes with `RtsOptsAll` — bypassing the
`OPTION_UNSAFE` gate that `+RTS --optimistic-linking -RTS` on the
command line is subject to.  Makes the runtime linker tolerate
undefined symbols when loading object files at TH-eval time;
splices that don't actually reference the missing symbol then
resolve fine instead of aborting the load.  `-rtsopts=all` is kept
so wrapper scripts / GHCRTS can still override at invocation.
`--optimistic-linking` is only available in GHC's RTS from 9.14
onwards; gated on the Nix side since cabal.project doesn't allow
`if` inside a `package` stanza.

Refactor `cabalProjectLocal` for the iserv-proxy project from
`//`-chained `optionalAttrs` blocks to a single string built via
`optionalString` concatenation so the new directive can compose
with the existing aarch64+<9.8 threaded gate without the last
`//` clobbering it.

Also drop the previous `allow-newer: *:base, *:bytestring` block
on GHC > 9.10.  The bounds in iserv-proxy.cabal (`base < 5`,
`bytestring < 0.13`, etc.) already accommodate current GHCs; if a
transitive package needs relaxation we'll re-add a more targeted
allow-newer once we know which one.

Pulled out of #2504 (`hkm/builder-v2`).
iserv-proxy: enable --optimistic-linking on GHC ≥ 9.14
Bake `--optimistic-linking` into iserv-proxy / iserv-proxy-interpreter
at link time via `-with-rtsopts`.  `GHC/Linker/Executable.hs` emits
this into the generated `main.c` as `__conf.rts_opts`, which
`setupRtsFlags` processes with `RtsOptsAll` — bypassing the
`OPTION_UNSAFE` gate that `+RTS --optimistic-linking -RTS` on the
command line is subject to.  Makes the runtime linker tolerate
undefined symbols when loading object files at TH-eval time;
splices that don't actually reference the missing symbol then
resolve fine instead of aborting the load.  `-rtsopts=all` is kept
so wrapper scripts / GHCRTS can still override at invocation.
`--optimistic-linking` is only available in GHC's RTS from 9.14
onwards; gated on the Nix side since cabal.project doesn't allow
`if` inside a `package` stanza.

Refactor `cabalProjectLocal` for the iserv-proxy project from
`//`-chained `optionalAttrs` blocks to a single string built via
`optionalString` concatenation so the new directive can compose
with the existing aarch64+<9.8 threaded gate without the last
`//` clobbering it.

Also drop the previous `allow-newer: *:base, *:bytestring` block
on GHC > 9.10.  The bounds in iserv-proxy.cabal (`base < 5`,
`bytestring < 0.13`, etc.) already accommodate current GHCs; if a
transitive package needs relaxation we'll re-add a more targeted
allow-newer once we know which one.

Pulled out of #2504 (`hkm/builder-v2`).
cabal-project: drop readFile defaults; inject platform cabalProjectLocal defaults (#2511)
Two related changes to `modules/cabal-project.nix`:

**1. `cabalProjectLocal` / `cabalProjectFreeze` no longer auto-load
from disk.**  The options were typed `nullOr lines` with
`readIfExists`-based defaults that did IFD lookups for
`cabal.project.local` / `cabal.project.freeze` in the project
source.  Internal projects (hadrian, ghc-extra-projects) explicitly
set them to `null` just to suppress those reads, and the nullability
also prevented `mkBefore` directives from merging cleanly.

Switch the types to plain `lines` (default `""`) — projects that
relied on the implicit `readFile` behaviour now do it explicitly:

    cabalProjectLocal = builtins.readFile ./cabal.project.local;

Internal callers that set `null` for IFD-avoidance lose the
explicit assignments — the new default is already IFD-free.

**2. Platform-conditional defaults are now injected into every
cabal project's `cabalProjectLocal`.**  Four `mkIf` blocks added:

  * **musl host** — `package * \n executable-static: True`.
    comp-builder adds `--ghc-option=-optl=-static` at build time;
    surfacing the toggle here makes plan-to-nix record
    `--enable-executable-static`.  Build artefacts are unchanged.
  * **x86_64-darwin host** — `package * \n library-for-ghci: True`.
    Mirrors what comp-builder passes for `!ghcjs && !wasm && !android`
    (always true on darwin).
  * **android host** —
    `package * \n ghc-options: -optl-static -optl-ldl` (plus
    `-optl-no-pie` on aarch32).  Mirrors `lib/check.nix`'s
    test-exe `setupBuildFlags` re-wrap.
  * **wasm GHC ≥ 9.12** — `package * \n shared: True`.  Wasm's RTS
    linker only loads `.so` files; `--disable-shared` (cabal's
    default given the real compiler's reported capabilities) would
    force a `.a`-only install that TH-eval can't load.

All four sit at `mkBefore` priority so a project's own
`cabalProjectLocal` overrides them.

Cache impact: plan-nix hashes change for affected platforms.
Internal GHC builds verified byte-identical (drvPath
`na9chax1gj2n6jx3d6v86v6fdah9r9fd-ghc-9.14.1.drv` matches master).

Changelog entry added describing both changes.

Pulled out of #2504 (`hkm/builder-v2`).
cabal-project: drop readFile defaults; inject platform cabalProjectLocal defaults
Two related changes to `modules/cabal-project.nix`:

**1. `cabalProjectLocal` / `cabalProjectFreeze` no longer auto-load
from disk.**  The options were typed `nullOr lines` with
`readIfExists`-based defaults that did IFD lookups for
`cabal.project.local` / `cabal.project.freeze` in the project
source.  Internal projects (hadrian, ghc-extra-projects) explicitly
set them to `null` just to suppress those reads, and the nullability
also prevented `mkBefore` directives from merging cleanly.

Switch the types to plain `lines` (default `""`) — projects that
relied on the implicit `readFile` behaviour now do it explicitly:

    cabalProjectLocal = builtins.readFile ./cabal.project.local;

Internal callers that set `null` for IFD-avoidance lose the
explicit assignments — the new default is already IFD-free.

**2. Platform-conditional defaults are now injected into every
cabal project's `cabalProjectLocal`.**  Four `mkIf` blocks added:

  * **musl host** — `package * \n executable-static: True`.
    comp-builder adds `--ghc-option=-optl=-static` at build time;
    surfacing the toggle here makes plan-to-nix record
    `--enable-executable-static`.  Build artefacts are unchanged.
  * **x86_64-darwin host** — `package * \n library-for-ghci: True`.
    Mirrors what comp-builder passes for `!ghcjs && !wasm && !android`
    (always true on darwin).
  * **android host** —
    `package * \n ghc-options: -optl-static -optl-ldl` (plus
    `-optl-no-pie` on aarch32).  Mirrors `lib/check.nix`'s
    test-exe `setupBuildFlags` re-wrap.
  * **wasm GHC ≥ 9.12** — `package * \n shared: True`.  Wasm's RTS
    linker only loads `.so` files; `--disable-shared` (cabal's
    default given the real compiler's reported capabilities) would
    force a `.a`-only install that TH-eval can't load.

All four sit at `mkBefore` priority so a project's own
`cabalProjectLocal` overrides them.

Cache impact: plan-nix hashes change for affected platforms.
Internal GHC builds verified byte-identical (drvPath
`na9chax1gj2n6jx3d6v86v6fdah9r9fd-ghc-9.14.1.drv` matches master).

Changelog entry added describing both changes.

Pulled out of #2504 (`hkm/builder-v2`).
Inject platform-conditional cabalProjectLocal defaults
Surface the static-linking / library-for-ghci / shared-libs flags
that comp-builder and `lib/check.nix` already apply at the
artefact level into the plan-to-nix step, so plan-nix's recorded
configure-args and UnitIds match what cabal v2-build would compute
against the real compiler.

Four mkIfs added to `modules/cabal-project.nix`:

* **musl host** — `package * \n executable-static: True`.
  comp-builder adds `--ghc-option=-optl=-static` at build time;
  surfacing the toggle here makes plan-to-nix record
  `--enable-executable-static`.  Build artefacts are unchanged.
* **x86_64-darwin host** — `package * \n library-for-ghci: True`.
  Mirrors what comp-builder passes for `!ghcjs && !wasm && !android`
  (always true on darwin).
* **android host** —
  `package * \n ghc-options: -optl-static -optl-ldl` (plus
  `-optl-no-pie` on aarch32).  Mirrors `lib/check.nix`'s test-exe
  setupBuildFlags re-wrap.
* **wasm GHC ≥ 9.12** — `package * \n shared: True`.  Wasm's RTS
  linker only loads `.so` files; `--disable-shared` (cabal's
  default given the real compiler's reported capabilities) would
  force a `.a`-only install that TH-eval can't load.

All four directives sit at `mkBefore` priority so a project's own
`cabalProjectLocal` overrides them.

Cache impact: plan-nix hashes change for affected platforms.

Changelog entry added in `changelog.md` describing the cache-bust
and the opt-out path.

Pulled out of #2504 (`hkm/builder-v2`).
Add `useLocalGhcLib` project option (#2510)
* Add `useLocalGhcLib` project option

Surface what `modules/configuration-nix.nix` used to do
unconditionally as an opt-in `useLocalGhcLib` flag, so the
`packages.ghc.src` override only fires when a project actually
constrains the `ghc` package (e.g. `ghc-lib-reinstallable`).

Four pieces:

* `modules/project-common.nix`: add the `useLocalGhcLib` option
  (default `false`).
* `modules/configuration-nix.nix`: drop the unconditional
  `packages.ghc.src` / `packages.ghc.package-description-override`
  overrides — they're moved into the per-project wiring below.
* `modules/stack-project.nix`: under `useLocalGhcLib`, re-apply
  the `packages.ghc.src` post-plan override.  Stack-to-nix can't
  use the cabal-project route, so this keeps the existing
  behaviour for stack users who flip the flag.
* `modules/cabal-project.nix`: under `useLocalGhcLib`, inject a
  `source-repository-package` block into `cabalProjectLocal`
  pointing at the configured-src + generated GHC tree, and add an
  `inputMap` entry so haskell.nix doesn't try to fetch the URL.
  Cabal then hashes the wrapped repo's content into
  `pkg-src-sha256` and installs `lib:ghc` like any other
  reinstallable dep.

Projects that need the previous always-on behaviour now set
`useLocalGhcLib = true` on the project module; everyone else gets
a smaller plan-nix and avoids the unconditional `configured-src`
materialisation.

Pulled out of #2504 (`hkm/builder-v2`).

* ghc-lib-reinstallable test + changelog: set useLocalGhcLib = true

Flips the new opt-in flag in both test variants and adds a
changelog entry telling users to do the same when they constrain
`lib:ghc`.
Add `useLocalGhcLib` project option
Surface what `modules/configuration-nix.nix` used to do
unconditionally as an opt-in `useLocalGhcLib` flag, so the
`packages.ghc.src` override only fires when a project actually
constrains the `ghc` package (e.g. `ghc-lib-reinstallable`).

Four pieces:

* `modules/project-common.nix`: add the `useLocalGhcLib` option
  (default `false`).
* `modules/configuration-nix.nix`: drop the unconditional
  `packages.ghc.src` / `packages.ghc.package-description-override`
  overrides — they're moved into the per-project wiring below.
* `modules/stack-project.nix`: under `useLocalGhcLib`, re-apply
  the `packages.ghc.src` post-plan override.  Stack-to-nix can't
  use the cabal-project route, so this keeps the existing
  behaviour for stack users who flip the flag.
* `modules/cabal-project.nix`: under `useLocalGhcLib`, inject a
  `source-repository-package` block into `cabalProjectLocal`
  pointing at the configured-src + generated GHC tree, and add an
  `inputMap` entry so haskell.nix doesn't try to fetch the URL.
  Cabal then hashes the wrapped repo's content into
  `pkg-src-sha256` and installs `lib:ghc` like any other
  reinstallable dep.

Projects that need the previous always-on behaviour now set
`useLocalGhcLib = true` on the project module; everyone else gets
a smaller plan-nix and avoids the unconditional `configured-src`
materialisation.

Pulled out of #2504 (`hkm/builder-v2`).
Extract dummy-ghc, make it cross-aware, and fix plan-nix UnitId stability (#2509)
Cabal-install reads `ghc --info` during plan elaboration to decide
per-unit settings (`--enable-shared` vs `--disable-shared`, RTS-way
inclusion, etc.) that feed into the recorded UnitId hash.  The
inline `dummy-ghc` script in `lib/call-cabal-project-to-nix.nix`
was a stripped-down stub whose `--info` output didn't match the
real cross GHCs', so plan-nix recorded UnitIds that diverged from
what a downstream cabal v2-build (or any consumer running cabal
against the real compiler) would compute.

Three changes:

1. **`lib/dummy-ghc.nix`** (new): extract the dummy-ghc script and
   emit cross-aware `--info` capabilities matching real cross GHCs
   for windows, ghcjs, wasm, android, static, native-musl, and
   native — `Support dynamic-too`, `Support shared libraries`,
   `RTS ways`, `Stage`, `GHC Dynamic`, the iserv-related fields,
   etc.

2. **`lib/call-cabal-project-to-nix.nix`**:
   * gate the dummy-ghc-pkg `-inplace` suffix on GHC ≥ 9.8 — older
     GHCs register pre-existing packages without it, so the dummy
     was synthesising the wrong package ids for plan-to-nix
     against ghc 8.10 / 9.0 / 9.2 / 9.4 / 9.6.
   * pin `CABAL_INSTALLED_PACKAGE_ID_OS` to the build platform's
     OS when running plan-to-nix.  Without this the patched
     cabal-install's installed-package-id format tracks the eval
     system; a darwin host evaluating a linux derivation gets the
     `VeryShort` form and forks UnitIds from what cabal v2-build
     on a linux builder would compute.

3. **`lib/default.nix`**: tighten `uniqueWithNameKey` so
   derivations (`.name = "<pkg>-<ver>"`) and module values
   (`.identifier.{name,version}`) can't collide on shared name
   fragments — partition into `unit-id:` / `id:` / `name:` buckets.
   Correctness is unchanged when buckets are correct; this just
   avoids the slow `lib.unique` fallback in more cases.

Pulled out of #2504 (`hkm/builder-v2`).