Skip to content

A new CI infrastructure

Ben Gamari requested to merge new-ci into master

The Problem

When I started looking at the problem of providing CI for head.hackage I considered two possible designs:

  1. Build upon cabal-install
  2. Build upon Nix's Haskell infrastructure

While I preferred (1), I found that integrating with cabal-install was quite difficult:

  • it does not produce logs for local packages, which was the obvious way to incorporate patched packages into the build plan
  • it is difficult to reconstruct why a package build failed (e.g. due to a planning failure, dependency failing to build, or an error in the package itself)

For these reasons it so happened that (2) ended up being a tad easier to implement. However, it suffers from a number of problems:

  • Nix's Haskell infrastructure doesn't handle multiple versions of a single package at all, yet we now have patches for multiple package versions in head.hackage
  • Nix's Haskell infrastructure doesn't handle flags, which can complicate building some packages
  • The Nix expressions ended up being rather difficult to maintain

The Solution

This MR moves the CI infrastructure back in the direction of (1), facilitated by workarounds that I found for the two issues described above.

The infrastructure revolves around the head-hackage-ci executable which provides a test-patches mode which gitlab-ci.yml invokes thusly:

head-hackage-ci test-patches --patches=./patches --with-compiler=$GHC

This mode does several things:

  1. Builds a local package repository (using the same script used to build https://ghc.gitlab.haskell.org/head.hackage/). (N.B. by pulling patched packages from a proper repository instead of using local packages we side-step the fact that cabal-install doesn't produce logs for local packages)

  2. Generate a cabal.project file containing:

    • a remote-repository stanza referring to this repository
    • constraints to ensure that we only choose patched package versions
    • some additional package stanzas to ensure that Cabal can find native library dependencies (these are defined in ci/build-deps.nix)
  3. Run cabal new-update (as well as perform a dummy build of the acme-box package to ensure that the package index cache is built, otherwise parallel builds can randomly fail)

  4. For each patched create a new working directory containing:

    • the previously generated cabal.project file
    • a test-$PKGNAME.cabal file defining a dummy package depending upon the library

    and perform the build. We use some heuristics depending upon:

    • the plan.json file
    • which log files exist
    • the contents of said log files

    to sort out what happened.

  5. After all the packages have been built produce a final report of the result.

While this is admittedly pretty hacky, in truth it's no worse than the somersaults which we had to perform in the Nix infrastructure. Reliably introspecting on failed builds seems to be messy business no matter which build system you use.

Remaining Issues

  • Parallel builds appear to be fragile due to what appear to be cabal-install bugs (I suspect https://github.com/haskell/cabal/issues/6222)
  • Currently the log output is contained in the JSON summary file. I think it would be better if it were rather placed in individual files as it is currently.
  • History needs sorting out
Edited by Ben Gamari

Merge request reports