Home / Input Output / haskell.nix
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)
May 27, 9-10 PM (0)
May 27, 10-11 PM (0)
May 27, 11-12 AM (0)
May 28, 12-1 AM (1)
May 28, 1-2 AM (1)
May 28, 2-3 AM (0)
May 28, 3-4 AM (0)
May 28, 4-5 AM (0)
May 28, 5-6 AM (0)
May 28, 6-7 AM (0)
May 28, 7-8 AM (0)
May 28, 8-9 AM (0)
May 28, 9-10 AM (0)
May 28, 10-11 AM (2)
May 28, 11-12 PM (1)
May 28, 12-1 PM (0)
May 28, 1-2 PM (0)
May 28, 2-3 PM (0)
May 28, 3-4 PM (0)
May 28, 4-5 PM (0)
May 28, 5-6 PM (0)
May 28, 6-7 PM (0)
May 28, 7-8 PM (0)
May 28, 8-9 PM (0)
May 28, 9-10 PM (0)
May 28, 10-11 PM (0)
May 28, 11-12 AM (0)
May 29, 12-1 AM (2)
May 29, 1-2 AM (0)
May 29, 2-3 AM (0)
May 29, 3-4 AM (0)
May 29, 4-5 AM (0)
May 29, 5-6 AM (0)
May 29, 6-7 AM (0)
May 29, 7-8 AM (0)
May 29, 8-9 AM (0)
May 29, 9-10 AM (0)
May 29, 10-11 AM (0)
May 29, 11-12 PM (0)
May 29, 12-1 PM (0)
May 29, 1-2 PM (0)
May 29, 2-3 PM (0)
May 29, 3-4 PM (0)
May 29, 4-5 PM (0)
May 29, 5-6 PM (0)
May 29, 6-7 PM (0)
May 29, 7-8 PM (0)
May 29, 8-9 PM (0)
May 29, 9-10 PM (0)
May 29, 10-11 PM (0)
May 29, 11-12 AM (0)
May 30, 12-1 AM (1)
May 30, 1-2 AM (0)
May 30, 2-3 AM (0)
May 30, 3-4 AM (0)
May 30, 4-5 AM (0)
May 30, 5-6 AM (0)
May 30, 6-7 AM (0)
May 30, 7-8 AM (0)
May 30, 8-9 AM (0)
May 30, 9-10 AM (0)
May 30, 10-11 AM (0)
May 30, 11-12 PM (0)
May 30, 12-1 PM (0)
May 30, 1-2 PM (0)
May 30, 2-3 PM (0)
May 30, 3-4 PM (0)
May 30, 4-5 PM (0)
May 30, 5-6 PM (0)
May 30, 6-7 PM (0)
May 30, 7-8 PM (0)
May 30, 8-9 PM (0)
May 30, 9-10 PM (0)
May 30, 10-11 PM (0)
May 30, 11-12 AM (0)
May 31, 12-1 AM (1)
May 31, 1-2 AM (0)
May 31, 2-3 AM (0)
May 31, 3-4 AM (0)
May 31, 4-5 AM (0)
May 31, 5-6 AM (0)
May 31, 6-7 AM (0)
May 31, 7-8 AM (0)
May 31, 8-9 AM (0)
May 31, 9-10 AM (0)
May 31, 10-11 AM (0)
May 31, 11-12 PM (0)
May 31, 12-1 PM (0)
May 31, 1-2 PM (0)
May 31, 2-3 PM (0)
May 31, 3-4 PM (0)
May 31, 4-5 PM (0)
May 31, 5-6 PM (0)
May 31, 6-7 PM (0)
May 31, 7-8 PM (0)
May 31, 8-9 PM (0)
May 31, 9-10 PM (0)
May 31, 10-11 PM (0)
May 31, 11-12 AM (0)
Jun 01, 12-1 AM (1)
Jun 01, 1-2 AM (0)
Jun 01, 2-3 AM (0)
Jun 01, 3-4 AM (0)
Jun 01, 4-5 AM (0)
Jun 01, 5-6 AM (0)
Jun 01, 6-7 AM (0)
Jun 01, 7-8 AM (0)
Jun 01, 8-9 AM (0)
Jun 01, 9-10 AM (0)
Jun 01, 10-11 AM (0)
Jun 01, 11-12 PM (0)
Jun 01, 12-1 PM (0)
Jun 01, 1-2 PM (0)
Jun 01, 2-3 PM (0)
Jun 01, 3-4 PM (0)
Jun 01, 4-5 PM (0)
Jun 01, 5-6 PM (0)
Jun 01, 6-7 PM (0)
Jun 01, 7-8 PM (0)
Jun 01, 8-9 PM (0)
Jun 01, 9-10 PM (0)
Jun 01, 10-11 PM (0)
Jun 01, 11-12 AM (0)
Jun 02, 12-1 AM (2)
Jun 02, 1-2 AM (0)
Jun 02, 2-3 AM (0)
Jun 02, 3-4 AM (0)
Jun 02, 4-5 AM (2)
Jun 02, 5-6 AM (0)
Jun 02, 6-7 AM (0)
Jun 02, 7-8 AM (7)
Jun 02, 8-9 AM (1)
Jun 02, 9-10 AM (0)
Jun 02, 10-11 AM (0)
Jun 02, 11-12 PM (0)
Jun 02, 12-1 PM (2)
Jun 02, 1-2 PM (1)
Jun 02, 2-3 PM (0)
Jun 02, 3-4 PM (0)
Jun 02, 4-5 PM (0)
Jun 02, 5-6 PM (0)
Jun 02, 6-7 PM (0)
Jun 02, 7-8 PM (0)
Jun 02, 8-9 PM (0)
Jun 02, 9-10 PM (0)
Jun 02, 10-11 PM (2)
Jun 02, 11-12 AM (0)
Jun 03, 12-1 AM (1)
Jun 03, 1-2 AM (0)
Jun 03, 2-3 AM (0)
Jun 03, 3-4 AM (0)
Jun 03, 4-5 AM (0)
Jun 03, 5-6 AM (0)
28 commits this week May 27, 2026 - Jun 03, 2026
v2: version-pin the full installed closure, not just the lib-dep closure
A test/bench slice (or any component that reaches a dep only through
its own package's library or a sibling component, rather than through
component.depends) seeds the blocks closure (blkFrags / transitive-deps)
but not the lib-dep closure (libFrags / lib-dep-slices).  The composed
cabal.project only emitted version pins from libFrags, so those deps
were installed-but-unpinned.  With allow-older + allow-boot-library-
installs the solver was then free to re-solve a reinstalled boot library
(e.g. text 2.1.4 -> GHC-bundled text 2.1.3), drifting every downstream
UnitId and failing checkAgainstPlan.

Emit a bare '<pkg> ==<ver>' pin for every unit in the installed
(blkFrags) closure; keep extra-packages and the 'source' qualifier
scoped to libFrags so build-tool exes aren't forced to rebuild from
source.  Surfaced by ghcjs tests.js-template-haskell.check (reinstalls
text).
comp-v2-builder: compose source-repository-package deps at build time; always allow boot-lib installs
Two fixes for source-repo / reinstallable-lib:ghc projects under v2,
surfaced by the ghc-lib-reinstallable-cabal test:

1. Build-time source-repository-package composition.  The cleanup
   commit dropped source-repo handling (cardano-node has none), which
   broke any project with source-repo deps — including lib:ghc via
   useLocalGhcLib.  Each source-repo package now emits its own
   `source-repository-package` block (wrapping its source in a minimal
   git repo) into its fragment, and the build-time composer collects
   them across the closure and omits those packages from
   `extra-packages:`.  The block references a derivation, so it's
   written with `writeText` (not `toFile`).

2. `allow-boot-library-installs: True` is now emitted unconditionally,
   combined with `allow-newer`/`allow-older` in the project skeleton
   (`solverRelaxations`).  It only *permits* reinstalling boot libs;
   cabal can act on it solely when a boot lib's source is in this
   slice's repo (one plan-nix reinstalled), so it's a no-op otherwise.
   This drops the `bootLibPkgNames` gating machinery and lets cabal
   pick the source-repo `ghc` for useLocalGhcLib slices.  Also added
   `allow-older: *:*` alongside `allow-newer: *:*` (harmless given the
   per-slice repo has one candidate version per package).

Verified: tests.ghc9141.native.tests.ghc-lib-reinstallable-cabal builds
(reinstalls lib:ghc end to end); Stream / happy / proto-lens-protobuf-types
unaffected.
default builderVersion to 2 (cabal v2-build slicing builder)
Make the cabal v2-build slicing builder (comp-v2-builder) the default
for all projects, so CI exercises it.  v2 eval is now at parity with v1
(per-slice work composed at build time, Nix touches only direct deps),
and the v2 builder handles the cases the test suite and cardano-node
exercise (multi-version deps, multi-sublib packages, build-tool
closures, Custom-build setup unit-ids).  Projects can still opt back to
the Setup.hs builder with `builderVersion = 1`.
v2 build-time composition: remove the now-dead Nix-side closure code
With the slicing repo and cabal.project both composed at build time,
migrate the two remaining Nix-cabal.project consumers and delete the
now-unused closure-assembly machinery (net ~-395 lines in
comp-v2-builder.nix):

  * checkAgainstPlan and docSlice now use the build-time path too
    (v2Fragment + slicePreBuildV2 + localRepo = null), so nothing
    forces the Nix-assembled cabal.project / slicingRepo anymore;
  * sublib reachability seeds (HASKELLNIX_EXTRA_SUBLIB_SEEDS) are
    composed at build time from the per-package fragments' sublib-seeds
    across the all-dep closure plus this slice's own target sublib —
    replacing the last Nix-side allDepClosure walk (extraSublibSeeds);
  * the 'is this a v2 slice' marker moves from passthru.transitiveTarballs
    to the existing passthru.isSlice (comp-v2-builder + shell-for-v2);
  * deleted: cabalProject, slicePreBuild, slicingRepo (+ repoCopyCmds /
    indexEntryCmds / depTarballsDeduped), depTransitives /
    depTransitiveTarballsOf / transitiveTarballs, sliceCanonicalNames +
    the six all*Blocks, depConstraints / libConstraintPins /
    libConstraintClosure / exeUnitsInAllDeps, extraPackages*,
    sourceRepo* / minimalSourceRepo / wrapAsMinimalRepo, allowBootLibBlock,
    and the pre-existing dead libDepClosure / libDepsOf / pkgPlanUnits /
    pkgLibDepClosure.

Kept (still used by the per-package fragment / build-time path): the
per-package block functions (flagBlockFor, ...), bootLibPkgNames,
allDepClosure (build-tool store composition), localCabalProject.

Two build-time fixes for multi-sublib packages: the repo index copy is
no-clobber, and the selfFrag carries its own sublib-seeds (the seeds
loop is robust under set -e/pipefail).

Verified: eval clean, ~15.7s user (v1 parity); Stream / code-page /
network / happy (multi-sublib + build-tool) build with unchanged
unit-ids.  (proto-lens-protobuf-types fails to build, but that's a
pre-existing v2 bug independent of this change — it fails identically
at a77f5524c, before any build-time-composition work.)
comp-v2-builder: pin Custom-build packages' setup-depends (notably Cabal)
A `build-type: Custom` package's unit-id hashes its custom-setup
configuration, including which `Cabal` the setup is built against.  The
v2 slice's cabal.project pinned library dep versions but left the
per-package custom-setup solve unconstrained, so when a slice's closure
contained a reinstalled `Cabal` (e.g. pulled in by another package's
`proto-lens-setup` custom-setup) cabal preferred it over the GHC-bundled
`Cabal` plan-nix had used — forking the Custom-build unit-id.

Concretely: `ghc-paths` (build-type: Custom, setup-depends Cabal) built
standalone as plan-nix's `ghc-pths-0.1.0.12-142b137f` (setup against
bundled Cabal-3.10.3.0), but inside `proto-lens-protobuf-types`' slice
its setup resolved against the reinstalled Cabal-3.16.1.0 →
`…-e732a98d`.  The pre-built `proto-lens-protoc` exe (linked against
142b137f) was then rejected, cabal rebuilt it + ghc-paths, and the
"exactly one expected unit" check failed.

Fix: emit per-package `constraints: <pkg>:setup.<dep> ==<ver>` for each
package's `components.setup.depends` (which plan-nix records), composed
at build time across the all-dep closure alongside the library
constraints.  cabal accepts the per-package `pkg:setup.dep` qualifier
(verified), so each Custom-build package's setup now resolves to the
same deps plan-nix used, reproducing its unit-id.

Verified: proto-lens-protobuf-types now builds (with the project's
`build-tools = [protobuf]` providing `protoc` for codegen); Stream
(Strm-0.4.7.2-a9e4dded) and happy (hppy-2.2-70e3af7a) unchanged.
v2 build-time composition (step 2a): assemble slicing repo at build time
When a slice carries a `v2Fragment`, assemble its hackage slicing repo
in the build phase from the per-package `repo-frag/`s — this package's
own source (passed directly, since its own fragment isn't built yet)
plus every dep's fragment reached through the same `transitive-deps`
closure walk used to compose the store.  Replaces referencing the
Nix-built `localRepo` for these slices.

cabal hashes package source + .cabal descriptions (not the index bytes
or the repo path), so the content-equivalent repo keeps unit-ids
identical: the Stream leaf slice still builds as Strm-0.4.7.2-a9e4dded.

(The Nix `slicingRepo` is still computed but unused for these slices;
it's removed in step 3 along with the other Nix-side closure walks.)
v2 build-time composition (step 3): drop the Nix-side closure walks
The real slice now assembles its cabal.project + slicing repo entirely
at build time from per-package fragments, so the per-slice dependency-
closure walk no longer runs in the Nix evaluator:

  * baseSlice's preBuild (slicePreBuildV2) only stages local test/bench
    sources — it no longer interpolates the Nix-assembled `cabalProject`,
    so the six `sliceCanonicalNames` block-assemblies, `depConstraints`
    /`libConstraintPins`/`libConstraintClosure`, `extraPackages`,
    `sourceRepoBlocks` and `allowBootLibBlock` are no longer forced;
  * localRepo is null for these slices, so `slicingRepo` (and the
    `transitiveTarballs` recursion behind it) is no longer forced;
  * the build-time content-equivalence diff harness is removed (it was
    only needed while developing the composer), and the local skeleton
    is written via redirect rather than `cp` (which would otherwise
    create cabal.project read-only and break the closure `>>` append).

`checkAgainstPlan` / `docSlice` keep the full Nix `cabalProject` path —
they don't carry a `v2Fragment` and are lazy (not forced on the main
slice / devShell path).

Effect on `nix path-info --derivation .#devShells…default`:
comp-v2-builder eval calls 13.2M -> 6.5M, total ~62M -> ~49M, CPU
~24s -> ~20s.  The remaining cost is dominated by the module system
(~18M lib/modules.nix calls, unchanged) — the inherent evaluation of
`config.packages.<id>` when each slice references its dep slices — which
is structural and not addressable by build-time composition.

Unit-ids unchanged throughout (Stream Strm-0.4.7.2-a9e4dded,
happy hppy-2.2-70e3af7a).
v2 build-time composition (step 2b): assemble cabal.project closure sections at build time
Compose the dependency-closure-derived parts of each slice's
cabal.project in the build phase, from per-package fragments, instead of
in the Nix evaluator:

  * extra-packages:     self + lib-dep-closure pins
  * the six per-package block groups (flags / ghc-options /
    configure-options / program-options / documentation / extra-lib-dirs)
  * allow-boot-library-installs (gated on closure boot-libs)
  * constraints:        self `any.<pkg> source` + lib-dep-closure pins

The Nix side now emits only the LOCAL/global skeleton
(`localCabalProject`) and each package's OWN fragment; the build script
appends the composed sections.  cabal hashes content, not field order,
so we emit local-then-closure and verify content-equivalence with an
order-insensitive diff against the (still-computed) Nix cabal.project —
empty for every slice tested.

Direct-deps-only in Nix (per design): the lib-dep / constraint closure
is seeded from this component's DIRECT lib-deps and DIRECT build tools
(`buildToolSlices`, not the Nix-walked `transitiveBuildToolSlices`).
Transitivity is accumulated at build time — each slice's emitted
`lib-dep-slices` folds in its own direct build tools' lib closures, so
following a dep's pointer reaches every transitive build tool's lib
closure without a Nix-side allDepClosure walk.

Two multi-sublib fixes (e.g. happy-lib's grammar/frontend/tabular, all
sharing pkg-name happy-lib):
  * repo index copy is no-clobber (`-n`) — duplicate identical .cabal
    across sublib fragments no longer hits the read-only first copy;
  * closure dedup is by pkg-name (`sort -k1,1 -u`), matching the Nix
    side's `sliceCanonicalNames` / `libConstraintPins` dedup, so a
    package's block / constraint / extra-packages entry appears once.

Verified content-identical (empty diff) + unchanged unit-ids on Stream
(leaf), code-page + network (hsc2hs), and happy (multi-sublib +
build-tool with reinstalled happy-lib).  No source-repo packages exist
in this plan, so source-repository-package block composition is deferred.
v2 build-time composition (step 1): emit per-package fragment in $out
First step of moving the v2 slicing-repo / cabal.project assembly out of
the Nix evaluator and into each slice's build phase, so a consumer never
has to walk its transitive dependency graph in Nix.

Each slice now writes its OWN contribution into $out (additive; not yet
consumed):

  * repo-frag/          — this package's source tarball + index .cabal
  * nix-support/v2-frag — the six per-package cabal.project blocks
                          (flags / ghc-options / configure-options /
                          program-options / documentation / extra-lib-dirs),
                          its constraint pin line, pkg-name, and sublib seeds
  * nix-support/lib-dep-slices — flattened lib-dep closure pointer
                          (built the same way as transitive-deps)

All values are computed once for THIS package (O(1) per slice), reusing
the existing per-package block functions.  Verified additive: the Stream
leaf slice's unit-id is unchanged (Strm-0.4.7.2-a9e4dded).

Steps 2 (compose at build time) and 3 (drop the Nix-side closure walks)
follow.
comp-v2-builder: compute project-global cabal.project data once (#2516)
The v2 builder derived three values from the install plan while emitting
every slice's cabal.project:

  * projectConfigPragmas — the `package *` / per-package pragma blocks
    parsed from each configured unit's `configure-args` (3 regex
    matches per arg, over every configured plan entry);
  * pkgDocEnabled       — `lib.any (... -haddock ...) planJson` per pkg;
  * planFlagsFor        — `lib.findFirst (...) planJson` per pkg.

All three depend only on the plan (+ ghc version and host platform),
never on the individual component — yet they were recomputed from
scratch for every one of the ~2000 component slices.  On cardano-node
that was the dominant evaluation cost: ~290M of 535M function calls and
the bulk of 125M `builtins.match` / 94M `lib.elem` primop calls.

Hoist them into a pure helper (builder/v2-project-globals.nix) computed
once behind the project-level `config.plan-json-v2-globals` option
(mirroring the existing `plan-json-by-id` index), and reduce the
per-slice sites to O(1) lookups.

`nix path-info --derivation .#devShells.aarch64-darwin.default` on
cardano-node drops from ~4:00 to ~26s, and the resulting devShell
derivation is byte-identical to before — this is a pure memoisation
with no change to any emitted cabal.project or slice.
comp-v2-builder: compute project-global cabal.project data once
The v2 builder derived three values from the install plan while emitting
every slice's cabal.project:

  * projectConfigPragmas — the `package *` / per-package pragma blocks
    parsed from each configured unit's `configure-args` (3 regex
    matches per arg, over every configured plan entry);
  * pkgDocEnabled       — `lib.any (... -haddock ...) planJson` per pkg;
  * planFlagsFor        — `lib.findFirst (...) planJson` per pkg.

All three depend only on the plan (+ ghc version and host platform),
never on the individual component — yet they were recomputed from
scratch for every one of the ~2000 component slices.  On cardano-node
that was the dominant evaluation cost: ~290M of 535M function calls and
the bulk of 125M `builtins.match` / 94M `lib.elem` primop calls.

Hoist them into a pure helper (builder/v2-project-globals.nix) computed
once behind the project-level `config.plan-json-v2-globals` option
(mirroring the existing `plan-json-by-id` index), and reduce the
per-slice sites to O(1) lookups.

`nix path-info --derivation .#devShells.aarch64-darwin.default` on
cardano-node drops from ~4:00 to ~26s, and the resulting devShell
derivation is byte-identical to before — this is a pure memoisation
with no change to any emitted cabal.project or slice.
comp-v2-builder: disambiguate dep lookups by version (#2515)
When the cabal plan holds more than one version of a package -- e.g. a
GHC boot library like text-2.0.2 alongside a cabal-reinstalled
text-2.1.4 -- the bare hsPkgs.<name> redirect is ambiguous and throws
"Multiple versions for <name>".

comp-v2-builder looked dependency packages up by bare name in eight
places (externalDepIds collapses deps to { name; version; } pairs,
dropping the unit id, and the helpers then re-looked-up by name only),
so any v2 build whose dependency closure spanned such a package failed
to evaluate.

Add a lookupDepPkg helper that prefers the unique <name>-<version>
redirect when the version is known and falls back to the bare name
otherwise, and route all the bare-name lookups through it.
comp-v2-builder: disambiguate dep lookups by version
When the cabal plan holds more than one version of a package -- e.g. a
GHC boot library like text-2.0.2 alongside a cabal-reinstalled
text-2.1.4 -- the bare hsPkgs.<name> redirect is ambiguous and throws
"Multiple versions for <name>".

comp-v2-builder looked dependency packages up by bare name in eight
places (externalDepIds collapses deps to { name; version; } pairs,
dropping the unit id, and the helpers then re-looked-up by name only),
so any v2 build whose dependency closure spanned such a package failed
to evaluate.

Add a lookupDepPkg helper that prefers the unique <name>-<version>
redirect when the version is known and falls back to the bare name
otherwise, and route all the bare-name lookups through it.
v2 builder: pin build-tools' lib closures in slice constraints (#2514)
* v2 builder: fix checkAgainstPlan call args (pname/version)

`checkAgainstPlan` was calling `buildCabalStoreSlice` with `name =`
but the slice builder requires `pname` + `version` (matches the
neighbouring `baseSlice` / `docSlice` callsites).  Evaluation
crashed with "function 'anonymous lambda' called without required
argument 'version'" the moment anything asked for the diagnostic
attribute.

* v2 builder: pin build-tools' lib closures in slice constraints

`libConstraintPins` previously walked only `libDepClosure` — the
slice's own lib closure — so reinstalled libs reached only through
`build-tool-depends:` never made it into the slice's
`extra-packages:` / `constraints:` blocks.  cabal in the slice then
solved each transitive build-tool (alex / happy / hsc2hs / ...)
against GHC-bundled `-inplace` versions instead of the reinstalls
plan-nix recorded, the tool's computed UnitId diverged from the
pre-built slice composed into the starting store, and cabal
rebuilt the tool from source.

Concrete failure: zlib's lib closure is `{base, bytestring}` — no
directory/filepath/process — but `build-tool-depends: hsc2hs:hsc2hs`
brings hsc2hs in, and plan-nix had reinstalled directory-1.3.11.0 /
filepath-1.5.5.0 / process-1.6.29.0.  hsc2hs in the slice resolved
against directory-1.3.10.0-inplace etc., computed
`hsc2hs-0.68.10-981b6fe4`, and refused the composed
`hsc2hs-0.68.10-bb34db40`.

Fix: extend the closure used by `libConstraintPins` to seed from
this slice's plan units AND every exe-component plan unit reached
via `allDepClosure`.  Each exe seed's lib closure (via `libDepsOf`)
contributes its reinstalled deps to the pin set.  Within a single
plan-nix every package has one version, so the first-wins
grouping is stable across the merged closure.

* v2 shell: add pkg-config and surface slices' C-side deps

cabal's solver inside the v2 shell wasn't seeing `pkg-config` or
the pkgconfig deps of any selected pkg / dep slice, so when a
package declared `pkgconfig-depends:` (e.g. `zlib` with the
default `+pkg-config` flag) cabal silently flipped the flag to
`-pkg-config` and computed a different unit-id than plan-nix had
recorded.  The composed cabal store held the `+pkg-config`
unit-id, so cabal didn't recognise its prebuilt slice and planned
a from-source rebuild — cascading to every package downstream of
zlib (streaming-commons, warp, websockets, wai-websockets, ...).

Fix:
1. Add `pkgs.buildPackages.cabalPkgConfigWrapper` to the v2
   shell's `nativeBuildInputs` unconditionally — same wrapper v1's
   `comp-builder.nix:345` threads via `--with-pkg-config=`, real
   pkg-config plus a `--libs --static` failure shim (#1642), NOT
   the fake `allPkgConfigWrapper`.  Always-on so the unit-id stays
   stable if the user adds a pkgconfig dep later.
2. Fold every selected component's and dep slice's
   `passthru.runtimeLibs` (= each slice's own `extraBuildInputs`,
   covering `component.libs / pkgconfig / frameworks`) into the
   shell's `buildInputs`.  Without this, pkg-config alone finds
   nothing — none of the project's `.pc` files would be on
   `PKG_CONFIG_PATH`.  Mirrors v1's `systemInputs` line in
   `shell-for.nix:108`, which collects the same set via stdenv
   propagation of `c.buildInputs ++ c.propagatedBuildInputs`; v2
   slices don't ride that propagation chain (they flow through
   the cabal store instead), so the inputs are surfaced
   explicitly.
v2 shell: add pkg-config and surface slices' C-side deps
cabal's solver inside the v2 shell wasn't seeing `pkg-config` or
the pkgconfig deps of any selected pkg / dep slice, so when a
package declared `pkgconfig-depends:` (e.g. `zlib` with the
default `+pkg-config` flag) cabal silently flipped the flag to
`-pkg-config` and computed a different unit-id than plan-nix had
recorded.  The composed cabal store held the `+pkg-config`
unit-id, so cabal didn't recognise its prebuilt slice and planned
a from-source rebuild — cascading to every package downstream of
zlib (streaming-commons, warp, websockets, wai-websockets, ...).

Fix:
1. Add `pkgs.buildPackages.cabalPkgConfigWrapper` to the v2
   shell's `nativeBuildInputs` unconditionally — same wrapper v1's
   `comp-builder.nix:345` threads via `--with-pkg-config=`, real
   pkg-config plus a `--libs --static` failure shim (#1642), NOT
   the fake `allPkgConfigWrapper`.  Always-on so the unit-id stays
   stable if the user adds a pkgconfig dep later.
2. Fold every selected component's and dep slice's
   `passthru.runtimeLibs` (= each slice's own `extraBuildInputs`,
   covering `component.libs / pkgconfig / frameworks`) into the
   shell's `buildInputs`.  Without this, pkg-config alone finds
   nothing — none of the project's `.pc` files would be on
   `PKG_CONFIG_PATH`.  Mirrors v1's `systemInputs` line in
   `shell-for.nix:108`, which collects the same set via stdenv
   propagation of `c.buildInputs ++ c.propagatedBuildInputs`; v2
   slices don't ride that propagation chain (they flow through
   the cabal store instead), so the inputs are surfaced
   explicitly.
v2 builder: pin build-tools' lib closures in slice constraints
`libConstraintPins` previously walked only `libDepClosure` — the
slice's own lib closure — so reinstalled libs reached only through
`build-tool-depends:` never made it into the slice's
`extra-packages:` / `constraints:` blocks.  cabal in the slice then
solved each transitive build-tool (alex / happy / hsc2hs / ...)
against GHC-bundled `-inplace` versions instead of the reinstalls
plan-nix recorded, the tool's computed UnitId diverged from the
pre-built slice composed into the starting store, and cabal
rebuilt the tool from source.

Concrete failure: zlib's lib closure is `{base, bytestring}` — no
directory/filepath/process — but `build-tool-depends: hsc2hs:hsc2hs`
brings hsc2hs in, and plan-nix had reinstalled directory-1.3.11.0 /
filepath-1.5.5.0 / process-1.6.29.0.  hsc2hs in the slice resolved
against directory-1.3.10.0-inplace etc., computed
`hsc2hs-0.68.10-981b6fe4`, and refused the composed
`hsc2hs-0.68.10-bb34db40`.

Fix: extend the closure used by `libConstraintPins` to seed from
this slice's plan units AND every exe-component plan unit reached
via `allDepClosure`.  Each exe seed's lib closure (via `libDepsOf`)
contributes its reinstalled deps to the pin set.  Within a single
plan-nix every package has one version, so the first-wins
grouping is stable across the merged closure.