+2
.github/CODEOWNERS
+2
.github/CODEOWNERS
+1
doc/default.nix
+1
doc/default.nix
···
+1
doc/functions.md
+1
doc/functions.md
+51
doc/functions/fileset.section.md
+51
doc/functions/fileset.section.md
···+<!-- TODO: Render this document in front of function documentation in case https://github.com/nix-community/nixdoc/issues/19 is ever supported -->+The [`lib.fileset`](#sec-functions-library-fileset) library allows you to work with _file sets_.+A file set is a mathematical set of local files that can be added to the Nix store for use in Nix derivations.+File sets are easy and safe to use, providing obvious and composable semantics with good error messages to prevent mistakes.+See the [function reference](#sec-functions-library-fileset) for function-specific documentation.+The file set library is currently very limited but is being expanded to include more functions over time.+All functions accepting file sets as arguments can also accept [paths](https://nixos.org/manual/nix/stable/language/values.html#type-path) as arguments.+- A path to a directory turns into a file set containing all files _recursively_ in that directory.+Because of this, a path to a directory that contains no files (recursively) will turn into a file set containing no files.+Only the [`toSource`](#function-library-lib.fileset.toSource) function adds files to the Nix store, and only those files contained in the `fileset` argument.+This is in contrast to using [paths in string interpolation](https://nixos.org/manual/nix/stable/language/values.html#type-path), which does add the entire referenced path to the store.+Here's a listing of which files get included when different path expressions get coerced to file sets:+- `./.` as a file set contains both `a/x` and `a/b/y` (`c/` does not contain any files and is therefore omitted).
+3
lib/README.md
+3
lib/README.md
+1
lib/default.nix
+1
lib/default.nix
+183
lib/fileset/README.md
+183
lib/fileset/README.md
···+The main goal of the file set library is to be able to select local files that should be added to the Nix store.+If the abstraction proves itself worthwhile but too slow, it can be still be optimized further.+The internal representation is versioned in order to allow file sets from different Nixpkgs versions to be composed with each other, see [`internal.nix`](./internal.nix) for the versions and conversions between them.+This section describes only the current representation, but past versions will have to be supported by the code.+Even entries that aren't included are present as `null` because it improves laziness and allows using this as a sort of `builtins.readDir` cache.+A directory with all its files included recursively, allowing early cutoff for some operations.+This specific string is chosen to be compatible with `builtins.readDir` for a simpler implementation.+These specific strings are chosen to be compatible with `builtins.readDir` for a simpler implementation.+Distinguishing between different file types is not strictly necessary for the functionality this library,+- (+) The point of this library is to provide high-level functions, users don't need to be concerned with how it's implemented+- (+) It allows adjustments to the representation, which is especially useful in the early days of the library.+- (+) It still allows the representation to be stabilized later if necessary and if it has proven itself+File set operations internally track the top-most directory that could influence the exact contents of a file set.+Specifically, `toSource` requires that the given `fileset` is completely determined by files within the directory specified by the `root` argument.+For example, even with `dir/file.txt` being the only file in `./.`, `toSource { root = ./dir; fileset = ./.; }` gives an error.+This is because `fileset` may as well be the result of filtering `./.` in a way that excludes `dir`.+- (+) This gives us the guarantee that adding new files to a project never breaks a file set expression.+- (-) It leads to errors when a sensible result could sometimes be returned, such as in the above example.+File sets can only represent a _set_ of local files, directories on their own are not representable.+- (+) There does not seem to be a sensible set of combinators when directories can be represented on their own.+- `./.` represents the files in `./.` _and_ the directory itself including its subdirectories, meaning that even if there's no files, the entire structure of `./.` is preserved+It could return the entire directory structure unchanged, but with all files removed, which would not be what one would expect.+What should the behavior be if `./foo` itself is excluded but all of its contents are included?+It leads to having to define when directories are recursed into, but then we're effectively back at how the `builtins.path`-based filters work.+- `./.` represents all files in `./.` _and_ the directory itself, but not its subdirectories, meaning that at least `./.` will be preserved even if it's empty.+In that case, `intersect ./. ./foo` should only include files and no directories themselves, since `./.` includes only `./.` as a directory, and same for `./foo`, so there's no overlap in directories.+But intuitively this operation should result in the same as `./foo` – everything else is just confusing.+- (-) Empty directories (even if they contain nested directories) are neither representable nor preserved when coercing from paths.+- (+) We can implement a workaround, allowing `toSource` to take an extra argument for ensuring certain extra directories exist in the result.+- (-) It slows down store imports, since the evaluator needs to traverse the entire tree to remove any empty directories+- Require importing the entire `root` into the store such that derivations can be used to do the filtering+- (+) The convenient path coercion like `union ./foo ./bar` wouldn't work for absolute paths, requiring more verbose alternate interfaces:+Verbose and dangerous because if `root` was a path, the entire path would get imported into the store.+Does not allow debug printing intermediate file set contents, since we don't know the paths contents before having a `root`.+Makes library functions impure since they depend on the contextual root path, questionable composability.+- (+) The point of the file set abstraction is to specify which files should get imported into the store.+This should be a separate abstraction as e.g. `pkgs.drvLayout` instead, which could have a similar interface but be specific to derivations.+Additional capabilities could be supported that can't be done at evaluation time, such as renaming files, creating new directories, setting executable bits, etc.+- (+) There's no point in using this library for a single file, since you can't do anything other than add it to the store or not.+And it would be unclear how the library should behave if the one file wouldn't be added to the store:+`toSource { root = ./file.nix; fileset = <empty>; }` has no reasonable result because returing an empty store path wouldn't match the file type, and there's no way to have an empty file store path, whatever that would mean.+- > The file set library is currently very limited but is being expanded to include more functions over time.+- Once a tracing function exists, `__noEval` in [internal.nix](./internal.nix) should mention it+- If/Once a function to convert `lib.sources` values into file sets exists, the `_coerce` and `toSource` functions should be updated to mention that function in the error when such a value is passed+- If/Once a function exists that can optionally include a path depending on whether it exists, the error message for the path not existing in `_coerce` should mention the new function
+94
lib/fileset/benchmark.sh
+94
lib/fileset/benchmark.sh
···+echo -e "Statistic $stat ($newValue) is \e[0;31m$percent% (+$(( newValue - oldValue )))\e[0m of the old value $oldValue" >&2+echo -e "Statistic $stat ($newValue) is \e[0;32m$percent% (-$(( oldValue - newValue )))\e[0m of the old value $oldValue" >&2
+131
lib/fileset/default.nix
+131
lib/fileset/default.nix
···+Add the local files contained in `fileset` to the store as a single [store path](https://nixos.org/manual/nix/stable/glossary#gloss-store-path) rooted at `root`.+The result is the store path as a string-like value, making it usable e.g. as the `src` of a derivation, or in string interpolation:+# The file set coerced from path ./bar could contain files outside the root ./foo, which is not allowed+(required) The local directory [path](https://nixos.org/manual/nix/stable/language/values.html#type-path) that will correspond to the root of the resulting store path.+Paths in [strings](https://nixos.org/manual/nix/stable/language/values.html#type-string), including Nix store paths, cannot be passed as `root`.+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.+Currently the only way to construct file sets is using [implicit coercion from paths](#sec-fileset-path-coercion).+If a directory does not recursively contain any file, it is omitted from the store path contents.+# We cannot rename matched attribute arguments, so let's work around it with an extra `let in` statement+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.''+lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` "${toString root}":+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.''+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.''
+274
lib/fileset/internal.nix
+274
lib/fileset/internal.nix
···+# - This file is internal, so the return value doesn't matter, no need to make things overridable+lib.fileset: Directly evaluating a file set is not supported. Use `lib.fileset.toSource` to turn it into a usable source instead.'';+${context} is a file set created from a future version of the file set library with a different internal representation:+Paths represented as strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.''+Nest a filesetTree under some extra components, while filling out all the other directory entries that aren't included with null+${elemAt extraComponents index} = recurse (index + 1) (append focusPath (elemAt extraComponents index));+# Triggers when we have the same as a `readDir path`, so we can turn it back into an equivalent "directory".+# The path may have no more components though, meaning the filter is running on the directory itself,+# If we do have more components, the filter runs on some entry inside this directory, so we need to recurse+# We don't use pathSlash here because we only needed the trailing slash for the prefix matching.+# Special case because the code below assumes that the _internalBase is always included in the result
+26
lib/fileset/mock-splitRoot.nix
+26
lib/fileset/mock-splitRoot.nix
···
+350
lib/fileset/tests.sh
+350
lib/fileset/tests.sh
···+# Crudely unquotes a JSON string by just taking everything between the first and the second quote.+# Check that a nix expression evaluates successfully (strictly, coercing to json, read-write-mode).+die "$expr should have evaluated to this regex pattern:\n\n$expectedResultRegex\n\nbut this was the actual result:\n\n$result"+if result=$(nix-instantiate --eval --strict --json --read-write-mode --show-trace 2>"$tmp/stderr" \+die "$expr should have errored with this regex pattern:\n\n$expectedErrorRegex\n\nbut this was the actual error:\n\n$stderr"+echo "Warning: Not checking that excluded files don't get accessed since inotifywait is not available" >&2+# [a/b] =1 # Declare that file a/b should exist and expect it to be included in the store path+# [c/d/]= # Declare that directory c/d/ should exist and expect it to be excluded in the store path+# Process the tree into separate arrays for included paths, excluded paths and excluded files.+exec inotifywait --format='%e %w' --event open,delete_self --monitor "${excludedFiles[@]}" 2>&1+# Call toSource with the fileset, triggering open events for all files that are added to the store+# For each path that should be excluded, make sure it doesn't occur in the resulting store path+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.'+expectFailure 'toSource { root = 10; fileset = ./.; }' 'lib.fileset.toSource: `root` is of type int, but it should be a path instead.'+expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;+' 'lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` "'"$work"'/foo/mock-root":+expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `root` '"$work"'/a does not exist.'+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.'+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.'+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.+\s*Paths represented as strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.'+expectFailure 'toSource { root = ./.; fileset = ./a; }' 'lib.fileset.toSource: `fileset` '"$work"'/a does not exist.'+expectFailure '_create ./. null' 'lib.fileset: Directly evaluating a file set is not supported. Use `lib.fileset.toSource` to turn it into a usable source instead.'+expectFailure '_coerce "<tests>: value" { _type = "fileset"; _internalVersion = 1; }' '<tests>: value is a file set created from a future version of the file set library with a different internal representation:+expectSuccess 'toSource { root = ./.; fileset = ./.; }' '"'"${NIX_STORE_DIR:-/nix/store}"'/.*-source"'+# We can't easily test this with the above functions because we can't write to the filesystem root and we don't want to make any assumptions which files are there in the sandbox+# TODO: Once we have combinators and a property testing library, derive property tests from https://en.wikipedia.org/wiki/Algebra_of_sets
+5
-1
lib/tests/release.nix
+5
-1
lib/tests/release.nix
·········