2

I have the following directory structure:

  • libgs - The basic Global Script abstract machine and standard library
  • gsi - A Global Script interpreter
  • gs2hs - A Global Script to Haskell compiler

Most, but not of course all, of this code is written in Global Script, and translated to Haskell by a Haskell program, hsgs2hs.

As such, the code in gsi and gs2hs both depend on the modules from libgs.

Because of somewhat sloppy code organization on my part, the compiler in gs2hs also depends on the front-end modules (parser, type-checker, etc.) from the gsi directory.

Legal aside: If it matters: my code is freely available online, but is not open-source, and its license does not permit redistribution through Hackage. End legal aside.

I can make this directory structure work by running

ghc --make -i../libgs gsi.hs -o gsi

in the gsi directory, and

ghc --make -i../libgs -i ../gsi gs2hs.hs -o gs2hs

in the gs2hs directory.

This has the problem that, every time I do both builds in sequence, GHC recompiles every single module in the libgs directory, and every shared module in the gsi directory, telling me 'flags changed'.

I figure, ok, I should probably be using packages for re-used code in Haskell, right? So I convert libgs to a package:

  • Add a libgs.cabal file to that directory, listing all the modules as exposed modules.
  • Add a libgs/install-all script that runs cabal install --lib --package-env $REPO_ROOT/package.env . and call it before building gsi and gs2hs
  • Add -package-env $REPO_ROOT/package.env to the GHC flags in the gsi and gs2hs directories.

No joy!

Now, any change to libgs at all - even just adding a new module to it - causes GHC to recompile every module in the gsi directory, telling me the fundamental module GSI.Value has changed. Even though it actually hasn't; the source code for that module and everything it depends on (which isn't much) is unchanged. Just the hash for the package it's coming from has changed.

How do I stop GHC from recompiling the world constantly, and get it to only recompile things when the result can actually be different?

Jonathan Cast
  • 4,569
  • 19
  • 34

1 Answers1

6

Add a libgs.cabal file to that directory, listing all the modules as exposed modules.

Ok, good.

Add a libgs/install-all script that runs cabal install --lib --package-env $REPO_ROOT/package.env

Don't do that. As a general rule, never use install --lib. It's usually better to let Cabal figure out when to install libraries. The easiest way is to put the executables in the package itself. You can have both a library: section in the .cabal file as well as arbitrarily many executable: gsi and executable: gs2hs ones.

Alternatively, you can keep libgs a package that doesn't care about executables, but have these in their own package each. Then you don't do any builds in the package directories themselves, but instead put a cabal.project file in your main src directory, saying

packages: ./libgs ./gsi ./gs2hs

Then run cabal new-build in that directory. It'll collectively store the require object files in its .dist-newstyle directory.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • Yeah, no, the second option you gave doesn't actually work, it still recompiles every module in the executable directories when you change anything at all in a library directory, whether it's necessary or not, which I'm pretty sure I was clear was the thing I wanted to fix. Guess putting everything in one source directory is the only way to go. That sucks. – Jonathan Cast Feb 23 '22 at 00:54