on 19052d8 help sign in
haskell.nix/lib/default.nix
355 lines 14 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 # For looking up the components attribute based on the cabal component type
66 prefixComponent =
67 lib.listToAttrs (
68 lib.mapAttrsToList (value: name: { inherit name value; })
69 componentPrefix);
71 applyComponents = f: config:
72 let
73 comps = config.components;
74 applyLibrary = cname: f { cname = config.package.identifier.name; ctype = "lib"; };
75 applySubComp = ctype: cname: f { inherit cname; ctype = componentPrefix.${ctype} or (throw "Missing component mapping for ${ctype}."); };
76 isBuildable = comp:
77 config.package.buildable # Set manually in a module (allows whole packages to be disabled)
78 && comp.buildable # Set based on `buildable` in `.cabal` files
79 && comp.planned; # Set if the component was in the `plan.json`
80 buildableAttrs = lib.filterAttrs (n: isBuildable);
81 libComp = if comps.library == null || !(isBuildable comps.library)
82 then {}
83 else lib.mapAttrs applyLibrary (removeAttrs comps (subComponentTypes ++ [ "setup" ]));
84 subComps = lib.mapAttrs
85 (ctype: attrs: lib.mapAttrs (applySubComp ctype) (buildableAttrs attrs))
86 (builtins.intersectAttrs (lib.genAttrs subComponentTypes (_: null)) comps);
87 in subComps // libComp;
89 isLibrary = componentId: componentId.ctype == "lib";
90 isExe = componentId: componentId.ctype == "exe";
91 isTest = componentId: componentId.ctype == "test";
92 isBenchmark = componentId: componentId.ctype == "bench";
93 isExecutableType = componentId:
94 isExe componentId
95 || isTest componentId
96 || isBenchmark componentId;
97 mayHaveExecutable = componentId:
98 isExecutableType componentId;
100 # Was there a reference to the package source in the `cabal.project` or `stack.yaml` file.
101 # This is used to make the default `packages` list for `shellFor`.
102 isLocalPackage = p: p.isLocal or false;
103 selectLocalPackages = ps: lib.filterAttrs (n: p: p != null && isLocalPackage p) ps;
105 # if it's a project package it has a src attribute set with an origSubDir attribute.
106 # project packages are a subset of localPackages
107 isProjectPackage = p: p.isProject or false;
108 selectProjectPackages = ps: lib.filterAttrs (n: p: p != null && isLocalPackage p && isProjectPackage p) ps;
110 # Format a componentId as it should appear as a target on the
111 # command line of the setup script.
112 componentTarget = componentId:"${componentId.ctype}:${componentId.cname}";
114 # Remove null or empty values from an attrset.
115 optionalHooks = lib.filterAttrs (_: hook: hook != null && hook != "");
117 # Avoid pkgs.callPackage for now. It does a lot of nonsense with OOP
118 # style programming that we should avoid until we know we want it.
120 # weakCallPackage: call a function or (importable expression)
121 # with scope + args.
122 #
123 # weakCallPackage scope f args
124 # will call f (scope // args)
125 #
126 # weakCallpackage scope ./path args
127 # will call the expression at ./path with (scope // args)
128 #
129 weakCallPackage = scope: f: args:
130 let f' = if lib.isFunction f then f else import f;
131 args' = (builtins.intersectAttrs (builtins.functionArgs f') scope) // args;
132 in f' args';
134 # Collect all (transitive) Haskell library dependencies of a
135 # component.
136 ## flatLibDepends :: Component -> [Package]
137 flatLibDepends = component:
138 let
139 makePairs = map (p: rec { key=val.name; val=(p.components.library or p); });
140 closure = builtins.genericClosure {
141 startSet = makePairs component.depends;
142 operator = {val,...}: makePairs val.config.depends;
143 };
144 in map ({val,...}: val) closure;
147 # Extracts a selection of components from a Haskell package set.
148 #
149 # This can be used to filter out all test suites or benchmarks of
150 # your project, so that they can be built in Hydra.
151 #
152 # For example:
153 #
154 # tests = collectComponents "tests" (package: package.identifier.name == "mypackage") hsPkgs;
155 #
156 # Will result in moving:
157 # from: hsPkgs.mypackage.components.tests.unit-tests
158 # to: tests.mypackage.unit-tests
159 #
160 collectComponents = group: packageSel: haskellPackages:
161 let packageToComponents = name: package:
162 # look for the components with this group if there are any
163 let components = package.components.${group} or {};
164 # set recurseForDerivations unless it's a derivation itself (e.g. the "library" component) or an empty set
165 in if lib.isDerivation components || components == {}
166 then components
167 else recurseIntoAttrs components;
168 packageFilter = name: package: (package.isHaskell or false) && packageSel package;
169 filteredPkgs = lib.filterAttrs packageFilter haskellPackages;
170 # at this point we can filter out packages that don't have any of the given kind of component
171 packagesByComponent = lib.filterAttrs (_: components: components != {}) (lib.mapAttrs packageToComponents filteredPkgs);
172 in recurseIntoAttrs packagesByComponent;
174 # Equivalent to collectComponents with (_: true) as selection function.
175 # Useful for pre-filtered package-set.
176 #
177 # For example:
178 #
179 # myHaskellPackages = selectProjectPackages hsPkgs;
180 # myTests = collectComponents' "tests" myHaskellPackages;
181 collectComponents' = group: collectComponents group (_: true);
183 # Extracts a selection of 'checks' from a Haskell package set.
184 #
185 # This can be used to collect all the test runs in your project, so that can be run in CI.
186 collectChecks = packageSel: haskellPackages:
187 let packageFilter = name: package: (package.isHaskell or false) && packageSel package;
188 in recurseIntoAttrs (lib.mapAttrs (_: p: p.checks) (lib.filterAttrs packageFilter haskellPackages));
190 # Equivalent to collectChecks with (_: true) as selection function.
191 # Useful for pre-filtered package-set.
192 collectChecks' = collectChecks (_: true);
194 # Replacement for lib.cleanSourceWith that has a subDir argument.
195 inherit (import ./clean-source-with.nix { inherit lib; }) cleanSourceWith canCleanSource;
197 # Use cleanSourceWith to filter just the files needed for a particular
198 # component of a package
199 cleanCabalComponent = import ./clean-cabal-component.nix { inherit lib cleanSourceWith canCleanSource; };
201 # Clean git directory based on `git ls-files --recurse-submodules`
202 cleanGit = import ./clean-git.nix {
203 inherit lib cleanSourceWith;
204 git = gitMinimal;
205 inherit (pkgs.evalPackages.buildPackages) runCommand;
206 };
208 # Some times it is handy to temporarily use a relative path between git
209 # repos. If the repos are individually cleaned this is not possible
210 # (since the cleaned version of one repo will never include the files
211 # of the other).
212 #
213 # `cleanGits` allows us to specify a root directory and any number of
214 # sub directories containing git repos.
215 #
216 # See docs/user-guide/clean-git.md for details of how to use this
217 # with `cabalProject`.
218 cleanGits = { src, gitDirs, name ? null, caller ? "cleanGits" }@args:
219 let
220 # List of filters, one for each git directory.
221 filters = builtins.map (subDir:
222 (pkgs.haskell-nix.haskellLib.cleanGit {
223 src = pkgs.haskell-nix.haskellLib.cleanSourceWith {
224 inherit src subDir;
225 };
226 }).filter) gitDirs;
227 in pkgs.haskell-nix.haskellLib.cleanSourceWith {
228 inherit src name caller;
229 # Keep files that match any of the filters
230 filter = path: type: pkgs.lib.any (f: f path type) filters;
231 };
233 # Check a test component
234 check = import ./check.nix {
235 inherit stdenv lib haskellLib srcOnly;
236 };
238 # Do coverage of a package
239 coverageReport = import ./cover.nix {
240 inherit stdenv lib haskellLib pkgs;
241 };
243 # Do coverage of a project
244 projectCoverageReport = import ./cover-project.nix {
245 inherit stdenv lib haskellLib pkgs;
246 };
248 # Use `isCrossHost` to identify when we are cross compiling and
249 # the code we are producing will not run on the build system
250 # without an emulator.
251 # In most cases we do not want to treat musl as a cross compiler.
252 # For instance when building ghc we want to include ghci.
253 isCrossHost = stdenv.hostPlatform != stdenv.buildPlatform
254 && !(stdenv.buildPlatform.isLinux && stdenv.hostPlatform.isMusl && stdenv.buildPlatform.isx86 && stdenv.hostPlatform.isx86);
255 # This is the same as isCrossHost but for use when building ghc itself
256 isCrossTarget = stdenv.targetPlatform != stdenv.hostPlatform
257 && !(stdenv.hostPlatform.isLinux && stdenv.targetPlatform.isMusl && stdenv.hostPlatform.isx86 && stdenv.targetPlatform.isx86);
258 # Native musl build-host-target combo
259 isNativeMusl = stdenv.hostPlatform.isMusl
260 && stdenv.buildPlatform == stdenv.hostPlatform
261 && stdenv.hostPlatform == stdenv.targetPlatform;
263 # Takes a version number or attr set of arguments (for cabalProject)
264 # and converts it to an attr set of arguments. This allows
265 # the use of "1.0.0.0" or { version = "1.0.0.0"; ... }
266 versionOrArgsToArgs = versionOrArgs:
267 if lib.isAttrs versionOrArgs
268 then versionOrArgs
269 else { version = versionOrArgs; };
271 # Find the resolver in the stack.yaml file and fetch it if a sha256 value is provided
272 fetchResolver = import ./fetch-resolver.nix {
273 inherit (pkgs.evalPackages) pkgs;
274 };
276 inherit (import ./cabal-project-parser.nix {
277 inherit pkgs;
278 }) parseIndexState parseBlock;
281 cabalToNixpkgsLicense = import ./spdx/cabal.nix pkgs;
283 # This function is like
284 # `src + (if subDir == "" then "" else "/" + subDir)`
285 # however when `includeSiblings` is set it maintains
286 # `src.origSrc` if there is one and instead adds to
287 # `src.origSubDir`. It uses `cleanSourceWith` when possible
288 # to keep `cleanSourceWith` support in the result.
289 appendSubDir = { src, subDir, includeSiblings ? false }:
290 if subDir == ""
291 then src
292 else
293 if haskellLib.canCleanSource src
294 then haskellLib.cleanSourceWith {
295 inherit src subDir includeSiblings;
296 }
297 else let name = src.name or "source" + "-" + __replaceStrings ["/"] ["-"] subDir;
298 in if includeSiblings
299 then rec {
300 # Keep `src.origSrc` so it can be used to allow references
301 # to other parts of that root.
302 inherit name;
303 origSrc = src.origSrc or src;
304 origSubDir = src.origSubDir or "" + "/" + subDir;
305 outPath = origSrc + origSubDir;
306 }
307 else {
308 # We are not going to need other parts of `origSrc` if there
309 # was one and we can ignore it
310 inherit name;
311 outPath = src + "/" + subDir;
312 };
314 # Givin a `src` split it into a `root` path (based on `src.origSrc` if
315 # present) and `subDir` (based on `src.origSubDir). The
316 # `root` will still use the `filter` of `src` if there was one.
317 rootAndSubDir = src: rec {
318 subDir = src.origSubDir or "";
319 root =
320 if subDir == ""
321 then src # if there was no subdir use the original src
322 else
323 # Use `cleanSourceWith` to make sure the `filter` is still used
324 if src ? origSrc && src ? filter
325 then haskellLib.cleanSourceWith {
326 name = src.name or "source" + "-root";
327 src = src.origSrc;
328 # Not passing src.origSubDir so that the result points `origSrc`
329 inherit (src) filter;
330 }
331 else src.origSrc or src; # If there is a subDir and origSrc (but no filter) use origSrc
332 };
334 # Run evalModules passing the project function argument (m) as a module along with
335 # the the a projectType module (../modules/cabal-project.nix or ../modules/stack-project.nix).
336 # The resulting config is then passed to the project function's implementation.
337 evalProjectModule = projectType: m: f: f
338 (lib.evalModules {
339 modules = (if builtins.isList m then m else [m]) ++ [
340 # Include ../modules/cabal-project.nix or ../modules/stack-project.nix
341 (import ../modules/project-common.nix)
342 (import projectType)
343 # Pass the pkgs to the modules
344 ({ config, lib, ... }: {
345 _module.args = {
346 inherit pkgs;
347 };
348 })
349 ];
350 }).config;
352 # Converts from a `compoent.depends` value to a library derivation.
353 # In the case of sublibs the `depends` value should already be the derivation.
354 dependToLib = d: d.components.library or d;