Skip to content

Commit 49f0cbd

Browse files
authored
lib.types: introduce a fileset type (NixOS#428293)
2 parents c487ed8 + ad1e615 commit 49f0cbd

File tree

6 files changed

+157
-12
lines changed

6 files changed

+157
-12
lines changed

lib/fileset/default.nix

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ let
101101

102102
inherit (import ./internal.nix { inherit lib; })
103103
_coerce
104+
_coerceResult
104105
_singleton
105106
_coerceMany
106107
_toSourceFilter
@@ -1005,4 +1006,49 @@ in
10051006
{
10061007
submodules = recurseSubmodules;
10071008
};
1009+
1010+
/**
1011+
The empty fileset. It can be useful as a default value or as starting accumulator for a folding operation.
1012+
1013+
# Type
1014+
1015+
```
1016+
empty :: FileSet
1017+
```
1018+
*/
1019+
empty = _emptyWithoutBase;
1020+
1021+
/**
1022+
Tests whether a given value is a fileset, or can be used in place of a fileset.
1023+
1024+
# Inputs
1025+
1026+
`value`
1027+
1028+
: The value to test
1029+
1030+
# Type
1031+
1032+
```
1033+
isFileset :: Any -> Bool
1034+
```
1035+
1036+
# Examples
1037+
:::{.example}
1038+
## `lib.fileset.isFileset` usage example
1039+
1040+
```nix
1041+
isFileset ./.
1042+
=> true
1043+
1044+
isFileset (unions [ ])
1045+
=> true
1046+
1047+
isFileset 1
1048+
=> false
1049+
```
1050+
1051+
:::
1052+
*/
1053+
isFileset = x: (_coerceResult "" x).success;
10081054
}

lib/fileset/internal.nix

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,27 @@ rec {
165165
_noEval = throw _noEvalMessage;
166166
};
167167

168-
# Coerce a value to a fileset, erroring when the value cannot be coerced.
169-
# The string gives the context for error messages.
170-
# Type: String -> (fileset | Path) -> fileset
171-
_coerce =
168+
# Coerce a value to a fileset. Return a set containing the attribute `success`
169+
# indicating whether coercing succeeded, and either `value` when `success ==
170+
# true`, or an error `message` when `success == false`. The string gives the
171+
# context for error messages.
172+
#
173+
# Type: String -> (fileset | Path) -> { success :: Bool, value :: fileset } ] -> { success :: Bool, message :: String }
174+
_coerceResult =
175+
let
176+
ok = value: {
177+
success = true;
178+
inherit value;
179+
};
180+
error = message: {
181+
success = false;
182+
inherit message;
183+
};
184+
in
172185
context: value:
173186
if value._type or "" == "fileset" then
174187
if value._internalVersion > _currentVersion then
175-
throw ''
188+
error ''
176189
${context} is a file set created from a future version of the file set library with a different internal representation:
177190
- Internal version of the file set: ${toString value._internalVersion}
178191
- Internal version of the library: ${toString _currentVersion}
@@ -184,27 +197,37 @@ rec {
184197
_currentVersion - value._internalVersion
185198
) migrations;
186199
in
187-
foldl' (value: migration: migration value) value migrationsToApply
200+
ok (foldl' (value: migration: migration value) value migrationsToApply)
188201
else
189-
value
202+
ok value
190203
else if !isPath value then
191204
if value ? _isLibCleanSourceWith then
192-
throw ''
205+
error ''
193206
${context} is a `lib.sources`-based value, but it should be a file set or a path instead.
194207
To convert a `lib.sources`-based value to a file set you can use `lib.fileset.fromSource`.
195208
Note that this only works for sources created from paths.''
196209
else if isStringLike value then
197-
throw ''
210+
error ''
198211
${context} ("${toString value}") is a string-like value, but it should be a file set or a path instead.
199212
Paths represented as strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.''
200213
else
201-
throw ''${context} is of type ${typeOf value}, but it should be a file set or a path instead.''
214+
error ''${context} is of type ${typeOf value}, but it should be a file set or a path instead.''
202215
else if !pathExists value then
203-
throw ''
216+
error ''
204217
${context} (${toString value}) is a path that does not exist.
205218
To create a file set from a path that may not exist, use `lib.fileset.maybeMissing`.''
206219
else
207-
_singleton value;
220+
ok (_singleton value);
221+
222+
# Coerce a value to a fileset, erroring when the value cannot be coerced.
223+
# The string gives the context for error messages.
224+
# Type: String -> (fileset | Path) -> fileset
225+
_coerce =
226+
context: value:
227+
let
228+
result = _coerceResult context value;
229+
in
230+
if result.success then result.value else throw result.message;
208231

209232
# Coerce many values to filesets, erroring when any value cannot be coerced,
210233
# or if the filesystem root of the values doesn't match.

lib/tests/modules.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,17 @@ checkConfigError 'A definition for option .* is not of type .path in the Nix sto
223223
checkConfigError 'A definition for option .* is not of type .path in the Nix store.. Definition values:\n\s*- In .*: ".*/store/.links"' config.pathInStore.bad4 ./types.nix
224224
checkConfigError 'A definition for option .* is not of type .path in the Nix store.. Definition values:\n\s*- In .*: "/foo/bar"' config.pathInStore.bad5 ./types.nix
225225

226+
# types.fileset
227+
checkConfigOutput '^0$' config.filesetCardinal.ok1 ./fileset.nix
228+
checkConfigOutput '^1$' config.filesetCardinal.ok2 ./fileset.nix
229+
checkConfigOutput '^1$' config.filesetCardinal.ok3 ./fileset.nix
230+
checkConfigOutput '^1$' config.filesetCardinal.ok4 ./fileset.nix
231+
checkConfigOutput '^0$' config.filesetCardinal.ok5 ./fileset.nix
232+
checkConfigError 'A definition for option .* is not of type .fileset.. Definition values:\n.*' config.filesetCardinal.err1 ./fileset.nix
233+
checkConfigError 'A definition for option .* is not of type .fileset.. Definition values:\n.*' config.filesetCardinal.err2 ./fileset.nix
234+
checkConfigError 'A definition for option .* is not of type .fileset.. Definition values:\n.*' config.filesetCardinal.err3 ./fileset.nix
235+
checkConfigError 'A definition for option .* is not of type .fileset.. Definition values:\n.*' config.filesetCardinal.err4 ./fileset.nix
236+
226237
# Check boolean option.
227238
checkConfigOutput '^false$' config.enable ./declare-enable.nix
228239
checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix

lib/tests/modules/fileset.nix

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{ config, lib, ... }:
2+
3+
let
4+
inherit (lib)
5+
mkOption
6+
mkIf
7+
types
8+
mapAttrs
9+
length
10+
;
11+
inherit (lib.fileset)
12+
empty
13+
unions
14+
toList
15+
;
16+
in
17+
18+
{
19+
options = {
20+
fileset = mkOption { type = with types; lazyAttrsOf fileset; };
21+
22+
## The following option is only here as a proxy to test `fileset` that does
23+
## not work so well with `modules.sh` because it is not JSONable. It exposes
24+
## the number of elements in the fileset.
25+
filesetCardinal = mkOption { default = mapAttrs (_: fs: length (toList fs)) config.fileset; };
26+
};
27+
28+
config = {
29+
fileset.ok1 = empty;
30+
fileset.ok2 = ./fileset;
31+
fileset.ok3 = unions [
32+
empty
33+
./fileset
34+
];
35+
# fileset.ok4: see imports below
36+
fileset.ok5 = mkIf false ./fileset;
37+
38+
fileset.err1 = 1;
39+
fileset.err2 = "foo";
40+
fileset.err3 = "./.";
41+
fileset.err4 = [ empty ];
42+
43+
};
44+
45+
imports = [
46+
{ fileset.ok4 = ./fileset; }
47+
{ fileset.ok4 = empty; }
48+
{ fileset.ok4 = ./fileset; }
49+
];
50+
}

lib/tests/modules/fileset/keepme

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Do not remove. This file is used by the tests in `../fileset.nix`.

lib/types.nix

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ let
6666
fixupOptionType
6767
mergeOptionDecls
6868
;
69+
inherit (lib.fileset)
70+
isFileset
71+
unions
72+
empty
73+
;
6974

7075
inAttrPosSuffix =
7176
v: name:
@@ -618,6 +623,15 @@ let
618623
};
619624
};
620625

626+
fileset = mkOptionType {
627+
name = "fileset";
628+
description = "fileset";
629+
descriptionClass = "noun";
630+
check = isFileset;
631+
merge = loc: defs: unions (map (x: x.value) defs);
632+
emptyValue.value = empty;
633+
};
634+
621635
# A package is a top-level store path (/nix/store/hash-name). This includes:
622636
# - derivations
623637
# - more generally, attribute sets with an `outPath` or `__toString` attribute

0 commit comments

Comments
 (0)