Merge pull request #309460 from r-ryantm/auto-update/home-manager
[NixPkgs.git] / lib / filesystem.nix
blobc416db02eb577c1ab8bbaf4fd53022a938480b9c
1 /*
2   Functions for querying information about the filesystem
3   without copying any files to the Nix store.
4 */
5 { lib }:
7 # Tested in lib/tests/filesystem.sh
8 let
9   inherit (builtins)
10     readDir
11     pathExists
12     toString
13     ;
15   inherit (lib.attrsets)
16     mapAttrs'
17     filterAttrs
18     ;
20   inherit (lib.filesystem)
21     pathType
22     ;
24   inherit (lib.strings)
25     hasSuffix
26     removeSuffix
27     ;
32   /*
33     The type of a path. The path needs to exist and be accessible.
34     The result is either "directory" for a directory, "regular" for a regular file, "symlink" for a symlink, or "unknown" for anything else.
36     Type:
37       pathType :: Path -> String
39     Example:
40       pathType /.
41       => "directory"
43       pathType /some/file.nix
44       => "regular"
45   */
46   pathType =
47     builtins.readFileType or
48     # Nix <2.14 compatibility shim
49     (path:
50       if ! pathExists path
51       # Fail irrecoverably to mimic the historic behavior of this function and
52       # the new builtins.readFileType
53       then abort "lib.filesystem.pathType: Path ${toString path} does not exist."
54       # The filesystem root is the only path where `dirOf / == /` and
55       # `baseNameOf /` is not valid. We can detect this and directly return
56       # "directory", since we know the filesystem root can't be anything else.
57       else if dirOf path == path
58       then "directory"
59       else (readDir (dirOf path)).${baseNameOf path}
60     );
62   /*
63     Whether a path exists and is a directory.
65     Type:
66       pathIsDirectory :: Path -> Bool
68     Example:
69       pathIsDirectory /.
70       => true
72       pathIsDirectory /this/does/not/exist
73       => false
75       pathIsDirectory /some/file.nix
76       => false
77   */
78   pathIsDirectory = path:
79     pathExists path && pathType path == "directory";
81   /*
82     Whether a path exists and is a regular file, meaning not a symlink or any other special file type.
84     Type:
85       pathIsRegularFile :: Path -> Bool
87     Example:
88       pathIsRegularFile /.
89       => false
91       pathIsRegularFile /this/does/not/exist
92       => false
94       pathIsRegularFile /some/file.nix
95       => true
96   */
97   pathIsRegularFile = path:
98     pathExists path && pathType path == "regular";
100   /*
101     A map of all haskell packages defined in the given path,
102     identified by having a cabal file with the same name as the
103     directory itself.
105     Type: Path -> Map String Path
106   */
107   haskellPathsInDir =
108     # The directory within to search
109     root:
110     let # Files in the root
111         root-files = builtins.attrNames (builtins.readDir root);
112         # Files with their full paths
113         root-files-with-paths =
114           map (file:
115             { name = file; value = root + "/${file}"; }
116           ) root-files;
117         # Subdirectories of the root with a cabal file.
118         cabal-subdirs =
119           builtins.filter ({ name, value }:
120             builtins.pathExists (value + "/${name}.cabal")
121           ) root-files-with-paths;
122     in builtins.listToAttrs cabal-subdirs;
123   /*
124     Find the first directory containing a file matching 'pattern'
125     upward from a given 'file'.
126     Returns 'null' if no directories contain a file matching 'pattern'.
128     Type: RegExp -> Path -> Nullable { path : Path; matches : [ MatchResults ]; }
129   */
130   locateDominatingFile =
131     # The pattern to search for
132     pattern:
133     # The file to start searching upward from
134     file:
135     let go = path:
136           let files = builtins.attrNames (builtins.readDir path);
137               matches = builtins.filter (match: match != null)
138                           (map (builtins.match pattern) files);
139           in
140             if builtins.length matches != 0
141               then { inherit path matches; }
142               else if path == /.
143                 then null
144                 else go (dirOf path);
145         parent = dirOf file;
146         isDir =
147           let base = baseNameOf file;
148               type = (builtins.readDir parent).${base} or null;
149           in file == /. || type == "directory";
150     in go (if isDir then file else parent);
153   /*
154     Given a directory, return a flattened list of all files within it recursively.
156     Type: Path -> [ Path ]
157   */
158   listFilesRecursive =
159     # The path to recursively list
160     dir:
161     lib.flatten (lib.mapAttrsToList (name: type:
162     if type == "directory" then
163       lib.filesystem.listFilesRecursive (dir + "/${name}")
164     else
165       dir + "/${name}"
166   ) (builtins.readDir dir));
168   /*
169     Transform a directory tree containing package files suitable for
170     `callPackage` into a matching nested attribute set of derivations.
172     For a directory tree like this:
174     ```
175     my-packages
176     ├── a.nix
177     ├── b.nix
178     ├── c
179     │  ├── my-extra-feature.patch
180     │  ├── package.nix
181     │  └── support-definitions.nix
182     └── my-namespace
183        ├── d.nix
184        ├── e.nix
185        └── f
186           └── package.nix
187     ```
189     `packagesFromDirectoryRecursive` will produce an attribute set like this:
191     ```nix
192     # packagesFromDirectoryRecursive {
193     #   callPackage = pkgs.callPackage;
194     #   directory = ./my-packages;
195     # }
196     {
197       a = pkgs.callPackage ./my-packages/a.nix { };
198       b = pkgs.callPackage ./my-packages/b.nix { };
199       c = pkgs.callPackage ./my-packages/c/package.nix { };
200       my-namespace = {
201         d = pkgs.callPackage ./my-packages/my-namespace/d.nix { };
202         e = pkgs.callPackage ./my-packages/my-namespace/e.nix { };
203         f = pkgs.callPackage ./my-packages/my-namespace/f/package.nix { };
204       };
205     }
206     ```
208     In particular:
209     - If the input directory contains a `package.nix` file, then
210       `callPackage <directory>/package.nix { }` is returned.
211     - Otherwise, the input directory's contents are listed and transformed into
212       an attribute set.
213       - If a file name has the `.nix` extension, it is turned into attribute
214         where:
215         - The attribute name is the file name without the `.nix` extension
216         - The attribute value is `callPackage <file path> { }`
217       - Other files are ignored.
218       - Directories are turned into an attribute where:
219         - The attribute name is the name of the directory
220         - The attribute value is the result of calling
221           `packagesFromDirectoryRecursive { ... }` on the directory.
223         As a result, directories with no `.nix` files (including empty
224         directories) will be transformed into empty attribute sets.
226     Example:
227       packagesFromDirectoryRecursive {
228         inherit (pkgs) callPackage;
229         directory = ./my-packages;
230       }
231       => { ... }
233       lib.makeScope pkgs.newScope (
234         self: packagesFromDirectoryRecursive {
235           callPackage = self.callPackage;
236           directory = ./my-packages;
237         }
238       )
239       => { ... }
241     Type:
242       packagesFromDirectoryRecursive :: AttrSet -> AttrSet
243   */
244   packagesFromDirectoryRecursive =
245     # Options.
246     {
247       /*
248         `pkgs.callPackage`
250         Type:
251           Path -> AttrSet -> a
252       */
253       callPackage,
254       /*
255         The directory to read package files from
257         Type:
258           Path
259       */
260       directory,
261       ...
262     }:
263     let
264       # Determine if a directory entry from `readDir` indicates a package or
265       # directory of packages.
266       directoryEntryIsPackage = basename: type:
267         type == "directory" || hasSuffix ".nix" basename;
269       # List directory entries that indicate packages in the given `path`.
270       packageDirectoryEntries = path:
271         filterAttrs directoryEntryIsPackage (readDir path);
273       # Transform a directory entry (a `basename` and `type` pair) into a
274       # package.
275       directoryEntryToAttrPair = subdirectory: basename: type:
276         let
277           path = subdirectory + "/${basename}";
278         in
279         if type == "regular"
280         then
281         {
282           name = removeSuffix ".nix" basename;
283           value = callPackage path { };
284         }
285         else
286         if type == "directory"
287         then
288         {
289           name = basename;
290           value = packagesFromDirectory path;
291         }
292         else
293         throw
294           ''
295             lib.filesystem.packagesFromDirectoryRecursive: Unsupported file type ${type} at path ${toString subdirectory}
296           '';
298       # Transform a directory into a package (if there's a `package.nix`) or
299       # set of packages (otherwise).
300       packagesFromDirectory = path:
301         let
302           defaultPackagePath = path + "/package.nix";
303         in
304         if pathExists defaultPackagePath
305         then callPackage defaultPackagePath { }
306         else mapAttrs'
307           (directoryEntryToAttrPair path)
308           (packageDirectoryEntries path);
309     in
310     packagesFromDirectory directory;