Files
haskell.nix/lib/default.nix
T
Hamish Mackenzie 0366537651 Use new cleanSourceWith to filter component source (#209)
When trying to build haskell packages from local source using nix (
source code you have used `git clone` to retrieve and have modified)
it is often annoying that small changes can trigger a lot of components
to be built unnecessarily.  For instance if you change the code for
a test in a package it will often trigger builds of the library and
all its dependencies.

To avoid this problem we could manually set up `cleanSourceWith` calls
in nix to filter out files that are not need, but to do this every
component would be difficult and error prone.

This change automates this process using the information in the
packages `.cabal` file to filter the source of each component
automatically  when using `callCabalPlanToNix` (we should be able to
add it to `callStackToNix` too).

## How does it work?

* A new version on `cleanSourceWith` is used that adds a `subDir`
  argument.  It allows descending into a subdirectory to compose with
  cleaning the source.

* A new `--full` argument is passed to `plan-to-nix` by
  `callCabalPlanToNix` telling it to output extra information about
  the cabal packages in the plan (directories and filenames).

* `callCabalProjectToNix` uses the new `subDir` argument of
  `cleanSourceWith` when setting the packageSrc.

* `comp-builder.nix` and `setup-builder.nix` use a new
  `cleanCabalComponent` function to further clean the packageSrc
  using the new information in the `package` and `component`.

## hpack wildcards
`package.yaml` files can include wild cards and these are not handled
yet (they do not map to anything in the `.cabal` file and so are lost).
They will need to be added manually to the appropriate field
(eg. `packages.xyz.package.dataFiles`)

If your hpack file does not contain wildcards or if you are willing to
add the wildcards entries manually in nix it is still useful to
enable cleaning on hpack.

For example:

```
packages.ghc-toolkit.package.cleanHpack = true;
packages.ghc-toolkit.components.library.extraSrcFiles = [
  "genapply/**/**"
  "boot-libs/**/**"
  "ghc-libdir/**/**"
  ];
```
2019-08-26 12:27:47 +02:00

123 lines
4.4 KiB
Nix

{ lib, haskellLib }:
with haskellLib;
{
# Within the package components, these are the attribute names of
# nested attrsets.
subComponentTypes = [
"sublibs"
"foreignlibs"
"exes"
"tests"
"benchmarks"
];
foldrAttrVals = f: z: attrs:
lib.foldr (g: acc: g acc) z (lib.mapAttrsToList (_name: f) attrs);
foldComponents = tys: f: z: conf:
let
comps = conf.components or {};
# ensure that comps.library exists and is not null.
libComp = acc: if (comps.library or null) != null then f comps.library acc else acc;
subComps = acc:
lib.foldr
(ty: acc': foldrAttrVals f acc' (comps.${ty} or {}))
acc
tys;
in libComp (subComps z);
getAllComponents = foldComponents subComponentTypes (c: acc: [c] ++ acc) [];
componentPrefix = {
# Are all of these right?
sublibs = "lib";
foreignlibs = "foreignlib";
exes = "exe";
tests = "test";
benchmarks = "bench";
};
applyComponents = f: config:
let
comps = config.components;
applyLibrary = cname: f { cname = config.package.identifier.name; ctype = "lib"; };
applySubComp = ctype: cname: f { inherit cname; ctype = componentPrefix.${ctype} or (throw "Missing component mapping for ${ctype}."); };
applyAllComp = f { cname = config.package.identifier.name; ctype = "all"; };
libComp = if comps.library == null then {} else lib.mapAttrs applyLibrary (removeAttrs comps (subComponentTypes ++ [ "all" ]));
subComps = lib.mapAttrs
(ctype: lib.mapAttrs (applySubComp ctype))
(builtins.intersectAttrs (lib.genAttrs subComponentTypes (_: null)) comps);
allComp = { all = applyAllComp comps.all; };
in subComps // libComp // allComp;
isLibrary = componentId: componentId.ctype == "lib";
isAll = componentId: componentId.ctype == "all";
isTest = componentId: componentId.ctype == "test";
isBenchmark = componentId: componentId.ctype == "bench";
# Format a componentId as it should appear as a target on the
# command line of the setup script.
componentTarget = componentId:
if componentId.ctype == "all" then ""
else "${componentId.ctype}:${componentId.cname}";
# Remove null or empty values from an attrset.
optionalHooks = lib.filterAttrs (_: hook: hook != null && hook != "");
# Avoid pkgs.callPackage for now. It does a lot of nonsense with OOP
# style programming that we should avoid until we know we want it.
# weakCallPackage: call a function or (importable expression)
# with scope + args.
#
# weakCallPackage scope f args
# will call f (scope // args)
#
# weakCallpackage scope ./path args
# will call the expression at ./path with (scope // args)
#
weakCallPackage = scope: f: args:
let f' = if lib.isFunction f then f else import f;
args' = (builtins.intersectAttrs (builtins.functionArgs f') scope) // args;
in f' args';
# Collect all (transitive) Haskell library dependencies of a
# component.
## flatLibDepends :: Component -> [Package]
flatLibDepends = component:
let
makePairs = map (p: rec { key="${val}"; val=(p.components.library or p); });
closure = builtins.genericClosure {
startSet = makePairs component.depends;
operator = {val,...}: makePairs val.config.depends;
};
in map ({val,...}: val) closure;
# Extracts a selection of components from a Haskell package set.
#
# This can be used to filter out all test suites or benchmarks of
# your project, so that they can be built in Hydra.
#
# For example:
#
# tests = collectComponents "tests" (package: package.identifier.name == "mypackage") hsPkgs;
#
# Will result in moving:
# from: hsPkgs.mypackage.components.tests.unit-tests
# to: tests.mypackage.unit-tests
#
collectComponents = group: packageSel: haskellPackages:
(mapAttrs (_: package: package.components.${group} // { recurseForDerivations = true; })
(filterAttrs (name: package: (package.isHaskell or false) && packageSel package) haskellPackages))
// { recurseForDerivations = true; };
# Replacement for lib.cleanSourceWith that has a subDir argument.
inherit (import ./clean-source-with.nix { inherit lib; }) cleanSourceWith canCleanSource;
# Use cleanSourceWith to filter just the files needed for a particular
# component of a package
cleanCabalComponent = import ./clean-cabal-component.nix { inherit lib cleanSourceWith; };
}