Easy incremental CI builds with GHC 9.4

Harry Garrood

We spend a huge amount of time recompiling the same code in CI over and over again.

What if we stopped doing that?

Ok, well, let's just cache our build products.

Wait, everything is still getting rebuilt. What?

Source file change checking (pre-9.4)

Action A.hs A.o B.hs B.o
Create A.hs and B.hs 0 - 0 -
First build 0 1 0 1
Another build (no-op) 0 1 0 1
Modify A.hs 2 1 0 1
Build again 2 3 0 1

Source file change checking in CI

Action A.hs A.o B.hs B.o
Check out repository 0 - 0 -
Restore cache 0 -1 0 -1
Build 0 1 0 1

Timestamps are pretty much meaningless for build systems.

What we actually care about is the file's contents.

As of GHC 9.4, source file change detection is based on hashes.

Source file change checking (as of 9.4)

Action A.hs A.hi B.hs B.hi
Create A.hs and B.hs cafe - beef -
First build cafe cafe beef beef
Another build (no-op) cafe cafe beef beef
Modify A.hs feed cafe beef beef
Build again feed feed beef beef

Source file change checking in CI (as of 9.4)

Action A.hs A.hi B.hs B.hi
Check out repository feed - beef -
Restore cache feed cafe beef beef
Build feed feed beef beef

As far as I am aware, timestamp-based change checking was the only remaining impediment to incremental CI builds with Haskell.

How do I use this?


- uses: actions/cache@v2
  with:
    path: |
      dist-newstyle
    key: dist-${{ runner.os }}-${{ matrix.ghc }}-${{ github.sha }}
    restore-keys: |
      dist-${{ runner.os }}-${{ matrix.ghc }}-
          

Don't settle for slow CI!