6

I understand that cabal will recompile a module if the interface of any of its dependencies has changed. It seems that this simple rule does not hold if the module contains Template Haskell. In that case, even just adding a trailing newline character to a file in the module's (transitive) dependencies will cause the cabal to recompile the file.

Minimal example:

file: Foo.hs

module Foo where
foo = "foo"

file: FooTH.hs

{-# LANGUAGE TemplateHaskell #-}
module FooTH where
import Data.Bifunctor.TH
import Foo

data FooPair a b = FooPair a b
$(deriveBifunctor ''FooPair)

file: MCVE.cabal

name:                MCVE
version:             0.1.0.0
synopsis:            MCVE
license:             MIT
license-file:        LICENSE
author:              tarleb
maintainer:          tarleb@example.com
build-type:          Simple
extra-source-files:  CHANGELOG.md
cabal-version:       >=1.10

library
  exposed-modules:     Foo
                     , FooTH
  build-depends:       base >=4.8 && <4.13
                     , bifunctors
  default-language:    Haskell2010

Adding a newline to Foo.hs, e.g. by running echo "\n" >> Foo.hs, will cause recompilation of module FooTH. This ceases to happen if the TH line in FooTH is commented out.

What is the reason for this, and is there a way to avoid this unnecessary recompilation?

tarleb
  • 19,863
  • 4
  • 51
  • 80
  • 2
    I think it is caused by GHC rather then by Cabal. If I am not wrong, Cabal executes GHC on any modified file. This means only GHC truly knows if significant changes have been made to the file, Cabal is not a compiler and so it doesn't know any better then to check the "modified" flag on the filesystem. I would guess there is some enforced recompilation rule for TH code in GHC. Will try to find something more. – Strong will Nov 03 '18 at 22:37

1 Answers1

1

There's a feature in Template Haskell called addDependentFile, which adds metadata to the .hi file indicating that the source file in question depends on another file as well. To my knowledge, Cabal will always ask GHC to try to build, though it may have more intelligent logic. Stack tries to bypass that process, and has logic to parse the addDependentFile information out.

Michael Snoyman
  • 31,100
  • 3
  • 48
  • 77
  • Interesting, thank you. However, the issue persists when using stack to build. I thought stack is using cabal under the hood, is that wrong? – tarleb Nov 04 '18 at 21:03
  • I haven't seen a description of a problem in this question, I thought you were just asking for information. – Michael Snoyman Nov 05 '18 at 05:12
  • You are right, the issue got a bit hidden. The problem is that TH files get recompiled needlessly sometimes, thereby slowing down the re-build. But I'd indeed already be happy to understand what exactly is happening. – tarleb Nov 05 '18 at 05:57
  • 2
    Doesn't sound needless to me. It seems like you've determined that, semantically, these two versions of the file are the same (with or without a newline). However, that's not always the case; the `file-embed` library would generate two different `ByteString`s. There's no way for GHC, Cabal, Stack, or any other build tool I can think of to determine what is or is not a semantically meaningful change, and therefore _must_ recompile in all cases. – Michael Snoyman Nov 05 '18 at 06:13
  • Ah, that makes sense! Thank you for clarifying! – tarleb Nov 05 '18 at 06:19