Merge pull request #273883 from tweag/path.hasStorePathPrefix

`lib.path.hasStorePathPrefix`: init

authored by Silvan Mosberger and committed by GitHub 2f3e65ad aef29dd4

+110 -1
+81
lib/path/default.nix
··· 9 split 10 match 11 typeOf 12 ; 13 14 inherit (lib.lists) ··· 23 take 24 drop 25 ; 26 27 inherit (lib.strings) 28 concatStringsSep ··· 119 if base == dirOf base then { root = base; inherit components; } 120 else recurse ([ (baseNameOf base) ] ++ components) (dirOf base); 121 in recurse []; 122 123 in /* No rec! Add dependencies on this file at the top. */ { 124 ··· 320 root = deconstructed.root; 321 subpath = joinRelPath deconstructed.components; 322 }; 323 324 /* 325 Whether a value is a valid subpath string.
··· 9 split 10 match 11 typeOf 12 + storeDir 13 ; 14 15 inherit (lib.lists) ··· 24 take 25 drop 26 ; 27 + 28 + listHasPrefix = lib.lists.hasPrefix; 29 30 inherit (lib.strings) 31 concatStringsSep ··· 122 if base == dirOf base then { root = base; inherit components; } 123 else recurse ([ (baseNameOf base) ] ++ components) (dirOf base); 124 in recurse []; 125 + 126 + # The components of the store directory, typically [ "nix" "store" ] 127 + storeDirComponents = splitRelPath ("./" + storeDir); 128 + # The number of store directory components, typically 2 129 + storeDirLength = length storeDirComponents; 130 + 131 + # Type: [ String ] -> Bool 132 + # 133 + # Whether path components have a store path as a prefix, according to 134 + # https://nixos.org/manual/nix/stable/store/store-path.html#store-path. 135 + componentsHaveStorePathPrefix = components: 136 + # path starts with the store directory (typically /nix/store) 137 + listHasPrefix storeDirComponents components 138 + # is not the store directory itself, meaning there's at least one extra component 139 + && storeDirComponents != components 140 + # and the first component after the store directory has the expected format. 141 + # NOTE: We could change the hash regex to be [0-9a-df-np-sv-z], 142 + # because these are the actual ASCII characters used by Nix's base32 implementation, 143 + # but this is not fully specified, so let's tie this too much to the currently implemented concept of store paths. 144 + # Similar reasoning applies to the validity of the name part. 145 + # We care more about discerning store path-ness on realistic values. Making it airtight would be fragile and slow. 146 + && match ".{32}-.+" (elemAt components storeDirLength) != null; 147 148 in /* No rec! Add dependencies on this file at the top. */ { 149 ··· 345 root = deconstructed.root; 346 subpath = joinRelPath deconstructed.components; 347 }; 348 + 349 + /* 350 + Whether a [path](https://nixos.org/manual/nix/stable/language/values.html#type-path) 351 + has a [store path](https://nixos.org/manual/nix/stable/store/store-path.html#store-path) 352 + as a prefix. 353 + 354 + :::{.note} 355 + As with all functions of this `lib.path` library, it does not work on paths in strings, 356 + which is how you'd typically get store paths. 357 + 358 + Instead, this function only handles path values themselves, 359 + which occur when Nix files in the store use relative path expressions. 360 + ::: 361 + 362 + Type: 363 + hasStorePathPrefix :: Path -> Bool 364 + 365 + Example: 366 + # Subpaths of derivation outputs have a store path as a prefix 367 + hasStorePathPrefix /nix/store/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo/bar/baz 368 + => true 369 + 370 + # The store directory itself is not a store path 371 + hasStorePathPrefix /nix/store 372 + => false 373 + 374 + # Derivation outputs are store paths themselves 375 + hasStorePathPrefix /nix/store/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo 376 + => true 377 + 378 + # Paths outside the Nix store don't have a store path prefix 379 + hasStorePathPrefix /home/user 380 + => false 381 + 382 + # Not all paths under the Nix store are store paths 383 + hasStorePathPrefix /nix/store/.links/10gg8k3rmbw8p7gszarbk7qyd9jwxhcfq9i6s5i0qikx8alkk4hq 384 + => false 385 + 386 + # Store derivations are also store paths themselves 387 + hasStorePathPrefix /nix/store/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo.drv 388 + => true 389 + */ 390 + hasStorePathPrefix = path: 391 + let 392 + deconstructed = deconstructPath path; 393 + in 394 + assert assertMsg 395 + (isPath path) 396 + "lib.path.hasStorePathPrefix: Argument is of type ${typeOf path}, but a path was expected"; 397 + assert assertMsg 398 + # This function likely breaks or needs adjustment if used with other filesystem roots, if they ever get implemented. 399 + # 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. 400 + # See also https://github.com/NixOS/nix/pull/6530#discussion_r1422843117 401 + (deconstructed.root == /. && toString deconstructed.root == "/") 402 + "lib.path.hasStorePathPrefix: Argument has a filesystem root (${toString deconstructed.root}) that's not /, which is currently not supported."; 403 + componentsHaveStorePathPrefix deconstructed.components; 404 405 /* 406 Whether a value is a valid subpath string.
+29 -1
lib/path/tests/unit.nix
··· 3 { libpath }: 4 let 5 lib = import libpath; 6 - inherit (lib.path) hasPrefix removePrefix append splitRoot subpath; 7 8 cases = lib.runTests { 9 # Test examples from the lib.path.append documentation ··· 89 testSplitRootExample4 = { 90 expr = (builtins.tryEval (splitRoot "/foo/bar")).success; 91 expected = false; 92 }; 93 94 # Test examples from the lib.path.subpath.isValid documentation
··· 3 { libpath }: 4 let 5 lib = import libpath; 6 + inherit (lib.path) hasPrefix removePrefix append splitRoot hasStorePathPrefix subpath; 7 + 8 + # This is not allowed generally, but we're in the tests here, so we'll allow ourselves. 9 + storeDirPath = /. + builtins.storeDir; 10 11 cases = lib.runTests { 12 # Test examples from the lib.path.append documentation ··· 92 testSplitRootExample4 = { 93 expr = (builtins.tryEval (splitRoot "/foo/bar")).success; 94 expected = false; 95 + }; 96 + 97 + testHasStorePathPrefixExample1 = { 98 + expr = hasStorePathPrefix (storeDirPath + "/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo/bar/baz"); 99 + expected = true; 100 + }; 101 + testHasStorePathPrefixExample2 = { 102 + expr = hasStorePathPrefix storeDirPath; 103 + expected = false; 104 + }; 105 + testHasStorePathPrefixExample3 = { 106 + expr = hasStorePathPrefix (storeDirPath + "/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo"); 107 + expected = true; 108 + }; 109 + testHasStorePathPrefixExample4 = { 110 + expr = hasStorePathPrefix /home/user; 111 + expected = false; 112 + }; 113 + testHasStorePathPrefixExample5 = { 114 + expr = hasStorePathPrefix (storeDirPath + "/.links/10gg8k3rmbw8p7gszarbk7qyd9jwxhcfq9i6s5i0qikx8alkk4hq"); 115 + expected = false; 116 + }; 117 + testHasStorePathPrefixExample6 = { 118 + expr = hasStorePathPrefix (storeDirPath + "/nvl9ic0pj1fpyln3zaqrf4cclbqdfn1j-foo.drv"); 119 + expected = true; 120 }; 121 122 # Test examples from the lib.path.subpath.isValid documentation