Implement a library for running weeder in haskell.nix projects

This commit is contained in:
Zhenya Vinogradov 2020-01-08 10:21:34 +03:00
parent d9843f63e1
commit 9867f52596
No known key found for this signature in database
GPG Key ID: 72DE8A2757FBA938
3 changed files with 152 additions and 0 deletions

74
README.md Normal file
View File

@ -0,0 +1,74 @@
A bunch of hacks making [weeder](https://github.com/ndmitchell/weeder) work for projects built with [haskell.nix](https://github.com/input-output-hk/haskell.nix/)
### Usage example
Suppose you have a project with three packages in a layout like this (note that package name and directory name may differ):
```
super-project
|-- stack.yaml
|-- super-package.cabal
|-- …
|-- prelude
|-- super-prelude.cabal
|-- …
|-- super-tool
|-- super-tool.cabal
|-- …
```
Import this library, providing nixpkgs package set:
```nix
weeder-hacks = import (builtins.fetchTarball https://github.com/serokell/haskell-nix-weeder/archive/master.tar.gz) { pkgs = import <nixpkgs> {}; };
```
In the call to `haskell-nix.stackProject` add options for each package to generate .dump-hi files used by weeder and save them to the build output:
```nix
hs-pkgs = haskell-nix.stackProject {
modules = [{
packages.super-package = {
# tell the compiler to generate '.dump-hi' files
package.ghcOptions = "-ddump-to-file -ddump-hi";
# save '.dump-hi' files to $out
postInstall = weeder-hacks.collect-dump-hi-files;
};
packages.super-prelude = {
package.ghcOptions = "-ddump-to-file -ddump-hi";
postInstall = weeder-hacks.collect-dump-hi-files;
};
packages.super-tool = {
package.ghcOptions = "-ddump-to-file -ddump-hi";
postInstall = weeder-hacks.collect-dump-hi-files;
};
}];
};
```
Invoke `weeder-script` derivation which generates a script for running weeder. Provide `hs-pkgs` and `local-packages` parameters:
```nix
weeder-script = weeder-hacks.weeder-script {
# package set returned by `haskell-nix.stackProject`
hs-pkgs = hs-pkgs;
# names and subdirectories of local packages
local-packages = [
{ name = "super-package"; subdirectory = "."; }
{ name = "super-prelude"; subdirectory = "prelude"; }
{ name = "super-tool"; subdirectory = "super-tool"; }
];
};
```
Run nix-build on the `weeder-script` derivation to generate the script, running the script will run weeder. Note that running the script will copy '.dump-hi' files locally, polluting your local directory.
``` shell
$ nix-build -A weeder-script -o run-weeder.sh # generates the script
$ ./run-weeder.sh # runs weeder
```

15
default.nix Normal file
View File

@ -0,0 +1,15 @@
{ pkgs # nixpkgs package set
}:
{
# script to collect all *.dump-hi files produced by the compiler
# into $out/dist-hi directory, preserving directory structure
collect-dump-hi-files = ''
mkdir $out/dist-hi
cd dist
find . -type f -name '*.dump-hi' -print0 | ${pkgs.cpio}/bin/cpio --pass-through --null --make-directories $out/dist-hi
'';
# generates a script for running weeder
weeder-script = { local-packages, hs-pkgs }: import ./weeder-script.nix { inherit pkgs local-packages hs-pkgs; };
}

63
weeder-script.nix Normal file
View File

@ -0,0 +1,63 @@
{ pkgs, # nixpkgs package set
local-packages, # list of local haskell packages in the project and their subdirectories
hs-pkgs # package set returned by `haskell-nix.stackProject`
}:
with rec {
# returns a list of all components (library + exes + tests + benchmarks) for a package
get-package-components = pkg: with pkgs.lib;
optional (pkg ? library) pkg.library
++ attrValues pkg.exes
++ attrValues pkg.tests
++ attrValues pkg.benchmarks;
# script code that collects 'dist-hi' directories from the listed components and puts them to ${dir}/dist-hi
get-package-dist-hi = components: dir: pkgs.lib.concatStringsSep "\n" (
[ "mkdir -p ${dir}/dist-hi" ]
++ builtins.map (cmp: ''
cp -r ${cmp}/dist-hi/. ${dir}/dist-hi/
chmod -R u+w ${dir}/dist-hi/ # make directories writeable because subsequent components may copy to the same directories
'') components
);
# a script that collects 'dist-hi' directories from all local packages
# and puts them into corresponding subdirectories
get-project-dist-hi = pkgs.lib.concatStringsSep "\n" (
map ({ name, subdirectory }:
get-package-dist-hi (get-package-components hs-pkgs.${name}.components) subdirectory
) local-packages
);
# local package list mimicking the output of `stack query locals`
local-packages-list = pkgs.writeText "locals" (builtins.toJSON (pkgs.lib.listToAttrs (
map ({ name, subdirectory }:
{ name = name; value = { path = subdirectory; }; }
) local-packages
)));
# fake stack binary to be used be weeder, which gives `stack query locals` output.
# real stack would also start fetching indexes and dependencies, we use this script to avoid that
fake-stack = pkgs.writeShellScriptBin "stack" ''
if [ "$1" != "query" -o "$2" != "locals" ]; then
echo "error: fake stack called with unexpected arguments:" "$@" > /dev/stderr
exit 1
fi
cat ${local-packages-list}
'';
# derivation which generates the final script for running weeder
weeder-script = pkgs.writeScript "run-weeder.sh" ''
# copy directories with '*.dump-hi' files to the package subdirectories
${get-project-dist-hi}
# add fake stack to the path, it will be called by weeder to get local package list
export PATH="${fake-stack}/bin:$PATH"
# run weeder
${pkgs.haskellPackages.weeder}/bin/weeder stack.yaml --dist dist-hi
'';
};
weeder-script