Recompilation issue with `stack test`

I’m using stack and have the following issue. It seems that every time I switch from stack build to stack test (and the other way) the full project is recompiled.

I understand the configuration flags are changing (from lib + exe to lib + exe + test), but is that normal or is there a workaround ?

1 Like

I think it’s normal, if the flags don’t change between library and tests the library DLLs are cached.

My problem seems to be that the flags change without my consent, building the test add a test flag to the library so there is no way (that I know) to cache the library between “normal” and “test”

What I often do is, instead of stack build, I run stack test --no-run-tests. That way all the code is compiled and type-checked, but there’s no running of tests. And doing stack test once you do want to run tests will just run the tests without having to compile the test code first. :+1:

3 Likes

My guess is that when its test suite component is included in the build of the project package, the project package has different dependencies, which is triggering the need for the rebuild.

If that is the case, try describing the package such that adding the test suite component to the build does not add dependencies to the package.

A related issue at the Stack repository is: `stack build; stack test` rebuilds everything twice · Issue #4977 · commercialhaskell/stack · GitHub.

The background to this is that Stack currently builds packages (made up of components) rather than components (of packages). The latter is known as component-based builds. The current sticking point for implementing component-based builds is that it degrades performance for ‘everyday’ use of Stack because of the overhead of all the calls to Cabal (the library) through its Setup executable.

5 Likes

This is exactly the problem described in the issue.

Apart from Hspec which is a dependency specific to the test-suite the dependency to the library, the dependencies of the library haven’t changed. However I realized that some compilation flags have changed.

This lead to the question how does stack decide to recompile is by checking the “configuration” flags (lib+exe+test) vs (lib) or by checking at the resulting ghc compilation/option flags.

Also does the compilation flag (ghc-options field) in the test suite applies to the test executable or to the library too (which could then explain the recompilation).

I think Hspec being\not being a dependency of the project package is a difference that will trigger the need for a rebuild. Try making it a dependency common to all components in the package decription (eg, if using package.yaml as the package description, add it to the top-level dependencies key.)

I don’t have an answer about the ghc-options field of a component of a project package to hand, but I’ll see if I can get you an answer. EDIT: Empirically, toggling -threaded as a GHC option (perhaps not the best choice of option to test this) for the test suit component did not trigger a rebuild of the project package. It is Cabal (the library) GHC that is deciding that a rebuild is not required. EDIT2: That is, Stack detects the change in the package description, and involves Cabal (the library). Cabal, in turn, passes things on to GHC.

EDIT3: In terms of the organisation of Stack’s work directories (which include artefacts from past builds of project packages), that is documented at Stack work directories - The Haskell Tool Stack. The project package work directory depends only on the platform and GHC version. The project work directory depends on that and other information, including information about immutable dependencies.

1 Like

Indeed, I looked at the stack code and find out that stack triggers a recompilation if the new dependencies are not a subset of the old.

and dependencies are, as you said per package not components (i.e. the unions of all the dependencies of all active component). This explains what seemed a random behavior stack build then stack test doesn’t trigger a recompilaion but stack test then stack build does. In the first case, hspec is added to the “package” dependencies and the so new.deps > old.deps so no recompilation. In the second case hspec disappear (new.deps < old.deps) triggering a recompilation.

However, when a file is changed stack build after stack test recompile everything. For some reasons (even though dependencies are ok) the configuration file in .stack-work/install/.../pkgdb/.*.conf has a new name (and a different ABI ???) ; the previous one can’t be found and this trigger a full recompilation.

Anyway, adding hspec (and all test specifyc dependencies) to top level dependencies seems to do the trick (even though not really elegant).

As it looks like pretty much everybody should encounter this problem I am suprised to not have found this workaround anywhere on the web.

3 Likes

I’ll see what I can add to Stack’s online documentation in this regard. EDIT: For now, I’ve added a FAQ.

1 Like

It seems that dependencies of the executable components interfere as well (they will be added to stack build deps but not stack test but I might me wrong). Maybe a new flag (which could be in stack.yaml and/or in ~/.stack/config.yaml) saying " use the deps of ALL components regardless " would help.

Not quite what you are after, but I am wondering about stack build --all, with --all as a synonym for --test --no-run-tests --bench --no-run-benchmarks. I would be reluctant to have a stack build-based command that ignored the package description (in the sense of treating a package as a dependency of a targetted component when it was not specified as a dependency in the description).

EDIT1: and/or change --no-run-tests and --no-run-benchmarks to --[no-]run-tests and --[no-]run benchmarks, so you could set them in a configuration file but override them at the command line. EDIT2: At the moment,

build:
  test: true
  test-arguments:
    no-run-tests: true
  bench: true
  benchmark-opts:
    no-run-benchmarks: true

does not ‘work’ because stack test reports:

Test running disabled by --no-run-tests flag.
2 Likes

Doesn’t --bench add some ghc flags so that for example things are compiled with -O2 ?

Maybe another way of seeing it is that the test not (new.deps Set.isSubsetOf old.deps) is just wrong.
It doesn’t matter if dependencies are have been added or removed; things which have been compiled will remain the same. What matters is if dependencies in both old and new have changed (different version) or not.

Modules requiring a new dependency to be added (which happen when new.deps > old.deps haven’t compiled anyway, there is therefore no need for recompilation, just normal compilation.

Having said that, now I know the problem the work around seem easy enough.

From Stack’s perspective --bench does not add GHC optimisation options to its call to Cabal (the library). I can’t see from Cabal’s online documentation that it increases the optimisation level either.

I need to change my setup to add -O2 to my benchmark then :wink:
In test development cycle it makes sense to compile fast (-O0) for quick feedback which is the exact opposite of benchmarks. Compiling with --bench --test might only be usefull for CI or equivalent (and in that case you might do want to run both as well).

I haven’t worked with stack for some time now but as far as I remember you can (in command) set custom .stack-work directory and use different ones for build and test so they don’t override their build artifacts. This way you can run either of them independently without recompilation.

2 Likes

You may be interested in the fix6643 branch version of Stack (if not using GHCup to manage Stack versions: stack upgrade --source-only --git --git-branch fix6643); see also Fix #6643 Respect `--no-run-tests`, `--no-run-benchmarks` when listing actions by mpilgrem · Pull Request #6645 · commercialhaskell/stack · GitHub.

The idea is that somebody with your use case could configure:

build:
  test: true
  test-arguments:
    no-run-tests: true
  bench: true
  benchmark-opts:
    no-run-benchmarks: true
notify-if-no-run-tests: false
notify-if-no-run-benchmarks: false

If you wanted to run the tests, you would command stack build --run-tests.

1 Like