···66A file set is a mathematical set of local files that can be added to the Nix store for use in Nix derivations.
77File sets are easy and safe to use, providing obvious and composable semantics with good error messages to prevent mistakes.
8899-These sections apply to the entire library.
109See the [function reference](#sec-functions-library-fileset) for function-specific documentation.
1111-1212-The file set library is currently somewhat limited but is being expanded to include more functions over time.
13101411## Implicit coercion from paths to file sets {#sec-fileset-path-coercion}
1512
+27-27
lib/fileset/default.nix
···122122 Paths in [strings](https://nixos.org/manual/nix/stable/language/values.html#type-string), including Nix store paths, cannot be passed as `root`.
123123 `root` has to be a directory.
124124125125-<!-- Ignore the indentation here, this is a nixdoc rendering bug that needs to be fixed: https://github.com/nix-community/nixdoc/issues/75 -->
126126-:::{.note}
127127-Changing `root` only affects the directory structure of the resulting store path, it does not change which files are added to the store.
128128-The only way to change which files get added to the store is by changing the `fileset` attribute.
129129-:::
125125+ :::{.note}
126126+ Changing `root` only affects the directory structure of the resulting store path, it does not change which files are added to the store.
127127+ The only way to change which files get added to the store is by changing the `fileset` attribute.
128128+ :::
130129 */
131130 root,
132131 /*
···135134 This argument can also be a path,
136135 which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
137136138138-<!-- Ignore the indentation here, this is a nixdoc rendering bug that needs to be fixed: https://github.com/nix-community/nixdoc/issues/75 -->
139139-:::{.note}
140140-If a directory does not recursively contain any file, it is omitted from the store path contents.
141141-:::
137137+ :::{.note}
138138+ If a directory does not recursively contain any file, it is omitted from the store path contents.
139139+ :::
142140143141 */
144142 fileset,
···156154 if ! isPath root then
157155 if isStringLike root then
158156 throw ''
159159- lib.fileset.toSource: `root` ("${toString root}") is a string-like value, but it should be a path instead.
157157+ lib.fileset.toSource: `root` (${toString root}) is a string-like value, but it should be a path instead.
160158 Paths in strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.''
161159 else
162160 throw ''
···165163 # See also ../path/README.md
166164 else if ! fileset._internalIsEmptyWithoutBase && rootFilesystemRoot != filesetFilesystemRoot then
167165 throw ''
168168- lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` ("${toString root}"):
169169- `root`: root "${toString rootFilesystemRoot}"
170170- `fileset`: root "${toString filesetFilesystemRoot}"
171171- Different roots are not supported.''
166166+ lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` (${toString root}):
167167+ `root`: Filesystem root is "${toString rootFilesystemRoot}"
168168+ `fileset`: Filesystem root is "${toString filesetFilesystemRoot}"
169169+ Different filesystem roots are not supported.''
172170 else if ! pathExists root then
173171 throw ''
174174- lib.fileset.toSource: `root` (${toString root}) does not exist.''
172172+ lib.fileset.toSource: `root` (${toString root}) is a path that does not exist.''
175173 else if pathType root != "directory" then
176174 throw ''
177175 lib.fileset.toSource: `root` (${toString root}) is a file, but it should be a directory instead. Potential solutions:
···223221 _unionMany
224222 (_coerceMany "lib.fileset.union" [
225223 {
226226- context = "first argument";
224224+ context = "First argument";
227225 value = fileset1;
228226 }
229227 {
230230- context = "second argument";
228228+ context = "Second argument";
231229 value = fileset2;
232230 }
233231 ]);
···269267 # which get [implicitly coerced to file sets](#sec-fileset-path-coercion).
270268 filesets:
271269 if ! isList filesets then
272272- throw "lib.fileset.unions: Expected argument to be a list, but got a ${typeOf filesets}."
270270+ throw ''
271271+ lib.fileset.unions: Argument is of type ${typeOf filesets}, but it should be a list instead.''
273272 else
274273 pipe filesets [
275274 # Annotate the elements with context, used by _coerceMany for better errors
276275 (imap0 (i: el: {
277277- context = "element ${toString i}";
276276+ context = "Element ${toString i}";
278277 value = el;
279278 }))
280279 (_coerceMany "lib.fileset.unions")
···325324 # The file set to filter based on the predicate function
326325 fileset:
327326 if ! isFunction predicate then
328328- throw "lib.fileset.fileFilter: Expected the first argument to be a function, but it's a ${typeOf predicate} instead."
327327+ throw ''
328328+ lib.fileset.fileFilter: First argument is of type ${typeOf predicate}, but it should be a function.''
329329 else
330330 _fileFilter predicate
331331- (_coerce "lib.fileset.fileFilter: second argument" fileset);
331331+ (_coerce "lib.fileset.fileFilter: Second argument" fileset);
332332333333 /*
334334 The file set containing all files that are in both of two given file sets.
···356356 let
357357 filesets = _coerceMany "lib.fileset.intersection" [
358358 {
359359- context = "first argument";
359359+ context = "First argument";
360360 value = fileset1;
361361 }
362362 {
363363- context = "second argument";
363363+ context = "Second argument";
364364 value = fileset2;
365365 }
366366 ];
···408408 let
409409 filesets = _coerceMany "lib.fileset.difference" [
410410 {
411411- context = "first argument (positive set)";
411411+ context = "First argument (positive set)";
412412 value = positive;
413413 }
414414 {
415415- context = "second argument (negative set)";
415415+ context = "Second argument (negative set)";
416416 value = negative;
417417 }
418418 ];
···456456 let
457457 # "fileset" would be a better name, but that would clash with the argument name,
458458 # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
459459- actualFileset = _coerce "lib.fileset.trace: argument" fileset;
459459+ actualFileset = _coerce "lib.fileset.trace: Argument" fileset;
460460 in
461461 seq
462462 (_printFileset actualFileset)
···503503 let
504504 # "fileset" would be a better name, but that would clash with the argument name,
505505 # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
506506- actualFileset = _coerce "lib.fileset.traceVal: argument" fileset;
506506+ actualFileset = _coerce "lib.fileset.traceVal: Argument" fileset;
507507 in
508508 seq
509509 (_printFileset actualFileset)
+4-7
lib/fileset/internal.nix
···77 isString
88 pathExists
99 readDir
1010- seq
1110 split
1211 trace
1312 typeOf
···1716 attrNames
1817 attrValues
1918 mapAttrs
2020- setAttrByPath
2119 zipAttrsWith
2220 ;
2321···2826 inherit (lib.lists)
2927 all
3028 commonPrefix
3131- drop
3229 elemAt
3330 filter
3431 findFirst
···179176 ${context} is of type ${typeOf value}, but it should be a file set or a path instead.''
180177 else if ! pathExists value then
181178 throw ''
182182- ${context} (${toString value}) does not exist.''
179179+ ${context} (${toString value}) is a path that does not exist.''
183180 else
184181 _singleton value;
185182···208205 if firstWithBase != null && differentIndex != null then
209206 throw ''
210207 ${functionContext}: Filesystem roots are not the same:
211211- ${(head list).context}: root "${toString firstBaseRoot}"
212212- ${(elemAt list differentIndex).context}: root "${toString (elemAt filesets differentIndex)._internalBaseRoot}"
213213- Different roots are not supported.''
208208+ ${(head list).context}: Filesystem root is "${toString firstBaseRoot}"
209209+ ${(elemAt list differentIndex).context}: Filesystem root is "${toString (elemAt filesets differentIndex)._internalBaseRoot}"
210210+ Different filesystem roots are not supported.''
214211 else
215212 filesets;
216213
+23-23
lib/fileset/tests.sh
···318318#### Error messages #####
319319320320# Absolute paths in strings cannot be passed as `root`
321321-expectFailure 'toSource { root = "/nix/store/foobar"; fileset = ./.; }' 'lib.fileset.toSource: `root` \("/nix/store/foobar"\) is a string-like value, but it should be a path instead.
321321+expectFailure 'toSource { root = "/nix/store/foobar"; fileset = ./.; }' 'lib.fileset.toSource: `root` \(/nix/store/foobar\) is a string-like value, but it should be a path instead.
322322\s*Paths in strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.'
323323324324# Only paths are accepted as `root`
···328328mkdir -p {foo,bar}/mock-root
329329expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
330330 toSource { root = ./foo/mock-root; fileset = ./bar/mock-root; }
331331-' 'lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` \("'"$work"'/foo/mock-root"\):
332332-\s*`root`: root "'"$work"'/foo/mock-root"
333333-\s*`fileset`: root "'"$work"'/bar/mock-root"
334334-\s*Different roots are not supported.'
331331+' 'lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` \('"$work"'/foo/mock-root\):
332332+\s*`root`: Filesystem root is "'"$work"'/foo/mock-root"
333333+\s*`fileset`: Filesystem root is "'"$work"'/bar/mock-root"
334334+\s*Different filesystem roots are not supported.'
335335rm -rf -- *
336336337337# `root` needs to exist
338338-expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `root` \('"$work"'/a\) does not exist.'
338338+expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `root` \('"$work"'/a\) is a path that does not exist.'
339339340340# `root` needs to be a file
341341touch a
···367367\s*Paths represented as strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.'
368368369369# Path coercion errors for non-existent paths
370370-expectFailure 'toSource { root = ./.; fileset = ./a; }' 'lib.fileset.toSource: `fileset` \('"$work"'/a\) does not exist.'
370370+expectFailure 'toSource { root = ./.; fileset = ./a; }' 'lib.fileset.toSource: `fileset` \('"$work"'/a\) is a path that does not exist.'
371371372372# File sets cannot be evaluated directly
373373expectFailure 'union ./. ./.' 'lib.fileset: Directly evaluating a file set is not supported.
···490490expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
491491 toSource { root = ./.; fileset = union ./foo/mock-root ./bar/mock-root; }
492492' 'lib.fileset.union: Filesystem roots are not the same:
493493-\s*first argument: root "'"$work"'/foo/mock-root"
494494-\s*second argument: root "'"$work"'/bar/mock-root"
495495-\s*Different roots are not supported.'
493493+\s*First argument: Filesystem root is "'"$work"'/foo/mock-root"
494494+\s*Second argument: Filesystem root is "'"$work"'/bar/mock-root"
495495+\s*Different filesystem roots are not supported.'
496496497497expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
498498 toSource { root = ./.; fileset = unions [ ./foo/mock-root ./bar/mock-root ]; }
499499' 'lib.fileset.unions: Filesystem roots are not the same:
500500-\s*element 0: root "'"$work"'/foo/mock-root"
501501-\s*element 1: root "'"$work"'/bar/mock-root"
502502-\s*Different roots are not supported.'
500500+\s*Element 0: Filesystem root is "'"$work"'/foo/mock-root"
501501+\s*Element 1: Filesystem root is "'"$work"'/bar/mock-root"
502502+\s*Different filesystem roots are not supported.'
503503rm -rf -- *
504504505505# Coercion errors show the correct context
506506-expectFailure 'toSource { root = ./.; fileset = union ./a ./.; }' 'lib.fileset.union: first argument \('"$work"'/a\) does not exist.'
507507-expectFailure 'toSource { root = ./.; fileset = union ./. ./b; }' 'lib.fileset.union: second argument \('"$work"'/b\) does not exist.'
508508-expectFailure 'toSource { root = ./.; fileset = unions [ ./a ./. ]; }' 'lib.fileset.unions: element 0 \('"$work"'/a\) does not exist.'
509509-expectFailure 'toSource { root = ./.; fileset = unions [ ./. ./b ]; }' 'lib.fileset.unions: element 1 \('"$work"'/b\) does not exist.'
506506+expectFailure 'toSource { root = ./.; fileset = union ./a ./.; }' 'lib.fileset.union: First argument \('"$work"'/a\) is a path that does not exist.'
507507+expectFailure 'toSource { root = ./.; fileset = union ./. ./b; }' 'lib.fileset.union: Second argument \('"$work"'/b\) is a path that does not exist.'
508508+expectFailure 'toSource { root = ./.; fileset = unions [ ./a ./. ]; }' 'lib.fileset.unions: Element 0 \('"$work"'/a\) is a path that does not exist.'
509509+expectFailure 'toSource { root = ./.; fileset = unions [ ./. ./b ]; }' 'lib.fileset.unions: Element 1 \('"$work"'/b\) is a path that does not exist.'
510510511511# unions needs a list
512512-expectFailure 'toSource { root = ./.; fileset = unions null; }' 'lib.fileset.unions: Expected argument to be a list, but got a null.'
512512+expectFailure 'toSource { root = ./.; fileset = unions null; }' 'lib.fileset.unions: Argument is of type null, but it should be a list instead.'
513513514514# The tree of later arguments should not be evaluated if a former argument already includes all files
515515tree=()
···603603expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
604604 toSource { root = ./.; fileset = intersection ./foo/mock-root ./bar/mock-root; }
605605' 'lib.fileset.intersection: Filesystem roots are not the same:
606606-\s*first argument: root "'"$work"'/foo/mock-root"
607607-\s*second argument: root "'"$work"'/bar/mock-root"
608608-\s*Different roots are not supported.'
606606+\s*First argument: Filesystem root is "'"$work"'/foo/mock-root"
607607+\s*Second argument: Filesystem root is "'"$work"'/bar/mock-root"
608608+\s*Different filesystem roots are not supported.'
609609rm -rf -- *
610610611611# Coercion errors show the correct context
612612-expectFailure 'toSource { root = ./.; fileset = intersection ./a ./.; }' 'lib.fileset.intersection: first argument \('"$work"'/a\) does not exist.'
613613-expectFailure 'toSource { root = ./.; fileset = intersection ./. ./b; }' 'lib.fileset.intersection: second argument \('"$work"'/b\) does not exist.'
612612+expectFailure 'toSource { root = ./.; fileset = intersection ./a ./.; }' 'lib.fileset.intersection: First argument \('"$work"'/a\) is a path that does not exist.'
613613+expectFailure 'toSource { root = ./.; fileset = intersection ./. ./b; }' 'lib.fileset.intersection: Second argument \('"$work"'/b\) is a path that does not exist.'
614614615615# The tree of later arguments should not be evaluated if a former argument already excludes all files
616616tree=(