···6A file set is a mathematical set of local files that can be added to the Nix store for use in Nix derivations.
7File sets are easy and safe to use, providing obvious and composable semantics with good error messages to prevent mistakes.
89-These sections apply to the entire library.
10See the [function reference](#sec-functions-library-fileset) for function-specific documentation.
11-12-The file set library is currently somewhat limited but is being expanded to include more functions over time.
1314## Implicit coercion from paths to file sets {#sec-fileset-path-coercion}
15
···6A file set is a mathematical set of local files that can be added to the Nix store for use in Nix derivations.
7File sets are easy and safe to use, providing obvious and composable semantics with good error messages to prevent mistakes.
809See the [function reference](#sec-functions-library-fileset) for function-specific documentation.
001011## Implicit coercion from paths to file sets {#sec-fileset-path-coercion}
12
+27-27
lib/fileset/default.nix
···122 Paths in [strings](https://nixos.org/manual/nix/stable/language/values.html#type-string), including Nix store paths, cannot be passed as `root`.
123 `root` has to be a directory.
124125-<!-- Ignore the indentation here, this is a nixdoc rendering bug that needs to be fixed: https://github.com/nix-community/nixdoc/issues/75 -->
126-:::{.note}
127-Changing `root` only affects the directory structure of the resulting store path, it does not change which files are added to the store.
128-The only way to change which files get added to the store is by changing the `fileset` attribute.
129-:::
130 */
131 root,
132 /*
···135 This argument can also be a path,
136 which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
137138-<!-- Ignore the indentation here, this is a nixdoc rendering bug that needs to be fixed: https://github.com/nix-community/nixdoc/issues/75 -->
139-:::{.note}
140-If a directory does not recursively contain any file, it is omitted from the store path contents.
141-:::
142143 */
144 fileset,
···156 if ! isPath root then
157 if isStringLike root then
158 throw ''
159- lib.fileset.toSource: `root` ("${toString root}") is a string-like value, but it should be a path instead.
160 Paths in strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.''
161 else
162 throw ''
···165 # See also ../path/README.md
166 else if ! fileset._internalIsEmptyWithoutBase && rootFilesystemRoot != filesetFilesystemRoot then
167 throw ''
168- lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` ("${toString root}"):
169- `root`: root "${toString rootFilesystemRoot}"
170- `fileset`: root "${toString filesetFilesystemRoot}"
171- Different roots are not supported.''
172 else if ! pathExists root then
173 throw ''
174- lib.fileset.toSource: `root` (${toString root}) does not exist.''
175 else if pathType root != "directory" then
176 throw ''
177 lib.fileset.toSource: `root` (${toString root}) is a file, but it should be a directory instead. Potential solutions:
···223 _unionMany
224 (_coerceMany "lib.fileset.union" [
225 {
226- context = "first argument";
227 value = fileset1;
228 }
229 {
230- context = "second argument";
231 value = fileset2;
232 }
233 ]);
···269 # which get [implicitly coerced to file sets](#sec-fileset-path-coercion).
270 filesets:
271 if ! isList filesets then
272- throw "lib.fileset.unions: Expected argument to be a list, but got a ${typeOf filesets}."
0273 else
274 pipe filesets [
275 # Annotate the elements with context, used by _coerceMany for better errors
276 (imap0 (i: el: {
277- context = "element ${toString i}";
278 value = el;
279 }))
280 (_coerceMany "lib.fileset.unions")
···325 # The file set to filter based on the predicate function
326 fileset:
327 if ! isFunction predicate then
328- throw "lib.fileset.fileFilter: Expected the first argument to be a function, but it's a ${typeOf predicate} instead."
0329 else
330 _fileFilter predicate
331- (_coerce "lib.fileset.fileFilter: second argument" fileset);
332333 /*
334 The file set containing all files that are in both of two given file sets.
···356 let
357 filesets = _coerceMany "lib.fileset.intersection" [
358 {
359- context = "first argument";
360 value = fileset1;
361 }
362 {
363- context = "second argument";
364 value = fileset2;
365 }
366 ];
···408 let
409 filesets = _coerceMany "lib.fileset.difference" [
410 {
411- context = "first argument (positive set)";
412 value = positive;
413 }
414 {
415- context = "second argument (negative set)";
416 value = negative;
417 }
418 ];
···456 let
457 # "fileset" would be a better name, but that would clash with the argument name,
458 # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
459- actualFileset = _coerce "lib.fileset.trace: argument" fileset;
460 in
461 seq
462 (_printFileset actualFileset)
···503 let
504 # "fileset" would be a better name, but that would clash with the argument name,
505 # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
506- actualFileset = _coerce "lib.fileset.traceVal: argument" fileset;
507 in
508 seq
509 (_printFileset actualFileset)
···122 Paths in [strings](https://nixos.org/manual/nix/stable/language/values.html#type-string), including Nix store paths, cannot be passed as `root`.
123 `root` has to be a directory.
124125+ :::{.note}
126+ Changing `root` only affects the directory structure of the resulting store path, it does not change which files are added to the store.
127+ The only way to change which files get added to the store is by changing the `fileset` attribute.
128+ :::
0129 */
130 root,
131 /*
···134 This argument can also be a path,
135 which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
136137+ :::{.note}
138+ If a directory does not recursively contain any file, it is omitted from the store path contents.
139+ :::
0140141 */
142 fileset,
···154 if ! isPath root then
155 if isStringLike root then
156 throw ''
157+ lib.fileset.toSource: `root` (${toString root}) is a string-like value, but it should be a path instead.
158 Paths in strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.''
159 else
160 throw ''
···163 # See also ../path/README.md
164 else if ! fileset._internalIsEmptyWithoutBase && rootFilesystemRoot != filesetFilesystemRoot then
165 throw ''
166+ lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` (${toString root}):
167+ `root`: Filesystem root is "${toString rootFilesystemRoot}"
168+ `fileset`: Filesystem root is "${toString filesetFilesystemRoot}"
169+ Different filesystem roots are not supported.''
170 else if ! pathExists root then
171 throw ''
172+ lib.fileset.toSource: `root` (${toString root}) is a path that does not exist.''
173 else if pathType root != "directory" then
174 throw ''
175 lib.fileset.toSource: `root` (${toString root}) is a file, but it should be a directory instead. Potential solutions:
···221 _unionMany
222 (_coerceMany "lib.fileset.union" [
223 {
224+ context = "First argument";
225 value = fileset1;
226 }
227 {
228+ context = "Second argument";
229 value = fileset2;
230 }
231 ]);
···267 # which get [implicitly coerced to file sets](#sec-fileset-path-coercion).
268 filesets:
269 if ! isList filesets then
270+ throw ''
271+ lib.fileset.unions: Argument is of type ${typeOf filesets}, but it should be a list instead.''
272 else
273 pipe filesets [
274 # Annotate the elements with context, used by _coerceMany for better errors
275 (imap0 (i: el: {
276+ context = "Element ${toString i}";
277 value = el;
278 }))
279 (_coerceMany "lib.fileset.unions")
···324 # The file set to filter based on the predicate function
325 fileset:
326 if ! isFunction predicate then
327+ throw ''
328+ lib.fileset.fileFilter: First argument is of type ${typeOf predicate}, but it should be a function.''
329 else
330 _fileFilter predicate
331+ (_coerce "lib.fileset.fileFilter: Second argument" fileset);
332333 /*
334 The file set containing all files that are in both of two given file sets.
···356 let
357 filesets = _coerceMany "lib.fileset.intersection" [
358 {
359+ context = "First argument";
360 value = fileset1;
361 }
362 {
363+ context = "Second argument";
364 value = fileset2;
365 }
366 ];
···408 let
409 filesets = _coerceMany "lib.fileset.difference" [
410 {
411+ context = "First argument (positive set)";
412 value = positive;
413 }
414 {
415+ context = "Second argument (negative set)";
416 value = negative;
417 }
418 ];
···456 let
457 # "fileset" would be a better name, but that would clash with the argument name,
458 # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
459+ actualFileset = _coerce "lib.fileset.trace: Argument" fileset;
460 in
461 seq
462 (_printFileset actualFileset)
···503 let
504 # "fileset" would be a better name, but that would clash with the argument name,
505 # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
506+ actualFileset = _coerce "lib.fileset.traceVal: Argument" fileset;
507 in
508 seq
509 (_printFileset actualFileset)
+4-7
lib/fileset/internal.nix
···7 isString
8 pathExists
9 readDir
10- seq
11 split
12 trace
13 typeOf
···17 attrNames
18 attrValues
19 mapAttrs
20- setAttrByPath
21 zipAttrsWith
22 ;
23···28 inherit (lib.lists)
29 all
30 commonPrefix
31- drop
32 elemAt
33 filter
34 findFirst
···179 ${context} is of type ${typeOf value}, but it should be a file set or a path instead.''
180 else if ! pathExists value then
181 throw ''
182- ${context} (${toString value}) does not exist.''
183 else
184 _singleton value;
185···208 if firstWithBase != null && differentIndex != null then
209 throw ''
210 ${functionContext}: Filesystem roots are not the same:
211- ${(head list).context}: root "${toString firstBaseRoot}"
212- ${(elemAt list differentIndex).context}: root "${toString (elemAt filesets differentIndex)._internalBaseRoot}"
213- Different roots are not supported.''
214 else
215 filesets;
216
···7 isString
8 pathExists
9 readDir
010 split
11 trace
12 typeOf
···16 attrNames
17 attrValues
18 mapAttrs
019 zipAttrsWith
20 ;
21···26 inherit (lib.lists)
27 all
28 commonPrefix
029 elemAt
30 filter
31 findFirst
···176 ${context} is of type ${typeOf value}, but it should be a file set or a path instead.''
177 else if ! pathExists value then
178 throw ''
179+ ${context} (${toString value}) is a path that does not exist.''
180 else
181 _singleton value;
182···205 if firstWithBase != null && differentIndex != null then
206 throw ''
207 ${functionContext}: Filesystem roots are not the same:
208+ ${(head list).context}: Filesystem root is "${toString firstBaseRoot}"
209+ ${(elemAt list differentIndex).context}: Filesystem root is "${toString (elemAt filesets differentIndex)._internalBaseRoot}"
210+ Different filesystem roots are not supported.''
211 else
212 filesets;
213
+23-23
lib/fileset/tests.sh
···318#### Error messages #####
319320# Absolute paths in strings cannot be passed as `root`
321-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.
322\s*Paths in strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.'
323324# Only paths are accepted as `root`
···328mkdir -p {foo,bar}/mock-root
329expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
330 toSource { root = ./foo/mock-root; fileset = ./bar/mock-root; }
331-' 'lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` \("'"$work"'/foo/mock-root"\):
332-\s*`root`: root "'"$work"'/foo/mock-root"
333-\s*`fileset`: root "'"$work"'/bar/mock-root"
334-\s*Different roots are not supported.'
335rm -rf -- *
336337# `root` needs to exist
338-expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `root` \('"$work"'/a\) does not exist.'
339340# `root` needs to be a file
341touch a
···367\s*Paths represented as strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.'
368369# Path coercion errors for non-existent paths
370-expectFailure 'toSource { root = ./.; fileset = ./a; }' 'lib.fileset.toSource: `fileset` \('"$work"'/a\) does not exist.'
371372# File sets cannot be evaluated directly
373expectFailure 'union ./. ./.' 'lib.fileset: Directly evaluating a file set is not supported.
···490expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
491 toSource { root = ./.; fileset = union ./foo/mock-root ./bar/mock-root; }
492' 'lib.fileset.union: Filesystem roots are not the same:
493-\s*first argument: root "'"$work"'/foo/mock-root"
494-\s*second argument: root "'"$work"'/bar/mock-root"
495-\s*Different roots are not supported.'
496497expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
498 toSource { root = ./.; fileset = unions [ ./foo/mock-root ./bar/mock-root ]; }
499' 'lib.fileset.unions: Filesystem roots are not the same:
500-\s*element 0: root "'"$work"'/foo/mock-root"
501-\s*element 1: root "'"$work"'/bar/mock-root"
502-\s*Different roots are not supported.'
503rm -rf -- *
504505# Coercion errors show the correct context
506-expectFailure 'toSource { root = ./.; fileset = union ./a ./.; }' 'lib.fileset.union: first argument \('"$work"'/a\) does not exist.'
507-expectFailure 'toSource { root = ./.; fileset = union ./. ./b; }' 'lib.fileset.union: second argument \('"$work"'/b\) does not exist.'
508-expectFailure 'toSource { root = ./.; fileset = unions [ ./a ./. ]; }' 'lib.fileset.unions: element 0 \('"$work"'/a\) does not exist.'
509-expectFailure 'toSource { root = ./.; fileset = unions [ ./. ./b ]; }' 'lib.fileset.unions: element 1 \('"$work"'/b\) does not exist.'
510511# unions needs a list
512-expectFailure 'toSource { root = ./.; fileset = unions null; }' 'lib.fileset.unions: Expected argument to be a list, but got a null.'
513514# The tree of later arguments should not be evaluated if a former argument already includes all files
515tree=()
···603expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
604 toSource { root = ./.; fileset = intersection ./foo/mock-root ./bar/mock-root; }
605' 'lib.fileset.intersection: Filesystem roots are not the same:
606-\s*first argument: root "'"$work"'/foo/mock-root"
607-\s*second argument: root "'"$work"'/bar/mock-root"
608-\s*Different roots are not supported.'
609rm -rf -- *
610611# Coercion errors show the correct context
612-expectFailure 'toSource { root = ./.; fileset = intersection ./a ./.; }' 'lib.fileset.intersection: first argument \('"$work"'/a\) does not exist.'
613-expectFailure 'toSource { root = ./.; fileset = intersection ./. ./b; }' 'lib.fileset.intersection: second argument \('"$work"'/b\) does not exist.'
614615# The tree of later arguments should not be evaluated if a former argument already excludes all files
616tree=(
···318#### Error messages #####
319320# Absolute paths in strings cannot be passed as `root`
321+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.
322\s*Paths in strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.'
323324# Only paths are accepted as `root`
···328mkdir -p {foo,bar}/mock-root
329expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
330 toSource { root = ./foo/mock-root; fileset = ./bar/mock-root; }
331+' 'lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` \('"$work"'/foo/mock-root\):
332+\s*`root`: Filesystem root is "'"$work"'/foo/mock-root"
333+\s*`fileset`: Filesystem root is "'"$work"'/bar/mock-root"
334+\s*Different filesystem roots are not supported.'
335rm -rf -- *
336337# `root` needs to exist
338+expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `root` \('"$work"'/a\) is a path that does not exist.'
339340# `root` needs to be a file
341touch a
···367\s*Paths represented as strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.'
368369# Path coercion errors for non-existent paths
370+expectFailure 'toSource { root = ./.; fileset = ./a; }' 'lib.fileset.toSource: `fileset` \('"$work"'/a\) is a path that does not exist.'
371372# File sets cannot be evaluated directly
373expectFailure 'union ./. ./.' 'lib.fileset: Directly evaluating a file set is not supported.
···490expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
491 toSource { root = ./.; fileset = union ./foo/mock-root ./bar/mock-root; }
492' 'lib.fileset.union: Filesystem roots are not the same:
493+\s*First argument: Filesystem root is "'"$work"'/foo/mock-root"
494+\s*Second argument: Filesystem root is "'"$work"'/bar/mock-root"
495+\s*Different filesystem roots are not supported.'
496497expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
498 toSource { root = ./.; fileset = unions [ ./foo/mock-root ./bar/mock-root ]; }
499' 'lib.fileset.unions: Filesystem roots are not the same:
500+\s*Element 0: Filesystem root is "'"$work"'/foo/mock-root"
501+\s*Element 1: Filesystem root is "'"$work"'/bar/mock-root"
502+\s*Different filesystem roots are not supported.'
503rm -rf -- *
504505# Coercion errors show the correct context
506+expectFailure 'toSource { root = ./.; fileset = union ./a ./.; }' 'lib.fileset.union: First argument \('"$work"'/a\) is a path that does not exist.'
507+expectFailure 'toSource { root = ./.; fileset = union ./. ./b; }' 'lib.fileset.union: Second argument \('"$work"'/b\) is a path that does not exist.'
508+expectFailure 'toSource { root = ./.; fileset = unions [ ./a ./. ]; }' 'lib.fileset.unions: Element 0 \('"$work"'/a\) is a path that does not exist.'
509+expectFailure 'toSource { root = ./.; fileset = unions [ ./. ./b ]; }' 'lib.fileset.unions: Element 1 \('"$work"'/b\) is a path that does not exist.'
510511# unions needs a list
512+expectFailure 'toSource { root = ./.; fileset = unions null; }' 'lib.fileset.unions: Argument is of type null, but it should be a list instead.'
513514# The tree of later arguments should not be evaluated if a former argument already includes all files
515tree=()
···603expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
604 toSource { root = ./.; fileset = intersection ./foo/mock-root ./bar/mock-root; }
605' 'lib.fileset.intersection: Filesystem roots are not the same:
606+\s*First argument: Filesystem root is "'"$work"'/foo/mock-root"
607+\s*Second argument: Filesystem root is "'"$work"'/bar/mock-root"
608+\s*Different filesystem roots are not supported.'
609rm -rf -- *
610611# Coercion errors show the correct context
612+expectFailure 'toSource { root = ./.; fileset = intersection ./a ./.; }' 'lib.fileset.intersection: First argument \('"$work"'/a\) is a path that does not exist.'
613+expectFailure 'toSource { root = ./.; fileset = intersection ./. ./b; }' 'lib.fileset.intersection: Second argument \('"$work"'/b\) is a path that does not exist.'
614615# The tree of later arguments should not be evaluated if a former argument already excludes all files
616tree=(