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!