/
default.nix
358 lines (318 loc) · 14.2 KB
/
default.nix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
{ pkgs, stdenv, lib, haskellLib, recurseIntoAttrs, srcOnly }:
with haskellLib;
let
# Why `final.evalPackages.buildPackages.gitMinimal`?
# Why not just final.evalPackages.gitMinimal?
#
# A problem arises when `evalPackages` is `buildPackages`.i
# As may be the case in a flake.
#
# It turns out `git` depends on `gdb` in a round about way:
# git -> openssh -> libfido2 -> systemd -> python libxml -> Cython -> gdb
# Somewhere in that chain there should perhaps be a `buildPackages` so
# that the `gdb` that is used is not the one for debugging code in
# the `final` (but instead the one for debugging code in
# `final.buildPackages`).
#
# Using `final.buildPackages.git` causes two problems:
#
# * Multiple versions of `git` (and that dependency chain
# to `gdb` are needed when cross compiling).
# * When `gdb` does not exist for `js`, so when cross
# compiling with ghcjs `final.buildPackages.git` fails
# to build at all.
inherit (pkgs.evalPackages.buildPackages) gitMinimal;
in {
# 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 = {
sublibs = "lib";
foreignlibs = "flib";
exes = "exe";
tests = "test";
benchmarks = "bench";
};
# For looking up the components attribute based on the cabal component type
prefixComponent =
lib.listToAttrs (
lib.mapAttrsToList (value: name: { inherit name value; })
componentPrefix);
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}."); };
buildableAttrs = lib.filterAttrs (n: comp: comp.buildable or true);
libComp = if comps.library == null || !(comps.library.buildable or true)
then {}
else lib.mapAttrs applyLibrary (removeAttrs comps (subComponentTypes ++ [ "setup" ]));
subComps = lib.mapAttrs
(ctype: attrs: lib.mapAttrs (applySubComp ctype) (buildableAttrs attrs))
(builtins.intersectAttrs (lib.genAttrs subComponentTypes (_: null)) comps);
in subComps // libComp;
isLibrary = componentId: componentId.ctype == "lib";
isExe = componentId: componentId.ctype == "exe";
isTest = componentId: componentId.ctype == "test";
isBenchmark = componentId: componentId.ctype == "bench";
isExecutableType = componentId:
isExe componentId
|| isTest componentId
|| isBenchmark componentId;
mayHaveExecutable = componentId:
isExecutableType componentId;
# Was there a reference to the package source in the `cabal.project` or `stack.yaml` file.
# This is used to make the default `packages` list for `shellFor`.
isLocalPackage = p: p.isLocal or false;
selectLocalPackages = ps: lib.filterAttrs (n: p: p != null && isLocalPackage p) ps;
# if it's a project package it has a src attribute set with an origSubDir attribute.
# project packages are a subset of localPackages
isProjectPackage = p: p.isProject or false;
selectProjectPackages = ps: lib.filterAttrs (n: p: p != null && isLocalPackage p && isProjectPackage p) ps;
# Format a componentId as it should appear as a target on the
# command line of the setup script.
componentTarget = componentId:"${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
# this is a minor improvement over the "cannot coerce set to string"
# error. It will now say:
#
# > The option `packages.Win32.package.identifier.name' is used but not defined.
#
# which indicates that the package.Win32 is missing and not defined.
getKey = x: if x ? "outPath" then "${x}" else (throw x.identifier.name);
makePairs = map (p: rec { key=getKey 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:
let packageToComponents = name: package:
# look for the components with this group if there are any
let components = package.components.${group} or {};
# set recurseForDerivations unless it's a derivation itself (e.g. the "library" component) or an empty set
in if lib.isDerivation components || components == {}
then components
else recurseIntoAttrs components;
packageFilter = name: package: (package.isHaskell or false) && packageSel package;
filteredPkgs = lib.filterAttrs packageFilter haskellPackages;
# at this point we can filter out packages that don't have any of the given kind of component
packagesByComponent = lib.filterAttrs (_: components: components != {}) (lib.mapAttrs packageToComponents filteredPkgs);
in recurseIntoAttrs packagesByComponent;
# Equivalent to collectComponents with (_: true) as selection function.
# Useful for pre-filtered package-set.
#
# For example:
#
# myHaskellPackages = selectProjectPackages hsPkgs;
# myTests = collectComponents' "tests" myHaskellPackages;
collectComponents' = group: collectComponents group (_: true);
# Extracts a selection of 'checks' from a Haskell package set.
#
# This can be used to collect all the test runs in your project, so that can be run in CI.
collectChecks = packageSel: haskellPackages:
let packageFilter = name: package: (package.isHaskell or false) && packageSel package;
in recurseIntoAttrs (lib.mapAttrs (_: p: p.checks) (lib.filterAttrs packageFilter haskellPackages));
# Equivalent to collectChecks with (_: true) as selection function.
# Useful for pre-filtered package-set.
collectChecks' = collectChecks (_: 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 canCleanSource; };
# Clean git directory based on `git ls-files --recurse-submodules`
cleanGit = import ./clean-git.nix {
inherit lib cleanSourceWith;
git = gitMinimal;
inherit (pkgs.evalPackages) runCommand;
};
# Some times it is handy to temporarily use a relative path between git
# repos. If the repos are individually cleaned this is not possible
# (since the cleaned version of one repo will never include the files
# of the other).
#
# `cleanGits` allows us to specify a root directory and any number of
# sub directories containing git repos.
#
# See docs/user-guide/clean-git.md for details of how to use this
# with `cabalProject`.
cleanGits = { src, gitDirs, name ? null, caller ? "cleanGits" }@args:
let
# List of filters, one for each git directory.
filters = builtins.map (subDir:
(pkgs.haskell-nix.haskellLib.cleanGit {
src = pkgs.haskell-nix.haskellLib.cleanSourceWith {
inherit src subDir;
};
}).filter) gitDirs;
in pkgs.haskell-nix.haskellLib.cleanSourceWith {
inherit src name caller;
# Keep files that match any of the filters
filter = path: type: pkgs.lib.any (f: f path type) filters;
};
# Check a test component
check = import ./check.nix {
inherit stdenv lib haskellLib srcOnly;
};
# Do coverage of a package
coverageReport = import ./cover.nix {
inherit stdenv lib haskellLib pkgs;
};
# Do coverage of a project
projectCoverageReport = import ./cover-project.nix {
inherit stdenv lib haskellLib pkgs;
};
# Use `isCrossHost` to identify when we are cross compiling and
# the code we are producing will not run on the build system
# without an emulator.
# In most cases we do not want to treat musl as a cross compiler.
# For instance when building ghc we want to include ghci.
isCrossHost = stdenv.hostPlatform != stdenv.buildPlatform
&& !(stdenv.buildPlatform.isLinux && stdenv.hostPlatform.isMusl && stdenv.buildPlatform.isx86 && stdenv.hostPlatform.isx86);
# This is the same as isCrossHost but for use when building ghc itself
isCrossTarget = stdenv.targetPlatform != stdenv.hostPlatform
&& !(stdenv.hostPlatform.isLinux && stdenv.targetPlatform.isMusl && stdenv.hostPlatform.isx86 && stdenv.targetPlatform.isx86);
# Native musl build-host-target combo
isNativeMusl = stdenv.hostPlatform.isMusl
&& stdenv.buildPlatform == stdenv.hostPlatform
&& stdenv.hostPlatform == stdenv.targetPlatform;
# Takes a version number or attr set of arguments (for cabalProject)
# and converts it to an attr set of arguments. This allows
# the use of "1.0.0.0" or { version = "1.0.0.0"; ... }
versionOrArgsToArgs = versionOrArgs:
if lib.isAttrs versionOrArgs
then versionOrArgs
else { version = versionOrArgs; };
# Find the resolver in the stack.yaml file and fetch it if a sha256 value is provided
fetchResolver = import ./fetch-resolver.nix {
inherit (pkgs.evalPackages) pkgs;
};
inherit (import ./cabal-project-parser.nix {
inherit pkgs;
}) parseIndexState parseBlock;
cabalToNixpkgsLicense = import ./spdx/cabal.nix pkgs;
# This function is like
# `src + (if subDir == "" then "" else "/" + subDir)`
# however when `includeSiblings` is set it maintains
# `src.origSrc` if there is one and instead adds to
# `src.origSubDir`. It uses `cleanSourceWith` when possible
# to keep `cleanSourceWith` support in the result.
appendSubDir = { src, subDir, includeSiblings ? false }:
if subDir == ""
then src
else
if haskellLib.canCleanSource src
then haskellLib.cleanSourceWith {
inherit src subDir includeSiblings;
}
else let name = src.name or "source" + "-" + __replaceStrings ["/"] ["-"] subDir;
in if includeSiblings
then rec {
# Keep `src.origSrc` so it can be used to allow references
# to other parts of that root.
inherit name;
origSrc = src.origSrc or src;
origSubDir = src.origSubDir or "" + "/" + subDir;
outPath = origSrc + origSubDir;
}
else {
# We are not going to need other parts of `origSrc` if there
# was one and we can ignore it
inherit name;
outPath = src + "/" + subDir;
};
# Givin a `src` split it into a `root` path (based on `src.origSrc` if
# present) and `subDir` (based on `src.origSubDir). The
# `root` will still use the `filter` of `src` if there was one.
rootAndSubDir = src: rec {
subDir = src.origSubDir or "";
root =
if subDir == ""
then src # if there was no subdir use the original src
else
# Use `cleanSourceWith` to make sure the `filter` is still used
if src ? origSrc && src ? filter
then haskellLib.cleanSourceWith {
name = src.name or "source" + "-root";
src = src.origSrc;
# Not passing src.origSubDir so that the result points `origSrc`
inherit (src) filter;
}
else src.origSrc or src; # If there is a subDir and origSrc (but no filter) use origSrc
};
# Run evalModules passing the project function argument (m) as a module along with
# the the a projectType module (../modules/cabal-project.nix or ../modules/stack-project.nix).
# The resulting config is then passed to the project function's implementation.
evalProjectModule = projectType: m: f: f
(lib.evalModules {
modules = (if builtins.isList m then m else [m]) ++ [
# Include ../modules/cabal-project.nix or ../modules/stack-project.nix
(import ../modules/project-common.nix)
(import projectType)
# Pass the pkgs to the modules
({ config, lib, ... }: {
_module.args = {
inherit pkgs;
};
})
];
}).config;
# Converts from a `compoent.depends` value to a library derivation.
# In the case of sublibs the `depends` value should already be the derivation.
dependToLib = d: d.components.library or d;
}