nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
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}