on stack-cache-native help sign in
haskell.nix/lib/default.nix
191 lines 7.8 kB history raw
1{ stdenv, lib, haskellLib, runCommand, git, recurseIntoAttrs, srcOnly }:
3with haskellLib;
5{
6 # Within the package components, these are the attribute names of
7 # nested attrsets.
8 subComponentTypes = [
9 "sublibs"
10 "foreignlibs"
11 "exes"
12 "tests"
13 "benchmarks"
14 ];
16 foldrAttrVals = f: z: attrs:
17 lib.foldr (g: acc: g acc) z (lib.mapAttrsToList (_name: f) attrs);
19 foldComponents = tys: f: z: conf:
20 let
21 comps = conf.components or {};
22 # ensure that comps.library exists and is not null.
23 libComp = acc: if (comps.library or null) != null then f comps.library acc else acc;
24 subComps = acc:
25 lib.foldr
26 (ty: acc': foldrAttrVals f acc' (comps.${ty} or {}))
27 acc
28 tys;
29 in libComp (subComps z);
31 getAllComponents = foldComponents subComponentTypes (c: acc:
32 lib.optional c.buildable c ++ acc) [];
34 componentPrefix = {
35 # Are all of these right?
36 sublibs = "lib";
37 foreignlibs = "foreignlib";
38 exes = "exe";
39 tests = "test";
40 benchmarks = "bench";
41 };
43 applyComponents = f: config:
44 let
45 comps = config.components;
46 applyLibrary = cname: f { cname = config.package.identifier.name; ctype = "lib"; };
47 applySubComp = ctype: cname: f { inherit cname; ctype = componentPrefix.${ctype} or (throw "Missing component mapping for ${ctype}."); };
48 applyAllComp = f { cname = config.package.identifier.name; ctype = "all"; };
49 buildableAttrs = lib.filterAttrs (n: comp: comp.buildable or true);
50 libComp = if comps.library == null || !(comps.library.buildable or true)
51 then {}
52 else lib.mapAttrs applyLibrary (removeAttrs comps (subComponentTypes ++ [ "all" "setup" ]));
53 subComps = lib.mapAttrs
54 (ctype: attrs: lib.mapAttrs (applySubComp ctype) (buildableAttrs attrs))
55 (builtins.intersectAttrs (lib.genAttrs subComponentTypes (_: null)) comps);
56 allComp = { all = applyAllComp comps.all; };
57 in subComps // libComp // allComp;
59 isLibrary = componentId: componentId.ctype == "lib";
60 isAll = componentId: componentId.ctype == "all";
61 isExe = componentId: componentId.ctype == "exe";
62 isTest = componentId: componentId.ctype == "test";
63 isBenchmark = componentId: componentId.ctype == "bench";
64 isExecutableType = componentId:
65 isExe componentId
66 || isTest componentId
67 || isBenchmark componentId;
68 mayHaveExecutable = componentId:
69 isExecutableType componentId
70 || isAll componentId;
72 # Was there a reference to the package source in the `cabal.project` or `stack.yaml` file.
73 # This is used to make the default `packages` list for `shellFor`.
74 isLocalPackage = p: p.isLocal or false;
75 selectLocalPackages = ps: lib.filterAttrs (n: p: p != null && isLocalPackage p) ps;
77 # if it's a project package it has a src attribute set with an origSubDir attribute.
78 # project packages are a subset of localPackages
79 isProjectPackage = p: p ? src && p.src ? origSubDir;
80 selectProjectPackages = ps: lib.filterAttrs (n: p: p != null && isLocalPackage p && isProjectPackage p) ps;
82 # Format a componentId as it should appear as a target on the
83 # command line of the setup script.
84 componentTarget = componentId:
85 if componentId.ctype == "all" then ""
86 else "${componentId.ctype}:${componentId.cname}";
88 # Remove null or empty values from an attrset.
89 optionalHooks = lib.filterAttrs (_: hook: hook != null && hook != "");
91 # Avoid pkgs.callPackage for now. It does a lot of nonsense with OOP
92 # style programming that we should avoid until we know we want it.
94 # weakCallPackage: call a function or (importable expression)
95 # with scope + args.
96 #
97 # weakCallPackage scope f args
98 # will call f (scope // args)
99 #
100 # weakCallpackage scope ./path args
101 # will call the expression at ./path with (scope // args)
102 #
103 weakCallPackage = scope: f: args:
104 let f' = if lib.isFunction f then f else import f;
105 args' = (builtins.intersectAttrs (builtins.functionArgs f') scope) // args;
106 in f' args';
108 # Collect all (transitive) Haskell library dependencies of a
109 # component.
110 ## flatLibDepends :: Component -> [Package]
111 flatLibDepends = component:
112 let
113 # this is a minor improvement over the "cannot coerce set to string"
114 # error. It will now say:
115 #
116 # > The option `packages.Win32.package.identifier.name' is used but not defined.
117 #
118 # which indicates that the package.Win32 is missing and not defined.
119 getKey = x: if x ? "outPath" then "${x}" else (throw x.identifier.name);
120 makePairs = map (p: rec { key=getKey val; val=(p.components.library or p); });
121 closure = builtins.genericClosure {
122 startSet = makePairs component.depends;
123 operator = {val,...}: makePairs val.config.depends;
124 };
125 in map ({val,...}: val) closure;
128 # Extracts a selection of components from a Haskell package set.
129 #
130 # This can be used to filter out all test suites or benchmarks of
131 # your project, so that they can be built in Hydra.
132 #
133 # For example:
134 #
135 # tests = collectComponents "tests" (package: package.identifier.name == "mypackage") hsPkgs;
136 #
137 # Will result in moving:
138 # from: hsPkgs.mypackage.components.tests.unit-tests
139 # to: tests.mypackage.unit-tests
140 #
141 collectComponents = group: packageSel: haskellPackages:
142 let packageToComponents = name: package:
143 # look for the components with this group if there are any
144 let components = package.components.${group} or {};
145 # set recurseForDerivations unless it's a derivation itself (e.g. the "library" component) or an empty set
146 in if lib.isDerivation components || components == {}
147 then components
148 else recurseIntoAttrs components;
149 packageFilter = name: package: (package.isHaskell or false) && packageSel package;
150 filteredPkgs = lib.filterAttrs packageFilter haskellPackages;
151 # at this point we can filter out packages that don't have any of the given kind of component
152 packagesByComponent = lib.filterAttrs (_: components: components != {}) (lib.mapAttrs packageToComponents filteredPkgs);
153 in recurseIntoAttrs packagesByComponent;
155 # Equivalent to collectComponents with (_: true) as selection function.
156 # Useful for pre-filtered package-set.
157 #
158 # For example:
159 #
160 # myHaskellPackages = selectProjectPackages hsPkgs;
161 # myTests = collectComponents' "tests" myHaskellPackages;
162 collectComponents' = group: collectComponents group (_: true);
164 # Replacement for lib.cleanSourceWith that has a subDir argument.
165 inherit (import ./clean-source-with.nix { inherit lib; }) cleanSourceWith canCleanSource;
167 # Use cleanSourceWith to filter just the files needed for a particular
168 # component of a package
169 cleanCabalComponent = import ./clean-cabal-component.nix { inherit lib cleanSourceWith; };
171 # Clean git directory based on `git ls-files --recurse-submodules`
172 cleanGit = import ./clean-git.nix {
173 inherit lib runCommand git cleanSourceWith;
174 };
176 # Check a test component
177 check = import ./check.nix {
178 inherit stdenv lib haskellLib srcOnly;
179 };
181 # Use `isCrossHost` to identify when we are cross compiling and
182 # the code we are producing will not run on the build system
183 # without an emulator.
184 # In most cases we do not want to treat musl as a cross compiler.
185 # For instance when building ghc we want to include ghci.
186 isCrossHost = stdenv.hostPlatform != stdenv.buildPlatform
187 && !(stdenv.buildPlatform.isLinux && stdenv.hostPlatform.isMusl);
188 # This is the same as isCrossHost but for use when building ghc itself
189 isCrossTarget = stdenv.targetPlatform != stdenv.hostPlatform
190 && !(stdenv.hostPlatform.isLinux && stdenv.targetPlatform.isMusl);