at 24.11-pre 8.7 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.attrsets) 16 mapAttrs' 17 filterAttrs 18 ; 19 20 inherit (lib.filesystem) 21 pathType 22 ; 23 24 inherit (lib.strings) 25 hasSuffix 26 removeSuffix 27 ; 28in 29 30{ 31 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. 35 36 Type: 37 pathType :: Path -> String 38 39 Example: 40 pathType /. 41 => "directory" 42 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 ); 61 62 /* 63 Whether a path exists and is a directory. 64 65 Type: 66 pathIsDirectory :: Path -> Bool 67 68 Example: 69 pathIsDirectory /. 70 => true 71 72 pathIsDirectory /this/does/not/exist 73 => false 74 75 pathIsDirectory /some/file.nix 76 => false 77 */ 78 pathIsDirectory = path: 79 pathExists path && pathType path == "directory"; 80 81 /* 82 Whether a path exists and is a regular file, meaning not a symlink or any other special file type. 83 84 Type: 85 pathIsRegularFile :: Path -> Bool 86 87 Example: 88 pathIsRegularFile /. 89 => false 90 91 pathIsRegularFile /this/does/not/exist 92 => false 93 94 pathIsRegularFile /some/file.nix 95 => true 96 */ 97 pathIsRegularFile = path: 98 pathExists path && pathType path == "regular"; 99 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. 104 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'. 127 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); 151 152 153 /* 154 Given a directory, return a flattened list of all files within it recursively. 155 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)); 167 168 /* 169 Transform a directory tree containing package files suitable for 170 `callPackage` into a matching nested attribute set of derivations. 171 172 For a directory tree like this: 173 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 ``` 188 189 `packagesFromDirectoryRecursive` will produce an attribute set like this: 190 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 ``` 207 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. 222 223 As a result, directories with no `.nix` files (including empty 224 directories) will be transformed into empty attribute sets. 225 226 Example: 227 packagesFromDirectoryRecursive { 228 inherit (pkgs) callPackage; 229 directory = ./my-packages; 230 } 231 => { ... } 232 233 lib.makeScope pkgs.newScope ( 234 self: packagesFromDirectoryRecursive { 235 callPackage = self.callPackage; 236 directory = ./my-packages; 237 } 238 ) 239 => { ... } 240 241 Type: 242 packagesFromDirectoryRecursive :: AttrSet -> AttrSet 243 */ 244 packagesFromDirectoryRecursive = 245 # Options. 246 { 247 /* 248 `pkgs.callPackage` 249 250 Type: 251 Path -> AttrSet -> a 252 */ 253 callPackage, 254 /* 255 The directory to read package files from 256 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; 268 269 # List directory entries that indicate packages in the given `path`. 270 packageDirectoryEntries = path: 271 filterAttrs directoryEntryIsPackage (readDir path); 272 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 ''; 297 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; 311}