A software engineer website

Nix: optimizing Haskell build size

Gautier DI FOLCO October 08, 2023 [dev] #haskell #nix #build #CI #optimization

Nearly three months ago I wrote a log about reducing Docker size, mainly by relying on static builds.

While perfectly relevant in this context, when I introduced Continuous Integration (CI) at my work (which was building with a vanilla callCabal2nix), the CI server disk filled-up in few hours.

For each build:

After a quick look at the size per files extension, we got:

Let's go for the most obvious:

We can wrap our final executable with:

exeOnly = b:
  with pkgs.haskell.lib;
  enableSharedExecutables (enableSharedLibraries
    (disableStaticLibraries
      (disableExecutableProfiling (disableLibraryProfiling b))));

Let's see the results:

Note: the build time drop comes mainly from the profiling support drop

Let's have look at the size per files extension, we got:

Note: the remaining a file seems to come from a previous derivation.

That's a good first step, but actually, I only intend to run it, not to use it as the input of another build, let's remove files manually:

exeOnly = b:
  with pkgs.haskell.lib;
  overrideCabal (enableSharedExecutables (enableSharedLibraries
    (disableStaticLibraries
      (disableExecutableProfiling (disableLibraryProfiling b)))))
  (drv: {
    postFixup = drv.postFixup or "" + ''
      ${pkgs.findutils}/bin/find $out -name '*.a' -exec rm \{\} \+
      ${pkgs.findutils}/bin/find $out -name '*.hi' -exec rm \{\} \+
    '';
  });

And the final results are:

Note: I guess the 10 seconds increase is due to find

Finally we only have the necessary files:

To sum-up: