Skip to main content

GitHub Actions Configuration Update

The Haskell GitHub Actions project has been very active lately. One recent change documents a model cabal workflow with caching in the README. In this blog entry, I review this model configuration and consider how I can improve my own configuration. There is an issue for adding a realistic workflow for Stack, and I plan to review the model Stack configure after the issue is resolved.

The model configuration specifies a top-level permissions key that restricts the jobs from writing to the repository content. This is a good precaution, so I am adding it to my configuration.

permissions:
  contents: read

The model configuration sets a fail-fast flag to false, so that in-progress and queued jobs in the matrix of a failed job are not automatically canceled. The matrix tests different GHC versions, and most failures are indeed not correlated. I am adding this to my configuration.

jobs:
  {job_name}:
    ...
    strategy:
      fail-fast: false
      matrix:
        ...
    ...

The model configuration sets some environment variables with the versions of GHC and Cabal that are installed, set by running the installed executables. I have been using matrix.ghc and expect it to always match the installed GHC version, but querying the executables is a nice precaution. I decided to go ahead and set these environment variables, as well as use them instead of matrix.ghc.

The model configuration caches using keys that include the following variables. When there is a cache miss, cache is restored using keys that include all but the final variable.

  1. runner.os
  2. env.GHC_VERSION
  3. env.CABAL_VERSION
  4. hashFiles('**/plan.json')

My configuration caches using keys that include the following variables. When there is a cache miss, cache is restored using keys that include all but the final variable.

  1. runner.os
  2. env.CURR_MONTH
  3. matrix.ghc
  4. hashFiles('cabal.project.freeze')

There are a number of differences. First, my configuration limits cache to the current month. This is done so that cache sizes do not grow unbounded. Without this, old dependencies are kept in the cache for as long as the cache is used, and the size of the cache grows and grows as newer versions of dependencies are tested. By using CURR_MONTH, cache is always recreated each month. The downside is that the first CI run of each month runs without cache and is therefore slower. I will keep using the current month in cache keys.

Another difference is that the model configuration includes the Cabal version in cache keys. I will add this, as well as update my keys so that the cabal jobs (which test the software using the latest version of Cabal for many GHC versions) and the cabal-version jobs (which test the software using many versions of Cabal) can share cache when appropriate.

Another difference is that the model configuration includes a hash of the build plan in cache keys, while my configuration includes a hash of the project freeze file instead. Which is better? Investigating, I noticed that the project freeze file includes the Hackage index state timestamp. The file therefore changes whenever there is a new Hackage state, not just when there is a change in dependency versions! I will change my configuration to use build plans instead of project freeze files.

$ grep '^index-state:' cabal.project.freeze
index-state: hackage.haskell.org 2023-03-17T05:02:44Z

The model configuration ensures that only dependencies are cached, not build artifacts. This is done by explicitly wrapping just dependency building within explicit steps to restore and save the cache. I like this and am changing my configuration to do the same.

Testing my updated configuration, I got a puzzling error.

Error: cabal: unrecognized 'v2-configure' option `--enable-benchmarks"'

It took me a moment before I noticed that a double quote is included at the end of that option! The problem is with the way I was trying to configure Cabal arguments in the environment.

CABAL_OPTS="--enable-tests --enable-benchmarks"
if [ -f "cabal-${GHC_VERSION}.project" ] ; then
  CABAL_OPTS="--project-file=cabal-${GHC_VERSION}.project ${CABAL_OPTS}"
fi
echo "CABAL_OPTS=\"${CABAL_OPTS}\"" | tee -a "${GITHUB_ENV}"

The --enable-tests and --enable-benchmarks options are passed to all Cabal commands. The --project-file option is only passed when there is a cabal.project file for the current GHC version. A line like the following is appended to the environment configuration file.

CABAL_OPTS="--enable-tests --enable-benchmarks"

The usage is not quoted so that the string is split into arguments by space. It is correct shell usage, but it is not working with GitHub Actions, so I removed the explicit quotes in the configuration.

echo "CABAL_OPTS=${CABAL_OPTS}" | tee -a "${GITHUB_ENV}"

With this change, a line like the following is appended to the environment configuration file. The updated configuration now works without issue.

CABAL_OPTS=--enable-tests --enable-benchmarks

My configuration is significantly improved thanks to the new documentation. Thank you to the Haskell GitHub Actions team!

Author

Travis Cardwell

Published

Tags