I am having difficulty with GHC’s recompilation checker, which difficulty has its origins in a Stack issue. I thought I would document it here, in case anyone had any advice, or I have got the the wrong end of the stick.
I think the learning is: if you have a project where one local/mutable package depends on another, you need to either:
- clear past local build artefacts (eg with
stack purge
); or - turn off the recompilation checker for all local packages, eg with Stack project configuration:
ghc-options:
$locals: -fforce-recomp
or the recompilation checker may result in odd results in ways that are difficult to predict.
My test example is set out at GitHub - mpilgrem/ghc-recomp-test: Test of effect of GHC's recompilation checker. It is a simple two-package project (packageA
, packageB
). The packageA
library exposes module LibA
:
module LibA where
message :: IO ()
message = putStrLn "Message #1"
The packageB
library depends on packageA
and exposes module LibB
:
module LibB (message) where
import LibA
packageB
also provides executable progB
, which depends on the packageB
library:
module Main where
import LibB
main :: IO ()
main = message
Stack’s project configuration is straightforward:
resolver: lts-21.4 # GHC 9.4.5
packages:
- packageA
- packageB
If you build without optimisation and then run the executable:
> stack build --ghc-options -O0 --exec progB
... build output ...
Message #1
If you then change the source code in LibA.hs
to change the message to, say, "Message #2"
, and do the same again, something unexpected happens (despite Stack asking for all the things affected by the change to be rebuilt):
> stack build --ghc-options -O0 --exec progB
... build output ...
Message #1
This unexpected result ("Message #1"
) does not occur if you build with Cabal’s default GHC optimisisation (-O
). You get the expected "Message #2"
.
The unexpected result is because, without optimisation, GHC’s LibA.hi
interface file does not change when that part of the source code changes, and so the ABI hash of the LibA
module is the same. (With optimisation, the content of LibA.hi
changes with that part of the source code, and the ABI hash changes.)
If you ask GHC to be verbose and informative (extracts only) …
> stack build --ghc-options "-O0 -v -ddump-hi-diffs" --exec progB
...
packageB> compile: input file src\LibB.hs
packageB> *** Checking old interface for LibB (use -ddump-hi-diffs for more details):
packageB> Considering whether compilation is required for LibB:
...
packageB> Checking interface for module LibA packageA-0.1.0.0-DHArZEtuWXUA5Cw1WZLOMV
packageB> Module fingerprint unchanged
packageB> [1 of 1] Skipping LibB
....
Message #1
… you can see GHC deciding to skip compiling LibB
, because the ‘module fingerprint’ of LibA
is unchanged. The fingerprint refers to the ABI hash. This can be seen in the corresponding output with optimisation:
> stack build --ghc-options "-v -ddump-hi-diffs" --exec progB
...
packageB> compile: input file src\LibB.hs
packageB> *** Checking old interface for LibB (use -ddump-hi-diffs for more details):
packageB> Considering whether compilation is required for LibB:
...
packageB> Checking interface for module LibA packageA-0.1.0.0-DHArZEtuWXUA5Cw1WZLOMV
packageB> Module fingerprint has changed 56d9247b6388186e749be6d9dd24e909 -> 585094af0aff42c5ce46c89eb1b01b37
packageB> [1 of 1] Compiling LibB [LibA changed]
...
Message #2
(The DHArZEtuWXUA5Cw1WZLOMV
is the ‘installed package ID’ chosen for packageA-0.1.0.0
by Cabal (the library). It also does not change when the source code of LibA.hs
changes.)
According to the output of GHC’s --show-iface
mode, LibA.hi
records that the source code has changed (via a different src_hash
). With "Message #1"
you have (extracts):
❯ stack build --ghc-options -O0 --exec "ghc --show-iface D:\Users\mike\Code\GitHub\ghc-recomp-test\packageA\.stack-work\dist\c3556505\build\LibA.hi"
...
interface LibA 9045
interface hash: 6270e3f4dda3678774feb1579a397a8e
ABI hash: 22af1d8c76150daffaa01a4c79158904
...
src_hash: e6301e8ba0fd9165e1c30aee5f55d4ac
...
and with "Message #2"
you have (extracts):
❯ stack build --ghc-options -O0 --exec "ghc --show-iface D:\Users\mike\Code\GitHub\ghc-recomp-test\packageA\.stack-work\dist\c3556505\build\LibA.hi"
...
interface LibA 9045
interface hash: 11cce6383dd95d775e45377c8d52fc40
ABI hash: 22af1d8c76150daffaa01a4c79158904
...
src_hash: c7b2150949b29027b7581ddca94687a0
...
I find it a little odd that GHC chooses not to compile LibB.hs
when it knows (presumably) that the source code of LibA
has changed (even if its ABI hash has not). I can see that not all source code changes may be substantive, but I would have thought it safer to assume that if the source code had changed, something important could be different.