head.hackage CI driver
This is the application which drives GHC's head.hackage
continuous
integration infrastructure, namely head-hackage-ci
. The general goals here
are three-fold:
- test that the patches in the
head.hackage
repository build - use these patches to smoke-test GHC pre-releases and nightly builds
- provide the patches as a Hackage repository (https://ghc.gitlab.haskell.org/head-hackage) for consumption by end-users
We accomplish this via the head-hackage-ci
executable. This executable has
two modes:
-
test-patches
is the primary driver of the CI testing process -
make-constraints
is a utility for producingcabal-install
constraints to ensure that only patched versions of packages are used in install plans
Naturally, many Haskell packages have dependencies on native libraries.
head-hackage-ci
supports two ways of providing these libraries:
-
from the host system: Here we just rely on the host system to provide native libraries; it is up to the user to ensure that the necessary packages are installed.
-
from nixpkgs: Here we use nix and the nixpkgs package set to provide native libraries. These dependencies are defined in
ci/build-deps.nix
. This mode is
Test procedure
The testing part of the CI process (goals (1) and (2) above) uses head-hackage-ci
's
test-patches
mode and some shell scripts (namely ci/config.sh
and
ci/build-repo.sh
) (and in the case of a Nix-based build, ci/build-deps.nix
).
The below is all orchestrated by run-ci.sh
:
-
Call
ci/config.sh
to determine the configuration of the run. This does the following:-
Identify the version of the compiler being tested (provided by the user via the
GHC
environment variable) -
Use the compiler version to find the set of packages that we expected to be broken.
-
Build a set of command-line arguments destined for
head-hackage-ci
from the broken-package set above and a set of "extra" packages defined inconfig.sh
-
-
If we are using
nixpkgs
to get native libraries: compute acabal.project
fragment from the dependency information inci/build-deps.nix
(this logic lives inci/default.nix
). -
Call
head-hackage-ci test-patches
with the computed arguments. This does the following:-
Determine the set of packages to test (determined by the contents of the
patches/
directory and additional packages provided via the--extra-package
flag) -
Build a local Hackage repository of patched packages using the
build-repo.sh
script (which itself depends uponhackage-repo-overlay-tool
andhackage-overlay-tool
) -
Build a
cabal.project
file containing configuration (e.g. the location of the local repository, the location of the compiler, where native dependencies are found, etc.) -
Call
cabal v2-update
to informcabal
of the patched package repository -
For each package to test:
- Create a new working directory
- Copy the previously constructed
cabal.project
into our working directory - Construct a dummy Cabal package depending only on the package under test
- Call
cabal new-build
to build the package - Examine the state of
plan.json
and the caballogs
directory to work out the outcome of the build
-
Write a JSON report (of type
Types.RunResult ()
) toresult.json
-
Examine the failed units and determine whether there were any unexpected failures.
-
Build plans and empty patches
When testing a package, the CI driver will construct a build plan that favors
versions of Hackage libraries with head.hackage
patches over versions of the same
library that lack patches. For example, if CI tests a library that depends on
library foo
that has two Hackage releases, 0.1 and 0.2, then if foo-0.1
has
a patch but foo-0.2
does not, then the driver will include foo-0.1
in the
build plan even though foo-0.2
has a more recent version number. This is done
to reduce the likelihood of subsequent Hackage releases of foo
breaking the
CI due to API changes.
Sometimes, this approach can work against you. Suppose that another library
bar
also depends on foo
. Moreover, bar
requires the use of foo-0.2
and
excludes foo-0.1
in its version bounds. Because foo-0.1
has a patch but
foo-0.2
does not, however, the CI driver will insist on using foo-0.1
when
constructing build plans, which means that it will fail to find a valid build
plan for bar
!
The simplest way to fix this sort of problem is to add a patch for foo-0.2
.
If there are patches for both foo-0.1
and foo-0.2
present, then the CI
driver will admit build plans with either version of foo
. In the event that
foo-0.2
already compiles with all supported versions of GHC, you can simply
add an empty patch by running touch patches/foo-0.2.patch
.