diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/asserts.nix | 29 | ||||
-rw-r--r-- | lib/customisation.nix | 105 | ||||
-rw-r--r-- | lib/fileset/README.md | 3 | ||||
-rw-r--r-- | lib/fileset/default.nix | 150 | ||||
-rw-r--r-- | lib/fileset/internal.nix | 169 | ||||
-rwxr-xr-x | lib/fileset/tests.sh | 226 | ||||
-rw-r--r-- | lib/fixed-points.nix | 2 | ||||
-rw-r--r-- | lib/licenses.nix | 15 | ||||
-rw-r--r-- | lib/meta.nix | 9 | ||||
-rw-r--r-- | lib/strings.nix | 14 | ||||
-rw-r--r-- | lib/systems/examples.nix | 1 | ||||
-rw-r--r-- | lib/systems/inspect.nix | 26 | ||||
-rw-r--r-- | lib/systems/parse.nix | 11 | ||||
-rwxr-xr-x | lib/tests/filesystem.sh | 10 | ||||
-rw-r--r-- | lib/tests/misc.nix | 33 |
15 files changed, 702 insertions, 101 deletions
diff --git a/lib/asserts.nix b/lib/asserts.nix index 98e0b490acf2..8d0a621f4c1c 100644 --- a/lib/asserts.nix +++ b/lib/asserts.nix @@ -50,4 +50,33 @@ rec { lib.generators.toPretty {} xs}, but is: ${ lib.generators.toPretty {} val}"; + /* Specialized `assertMsg` for checking if every one of `vals` is one of the elements + of the list `xs`. Useful for checking lists of supported attributes. + + Example: + let sslLibraries = [ "libressl" "bearssl" ]; + in assertEachOneOf "sslLibraries" sslLibraries [ "openssl" "bearssl" ] + stderr> error: each element in sslLibraries must be one of [ + stderr> "openssl" + stderr> "bearssl" + stderr> ], but is: [ + stderr> "libressl" + stderr> "bearssl" + stderr> ] + + Type: + assertEachOneOf :: String -> List ComparableVal -> List ComparableVal -> Bool + */ + assertEachOneOf = + # The name of the variable the user entered `val` into, for inclusion in the error message + name: + # The list of values of what the user provided, to be compared against the values in `xs` + vals: + # The list of valid values + xs: + assertMsg + (lib.all (val: lib.elem val xs) vals) + "each element in ${name} must be one of ${ + lib.generators.toPretty {} xs}, but is: ${ + lib.generators.toPretty {} vals}"; } diff --git a/lib/customisation.nix b/lib/customisation.nix index 5ef4f29e6f6a..c7d40339d05f 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -13,16 +13,7 @@ rec { scenarios (e.g. in ~/.config/nixpkgs/config.nix). For instance, if you want to "patch" the derivation returned by a package function in Nixpkgs to build another version than what the - function itself provides, you can do something like this: - - mySed = overrideDerivation pkgs.gnused (oldAttrs: { - name = "sed-4.2.2-pre"; - src = fetchurl { - url = ftp://alpha.gnu.org/gnu/sed/sed-4.2.2-pre.tar.bz2; - hash = "sha256-MxBJRcM2rYzQYwJ5XKxhXTQByvSg5jZc5cSHEZoB2IY="; - }; - patches = []; - }); + function itself provides. For another application, see build-support/vm, where this function is used to build arbitrary derivations inside a QEMU @@ -35,6 +26,19 @@ rec { You should in general prefer `drv.overrideAttrs` over this function; see the nixpkgs manual for more information on overriding. + + Example: + mySed = overrideDerivation pkgs.gnused (oldAttrs: { + name = "sed-4.2.2-pre"; + src = fetchurl { + url = ftp://alpha.gnu.org/gnu/sed/sed-4.2.2-pre.tar.bz2; + hash = "sha256-MxBJRcM2rYzQYwJ5XKxhXTQByvSg5jZc5cSHEZoB2IY="; + }; + patches = []; + }); + + Type: + overrideDerivation :: Derivation -> ( Derivation -> AttrSet ) -> Derivation */ overrideDerivation = drv: f: let @@ -55,6 +59,10 @@ rec { injects `override` attribute which can be used to override arguments of the function. + Please refer to documentation on [`<pkg>.overrideDerivation`](#sec-pkg-overrideDerivation) to learn about `overrideDerivation` and caveats + related to its use. + + Example: nix-repl> x = {a, b}: { result = a + b; } nix-repl> y = lib.makeOverridable x { a = 1; b = 2; } @@ -65,9 +73,8 @@ rec { nix-repl> y.override { a = 10; } { override = «lambda»; overrideDerivation = «lambda»; result = 12; } - Please refer to "Nixpkgs Contributors Guide" section - "<pkg>.overrideDerivation" to learn about `overrideDerivation` and caveats - related to its use. + Type: + makeOverridable :: (AttrSet -> a) -> AttrSet -> a */ makeOverridable = f: lib.setFunctionArgs (origArgs: let @@ -105,20 +112,29 @@ rec { `autoArgs`. This function is intended to be partially parameterised, e.g., + ```nix callPackage = callPackageWith pkgs; pkgs = { libfoo = callPackage ./foo.nix { }; libbar = callPackage ./bar.nix { }; }; + ``` If the `libbar` function expects an argument named `libfoo`, it is automatically passed as an argument. Overrides or missing arguments can be supplied in `args`, e.g. + ```nix libbar = callPackage ./bar.nix { libfoo = null; enableX11 = true; }; + ``` + + <!-- TODO: Apply "Example:" tag to the examples above --> + + Type: + callPackageWith :: AttrSet -> ((AttrSet -> a) | Path) -> AttrSet -> a */ callPackageWith = autoArgs: fn: args: let @@ -129,7 +145,7 @@ rec { # This includes automatic ones and ones passed explicitly allArgs = builtins.intersectAttrs fargs autoArgs // args; - # A list of argument names that the function requires, but + # a list of argument names that the function requires, but # wouldn't be passed to it missingArgs = lib.attrNames # Filter out arguments that have a default value @@ -176,7 +192,11 @@ rec { /* Like callPackage, but for a function that returns an attribute set of derivations. The override function is added to the - individual attributes. */ + individual attributes. + + Type: + callPackagesWith :: AttrSet -> ((AttrSet -> AttrSet) | Path) -> AttrSet -> AttrSet + */ callPackagesWith = autoArgs: fn: args: let f = if lib.isFunction fn then fn else import fn; @@ -193,7 +213,11 @@ rec { /* Add attributes to each output of a derivation without changing - the derivation itself and check a given condition when evaluating. */ + the derivation itself and check a given condition when evaluating. + + Type: + extendDerivation :: Bool -> Any -> Derivation -> Derivation + */ extendDerivation = condition: passthru: drv: let outputs = drv.outputs or [ "out" ]; @@ -227,7 +251,11 @@ rec { /* Strip a derivation of all non-essential attributes, returning only those needed by hydra-eval-jobs. Also strictly evaluate the result to ensure that there are no thunks kept alive to prevent - garbage collection. */ + garbage collection. + + Type: + hydraJob :: (Derivation | Null) -> (Derivation | Null) + */ hydraJob = drv: let outputs = drv.outputs or ["out"]; @@ -265,7 +293,11 @@ rec { called with the overridden packages. The package sets may be hierarchical: the packages in the set are called with the scope provided by `newScope` and the set provides a `newScope` attribute - which can form the parent scope for later package sets. */ + which can form the parent scope for later package sets. + + Type: + makeScope :: (AttrSet -> ((AttrSet -> a) | Path) -> AttrSet -> a) -> (AttrSet -> AttrSet) -> AttrSet + */ makeScope = newScope: f: let self = f self // { newScope = scope: newScope (self // scope); @@ -287,13 +319,48 @@ rec { { inherit otherSplices keep extra f; }; /* Like makeScope, but aims to support cross compilation. It's still ugly, but - hopefully it helps a little bit. */ + hopefully it helps a little bit. + + Type: + makeScopeWithSplicing' :: + { splicePackages :: Splice -> AttrSet + , newScope :: AttrSet -> ((AttrSet -> a) | Path) -> AttrSet -> a + } + -> { otherSplices :: Splice, keep :: AttrSet -> AttrSet, extra :: AttrSet -> AttrSet } + -> AttrSet + + Splice :: + { pkgsBuildBuild :: AttrSet + , pkgsBuildHost :: AttrSet + , pkgsBuildTarget :: AttrSet + , pkgsHostHost :: AttrSet + , pkgsHostTarget :: AttrSet + , pkgsTargetTarget :: AttrSet + } + */ makeScopeWithSplicing' = { splicePackages , newScope }: { otherSplices + # Attrs from `self` which won't be spliced. + # Avoid using keep, it's only used for a python hook workaround, added in PR #104201. + # ex: `keep = (self: { inherit (self) aAttr; })` , keep ? (_self: {}) + # Additional attrs to add to the sets `callPackage`. + # When the package is from a subset (but not a subset within a package IS #211340) + # within `spliced0` it will be spliced. + # When using an package outside the set but it's available from `pkgs`, use the package from `pkgs.__splicedPackages`. + # If the package is not available within the set or in `pkgs`, such as a package in a let binding, it will not be spliced + # ex: + # ``` + # nix-repl> darwin.apple_sdk.frameworks.CoreFoundation + # «derivation ...CoreFoundation-11.0.0.drv» + # nix-repl> darwin.CoreFoundation + # error: attribute 'CoreFoundation' missing + # nix-repl> darwin.callPackage ({ CoreFoundation }: CoreFoundation) { } + # «derivation ...CoreFoundation-11.0.0.drv» + # ``` , extra ? (_spliced0: {}) , f }: diff --git a/lib/fileset/README.md b/lib/fileset/README.md index d7398438826e..8518d88a7d64 100644 --- a/lib/fileset/README.md +++ b/lib/fileset/README.md @@ -225,6 +225,9 @@ Arguments: This use case makes little sense for files that are already in 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. +- (+) An API for filtering/transforming Nix store paths could be much more powerful, + because it's not limited to just what is possible at evaluation time with `builtins.path`. + Operations such as moving and adding files would be supported. ### Single files diff --git a/lib/fileset/default.nix b/lib/fileset/default.nix index 640d0b49d3ea..372d445269f5 100644 --- a/lib/fileset/default.nix +++ b/lib/fileset/default.nix @@ -8,8 +8,10 @@ let _toSourceFilter _fromSourceFilter _unionMany + _fileFilter _printFileset _intersection + _difference ; inherit (builtins) @@ -43,6 +45,7 @@ let ; inherit (lib.trivial) + isFunction pipe ; @@ -121,11 +124,10 @@ 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. -<!-- Ignore the indentation here, this is a nixdoc rendering bug that needs to be fixed: https://github.com/nix-community/nixdoc/issues/75 --> -:::{.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. -::: + :::{.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. + ::: */ root, /* @@ -134,10 +136,9 @@ 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). -<!-- Ignore the indentation here, this is a nixdoc rendering bug that needs to be fixed: https://github.com/nix-community/nixdoc/issues/75 --> -:::{.note} -If a directory does not recursively contain any file, it is omitted from the store path contents. -::: + :::{.note} + If a directory does not recursively contain any file, it is omitted from the store path contents. + ::: */ fileset, @@ -160,7 +161,7 @@ If a directory does not recursively contain any file, it is omitted from the sto Note that this only works for sources created from paths.'' else 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 '' @@ -169,13 +170,13 @@ If a directory does not recursively contain any file, it is omitted from the sto # See also ../path/README.md else if ! fileset._internalIsEmptyWithoutBase && rootFilesystemRoot != filesetFilesystemRoot then throw '' - 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.'' + lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` (${toString root}): + `root`: Filesystem root is "${toString rootFilesystemRoot}" + `fileset`: Filesystem root is "${toString filesetFilesystemRoot}" + Different filesystem 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}) is a path that 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: @@ -296,11 +297,11 @@ If a directory does not recursively contain any file, it is omitted from the sto _unionMany (_coerceMany "lib.fileset.union" [ { - context = "first argument"; + context = "First argument"; value = fileset1; } { - context = "second argument"; + context = "Second argument"; value = fileset2; } ]); @@ -342,12 +343,13 @@ If a directory does not recursively contain any file, it is omitted from the sto # which get [implicitly coerced to file sets](#sec-fileset-path-coercion). filesets: if ! isList filesets then - throw "lib.fileset.unions: Expected argument to be a list, but got a ${typeOf filesets}." + throw '' + lib.fileset.unions: Argument is of type ${typeOf filesets}, but it should be a list instead.'' else pipe filesets [ # Annotate the elements with context, used by _coerceMany for better errors (imap0 (i: el: { - context = "element ${toString i}"; + context = "Element ${toString i}"; value = el; })) (_coerceMany "lib.fileset.unions") @@ -355,6 +357,56 @@ If a directory does not recursively contain any file, it is omitted from the sto ]; /* + Filter a file set to only contain files matching some predicate. + + Type: + fileFilter :: + ({ + name :: String, + type :: String, + ... + } -> Bool) + -> FileSet + -> FileSet + + Example: + # Include all regular `default.nix` files in the current directory + fileFilter (file: file.name == "default.nix") ./. + + # Include all non-Nix files from the current directory + fileFilter (file: ! hasSuffix ".nix" file.name) ./. + + # Include all files that start with a "." in the current directory + fileFilter (file: hasPrefix "." file.name) ./. + + # Include all regular files (not symlinks or others) in the current directory + fileFilter (file: file.type == "regular") + */ + fileFilter = + /* + The predicate function to call on all files contained in given file set. + A file is included in the resulting file set if this function returns true for it. + + This function is called with an attribute set containing these attributes: + + - `name` (String): The name of the file + + - `type` (String, one of `"regular"`, `"symlink"` or `"unknown"`): The type of the file. + This matches result of calling [`builtins.readFileType`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-readFileType) on the file's path. + + Other attributes may be added in the future. + */ + predicate: + # The file set to filter based on the predicate function + fileset: + if ! isFunction predicate then + throw '' + lib.fileset.fileFilter: First argument is of type ${typeOf predicate}, but it should be a function.'' + else + _fileFilter predicate + (_coerce "lib.fileset.fileFilter: Second argument" fileset); + + /* The file set containing all files that are in both of two given file sets. See also [Intersection (set theory)](https://en.wikipedia.org/wiki/Intersection_(set_theory)). @@ -380,11 +432,11 @@ If a directory does not recursively contain any file, it is omitted from the sto let filesets = _coerceMany "lib.fileset.intersection" [ { - context = "first argument"; + context = "First argument"; value = fileset1; } { - context = "second argument"; + context = "Second argument"; value = fileset2; } ]; @@ -394,6 +446,58 @@ If a directory does not recursively contain any file, it is omitted from the sto (elemAt filesets 1); /* + The file set containing all files from the first file set that are not in the second file set. + See also [Difference (set theory)](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement). + + The given file sets are evaluated as lazily as possible, + with the first argument being evaluated first if needed. + + Type: + union :: FileSet -> FileSet -> FileSet + + Example: + # Create a file set containing all files from the current directory, + # except ones under ./tests + difference ./. ./tests + + let + # A set of Nix-related files + nixFiles = unions [ ./default.nix ./nix ./tests/default.nix ]; + in + # Create a file set containing all files under ./tests, except ones in `nixFiles`, + # meaning only without ./tests/default.nix + difference ./tests nixFiles + */ + difference = + # The positive file set. + # The result can only contain files that are also in this file set. + # + # This argument can also be a path, + # which gets [implicitly coerced to a file set](#sec-fileset-path-coercion). + positive: + # The negative file set. + # The result will never contain files that are also in this file set. + # + # This argument can also be a path, + # which gets [implicitly coerced to a file set](#sec-fileset-path-coercion). + negative: + let + filesets = _coerceMany "lib.fileset.difference" [ + { + context = "First argument (positive set)"; + value = positive; + } + { + context = "Second argument (negative set)"; + value = negative; + } + ]; + in + _difference + (elemAt filesets 0) + (elemAt filesets 1); + + /* Incrementally evaluate and trace a file set in a pretty way. This function is only intended for debugging purposes. The exact tracing format is unspecified and may change. @@ -428,7 +532,7 @@ If a directory does not recursively contain any file, it is omitted from the sto let # "fileset" would be a better name, but that would clash with the argument name, # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76 - actualFileset = _coerce "lib.fileset.trace: argument" fileset; + actualFileset = _coerce "lib.fileset.trace: Argument" fileset; in seq (_printFileset actualFileset) @@ -475,7 +579,7 @@ If a directory does not recursively contain any file, it is omitted from the sto let # "fileset" would be a better name, but that would clash with the argument name, # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76 - actualFileset = _coerce "lib.fileset.traceVal: argument" fileset; + actualFileset = _coerce "lib.fileset.traceVal: Argument" fileset; in seq (_printFileset actualFileset) diff --git a/lib/fileset/internal.nix b/lib/fileset/internal.nix index 45115d5a8a3d..717253f45715 100644 --- a/lib/fileset/internal.nix +++ b/lib/fileset/internal.nix @@ -7,7 +7,6 @@ let isString pathExists readDir - seq split trace typeOf @@ -17,7 +16,6 @@ let attrNames attrValues mapAttrs - setAttrByPath zipAttrsWith ; @@ -28,7 +26,6 @@ let inherit (lib.lists) all commonPrefix - drop elemAt filter findFirst @@ -184,7 +181,7 @@ rec { ${context} is of type ${typeOf value}, but it should be a file set or a path instead.'' else if ! pathExists value then throw '' - ${context} (${toString value}) does not exist.'' + ${context} (${toString value}) is a path that does not exist.'' else _singleton value; @@ -213,9 +210,9 @@ rec { if firstWithBase != null && differentIndex != null then throw '' ${functionContext}: Filesystem roots are not the same: - ${(head list).context}: root "${toString firstBaseRoot}" - ${(elemAt list differentIndex).context}: root "${toString (elemAt filesets differentIndex)._internalBaseRoot}" - Different roots are not supported.'' + ${(head list).context}: Filesystem root is "${toString firstBaseRoot}" + ${(elemAt list differentIndex).context}: Filesystem root is "${toString (elemAt filesets differentIndex)._internalBaseRoot}" + Different filesystem roots are not supported.'' else filesets; @@ -429,7 +426,7 @@ rec { # Filter suited when there's some files # This can't be used for when there's no files, because the base directory is always included nonEmpty = - path: _: + path: type: let # Add a slash to the path string, turning "/foo" to "/foo/", # making sure to not have any false prefix matches below. @@ -438,25 +435,37 @@ rec { # meaning this function can never receive "/" as an argument pathSlash = path + "/"; in - # Same as `hasPrefix pathSlash baseString`, but more efficient. - # With base /foo/bar we need to include /foo: - # hasPrefix "/foo/" "/foo/bar/" - if substring 0 (stringLength pathSlash) baseString == pathSlash then - true - # Same as `! hasPrefix baseString pathSlash`, but more efficient. - # With base /foo/bar we need to exclude /baz - # ! hasPrefix "/baz/" "/foo/bar/" - else if substring 0 baseLength pathSlash != baseString then - false - else - # Same as `removePrefix baseString path`, but more efficient. - # From the above code we know that hasPrefix baseString pathSlash holds, so this is safe. - # We don't use pathSlash here because we only needed the trailing slash for the prefix matching. - # With base /foo and path /foo/bar/baz this gives - # inTree (split "/" (removePrefix "/foo/" "/foo/bar/baz")) - # == inTree (split "/" "bar/baz") - # == inTree [ "bar" "baz" ] - inTree (split "/" (substring baseLength (-1) path)); + ( + # Same as `hasPrefix pathSlash baseString`, but more efficient. + # With base /foo/bar we need to include /foo: + # hasPrefix "/foo/" "/foo/bar/" + if substring 0 (stringLength pathSlash) baseString == pathSlash then + true + # Same as `! hasPrefix baseString pathSlash`, but more efficient. + # With base /foo/bar we need to exclude /baz + # ! hasPrefix "/baz/" "/foo/bar/" + else if substring 0 baseLength pathSlash != baseString then + false + else + # Same as `removePrefix baseString path`, but more efficient. + # From the above code we know that hasPrefix baseString pathSlash holds, so this is safe. + # We don't use pathSlash here because we only needed the trailing slash for the prefix matching. + # With base /foo and path /foo/bar/baz this gives + # inTree (split "/" (removePrefix "/foo/" "/foo/bar/baz")) + # == inTree (split "/" "bar/baz") + # == inTree [ "bar" "baz" ] + inTree (split "/" (substring baseLength (-1) path)) + ) + # This is a way have an additional check in case the above is true without any significant performance cost + && ( + # This relies on the fact that Nix only distinguishes path types "directory", "regular", "symlink" and "unknown", + # so everything except "unknown" is allowed, seems reasonable to rely on that + type != "unknown" + || throw '' + lib.fileset.toSource: `fileset` contains a file that cannot be added to the store: ${path} + This file is neither a regular file nor a symlink, the only file types supported by the Nix store. + Therefore the file set cannot be added to the Nix store as is. Make sure to not include that file to avoid this error.'' + ); in # Special case because the code below assumes that the _internalBase is always included in the result # which shouldn't be done when we have no files at all in the base @@ -696,4 +705,110 @@ rec { else # In all other cases it's the rhs rhs; + + # Compute the set difference between two file sets. + # The filesets must already be coerced and validated to be in the same filesystem root. + # Type: Fileset -> Fileset -> Fileset + _difference = positive: negative: + let + # The common base components prefix, e.g. + # (/foo/bar, /foo/bar/baz) -> /foo/bar + # (/foo/bar, /foo/baz) -> /foo + commonBaseComponentsLength = + # TODO: Have a `lib.lists.commonPrefixLength` function such that we don't need the list allocation from commonPrefix here + length ( + commonPrefix + positive._internalBaseComponents + negative._internalBaseComponents + ); + + # We need filesetTree's with the same base to be able to compute the difference between them + # This here is the filesetTree from the negative file set, but for a base path that matches the positive file set. + # Examples: + # For `difference /foo /foo/bar`, `negativeTreeWithPositiveBase = { bar = "directory"; }` + # because under the base path of `/foo`, only `bar` from the negative file set is included + # For `difference /foo/bar /foo`, `negativeTreeWithPositiveBase = "directory"` + # because under the base path of `/foo/bar`, everything from the negative file set is included + # For `difference /foo /bar`, `negativeTreeWithPositiveBase = null` + # because under the base path of `/foo`, nothing from the negative file set is included + negativeTreeWithPositiveBase = + if commonBaseComponentsLength == length positive._internalBaseComponents then + # The common prefix is the same as the positive base path, so the second path is equal or longer. + # We need to _shorten_ the negative filesetTree to the same base path as the positive one + # E.g. for `difference /foo /foo/bar` the common prefix is /foo, equal to the positive file set's base + # So we need to shorten the base of the tree for the negative argument from /foo/bar to just /foo + _shortenTreeBase positive._internalBaseComponents negative + else if commonBaseComponentsLength == length negative._internalBaseComponents then + # The common prefix is the same as the negative base path, so the first path is longer. + # We need to lengthen the negative filesetTree to the same base path as the positive one. + # E.g. for `difference /foo/bar /foo` the common prefix is /foo, equal to the negative file set's base + # So we need to lengthen the base of the tree for the negative argument from /foo to /foo/bar + _lengthenTreeBase positive._internalBaseComponents negative + else + # The common prefix is neither the first nor the second path. + # This means there's no overlap between the two file sets, + # and nothing from the negative argument should get removed from the positive one + # E.g for `difference /foo /bar`, we remove nothing to get the same as `/foo` + null; + + resultingTree = + _differenceTree + positive._internalBase + positive._internalTree + negativeTreeWithPositiveBase; + in + # If the first file set is empty, we can never have any files in the result + if positive._internalIsEmptyWithoutBase then + _emptyWithoutBase + # If the second file set is empty, nothing gets removed, so the result is just the first file set + else if negative._internalIsEmptyWithoutBase then + positive + else + # We use the positive file set base for the result, + # because only files from the positive side may be included, + # which is what base path is for + _create positive._internalBase resultingTree; + + # Computes the set difference of two filesetTree's + # Type: Path -> filesetTree -> filesetTree + _differenceTree = path: lhs: rhs: + # If the lhs doesn't have any files, or the right hand side includes all files + if lhs == null || isString rhs then + # The result will always be empty + null + # If the right hand side has no files + else if rhs == null then + # The result is always the left hand side, because nothing gets removed + lhs + else + # Otherwise we always have two attribute sets to recurse into + mapAttrs (name: lhsValue: + _differenceTree (path + "/${name}") lhsValue (rhs.${name} or null) + ) (_directoryEntries path lhs); + + _fileFilter = predicate: fileset: + let + recurse = path: tree: + mapAttrs (name: subtree: + if isAttrs subtree || subtree == "directory" then + recurse (path + "/${name}") subtree + else if + predicate { + inherit name; + type = subtree; + # To ensure forwards compatibility with more arguments being added in the future, + # adding an attribute which can't be deconstructed :) + "lib.fileset.fileFilter: The predicate function passed as the first argument must be able to handle extra attributes for future compatibility. If you're using `{ name, file }:`, use `{ name, file, ... }:` instead." = null; + } + then + subtree + else + null + ) (_directoryEntries path tree); + in + if fileset._internalIsEmptyWithoutBase then + _emptyWithoutBase + else + _create fileset._internalBase + (recurse fileset._internalBase fileset._internalTree); } diff --git a/lib/fileset/tests.sh b/lib/fileset/tests.sh index 617794524824..796a03b52f0e 100755 --- a/lib/fileset/tests.sh +++ b/lib/fileset/tests.sh @@ -336,7 +336,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.' expectFailure 'toSource { root = cleanSourceWith { src = ./.; }; fileset = ./.; }' 'lib.fileset.toSource: `root` is a `lib.sources`-based value, but it should be a path instead. @@ -350,21 +350,21 @@ expectFailure 'toSource { root = 10; fileset = ./.; }' 'lib.fileset.toSource: `r mkdir -p {foo,bar}/mock-root expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).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"\): -\s*`root`: root "'"$work"'/foo/mock-root" -\s*`fileset`: root "'"$work"'/bar/mock-root" -\s*Different roots are not supported.' -rm -rf * +' 'lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` \('"$work"'/foo/mock-root\): +\s*`root`: Filesystem root is "'"$work"'/foo/mock-root" +\s*`fileset`: Filesystem root is "'"$work"'/bar/mock-root" +\s*Different filesystem 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\) is a path that 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: \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 * +rm -rf -- * # The fileset argument should be evaluated, even if the directory is empty expectFailure 'toSource { root = ./.; fileset = abort "This should be evaluated"; }' 'evaluation aborted with the following error message: '\''This should be evaluated'\' @@ -374,7 +374,14 @@ 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: \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.' -rm -rf * +rm -rf -- * + +# non-regular and non-symlink files cannot be added to the Nix store +mkfifo a +expectFailure 'toSource { root = ./.; fileset = ./a; }' 'lib.fileset.toSource: `fileset` contains a file that cannot be added to the store: '"$work"'/a +\s*This file is neither a regular file nor a symlink, the only file types supported by the Nix store. +\s*Therefore the file set cannot be added to the Nix store as is. Make sure to not include that file to avoid this error.' +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 file set or a path instead.' @@ -385,7 +392,7 @@ expectFailure 'toSource { root = ./.; fileset = cleanSourceWith { src = ./.; }; \s*Note that this only works for sources created from paths.' # 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\) is a path that does not exist.' # File sets cannot be evaluated directly expectFailure 'union ./. ./.' 'lib.fileset: Directly evaluating a file set is not supported. @@ -508,26 +515,26 @@ mkdir -p {foo,bar}/mock-root expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset; toSource { root = ./.; fileset = union ./foo/mock-root ./bar/mock-root; } ' 'lib.fileset.union: Filesystem roots are not the same: -\s*first argument: root "'"$work"'/foo/mock-root" -\s*second argument: root "'"$work"'/bar/mock-root" -\s*Different roots are not supported.' +\s*First argument: Filesystem root is "'"$work"'/foo/mock-root" +\s*Second argument: Filesystem root is "'"$work"'/bar/mock-root" +\s*Different filesystem roots are not supported.' expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset; toSource { root = ./.; fileset = unions [ ./foo/mock-root ./bar/mock-root ]; } ' 'lib.fileset.unions: Filesystem roots are not the same: -\s*element 0: root "'"$work"'/foo/mock-root" -\s*element 1: root "'"$work"'/bar/mock-root" -\s*Different roots are not supported.' -rm -rf * +\s*Element 0: Filesystem root is "'"$work"'/foo/mock-root" +\s*Element 1: Filesystem root is "'"$work"'/bar/mock-root" +\s*Different filesystem 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 \('"$work"'/a\) does not exist.' -expectFailure 'toSource { root = ./.; fileset = unions [ ./. ./b ]; }' 'lib.fileset.unions: element 1 \('"$work"'/b\) does not exist.' +expectFailure 'toSource { root = ./.; fileset = union ./a ./.; }' 'lib.fileset.union: First argument \('"$work"'/a\) is a path that does not exist.' +expectFailure 'toSource { root = ./.; fileset = union ./. ./b; }' 'lib.fileset.union: Second argument \('"$work"'/b\) is a path that does not exist.' +expectFailure 'toSource { root = ./.; fileset = unions [ ./a ./. ]; }' 'lib.fileset.unions: Element 0 \('"$work"'/a\) is a path that does not exist.' +expectFailure 'toSource { root = ./.; fileset = unions [ ./. ./b ]; }' 'lib.fileset.unions: Element 1 \('"$work"'/b\) is a path that does not exist.' # unions needs a list -expectFailure 'toSource { root = ./.; fileset = unions null; }' 'lib.fileset.unions: Expected argument to be a list, but got a null.' +expectFailure 'toSource { root = ./.; fileset = unions null; }' 'lib.fileset.unions: Argument is of type null, but it should be a list instead.' # The tree of later arguments should not be evaluated if a former argument already includes all files tree=() @@ -621,14 +628,14 @@ mkdir -p {foo,bar}/mock-root expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset; toSource { root = ./.; fileset = intersection ./foo/mock-root ./bar/mock-root; } ' 'lib.fileset.intersection: Filesystem roots are not the same: -\s*first argument: root "'"$work"'/foo/mock-root" -\s*second argument: root "'"$work"'/bar/mock-root" -\s*Different roots are not supported.' +\s*First argument: Filesystem root is "'"$work"'/foo/mock-root" +\s*Second argument: Filesystem root is "'"$work"'/bar/mock-root" +\s*Different filesystem roots are not supported.' rm -rf -- * # Coercion errors show the correct context -expectFailure 'toSource { root = ./.; fileset = intersection ./a ./.; }' 'lib.fileset.intersection: first argument \('"$work"'/a\) does not exist.' -expectFailure 'toSource { root = ./.; fileset = intersection ./. ./b; }' 'lib.fileset.intersection: second argument \('"$work"'/b\) does not exist.' +expectFailure 'toSource { root = ./.; fileset = intersection ./a ./.; }' 'lib.fileset.intersection: First argument \('"$work"'/a\) is a path that does not exist.' +expectFailure 'toSource { root = ./.; fileset = intersection ./. ./b; }' 'lib.fileset.intersection: Second argument \('"$work"'/b\) is a path that does not exist.' # The tree of later arguments should not be evaluated if a former argument already excludes all files tree=( @@ -702,6 +709,171 @@ tree=( ) checkFileset 'intersection (unions [ ./a/b ./c/d ./c/e ]) (unions [ ./a ./c/d/f ./c/e ])' +## Difference + +# Subtracting something from itself results in nothing +tree=( + [a]=0 +) +checkFileset 'difference ./. ./.' + +# The tree of the second argument should not be evaluated if not needed +checkFileset 'difference _emptyWithoutBase (_create ./. (abort "This should not be used!"))' +checkFileset 'difference (_create ./. null) (_create ./. (abort "This should not be used!"))' + +# Subtracting nothing gives the same thing back +tree=( + [a]=1 +) +checkFileset 'difference ./. _emptyWithoutBase' +checkFileset 'difference ./. (_create ./. null)' + +# Subtracting doesn't influence the base path +mkdir a b +touch {a,b}/x +expectEqual 'toSource { root = ./a; fileset = difference ./a ./b; }' 'toSource { root = ./a; fileset = ./a; }' +rm -rf -- * + +# Also not the other way around +mkdir a +expectFailure 'toSource { root = ./a; fileset = difference ./. ./a; }' '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.' +rm -rf -- * + +# Difference actually works +# We test all combinations of ./., ./a, ./a/x and ./b +tree=( + [a/x]=0 + [a/y]=0 + [b]=0 + [c]=0 +) +checkFileset 'difference ./. ./.' +checkFileset 'difference ./a ./.' +checkFileset 'difference ./a/x ./.' +checkFileset 'difference ./b ./.' +checkFileset 'difference ./a ./a' +checkFileset 'difference ./a/x ./a' +checkFileset 'difference ./a/x ./a/x' +checkFileset 'difference ./b ./b' +tree=( + [a/x]=0 + [a/y]=0 + [b]=1 + [c]=1 +) +checkFileset 'difference ./. ./a' +tree=( + [a/x]=1 + [a/y]=1 + [b]=0 + [c]=0 +) +checkFileset 'difference ./a ./b' +tree=( + [a/x]=1 + [a/y]=0 + [b]=0 + [c]=0 +) +checkFileset 'difference ./a/x ./b' +tree=( + [a/x]=0 + [a/y]=1 + [b]=0 + [c]=0 +) +checkFileset 'difference ./a ./a/x' +tree=( + [a/x]=0 + [a/y]=0 + [b]=1 + [c]=0 +) +checkFileset 'difference ./b ./a' +checkFileset 'difference ./b ./a/x' +tree=( + [a/x]=0 + [a/y]=1 + [b]=1 + [c]=1 +) +checkFileset 'difference ./. ./a/x' +tree=( + [a/x]=1 + [a/y]=1 + [b]=0 + [c]=1 +) +checkFileset 'difference ./. ./b' + +## File filter + +# The predicate is not called when there's no files +tree=() +checkFileset 'fileFilter (file: abort "this is not needed") ./.' +checkFileset 'fileFilter (file: abort "this is not needed") _emptyWithoutBase' + +# The predicate must be able to handle extra attributes +touch a +expectFailure 'toSource { root = ./.; fileset = fileFilter ({ name, type }: true) ./.; }' 'called with unexpected argument '\''"lib.fileset.fileFilter: The predicate function passed as the first argument must be able to handle extra attributes for future compatibility. If you'\''re using `\{ name, file \}:`, use `\{ name, file, ... \}:` instead."'\' +rm -rf -- * + +# .name is the name, and it works correctly, even recursively +tree=( + [a]=1 + [b]=0 + [c/a]=1 + [c/b]=0 + [d/c/a]=1 + [d/c/b]=0 +) +checkFileset 'fileFilter (file: file.name == "a") ./.' +tree=( + [a]=0 + [b]=1 + [c/a]=0 + [c/b]=1 + [d/c/a]=0 + [d/c/b]=1 +) +checkFileset 'fileFilter (file: file.name != "a") ./.' + +# `.type` is the file type +mkdir d +touch d/a +ln -s d/b d/b +mkfifo d/c +expectEqual \ + 'toSource { root = ./.; fileset = fileFilter (file: file.type == "regular") ./.; }' \ + 'toSource { root = ./.; fileset = ./d/a; }' +expectEqual \ + 'toSource { root = ./.; fileset = fileFilter (file: file.type == "symlink") ./.; }' \ + 'toSource { root = ./.; fileset = ./d/b; }' +expectEqual \ + 'toSource { root = ./.; fileset = fileFilter (file: file.type == "unknown") ./.; }' \ + 'toSource { root = ./.; fileset = ./d/c; }' +expectEqual \ + 'toSource { root = ./.; fileset = fileFilter (file: file.type != "regular") ./.; }' \ + 'toSource { root = ./.; fileset = union ./d/b ./d/c; }' +expectEqual \ + 'toSource { root = ./.; fileset = fileFilter (file: file.type != "symlink") ./.; }' \ + 'toSource { root = ./.; fileset = union ./d/a ./d/c; }' +expectEqual \ + 'toSource { root = ./.; fileset = fileFilter (file: file.type != "unknown") ./.; }' \ + 'toSource { root = ./.; fileset = union ./d/a ./d/b; }' +rm -rf -- * + +# It's lazy +tree=( + [b]=1 + [c/a]=1 +) +# Note that union evaluates the first argument first if necessary, that's why we can use ./c/a here +checkFileset 'union ./c/a (fileFilter (file: assert file.name != "a"; true) ./.)' +# but here we need to use ./c +checkFileset 'union (fileFilter (file: assert file.name != "a"; true) ./.) ./c' ## Tracing diff --git a/lib/fixed-points.nix b/lib/fixed-points.nix index 3444e95e15ad..3b5fdc9e8ea1 100644 --- a/lib/fixed-points.nix +++ b/lib/fixed-points.nix @@ -45,7 +45,7 @@ rec { } ``` - This is where `fix` comes in, it contains the syntactic that's not in `f` anymore. + This is where `fix` comes in, it contains the syntactic recursion that's not in `f` anymore. ```nix nix-repl> fix = f: diff --git a/lib/licenses.nix b/lib/licenses.nix index d9555ca66cb9..a90bab0b15dd 100644 --- a/lib/licenses.nix +++ b/lib/licenses.nix @@ -877,6 +877,21 @@ in mkLicense lset) ({ fullName = "Non-Profit Open Software License 3.0"; }; + nvidiaCuda = { + shortName = "CUDA EULA"; + fullName = "CUDA Toolkit End User License Agreement (EULA)"; + url = "https://docs.nvidia.com/cuda/eula/index.html#cuda-toolkit-supplement-license-agreement"; + free = false; + }; + + nvidiaCudaRedist = { + shortName = "CUDA EULA"; + fullName = "CUDA Toolkit End User License Agreement (EULA)"; + url = "https://docs.nvidia.com/cuda/eula/index.html#cuda-toolkit-supplement-license-agreement"; + free = false; + redistributable = true; + }; + obsidian = { fullName = "Obsidian End User Agreement"; url = "https://obsidian.md/eula"; diff --git a/lib/meta.nix b/lib/meta.nix index 44730a71551e..2e817c42327d 100644 --- a/lib/meta.nix +++ b/lib/meta.nix @@ -162,5 +162,12 @@ rec { getExe' pkgs.imagemagick "convert" => "/nix/store/5rs48jamq7k6sal98ymj9l4k2bnwq515-imagemagick-7.1.1-15/bin/convert" */ - getExe' = x: y: "${lib.getBin x}/bin/${y}"; + getExe' = x: y: + assert lib.assertMsg (lib.isDerivation x) + "lib.meta.getExe': The first argument is of type ${builtins.typeOf x}, but it should be a derivation instead."; + assert lib.assertMsg (lib.isString y) + "lib.meta.getExe': The second argument is of type ${builtins.typeOf y}, but it should be a string instead."; + assert lib.assertMsg (builtins.length (lib.splitString "/" y) == 1) + "lib.meta.getExe': The second argument \"${y}\" is a nested path with a \"/\" character, but it should just be the name of the executable instead."; + "${lib.getBin x}/bin/${y}"; } diff --git a/lib/strings.nix b/lib/strings.nix index 628669d86bbd..695aaaacd348 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -144,6 +144,20 @@ rec { */ concatLines = concatMapStrings (s: s + "\n"); + /* + Replicate a string n times, + and concatenate the parts into a new string. + + Type: replicate :: int -> string -> string + + Example: + replicate 3 "v" + => "vvv" + replicate 5 "hello" + => "hellohellohellohellohello" + */ + replicate = n: s: concatStrings (lib.lists.replicate n s); + /* Construct a Unix-style, colon-separated search path consisting of the given `subDir` appended to each of the given paths. diff --git a/lib/systems/examples.nix b/lib/systems/examples.nix index 0e704b7d7de0..75578b974945 100644 --- a/lib/systems/examples.nix +++ b/lib/systems/examples.nix @@ -115,6 +115,7 @@ rec { }; gnu64 = { config = "x86_64-unknown-linux-gnu"; }; + gnu64_simplekernel = gnu64 // platforms.pc_simplekernel; # see test/cross/default.nix gnu32 = { config = "i686-unknown-linux-gnu"; }; musl64 = { config = "x86_64-unknown-linux-musl"; }; diff --git a/lib/systems/inspect.nix b/lib/systems/inspect.nix index 022e459c3945..073df78797c7 100644 --- a/lib/systems/inspect.nix +++ b/lib/systems/inspect.nix @@ -100,6 +100,32 @@ rec { ]; }; + # given two patterns, return a pattern which is their logical AND. + # Since a pattern is a list-of-disjuncts, this needs to + patternLogicalAnd = pat1_: pat2_: + let + # patterns can be either a list or a (bare) singleton; turn + # them into singletons for uniform handling + pat1 = lib.toList pat1_; + pat2 = lib.toList pat2_; + in + lib.concatMap (attr1: + map (attr2: + lib.recursiveUpdateUntil + (path: subattr1: subattr2: + if (builtins.intersectAttrs subattr1 subattr2) == {} || subattr1 == subattr2 + then true + else throw '' + pattern conflict at path ${toString path}: + ${builtins.toJSON subattr1} + ${builtins.toJSON subattr2} + '') + attr1 + attr2 + ) + pat2) + pat1; + matchAnyAttrs = patterns: if builtins.isList patterns then attrs: any (pattern: matchAttrs pattern attrs) patterns else matchAttrs patterns; diff --git a/lib/systems/parse.nix b/lib/systems/parse.nix index 34bfd94b3ce5..b69ad669e187 100644 --- a/lib/systems/parse.nix +++ b/lib/systems/parse.nix @@ -29,6 +29,15 @@ let assert type.check value; setType type.name ({ inherit name; } // value)); + # gnu-config will ignore the portion of a triple matching the + # regex `e?abi.*$` when determining the validity of a triple. In + # other words, `i386-linuxabichickenlips` is a valid triple. + removeAbiSuffix = x: + let match = builtins.match "(.*)e?abi.*" x; + in if match==null + then x + else lib.elemAt match 0; + in rec { @@ -466,7 +475,7 @@ rec { else vendors.unknown; kernel = if hasPrefix "darwin" args.kernel then getKernel "darwin" else if hasPrefix "netbsd" args.kernel then getKernel "netbsd" - else getKernel args.kernel; + else getKernel (removeAbiSuffix args.kernel); abi = /**/ if args ? abi then getAbi args.abi else if isLinux parsed || isWindows parsed then diff --git a/lib/tests/filesystem.sh b/lib/tests/filesystem.sh index cfd333d0001b..7e7e03bc667d 100755 --- a/lib/tests/filesystem.sh +++ b/lib/tests/filesystem.sh @@ -64,8 +64,14 @@ expectSuccess "pathType $PWD/directory" '"directory"' expectSuccess "pathType $PWD/regular" '"regular"' expectSuccess "pathType $PWD/symlink" '"symlink"' expectSuccess "pathType $PWD/fifo" '"unknown"' -# Different errors depending on whether the builtins.readFilePath primop is available or not -expectFailure "pathType $PWD/non-existent" "error: (evaluation aborted with the following error message: 'lib.filesystem.pathType: Path $PWD/non-existent does not exist.'|getting status of '$PWD/non-existent': No such file or directory)" + +# Only check error message when a Nixpkgs-specified error is thrown, +# which is only the case when `readFileType` is not available +# and the fallback implementation needs to be used. +if [[ "$(nix-instantiate --eval --expr 'builtins ? readFileType')" == false ]]; then + expectFailure "pathType $PWD/non-existent" \ + "error: evaluation aborted with the following error message: 'lib.filesystem.pathType: Path $PWD/non-existent does not exist.'" +fi expectSuccess "pathIsDirectory /." "true" expectSuccess "pathIsDirectory $PWD/directory" "true" diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index 2e7fda2b1f8b..0d30e93aafb9 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -191,6 +191,11 @@ runTests { expected = "a\nb\nc\n"; }; + testReplicateString = { + expr = strings.replicate 5 "hello"; + expected = "hellohellohellohellohello"; + }; + testSplitStringsSimple = { expr = strings.splitString "." "a.b.c.d"; expected = [ "a" "b" "c" "d" ]; @@ -1906,4 +1911,32 @@ runTests { expr = (with types; either int (listOf (either bool str))).description; expected = "signed integer or list of (boolean or string)"; }; + +# Meta + testGetExe'Output = { + expr = getExe' { + type = "derivation"; + out = "somelonghash"; + bin = "somelonghash"; + } "executable"; + expected = "somelonghash/bin/executable"; + }; + + testGetExeOutput = { + expr = getExe { + type = "derivation"; + out = "somelonghash"; + bin = "somelonghash"; + meta.mainProgram = "mainProgram"; + }; + expected = "somelonghash/bin/mainProgram"; + }; + + testGetExe'FailureFirstArg = testingThrow ( + getExe' "not a derivation" "executable" + ); + + testGetExe'FailureSecondArg = testingThrow ( + getExe' { type = "derivation"; } "dir/executable" + ); } |