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.
runner.os
env.GHC_VERSION
env.CABAL_VERSION
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.
runner.os
env.CURR_MONTH
matrix.ghc
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!