From 94e103ee3f2096fd8b0484988ef65ff0e68fd73f Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Fri, 15 Sep 2023 00:08:04 +0200 Subject: [PATCH] lib.fileset: Minor changes from feedback Co-authored-by: Robert Hensing Co-authored-by: Valentin Gagarin --- lib/fileset/README.md | 4 +-- lib/fileset/default.nix | 69 +++++++++++++++++++--------------------- lib/fileset/internal.nix | 9 ++---- lib/fileset/tests.sh | 28 ++++++++-------- 4 files changed, 52 insertions(+), 58 deletions(-) diff --git a/lib/fileset/README.md b/lib/fileset/README.md index c50f7936aa77..6e57f1f8f2b4 100644 --- a/lib/fileset/README.md +++ b/lib/fileset/README.md @@ -50,11 +50,11 @@ An attribute set with these values: - `_internalBaseRoot` (path): The filesystem root of `_internalBase`, same as `(lib.path.splitRoot _internalBase).root`. - This is here because this needs to be computed anyways, and this computation shouldn't be duplicated. + This is here because this needs to be computed anyway, and this computation shouldn't be duplicated. - `_internalBaseComponents` (list of strings): The path components of `_internalBase`, same as `lib.path.subpath.components (lib.path.splitRoot _internalBase).subpath`. - This is here because this needs to be computed anyways, and this computation shouldn't be duplicated. + This is here because this needs to be computed anyway, and this computation shouldn't be duplicated. - `_internalTree` ([filesetTree](#filesettree)): A tree representation of all included files under `_internalBase`. diff --git a/lib/fileset/default.nix b/lib/fileset/default.nix index 2c8997d71fc2..88c8dcd1a70b 100644 --- a/lib/fileset/default.nix +++ b/lib/fileset/default.nix @@ -36,6 +36,10 @@ let cleanSourceWith ; + inherit (lib.trivial) + pipe + ; + in { /* @@ -111,7 +115,7 @@ in { Paths in [strings](https://nixos.org/manual/nix/stable/language/values.html#type-string), including Nix store paths, cannot be passed as `root`. `root` has to be a directory. - + :::{.note} Changing `root` only affects the directory structure of the resulting store path, it does not change which files are added to the store. The only way to change which files get added to the store is by changing the `fileset` attribute. @@ -124,7 +128,7 @@ The only way to change which files get added to the store is by changing the `fi This argument can also be a path, which gets [implicitly coerced to a file set](#sec-fileset-path-coercion). - + :::{.note} If a directory does not recursively contain any file, it is omitted from the store path contents. ::: @@ -134,18 +138,18 @@ If a directory does not recursively contain any file, it is omitted from the sto }: let # We cannot rename matched attribute arguments, so let's work around it with an extra `let in` statement - maybeFileset = fileset; + filesetArg = fileset; in let - fileset = _coerce "lib.fileset.toSource: `fileset`" maybeFileset; + fileset = _coerce "lib.fileset.toSource: `fileset`" filesetArg; rootFilesystemRoot = (splitRoot root).root; filesetFilesystemRoot = (splitRoot fileset._internalBase).root; - filter = _toSourceFilter fileset; + sourceFilter = _toSourceFilter fileset; in if ! isPath root then if isStringLike root then throw '' - lib.fileset.toSource: `root` "${toString root}" is a string-like value, but it should be a path instead. + lib.fileset.toSource: `root` ("${toString root}") is a string-like value, but it should be a path instead. Paths in strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.'' else throw '' @@ -154,29 +158,29 @@ If a directory does not recursively contain any file, it is omitted from the sto # See also ../path/README.md else if rootFilesystemRoot != filesetFilesystemRoot then throw '' - lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` "${toString root}": + lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` ("${toString root}"): `root`: root "${toString rootFilesystemRoot}" `fileset`: root "${toString filesetFilesystemRoot}" Different roots are not supported.'' else if ! pathExists root then throw '' - lib.fileset.toSource: `root` ${toString root} does not exist.'' + lib.fileset.toSource: `root` (${toString root}) does not exist.'' else if pathType root != "directory" then throw '' - lib.fileset.toSource: `root` ${toString root} is a file, but it should be a directory instead. Potential solutions: + lib.fileset.toSource: `root` (${toString root}) is a file, but it should be a directory instead. Potential solutions: - If you want to import the file into the store _without_ a containing directory, use string interpolation or `builtins.path` instead of this function. - If you want to import the file into the store _with_ a containing directory, set `root` to the containing directory, such as ${toString (dirOf root)}, and set `fileset` to the file path.'' else if ! hasPrefix root fileset._internalBase then throw '' - lib.fileset.toSource: `fileset` could contain files in ${toString fileset._internalBase}, which is not under the `root` ${toString root}. Potential solutions: + lib.fileset.toSource: `fileset` could contain files in ${toString fileset._internalBase}, which is not under the `root` (${toString root}). Potential solutions: - Set `root` to ${toString fileset._internalBase} or any directory higher up. This changes the layout of the resulting store path. - - Set `fileset` to a file set that cannot contain files outside the `root` ${toString root}. This could change the files included in the result.'' + - Set `fileset` to a file set that cannot contain files outside the `root` (${toString root}). This could change the files included in the result.'' else - builtins.seq filter + builtins.seq sourceFilter cleanSourceWith { name = "source"; src = root; - inherit filter; + filter = sourceFilter; }; /* @@ -209,8 +213,8 @@ If a directory does not recursively contain any file, it is omitted from the sto # This argument can also be a path, # which gets [implicitly coerced to a file set](#sec-fileset-path-coercion). fileset2: - let - filesets = _coerceMany "lib.fileset.union" [ + _unionMany + (_coerceMany "lib.fileset.union" [ { context = "first argument"; value = fileset1; @@ -219,9 +223,7 @@ If a directory does not recursively contain any file, it is omitted from the sto context = "second argument"; value = fileset2; } - ]; - in - _unionMany filesets; + ]); /* The file set containing all files that are in any of the given file sets. @@ -260,25 +262,20 @@ If a directory does not recursively contain any file, it is omitted from the sto # The elements can also be paths, # which get [implicitly coerced to file sets](#sec-fileset-path-coercion). filesets: - let - # We cannot rename matched attribute arguments, so let's work around it with an extra `let in` statement - maybeFilesets = filesets; - in - let - # Annotate the elements with context, used by _coerceMany for better errors - annotated = imap0 (i: el: { - context = "element ${toString i} of the argument"; - value = el; - }) maybeFilesets; - - filesets = _coerceMany "lib.fileset.unions" annotated; - in - if ! isList maybeFilesets then - throw "lib.fileset.unions: Expected argument to be a list, but got a ${typeOf maybeFilesets}." - else if maybeFilesets == [ ] then - # TODO: This could be supported, but requires an extra internal representation for the empty file set + if ! isList filesets then + throw "lib.fileset.unions: Expected argument to be a list, but got a ${typeOf filesets}." + else if filesets == [ ] then + # TODO: This could be supported, but requires an extra internal representation for the empty file set, which would be special for not having a base path. throw "lib.fileset.unions: Expected argument to be a list with at least one element, but it contains no elements." else - _unionMany filesets; + pipe filesets [ + # Annotate the elements with context, used by _coerceMany for better errors + (imap0 (i: el: { + context = "element ${toString i}"; + value = el; + })) + (_coerceMany "lib.fileset.unions") + _unionMany + ]; } diff --git a/lib/fileset/internal.nix b/lib/fileset/internal.nix index 3462b510b367..2c329edb390d 100644 --- a/lib/fileset/internal.nix +++ b/lib/fileset/internal.nix @@ -135,14 +135,14 @@ rec { else if ! isPath value then if isStringLike value then throw '' - ${context} "${toString value}" is a string-like value, but it should be a path instead. + ${context} ("${toString value}") is a string-like value, but it should be a path instead. Paths represented as strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.'' else throw '' ${context} is of type ${typeOf value}, but it should be a path instead.'' else if ! pathExists value then throw '' - ${context} ${toString value} does not exist.'' + ${context} (${toString value}) does not exist.'' else _singleton value; @@ -341,9 +341,6 @@ rec { # The common base path assembled from a filesystem root and the common components commonBase = append first._internalBaseRoot (join commonBaseComponents); - # The number of path components common to all base paths - commonBaseComponentsCount = length commonBaseComponents; - # A list of filesetTree's that all have the same base path # This is achieved by nesting the trees into the components they have over the common base path # E.g. `union /foo/bar /foo/baz` has the base path /foo @@ -352,7 +349,7 @@ rec { # Therefore allowing combined operations over them. trees = map (fileset: setAttrByPath - (drop commonBaseComponentsCount fileset._internalBaseComponents) + (drop (length commonBaseComponents) fileset._internalBaseComponents) fileset._internalTree ) filesets; diff --git a/lib/fileset/tests.sh b/lib/fileset/tests.sh index 7d38aaa7f41d..0ea96859e7a3 100755 --- a/lib/fileset/tests.sh +++ b/lib/fileset/tests.sh @@ -236,7 +236,7 @@ checkFileset() ( #### Error messages ##### # Absolute paths in strings cannot be passed as `root` -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. +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. \s*Paths in strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.' # Only paths are accepted as `root` @@ -246,18 +246,18 @@ expectFailure 'toSource { root = 10; fileset = ./.; }' 'lib.fileset.toSource: `r mkdir -p {foo,bar}/mock-root expectFailure 'with ((import ).extend (import )).fileset; toSource { root = ./foo/mock-root; fileset = ./bar/mock-root; } -' 'lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` "'"$work"'/foo/mock-root": +' 'lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` \("'"$work"'/foo/mock-root"\): \s*`root`: root "'"$work"'/foo/mock-root" \s*`fileset`: root "'"$work"'/bar/mock-root" \s*Different roots are not supported.' rm -rf * # `root` needs to exist -expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `root` '"$work"'/a does not exist.' +expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `root` \('"$work"'/a\) does not exist.' # `root` needs to be a file touch a -expectFailure 'toSource { root = ./a; fileset = ./a; }' 'lib.fileset.toSource: `root` '"$work"'/a is a file, but it should be a directory instead. Potential solutions: +expectFailure 'toSource { root = ./a; fileset = ./a; }' 'lib.fileset.toSource: `root` \('"$work"'/a\) is a file, but it should be a directory instead. Potential solutions: \s*- If you want to import the file into the store _without_ a containing directory, use string interpolation or `builtins.path` instead of this function. \s*- If you want to import the file into the store _with_ a containing directory, set `root` to the containing directory, such as '"$work"', and set `fileset` to the file path.' rm -rf * @@ -267,18 +267,18 @@ expectFailure 'toSource { root = ./.; fileset = abort "This should be evaluated" # Only paths under `root` should be able to influence the result mkdir a -expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `fileset` could contain files in '"$work"', which is not under the `root` '"$work"'/a. Potential solutions: +expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `fileset` could contain files in '"$work"', which is not under the `root` \('"$work"'/a\). Potential solutions: \s*- Set `root` to '"$work"' or any directory higher up. This changes the layout of the resulting store path. -\s*- Set `fileset` to a file set that cannot contain files outside the `root` '"$work"'/a. This could change the files included in the result.' +\s*- Set `fileset` to a file set that cannot contain files outside the `root` \('"$work"'/a\). This could change the files included in the result.' rm -rf * # Path coercion only works for paths expectFailure 'toSource { root = ./.; fileset = 10; }' 'lib.fileset.toSource: `fileset` is of type int, but it should be a path instead.' -expectFailure 'toSource { root = ./.; fileset = "/some/path"; }' 'lib.fileset.toSource: `fileset` "/some/path" is a string-like value, but it should be a path instead. +expectFailure 'toSource { root = ./.; fileset = "/some/path"; }' 'lib.fileset.toSource: `fileset` \("/some/path"\) is a string-like value, but it should be a path instead. \s*Paths represented as strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.' # Path coercion errors for non-existent paths -expectFailure 'toSource { root = ./.; fileset = ./a; }' 'lib.fileset.toSource: `fileset` '"$work"'/a does not exist.' +expectFailure 'toSource { root = ./.; fileset = ./a; }' 'lib.fileset.toSource: `fileset` \('"$work"'/a\) does not exist.' # File sets cannot be evaluated directly expectFailure 'union ./. ./.' 'lib.fileset: Directly evaluating a file set is not supported. Use `lib.fileset.toSource` to turn it into a usable source instead.' @@ -395,16 +395,16 @@ expectFailure 'with ((import ).extend (import ).extend (import )).fileset; toSource { root = ./.; fileset = unions [ ./foo/mock-root ./bar/mock-root ]; } ' 'lib.fileset.unions: Filesystem roots are not the same: -\s*element 0 of the argument: root "'"$work"'/foo/mock-root" -\s*element 1 of the argument: root "'"$work"'/bar/mock-root" +\s*element 0: root "'"$work"'/foo/mock-root" +\s*element 1: root "'"$work"'/bar/mock-root" \s*Different roots are not supported.' rm -rf * # Coercion errors show the correct context -expectFailure 'toSource { root = ./.; fileset = union ./a ./.; }' 'lib.fileset.union: first argument '"$work"'/a does not exist.' -expectFailure 'toSource { root = ./.; fileset = union ./. ./b; }' 'lib.fileset.union: second argument '"$work"'/b does not exist.' -expectFailure 'toSource { root = ./.; fileset = unions [ ./a ./. ]; }' 'lib.fileset.unions: element 0 of the argument '"$work"'/a does not exist.' -expectFailure 'toSource { root = ./.; fileset = unions [ ./. ./b ]; }' 'lib.fileset.unions: element 1 of the argument '"$work"'/b does not exist.' +expectFailure 'toSource { root = ./.; fileset = union ./a ./.; }' 'lib.fileset.union: first argument \('"$work"'/a\) does not exist.' +expectFailure 'toSource { root = ./.; fileset = union ./. ./b; }' 'lib.fileset.union: second argument \('"$work"'/b\) does not exist.' +expectFailure 'toSource { root = ./.; fileset = unions [ ./a ./. ]; }' 'lib.fileset.unions: element 0 \('"$work"'/a\) does not exist.' +expectFailure 'toSource { root = ./.; fileset = unions [ ./. ./b ]; }' 'lib.fileset.unions: element 1 \('"$work"'/b\) does not exist.' # unions needs a list with at least 1 element expectFailure 'toSource { root = ./.; fileset = unions null; }' 'lib.fileset.unions: Expected argument to be a list, but got a null.'