build-env 
build-env is a utility to build a set of Cabal packages (computed
by cabal-install's solver) into a free-standing package database.
This enables the compilation of Haskell packages (with their dependencies)
in hermetic build environments.
Contents
Example
$ build-env build lens -f sources -o install -j8
$ ghci -package-db install/package.conf -package lens
ghci> :ty Control.Lens.Lens
Control.Lens.Lens
  :: Control.Lens.Type.Lens s t a b
     -> Control.Lens.Reified.ReifiedLens s t a b
In this example, the build-env build invocation:
- computes a build plan for 
lens,
 
- fetches 
lens and its dependencies into the sources directory,
 
- configures and builds all the packages, with up to 
8 units building
concurrently, and registers the libraries into the package database at
install/package.conf.
 
We then tell ghci to use this package database, making lens available.
Commands
build-env has three distinct modes of operation:
plan computes a build plan, outputting a plan.json Cabal plan.
 
fetch fetches sources.
 
build executes a build plan.
 
Each command subsumes the functionality of the previous ones in the above list.
In particular, in the previous example, the build command computed a build
plan, fetched the sources, and built the plan. The same could have been achieved
with three separate invocations:
$ build-env plan lens -p lens-plan.json
$ build-env fetch -p lens-plan.json -f sources
$ build-env build -p lens-plan.json -f sources -o install -j8 --prefetched
Being able to separate these steps affords us some extra flexibility, as
subsequent sections will explain.
Note: it is preferable to pass a previously computed build plan when calling
build-env build --prefetched: an invocation of the form build-env build a b c --prefetched
will compute a new build plan, and this build plan could be different from
the plan that was previously fetched, for example if you have run cabal update
in the meantime.
Another option is to pass --index-state, which pins down a specific Hackage
index state.
Parallelism
build-env supports parallel builds in much the same way as cabal-install does:
-jN controls how many packages are built concurrently.
 
--configure-arg --ghc-option=-jN controls the parallelism afforded to
GHC invocations when building individual packages.
 
--jsem N allows build-env and GHC to coordinate usage of CPU resources.
This is usually the fastest, but it requires:
- GHC >= 9.8,
 
- that GHC and 
build-env have been compiled against the same libc implementation
(see GHC issue #25087).
 
 
What does build-env do?
- plan: 
build-env calls cabal build --dry-run on a dummy
project with the dependencies and constraints that were asked for.
This produces a Cabal plan in the form of a plan.json, which build-env
parses.
Required executables: cabal, ghc.
 
- fetch: 
build-env calls cabal get on each package in the build plan.
Required executables: cabal.
 
- build: 
build-env builds the dependencies by compiling their Setup
scripts and calling Setup configure, Setup build,
Setup haddock (optional), Setup copy, and, for libraries, Setup register
and ghc-pkg register.
Required executables: ghc, ghc-pkg.
 
Note that, when building packages, build-env passes the following information
when running the Setup script:
with-compiler, prefix and destdir options supplied by the user,
 
cid, datadir, datasubdir, bindir and builddir options supplied by build-env,
 
- package flags and 
dependency arguments, obtained from the build plan,
 
<pkg>_datadir environment variables supplied by build-env.
 
These options cannot be overridden. If you absolutely need to change some of
these, please file a bug on the issue tracker asking for this customisability.
Hermetic builds
When working in a hermetic build environment, we might need to compute a build
plan and fetch all the dependencies first, before doing an offline build.
This can be achieved by two separate build-env invocations.
In a local environment (with access to internet):
$ build-env fetch lens -f sources --output-plan plan.json
In a build environment in which build-env has been provisioned:
$ build-env build --prefetched -p plan.json -f sources -o install -j8
In this example:
- the 
fetch command computes a plan which is written to
plan.json (using the Cabal JSON plan format), and fetches all
the required sources, putting them into the sources directory;
 
- the 
build command reads in the build plan and performs the build.
 
Narrowing a build down (for debugging)
When attempting to execute a large build plan, it can be useful to be able
to refine the build down to a small set of problematic packages, for debugging
purposes. For this, you can use --only, e.g.:
$ build-env build --prefetched -p plan.json -f sources -o install --only badPackage
Instead of building the full plan from plan.json, this will instead restrict
to only building units from package badPackage and their transitive dependencies.
Multiple --only arguments combine additively, e.g. --only pkg1 --only pkg2
will build the units from the build plan that belong to pkg1 or pkg2, and
transitive dependencies thereof.
Note that --only only affects which packages are built; it does not change
the computation of the build plan nor which packages are fetched.
You can also resume a build where it left off by passing --resume; this will
avoid rebuilding any of the units that have already been registered in the
package database.
Bootstrapping
In the example from ยง Hermetic builds, we ran
build-env build to perform a build. However, this requires that the build
environment provision build-env.
To avoid this requirement, it is also possible to ask build-env to output
a shell script containing the build steps to execute.
In a local environment (with access to internet):
$ build-env build lens -f sources -o install --script build_lens.sh
In a build environment:
$ ./build_lens.sh
The downside is that we lose any parallelism from this approach, as the
generated build script is inherently sequential.
One particularly useful application is the bootstrapping of build-env itself,
as this supplies a mechanism for the provisioning of build-env in build
environments.
Bootstrap arguments
If your build environment provides additional variables that need to be passed
to the build script, you can pass them to the shell script using variables.
For example, in the local environment, you can first obtain all the information
needed to return a build script:
$ build-env fetch lens -f sources --output-plan myPlan.json
Next, compute a build script containing references to variables:
$ build-env build -p myPlan.json -f sources -o install --prefetched --configure-arg \$myArgs --script script.sh
This will output a script which passes $myArgs to each Setup configure
invocation. In the build environment, you can then set the value of $myArgs
before running the shell script script.sh. Note that the build script
does not insert additional quotation marks around $myArgs, which allows
passing multiple arguments at once (this is crucial when one doesn't know
the number of arguments ahead of time).
If you want to set --fetchdir, --prefix or --destdir to variables,
you should pass the --variables flag. This will set these to the values
$SOURCES, $PREFIX and $DESTDIR in the output shell script, respectively.
These should be set to absolute paths before running the script.
The script will also use $GHC and $GHCPKG variables for the compiler.
Specifying packages
Instead of passing the required packages through the command line,
it can be more convenient to ask build-env to read them from files:
$ build-env build --seeds SEEDS --freeze cabal.config -f sources -o install
This will build a plan for the packages specified in the SEEDS file,
with versions of packages that aren't in the SEEDS file constrained as
specified by the cabal.config file.
Each line in the seeds file should be one of the following:
- 
A Cabal unit, in the format unit +flag1 -flag2 >= 0.1 && < 0.3.
A unit can be of the form pkgName, lib:pkgName, exe:pkgName,
pkgName:lib:compName, ... as per cabal component syntax.
The unit name must be followed by a space.
Flags and constraints are optional.
When both are present, flags must precede constraints.
Constraints must use valid Cabal constraint syntax.
 
- 
An allow-newer specification such as:
allow-newer: pkg1:pkg2,pkg3:base,*:ghc
This uses Cabal allow-newer syntax.
 
- 
An empty line or comment (starting with --).
 
The cabal.config file should use valid cabal.project syntax, e.g.:
constraints: pkg1 == 0.1,
             pkg2 >= 3.0
Only the constraints are processed; everything else from the cabal.config
file is ignored.
Local packages
To specify that a package should be found locally rather than fetched from
Hackage, use --local:
build-env build myPkg --local myPkg=../pkgs/myPkg
The part to the right of the = sign corresponds to what you would write
in a cabal.project file: the directory in which myPkg.cabal is located.
If this is a relative path, it will be interpreted relative to the working
directory of the build-env invocation, which can be overriden by passing
--cwd <dir> (works like the -C argument to make).