Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at master 13 kB view raw
1/** 2 Functions for querying information about the filesystem 3 without copying any files to the Nix store. 4*/ 5{ lib }: 6 7# Tested in lib/tests/filesystem.sh 8let 9 inherit (builtins) 10 readDir 11 pathExists 12 toString 13 ; 14 15 inherit (lib.filesystem) 16 pathIsDirectory 17 pathIsRegularFile 18 pathType 19 packagesFromDirectoryRecursive 20 ; 21 22 inherit (lib.strings) 23 hasSuffix 24 ; 25in 26 27{ 28 29 /** 30 The type of a path. The path needs to exist and be accessible. 31 The result is either "directory" for a directory, "regular" for a regular file, "symlink" for a symlink, or "unknown" for anything else. 32 33 # Inputs 34 35 path 36 37 : The path to query 38 39 # Type 40 41 ``` 42 pathType :: Path -> String 43 ``` 44 45 # Examples 46 :::{.example} 47 ## `lib.filesystem.pathType` usage example 48 49 ```nix 50 pathType /. 51 => "directory" 52 53 pathType /some/file.nix 54 => "regular" 55 ``` 56 57 ::: 58 */ 59 pathType = 60 builtins.readFileType or 61 # Nix <2.14 compatibility shim 62 ( 63 path: 64 if 65 !pathExists path 66 # Fail irrecoverably to mimic the historic behavior of this function and 67 # the new builtins.readFileType 68 then 69 abort "lib.filesystem.pathType: Path ${toString path} does not exist." 70 # The filesystem root is the only path where `dirOf / == /` and 71 # `baseNameOf /` is not valid. We can detect this and directly return 72 # "directory", since we know the filesystem root can't be anything else. 73 else if dirOf path == path then 74 "directory" 75 else 76 (readDir (dirOf path)).${baseNameOf path} 77 ); 78 79 /** 80 Whether a path exists and is a directory. 81 82 # Inputs 83 84 `path` 85 86 : 1\. Function argument 87 88 # Type 89 90 ``` 91 pathIsDirectory :: Path -> Bool 92 ``` 93 94 # Examples 95 :::{.example} 96 ## `lib.filesystem.pathIsDirectory` usage example 97 98 ```nix 99 pathIsDirectory /. 100 => true 101 102 pathIsDirectory /this/does/not/exist 103 => false 104 105 pathIsDirectory /some/file.nix 106 => false 107 ``` 108 109 ::: 110 */ 111 pathIsDirectory = path: pathExists path && pathType path == "directory"; 112 113 /** 114 Whether a path exists and is a regular file, meaning not a symlink or any other special file type. 115 116 # Inputs 117 118 `path` 119 120 : 1\. Function argument 121 122 # Type 123 124 ``` 125 pathIsRegularFile :: Path -> Bool 126 ``` 127 128 # Examples 129 :::{.example} 130 ## `lib.filesystem.pathIsRegularFile` usage example 131 132 ```nix 133 pathIsRegularFile /. 134 => false 135 136 pathIsRegularFile /this/does/not/exist 137 => false 138 139 pathIsRegularFile /some/file.nix 140 => true 141 ``` 142 143 ::: 144 */ 145 pathIsRegularFile = path: pathExists path && pathType path == "regular"; 146 147 /** 148 A map of all haskell packages defined in the given path, 149 identified by having a cabal file with the same name as the 150 directory itself. 151 152 # Inputs 153 154 `root` 155 156 : The directory within to search 157 158 # Type 159 160 ``` 161 Path -> Map String Path 162 ``` 163 */ 164 haskellPathsInDir = 165 root: 166 let 167 # Files in the root 168 root-files = builtins.attrNames (builtins.readDir root); 169 # Files with their full paths 170 root-files-with-paths = map (file: { 171 name = file; 172 value = root + "/${file}"; 173 }) root-files; 174 # Subdirectories of the root with a cabal file. 175 cabal-subdirs = builtins.filter ( 176 { name, value }: builtins.pathExists (value + "/${name}.cabal") 177 ) root-files-with-paths; 178 in 179 builtins.listToAttrs cabal-subdirs; 180 /** 181 Find the first directory containing a file matching 'pattern' 182 upward from a given 'file'. 183 Returns 'null' if no directories contain a file matching 'pattern'. 184 185 # Inputs 186 187 `pattern` 188 189 : The pattern to search for 190 191 `file` 192 193 : The file to start searching upward from 194 195 # Type 196 197 ``` 198 RegExp -> Path -> Nullable { path : Path; matches : [ MatchResults ]; } 199 ``` 200 */ 201 locateDominatingFile = 202 pattern: file: 203 let 204 go = 205 path: 206 let 207 files = builtins.attrNames (builtins.readDir path); 208 matches = builtins.filter (match: match != null) (map (builtins.match pattern) files); 209 in 210 if builtins.length matches != 0 then 211 { inherit path matches; } 212 else if path == /. then 213 null 214 else 215 go (dirOf path); 216 parent = dirOf file; 217 isDir = 218 let 219 base = baseNameOf file; 220 type = (builtins.readDir parent).${base} or null; 221 in 222 file == /. || type == "directory"; 223 in 224 go (if isDir then file else parent); 225 226 /** 227 Given a directory, return a flattened list of all files within it recursively. 228 229 # Inputs 230 231 `dir` 232 233 : The path to recursively list 234 235 # Type 236 237 ``` 238 Path -> [ Path ] 239 ``` 240 */ 241 listFilesRecursive = 242 let 243 # We only flatten at the very end, as flatten is recursive. 244 internalFunc = 245 dir: 246 (lib.mapAttrsToList ( 247 name: type: if type == "directory" then internalFunc (dir + "/${name}") else dir + "/${name}" 248 ) (builtins.readDir dir)); 249 in 250 dir: lib.flatten (internalFunc dir); 251 252 /** 253 Transform a directory tree containing package files suitable for 254 `callPackage` into a matching nested attribute set of derivations. 255 256 For a directory tree like this: 257 258 ``` 259 my-packages 260 a.nix 261 b.nix 262 c 263 my-extra-feature.patch 264 package.nix 265 support-definitions.nix 266 my-namespace 267 d.nix 268 e.nix 269 f 270 package.nix 271 ``` 272 273 `packagesFromDirectoryRecursive` will produce an attribute set like this: 274 275 ```nix 276 # packagesFromDirectoryRecursive { 277 # callPackage = pkgs.callPackage; 278 # directory = ./my-packages; 279 # } 280 { 281 a = pkgs.callPackage ./my-packages/a.nix { }; 282 b = pkgs.callPackage ./my-packages/b.nix { }; 283 c = pkgs.callPackage ./my-packages/c/package.nix { }; 284 my-namespace = { 285 d = pkgs.callPackage ./my-packages/my-namespace/d.nix { }; 286 e = pkgs.callPackage ./my-packages/my-namespace/e.nix { }; 287 f = pkgs.callPackage ./my-packages/my-namespace/f/package.nix { }; 288 }; 289 } 290 ``` 291 292 In particular: 293 - If the input directory contains a `package.nix` file, then 294 `callPackage <directory>/package.nix { }` is returned. 295 - Otherwise, the input directory's contents are listed and transformed into 296 an attribute set. 297 - If a regular file's name has the `.nix` extension, it is turned into attribute 298 where: 299 - The attribute name is the file name without the `.nix` extension 300 - The attribute value is `callPackage <file path> { }` 301 - Directories are turned into an attribute where: 302 - The attribute name is the name of the directory 303 - The attribute value is the result of calling 304 `packagesFromDirectoryRecursive { ... }` on the directory. 305 306 As a result, directories with no `.nix` files (including empty 307 directories) will be transformed into empty attribute sets. 308 - Other files are ignored, including symbolic links to directories and to regular `.nix` 309 files; this is because nixlang code cannot distinguish the type of a link's target. 310 311 # Type 312 313 ``` 314 packagesFromDirectoryRecursive :: { 315 callPackage :: Path -> {} -> a, 316 newScope? :: AttrSet -> scope, 317 directory :: Path, 318 } -> AttrSet 319 ``` 320 321 # Inputs 322 323 `callPackage` 324 : The function used to convert a Nix file's path into a leaf of the attribute set. 325 It is typically the `callPackage` function, taken from either `pkgs` or a new scope corresponding to the `directory`. 326 327 `newScope` 328 : If present, this function is used when recursing into a directory, to generate a new scope. 329 The arguments are updated with the scope's `callPackage` and `newScope` functions, so packages can require 330 anything in their scope, or in an ancestor of their scope. 331 332 `directory` 333 : The directory to read package files from. 334 335 # Examples 336 :::{.example} 337 ## Basic use of `lib.packagesFromDirectoryRecursive` 338 339 ```nix 340 packagesFromDirectoryRecursive { 341 inherit (pkgs) callPackage; 342 directory = ./my-packages; 343 } 344 => { ... } 345 ``` 346 347 In this case, `callPackage` will only search `pkgs` for a file's input parameters. 348 In other words, a file cannot refer to another file in the directory in its input parameters. 349 ::: 350 351 ::::{.example} 352 ## Create a scope for the nix files found in a directory 353 ```nix 354 packagesFromDirectoryRecursive { 355 inherit (pkgs) callPackage newScope; 356 directory = ./my-packages; 357 } 358 => { ... } 359 ``` 360 361 For example, take the following directory structure: 362 ``` 363 my-packages 364 a.nix { b }: assert b ? b1; ... 365 b 366 b1.nix { a }: ... 367 b2.nix 368 ``` 369 370 Here, `b1.nix` can specify `{ a }` as a parameter, which `callPackage` will resolve as expected. 371 Likewise, `a.nix` receive an attrset corresponding to the contents of the `b` directory. 372 373 :::{.note} 374 `a.nix` cannot directly take as inputs packages defined in a child directory, such as `b1`. 375 ::: 376 :::: 377 */ 378 packagesFromDirectoryRecursive = 379 let 380 inherit (lib) 381 concatMapAttrs 382 id 383 makeScope 384 recurseIntoAttrs 385 removeSuffix 386 ; 387 388 # Generate an attrset corresponding to a given directory. 389 # This function is outside `packagesFromDirectoryRecursive`'s lambda expression, 390 # to prevent accidentally using its parameters. 391 processDir = 392 { callPackage, directory, ... }@args: 393 concatMapAttrs ( 394 name: type: 395 # for each directory entry 396 let 397 path = directory + "/${name}"; 398 in 399 if type == "directory" then 400 { 401 # recurse into directories 402 "${name}" = packagesFromDirectoryRecursive ( 403 args 404 // { 405 directory = path; 406 } 407 ); 408 } 409 else if type == "regular" && hasSuffix ".nix" name then 410 { 411 # call .nix files 412 "${removeSuffix ".nix" name}" = callPackage path { }; 413 } 414 else if type == "regular" then 415 { 416 # ignore non-nix files 417 } 418 else 419 throw '' 420 lib.filesystem.packagesFromDirectoryRecursive: Unsupported file type ${type} at path ${toString path} 421 '' 422 ) (builtins.readDir directory); 423 in 424 { 425 callPackage, 426 newScope ? throw "lib.packagesFromDirectoryRecursive: newScope wasn't passed in args", 427 directory, 428 }@args: 429 let 430 defaultPath = directory + "/package.nix"; 431 in 432 if pathExists defaultPath then 433 # if `${directory}/package.nix` exists, call it directly 434 callPackage defaultPath { } 435 else if args ? newScope then 436 # Create a new scope and mark it `recurseForDerivations`. 437 # This lets the packages refer to each other. 438 # See: 439 # [lib.makeScope](https://nixos.org/manual/nixpkgs/unstable/#function-library-lib.customisation.makeScope) and 440 # [lib.recurseIntoAttrs](https://nixos.org/manual/nixpkgs/unstable/#function-library-lib.customisation.makeScope) 441 recurseIntoAttrs ( 442 makeScope newScope ( 443 self: 444 # generate the attrset representing the directory, using the new scope's `callPackage` and `newScope` 445 processDir ( 446 args 447 // { 448 inherit (self) callPackage newScope; 449 } 450 ) 451 ) 452 ) 453 else 454 processDir args; 455 456 /** 457 Append `/default.nix` if the passed path is a directory. 458 459 # Type 460 461 ``` 462 resolveDefaultNix :: (Path | String) -> (Path | String) 463 ``` 464 465 # Inputs 466 467 A single argument which can be a [path](https://nix.dev/manual/nix/stable/language/types#type-path) value or a string containing an absolute path. 468 469 # Output 470 471 If the input refers to a directory that exists, the output is that same path with `/default.nix` appended. 472 Furthermore, if the input is a string that ends with `/`, `default.nix` is appended to it. 473 Otherwise, the input is returned unchanged. 474 475 # Examples 476 :::{.example} 477 ## `lib.filesystem.resolveDefaultNix` usage example 478 479 This expression checks whether `a` and `b` refer to the same locally available Nix file path. 480 481 ```nix 482 resolveDefaultNix a == resolveDefaultNix b 483 ``` 484 485 For instance, if `a` is `/some/dir` and `b` is `/some/dir/default.nix`, and `/some/dir/` exists, the expression evaluates to `true`, despite `a` and `b` being different references to the same Nix file. 486 */ 487 resolveDefaultNix = 488 v: 489 if pathIsDirectory v then 490 v + "/default.nix" 491 else if lib.isString v && hasSuffix "/" v then 492 # A path ending in `/` can only refer to a directory, so we take the hint, even if we can't verify the validity of the path's `/` assertion. 493 # A `/` is already present, so we don't add another one. 494 v + "default.nix" 495 else 496 v; 497}