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