lib.path.hasStorePathPrefix: init

Co-authored-by: Robert Hensing <roberth@users.noreply.github.com>
This commit is contained in:
Silvan Mosberger 2023-12-13 04:33:31 +01:00
parent c4e2e52a21
commit d4b7b15407
2 changed files with 110 additions and 1 deletions

View file

@ -9,6 +9,7 @@ let
split
match
typeOf
storeDir
;
inherit (lib.lists)
@ -24,6 +25,8 @@ let
drop
;
listHasPrefix = lib.lists.hasPrefix;
inherit (lib.strings)
concatStringsSep
substring
@ -120,6 +123,28 @@ let
else recurse ([ (baseNameOf base) ] ++ components) (dirOf base);
in recurse [];
# The components of the store directory, typically [ "nix" "store" ]
storeDirComponents = splitRelPath ("./" + storeDir);
# The number of store directory components, typically 2
storeDirLength = length storeDirComponents;
# Type: [ String ] -> Bool
#
# Whether path components have a store path as a prefix, according to
# https://nixos.org/manual/nix/stable/store/store-path.html#store-path.
componentsHaveStorePathPrefix = components:
# path starts with the store directory (typically /nix/store)
listHasPrefix storeDirComponents components
# is not the store directory itself, meaning there's at least one extra component
&& storeDirComponents != components
# and the first component after the store directory has the expected format.
# NOTE: We could change the hash regex to be [0-9a-df-np-sv-z],
# because these are the actual ASCII characters used by Nix's base32 implementation,
# but this is not fully specified, so let's tie this too much to the currently implemented concept of store paths.
# Similar reasoning applies to the validity of the name part.
# We care more about discerning store path-ness on realistic values. Making it airtight would be fragile and slow.
&& match ".{32}-.+" (elemAt components storeDirLength) != null;
in /* No rec! Add dependencies on this file at the top. */ {
/*
@ -321,6 +346,62 @@ in /* No rec! Add dependencies on this file at the top. */ {
subpath = joinRelPath deconstructed.components;
};
/*
Whether a [path](https://nixos.org/manual/nix/stable/language/values.html#type-path)
has a [store path](https://nixos.org/manual/nix/stable/store/store-path.html#store-path)
as a prefix.
:::{.note}
As with all functions of this `lib.path` library, it does not work on paths in strings,
which is how you'd typically get store paths.
Instead, this function only handles path values themselves,
which occur when Nix files in the store use relative path expressions.
:::
Type:
hasStorePathPrefix :: Path -> Bool
Example:
# Subpaths of derivation outputs have a store path as a prefix
hasStorePathPrefix /nix/store/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo/bar/baz
=> true
# The store directory itself is not a store path
hasStorePathPrefix /nix/store
=> false
# Derivation outputs are store paths themselves
hasStorePathPrefix /nix/store/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo
=> true
# Paths outside the Nix store don't have a store path prefix
hasStorePathPrefix /home/user
=> false
# Not all paths under the Nix store are store paths
hasStorePathPrefix /nix/store/.links/10gg8k3rmbw8p7gszarbk7qyd9jwxhcfq9i6s5i0qikx8alkk4hq
=> false
# Store derivations are also store paths themselves
hasStorePathPrefix /nix/store/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo.drv
=> true
*/
hasStorePathPrefix = path:
let
deconstructed = deconstructPath path;
in
assert assertMsg
(isPath path)
"lib.path.hasStorePathPrefix: Argument is of type ${typeOf path}, but a path was expected";
assert assertMsg
# This function likely breaks or needs adjustment if used with other filesystem roots, if they ever get implemented.
# Let's try to error nicely in such a case, though it's unclear how an implementation would work even and whether this could be detected.
# See also https://github.com/NixOS/nix/pull/6530#discussion_r1422843117
(deconstructed.root == /. && toString deconstructed.root == "/")
"lib.path.hasStorePathPrefix: Argument has a filesystem root (${toString deconstructed.root}) that's not /, which is currently not supported.";
componentsHaveStorePathPrefix deconstructed.components;
/*
Whether a value is a valid subpath string.

View file

@ -3,7 +3,10 @@
{ libpath }:
let
lib = import libpath;
inherit (lib.path) hasPrefix removePrefix append splitRoot subpath;
inherit (lib.path) hasPrefix removePrefix append splitRoot hasStorePathPrefix subpath;
# This is not allowed generally, but we're in the tests here, so we'll allow ourselves.
storeDirPath = /. + builtins.storeDir;
cases = lib.runTests {
# Test examples from the lib.path.append documentation
@ -91,6 +94,31 @@ let
expected = false;
};
testHasStorePathPrefixExample1 = {
expr = hasStorePathPrefix (storeDirPath + "/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo/bar/baz");
expected = true;
};
testHasStorePathPrefixExample2 = {
expr = hasStorePathPrefix storeDirPath;
expected = false;
};
testHasStorePathPrefixExample3 = {
expr = hasStorePathPrefix (storeDirPath + "/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo");
expected = true;
};
testHasStorePathPrefixExample4 = {
expr = hasStorePathPrefix /home/user;
expected = false;
};
testHasStorePathPrefixExample5 = {
expr = hasStorePathPrefix (storeDirPath + "/.links/10gg8k3rmbw8p7gszarbk7qyd9jwxhcfq9i6s5i0qikx8alkk4hq");
expected = false;
};
testHasStorePathPrefixExample6 = {
expr = hasStorePathPrefix (storeDirPath + "/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo.drv");
expected = true;
};
# Test examples from the lib.path.subpath.isValid documentation
testSubpathIsValidExample1 = {
expr = subpath.isValid null;