on e94de40 help sign in
haskell.nix/lib/default.nix
276 lines 11 kB history raw
1{ pkgs, stdenv, lib, haskellLib, recurseIntoAttrs, srcOnly }:
4with haskellLib;
6let
7 # Why `final.evalPackages.buildPackages.gitMinimal`?
8 # Why not just final.evalPackages.gitMinimal?
9 #
10 # A problem arises when `evalPackages` is `buildPackages`.i
11 # As may be the case in a flake.
12 #
13 # It turns out `git` depends on `gdb` in a round about way:
14 # git -> openssh -> libfido2 -> systemd -> python libxml -> Cython -> gdb
15 # Somewhere in that chain there should perhaps be a `buildPackages` so
16 # that the `gdb` that is used is not the one for debugging code in
17 # the `final` (but instead the one for debugging code in
18 # `final.buildPackages`).
19 #
20 # Using `final.buildPackages.git` causes two problems:
21 #
22 # * Multiple versions of `git` (and that dependency chain
23 # to `gdb` are needed when cross compiling).
24 # * When `gdb` does not exist for `js`, so when cross
25 # compiling with ghcjs `final.buildPackages.git` fails
26 # to build at all.
27 inherit (pkgs.evalPackages.buildPackages) gitMinimal;
29in {
30 # Within the package components, these are the attribute names of
31 # nested attrsets.
32 subComponentTypes = [
33 "sublibs"
34 "foreignlibs"
35 "exes"
36 "tests"
37 "benchmarks"
38 ];
40 foldrAttrVals = f: z: attrs:
41 lib.foldr (g: acc: g acc) z (lib.mapAttrsToList (_name: f) attrs);
43 foldComponents = tys: f: z: conf:
44 let
45 comps = conf.components or {};
46 # ensure that comps.library exists and is not null.
47 libComp = acc: if (comps.library or null) != null then f comps.library acc else acc;
48 subComps = acc:
49 lib.foldr
50 (ty: acc': foldrAttrVals f acc' (comps.${ty} or {}))
51 acc
52 tys;
53 in libComp (subComps z);
55 getAllComponents = foldComponents subComponentTypes (c: acc: [c] ++ acc) [];
57 componentPrefix = {
58 sublibs = "lib";
59 foreignlibs = "flib";
60 exes = "exe";
61 tests = "test";
62 benchmarks = "bench";
63 };
65 applyComponents = f: config:
66 let
67 comps = config.components;
68 applyLibrary = cname: f { cname = config.package.identifier.name; ctype = "lib"; };
69 applySubComp = ctype: cname: f { inherit cname; ctype = componentPrefix.${ctype} or (throw "Missing component mapping for ${ctype}."); };
70 buildableAttrs = lib.filterAttrs (n: comp: comp.buildable or true);
71 libComp = if comps.library == null || !(comps.library.buildable or true)
72 then {}
73 else lib.mapAttrs applyLibrary (removeAttrs comps (subComponentTypes ++ [ "setup" ]));
74 subComps = lib.mapAttrs
75 (ctype: attrs: lib.mapAttrs (applySubComp ctype) (buildableAttrs attrs))
76 (builtins.intersectAttrs (lib.genAttrs subComponentTypes (_: null)) comps);
77 in subComps // libComp;
79 isLibrary = componentId: componentId.ctype == "lib";
80 isExe = componentId: componentId.ctype == "exe";
81 isTest = componentId: componentId.ctype == "test";
82 isBenchmark = componentId: componentId.ctype == "bench";
83 isExecutableType = componentId:
84 isExe componentId
85 || isTest componentId
86 || isBenchmark componentId;
87 mayHaveExecutable = componentId:
88 isExecutableType componentId;
90 # Was there a reference to the package source in the `cabal.project` or `stack.yaml` file.
91 # This is used to make the default `packages` list for `shellFor`.
92 isLocalPackage = p: p.isLocal or false;
93 selectLocalPackages = ps: lib.filterAttrs (n: p: p != null && isLocalPackage p) ps;
95 # if it's a project package it has a src attribute set with an origSubDir attribute.
96 # project packages are a subset of localPackages
97 isProjectPackage = p: p ? src && p.src ? origSubDir;
98 selectProjectPackages = ps: lib.filterAttrs (n: p: p != null && isLocalPackage p && isProjectPackage p) ps;
100 # Format a componentId as it should appear as a target on the
101 # command line of the setup script.
102 componentTarget = componentId:"${componentId.ctype}:${componentId.cname}";
104 # Remove null or empty values from an attrset.
105 optionalHooks = lib.filterAttrs (_: hook: hook != null && hook != "");
107 # Avoid pkgs.callPackage for now. It does a lot of nonsense with OOP
108 # style programming that we should avoid until we know we want it.
110 # weakCallPackage: call a function or (importable expression)
111 # with scope + args.
112 #
113 # weakCallPackage scope f args
114 # will call f (scope // args)
115 #
116 # weakCallpackage scope ./path args
117 # will call the expression at ./path with (scope // args)
118 #
119 weakCallPackage = scope: f: args:
120 let f' = if lib.isFunction f then f else import f;
121 args' = (builtins.intersectAttrs (builtins.functionArgs f') scope) // args;
122 in f' args';
124 # Collect all (transitive) Haskell library dependencies of a
125 # component.
126 ## flatLibDepends :: Component -> [Package]
127 flatLibDepends = component:
128 let
129 # this is a minor improvement over the "cannot coerce set to string"
130 # error. It will now say:
131 #
132 # > The option `packages.Win32.package.identifier.name' is used but not defined.
133 #
134 # which indicates that the package.Win32 is missing and not defined.
135 getKey = x: if x ? "outPath" then "${x}" else (throw x.identifier.name);
136 makePairs = map (p: rec { key=getKey val; val=(p.components.library or p); });
137 closure = builtins.genericClosure {
138 startSet = makePairs component.depends;
139 operator = {val,...}: makePairs val.config.depends;
140 };
141 in map ({val,...}: val) closure;
144 # Extracts a selection of components from a Haskell package set.
145 #
146 # This can be used to filter out all test suites or benchmarks of
147 # your project, so that they can be built in Hydra.
148 #
149 # For example:
150 #
151 # tests = collectComponents "tests" (package: package.identifier.name == "mypackage") hsPkgs;
152 #
153 # Will result in moving:
154 # from: hsPkgs.mypackage.components.tests.unit-tests
155 # to: tests.mypackage.unit-tests
156 #
157 collectComponents = group: packageSel: haskellPackages:
158 let packageToComponents = name: package:
159 # look for the components with this group if there are any
160 let components = package.components.${group} or {};
161 # set recurseForDerivations unless it's a derivation itself (e.g. the "library" component) or an empty set
162 in if lib.isDerivation components || components == {}
163 then components
164 else recurseIntoAttrs components;
165 packageFilter = name: package: (package.isHaskell or false) && packageSel package;
166 filteredPkgs = lib.filterAttrs packageFilter haskellPackages;
167 # at this point we can filter out packages that don't have any of the given kind of component
168 packagesByComponent = lib.filterAttrs (_: components: components != {}) (lib.mapAttrs packageToComponents filteredPkgs);
169 in recurseIntoAttrs packagesByComponent;
171 # Equivalent to collectComponents with (_: true) as selection function.
172 # Useful for pre-filtered package-set.
173 #
174 # For example:
175 #
176 # myHaskellPackages = selectProjectPackages hsPkgs;
177 # myTests = collectComponents' "tests" myHaskellPackages;
178 collectComponents' = group: collectComponents group (_: true);
180 # Extracts a selection of 'checks' from a Haskell package set.
181 #
182 # This can be used to collect all the test runs in your project, so that can be run in CI.
183 collectChecks = packageSel: haskellPackages:
184 let packageFilter = name: package: (package.isHaskell or false) && packageSel package;
185 in recurseIntoAttrs (lib.mapAttrs (_: p: p.checks) (lib.filterAttrs packageFilter haskellPackages));
187 # Equivalent to collectChecks with (_: true) as selection function.
188 # Useful for pre-filtered package-set.
189 collectChecks' = collectChecks (_: true);
191 # Replacement for lib.cleanSourceWith that has a subDir argument.
192 inherit (import ./clean-source-with.nix { inherit lib; }) cleanSourceWith canCleanSource;
194 # Use cleanSourceWith to filter just the files needed for a particular
195 # component of a package
196 cleanCabalComponent = import ./clean-cabal-component.nix { inherit lib cleanSourceWith; };
198 # Clean git directory based on `git ls-files --recurse-submodules`
199 cleanGit = import ./clean-git.nix {
200 inherit lib cleanSourceWith;
201 git = gitMinimal;
202 inherit (pkgs.evalPackages) runCommand;
203 };
205 # Some times it is handy to temporarily use a relative path between git
206 # repos. If the repos are individually cleaned this is not possible
207 # (since the cleaned version of one repo will never include the files
208 # of the other).
209 #
210 # `cleanGits` allows us to specify a root directory and any number of
211 # sub directories containing git repos.
212 #
213 # See docs/user-guide/clean-git.md for details of how to use this
214 # with `cabalProject`.
215 cleanGits = { src, gitDirs, name ? null, caller ? "cleanGits" }@args:
216 let
217 # List of filters, one for each git directory.
218 filters = builtins.map (subDir:
219 (pkgs.haskell-nix.haskellLib.cleanGit {
220 src = pkgs.haskell-nix.haskellLib.cleanSourceWith {
221 inherit src subDir;
222 };
223 }).filter) gitDirs;
224 in pkgs.haskell-nix.haskellLib.cleanSourceWith {
225 inherit src name caller;
226 # Keep files that match any of the filters
227 filter = path: type: pkgs.lib.any (f: f path type) filters;
228 };
230 # Check a test component
231 check = import ./check.nix {
232 inherit stdenv lib haskellLib srcOnly;
233 };
235 # Do coverage of a package
236 coverageReport = import ./cover.nix {
237 inherit stdenv lib haskellLib pkgs;
238 };
240 # Do coverage of a project
241 projectCoverageReport = import ./cover-project.nix {
242 inherit stdenv lib haskellLib pkgs;
243 };
245 # Use `isCrossHost` to identify when we are cross compiling and
246 # the code we are producing will not run on the build system
247 # without an emulator.
248 # In most cases we do not want to treat musl as a cross compiler.
249 # For instance when building ghc we want to include ghci.
250 isCrossHost = stdenv.hostPlatform != stdenv.buildPlatform
251 && !(stdenv.buildPlatform.isLinux && stdenv.hostPlatform.isMusl && stdenv.buildPlatform.isx86 && stdenv.hostPlatform.isx86);
252 # This is the same as isCrossHost but for use when building ghc itself
253 isCrossTarget = stdenv.targetPlatform != stdenv.hostPlatform
254 && !(stdenv.hostPlatform.isLinux && stdenv.targetPlatform.isMusl && stdenv.hostPlatform.isx86 && stdenv.targetPlatform.isx86);
255 # Native musl build-host-target combo
256 isNativeMusl = stdenv.hostPlatform.isMusl
257 && stdenv.buildPlatform == stdenv.hostPlatform
258 && stdenv.hostPlatform == stdenv.targetPlatform;
260 # Takes a version number or attr set of arguments (for cabalProject)
261 # and converts it to an attr set of arguments. This allows
262 # the use of "1.0.0.0" or { version = "1.0.0.0"; ... }
263 versionOrArgsToArgs = versionOrArgs:
264 if lib.isAttrs versionOrArgs
265 then versionOrArgs
266 else { version = versionOrArgs; };
268 # Find the resolver in the stack.yaml file and fetch it if a sha256 value is provided
269 fetchResolver = import ./fetch-resolver.nix {
270 inherit (pkgs.evalPackages) pkgs;
271 };
273 inherit (import ./cabal-project-parser.nix {
274 inherit pkgs;
275 }) parseIndexState parseBlock;