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 dir: 243 lib.flatten ( 244 lib.mapAttrsToList ( 245 name: type: 246 if type == "directory" then 247 lib.filesystem.listFilesRecursive (dir + "/${name}") 248 else 249 dir + "/${name}" 250 ) (builtins.readDir dir) 251 ); 252 253 /** 254 Transform a directory tree containing package files suitable for 255 `callPackage` into a matching nested attribute set of derivations. 256 257 For a directory tree like this: 258 259 ``` 260 my-packages 261 a.nix 262 b.nix 263 c 264 my-extra-feature.patch 265 package.nix 266 support-definitions.nix 267 my-namespace 268 d.nix 269 e.nix 270 f 271 package.nix 272 ``` 273 274 `packagesFromDirectoryRecursive` will produce an attribute set like this: 275 276 ```nix 277 # packagesFromDirectoryRecursive { 278 # callPackage = pkgs.callPackage; 279 # directory = ./my-packages; 280 # } 281 { 282 a = pkgs.callPackage ./my-packages/a.nix { }; 283 b = pkgs.callPackage ./my-packages/b.nix { }; 284 c = pkgs.callPackage ./my-packages/c/package.nix { }; 285 my-namespace = { 286 d = pkgs.callPackage ./my-packages/my-namespace/d.nix { }; 287 e = pkgs.callPackage ./my-packages/my-namespace/e.nix { }; 288 f = pkgs.callPackage ./my-packages/my-namespace/f/package.nix { }; 289 }; 290 } 291 ``` 292 293 In particular: 294 - If the input directory contains a `package.nix` file, then 295 `callPackage <directory>/package.nix { }` is returned. 296 - Otherwise, the input directory's contents are listed and transformed into 297 an attribute set. 298 - If a regular file's name has the `.nix` extension, it is turned into attribute 299 where: 300 - The attribute name is the file name without the `.nix` extension 301 - The attribute value is `callPackage <file path> { }` 302 - Directories are turned into an attribute where: 303 - The attribute name is the name of the directory 304 - The attribute value is the result of calling 305 `packagesFromDirectoryRecursive { ... }` on the directory. 306 307 As a result, directories with no `.nix` files (including empty 308 directories) will be transformed into empty attribute sets. 309 - Other files are ignored, including symbolic links to directories and to regular `.nix` 310 files; this is because nixlang code cannot distinguish the type of a link's target. 311 312 # Type 313 314 ``` 315 packagesFromDirectoryRecursive :: { 316 callPackage :: Path -> {} -> a, 317 newScope? :: AttrSet -> scope, 318 directory :: Path, 319 } -> AttrSet 320 ``` 321 322 # Inputs 323 324 `callPackage` 325 : The function used to convert a Nix file's path into a leaf of the attribute set. 326 It is typically the `callPackage` function, taken from either `pkgs` or a new scope corresponding to the `directory`. 327 328 `newScope` 329 : If present, this function is used when recursing into a directory, to generate a new scope. 330 The arguments are updated with the scope's `callPackage` and `newScope` functions, so packages can require 331 anything in their scope, or in an ancestor of their scope. 332 333 `directory` 334 : The directory to read package files from. 335 336 # Examples 337 :::{.example} 338 ## Basic use of `lib.packagesFromDirectoryRecursive` 339 340 ```nix 341 packagesFromDirectoryRecursive { 342 inherit (pkgs) callPackage; 343 directory = ./my-packages; 344 } 345 => { ... } 346 ``` 347 348 In this case, `callPackage` will only search `pkgs` for a file's input parameters. 349 In other words, a file cannot refer to another file in the directory in its input parameters. 350 ::: 351 352 ::::{.example} 353 ## Create a scope for the nix files found in a directory 354 ```nix 355 packagesFromDirectoryRecursive { 356 inherit (pkgs) callPackage newScope; 357 directory = ./my-packages; 358 } 359 => { ... } 360 ``` 361 362 For example, take the following directory structure: 363 ``` 364 my-packages 365 a.nix { b }: assert b ? b1; ... 366 b 367 b1.nix { a }: ... 368 b2.nix 369 ``` 370 371 Here, `b1.nix` can specify `{ a }` as a parameter, which `callPackage` will resolve as expected. 372 Likewise, `a.nix` receive an attrset corresponding to the contents of the `b` directory. 373 374 :::{.note} 375 `a.nix` cannot directly take as inputs packages defined in a child directory, such as `b1`. 376 ::: 377 :::: 378 */ 379 packagesFromDirectoryRecursive = 380 let 381 inherit (lib) 382 concatMapAttrs 383 id 384 makeScope 385 recurseIntoAttrs 386 removeSuffix 387 ; 388 389 # Generate an attrset corresponding to a given directory. 390 # This function is outside `packagesFromDirectoryRecursive`'s lambda expression, 391 # to prevent accidentally using its parameters. 392 processDir = 393 { callPackage, directory, ... }@args: 394 concatMapAttrs ( 395 name: type: 396 # for each directory entry 397 let 398 path = directory + "/${name}"; 399 in 400 if type == "directory" then 401 { 402 # recurse into directories 403 "${name}" = packagesFromDirectoryRecursive ( 404 args 405 // { 406 directory = path; 407 } 408 ); 409 } 410 else if type == "regular" && hasSuffix ".nix" name then 411 { 412 # call .nix files 413 "${removeSuffix ".nix" name}" = callPackage path { }; 414 } 415 else if type == "regular" then 416 { 417 # ignore non-nix files 418 } 419 else 420 throw '' 421 lib.filesystem.packagesFromDirectoryRecursive: Unsupported file type ${type} at path ${toString path} 422 '' 423 ) (builtins.readDir directory); 424 in 425 { 426 callPackage, 427 newScope ? throw "lib.packagesFromDirectoryRecursive: newScope wasn't passed in args", 428 directory, 429 }@args: 430 let 431 defaultPath = directory + "/package.nix"; 432 in 433 if pathExists defaultPath then 434 # if `${directory}/package.nix` exists, call it directly 435 callPackage defaultPath { } 436 else if args ? newScope then 437 # Create a new scope and mark it `recurseForDerivations`. 438 # This lets the packages refer to each other. 439 # See: 440 # [lib.makeScope](https://nixos.org/manual/nixpkgs/unstable/#function-library-lib.customisation.makeScope) and 441 # [lib.recurseIntoAttrs](https://nixos.org/manual/nixpkgs/unstable/#function-library-lib.customisation.makeScope) 442 recurseIntoAttrs ( 443 makeScope newScope ( 444 self: 445 # generate the attrset representing the directory, using the new scope's `callPackage` and `newScope` 446 processDir ( 447 args 448 // { 449 inherit (self) callPackage newScope; 450 } 451 ) 452 ) 453 ) 454 else 455 processDir args; 456 457 /** 458 Append `/default.nix` if the passed path is a directory. 459 460 # Type 461 462 ``` 463 resolveDefaultNix :: (Path | String) -> (Path | String) 464 ``` 465 466 # Inputs 467 468 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. 469 470 # Output 471 472 If the input refers to a directory that exists, the output is that same path with `/default.nix` appended. 473 Furthermore, if the input is a string that ends with `/`, `default.nix` is appended to it. 474 Otherwise, the input is returned unchanged. 475 476 # Examples 477 :::{.example} 478 ## `lib.filesystem.resolveDefaultNix` usage example 479 480 This expression checks whether `a` and `b` refer to the same locally available Nix file path. 481 482 ```nix 483 resolveDefaultNix a == resolveDefaultNix b 484 ``` 485 486 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. 487 */ 488 resolveDefaultNix = 489 v: 490 if pathIsDirectory v then 491 v + "/default.nix" 492 else if lib.isString v && hasSuffix "/" v then 493 # 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. 494 # A `/` is already present, so we don't add another one. 495 v + "default.nix" 496 else 497 v; 498}