V1 Component Builder (Setup.hs-based)
The v1 builder is haskell.nix’s original component builder. It produces
one nix derivation per Cabal component by invoking that package’s
Setup.hs directly. v2 (see builder-v2.md) is the default; opt
back in to v1 with the project-level builderVersion = 1; option.
Setting builderVersion = 1 makes
hsPkgs.<pkg>.components.library (and exes / tests / benchmarks /
sublibs) be the v1 derivation. Switching builders is project-wide
— there’s no per-component opt-in.
What v1 produces
For each component cabal would build (lib, lib:<sublib>,
exe:<name>, test:<name>, bench:<name>), v1 produces a separate
nix derivation. The library derivation’s $out looks like a
fully-installed package — lib/, lib/<ghcDir>/<pkg-id>/...,
package.conf.d/<pkg-id>.conf, etc. — so that downstream components
can register it via ghc-pkg. Exe-like derivations install the
binary to $out/bin/<name> (with the platform-appropriate extension).
Per-component derivations let cabal-cached nix-builds share
dependencies across projects: two projects whose library derivations
hash to the same path get the same /nix/store output, regardless of
what else is in either project.
High-level flow
Given the module-system config (config.packages.<pkg> produced by
modules/plan.nix from plan-json/Stackage snapshots), the builder:
-
modules/component-driver.nixiteratesconfig.packagesand callsbuilder.build-package config pkgfor each non-null, non-pre-existing entry. The result lands inconfig.hsPkgs. -
builder/hspkg-builder.nixreceivespkg, walks every buildable component inpkg.components, and for each one calls eithercomp-builder(v1) orcomp-v2-builder(v2) — whicheverbuilderVersionselects. Only one derivation is produced per component;pkg.components.library(etc.) IS that derivation. -
builder/comp-builder.nixis the v1 component derivation. It stages the package source, runsSetup.hs configurewith the component selected, thenSetup.hs buildandSetup.hs install, and captures the resulting installed component as$out. -
builder/setup-builder.nixbuilds theSetup.hsbinary itself (usingsetup-depends). It runs on the build platform regardless of the project’s host platform — Setup is a build-time tool. -
builder/make-config-files.nixassembles the per-componentpackage.conf.d(a ghc-pkg database) from the component’s transitive library deps. Each dep contributes its own pre-built<pkg-id>.conffile;make-config-filesconcatenates them and runsghc-pkg recache. -
builder/ghc-for-component-wrapper.nixwrapsghcwithGHC_PACKAGE_PATH=<package.conf.d>so the component’sSetup.hs buildsees exactly the deps assembled in step 5 — no more, no less. -
builder/haddock-builder.nixrunsSetup.hs haddockafterwards (ifdoHaddockis enabled), producing the haddock output as a separate derivation alongside the library.
Data flow per component
plan.nix cabal-to-nix output
│ │
▼ ▼
component.depends ──── component.libs / pkgconfig / build-tools
│
▼
make-config-files ── package.conf.d (real ghc-pkg db of deps)
│
▼
ghc-for-component ─ wraps GHC with GHC_PACKAGE_PATH
│
▼
comp-builder ─ runs Setup.hs configure / build / install
│
▼
$out
├─ lib/<ghcDir>/<pkg-id>/... (library files)
├─ <pkg-id>.conf (registration file)
├─ bin/<exe> (for exe / test / bench)
└─ ...
How dependencies get found
For each component:
-
Library deps: every dep’s v1 derivation appears in the component’s
propagatedBuildInputschain.make-config-fileswalks that chain viahaskellLib.flatLibDependsto gather every transitive lib’s<pkg-id>.confand merge them into onepackage.conf.d. GHC is then started with that as its package db. -
System libs / frameworks / pkg-config: stdenv puts each in
buildInputs; the cc-wrapper’sNIX_LDFLAGS/NIX_CFLAGS_COMPILEvariables make them visible toSetup.hs configure’scheckForeignDepsprobe. -
build-tool-depends(alex, happy, hsc2hs, etc.): each tool is resolved viahsPkgs.pkgsBuildBuild.<tool>.components.exes.<tool>(build-platform — the tool runs at build time, not at runtime on the host) and added tonativeBuildInputs.
How configure flags are derived
builder/comp-builder.nix computes the Setup.hs configure command
line from a fixed set of inputs:
--with-ghc=,--with-ghc-pkg=,--with-hsc2hs=(cross-aware via${ghc.targetPrefix}),--with-gcc=,--with-ar=,--with-strip=,--with-ld=from the cross stdenv’scc.targetPrefix/cc.bintools.targetPrefix,--with-pkg-config=frombuildPackages.cabalPkgConfigWrapper(only when the component haspkgconfig-depends),--enable-library-vanilla,--enable-shared, profiling / documentation toggles from the module config,--package-db=pointing at the deppackage.conf.dfrom make-config-files,--cid=<unit-id>taken from plan.json’s resolved unit id, and--dependency=<dep-name>=<dep-unit-id>for each lib dep,--ghc-option=<o>for every entry in the component’sghcOptions(sourced from plan.json’sconfigure-argsviamodules/install-plan/configure-args.nix, plus anything set directly in usermodules),--configure-option=<o>forconfigureOptions.
These flags determine cabal’s pkgHashConfigInputs and therefore the
final UnitId. Two projects whose components reach Setup.hs configure
with identical flags + identical sources land at the same nix store
path.
Testing & checking
haskellLib.check <comp> wraps a test or benchmark derivation in
another derivation that actually runs the binary and captures stdout
to test-stdout. v1 splits build-time (the comp derivation) from
check-time (the wrapped check derivation) so that callers who only
want the binary (e.g. for offline inspection) don’t pay the test-run
cost.
For cross-compiled hosts whose binaries can’t run on the build host
directly (windows: wine; ghcjs: node; wasm: wasmtime), the
component’s module config sets testWrapper, a list prepended to the
exe path inside check.
Key files
| Path | Role |
|---|---|
modules/component-driver.nix | iterates config.packages → config.hsPkgs |
builder/default.nix | wires up the builders (v1 + v2 + setup + …) |
builder/hspkg-builder.nix | per-package: build every component |
builder/comp-builder.nix | per-component: the v1 derivation |
builder/setup-builder.nix | builds Setup.hs |
builder/make-config-files.nix | assembles dep package.conf.d |
builder/ghc-for-component-wrapper.nix | wraps GHC with the right package db |
builder/haddock-builder.nix | runs Setup.hs haddock |
builder/shell-for.nix | v1 nix-shell (pre-populates ghc-pkg db) |
lib/check.nix | runs test/bench binaries, captures stdout |
modules/package.nix | per-package option declarations |
modules/component.nix | per-component option declarations |
When to prefer v1
- You want a build that mirrors what cabal would produce via
Setup.hsdirectly — useful for projects that depend on Custom build-types’ specific Setup behaviour. - You need haddocks / per-component derivations / the
haskellLib.checkseparation between build and run.
For a project where you want fewer derivations, faster eval at
high-component-count projects, and a closer match to what
cabal v2-build produces in development, v2 is now an option — see
builder-v2.md.