at 25.11-pre 11 kB view raw
1# Functions for copying sources to the Nix store. 2{ lib }: 3 4# Tested in lib/tests/sources.sh 5let 6 inherit (builtins) 7 match 8 split 9 storeDir 10 ; 11 inherit (lib) 12 boolToString 13 filter 14 isString 15 readFile 16 ; 17 inherit (lib.filesystem) 18 pathIsRegularFile 19 ; 20 21 /** 22 A basic filter for `cleanSourceWith` that removes 23 directories of version control system, backup files (*~) 24 and some generated files. 25 26 # Inputs 27 28 `name` 29 30 : 1\. Function argument 31 32 `type` 33 34 : 2\. Function argument 35 */ 36 cleanSourceFilter = 37 name: type: 38 let 39 baseName = baseNameOf (toString name); 40 in 41 !( 42 # Filter out version control software files/directories 43 ( 44 baseName == ".git" 45 || type == "directory" && (baseName == ".svn" || baseName == "CVS" || baseName == ".hg") 46 ) 47 || 48 # Filter out editor backup / swap files. 49 lib.hasSuffix "~" baseName 50 || match "^\\.sw[a-z]$" baseName != null 51 || match "^\\..*\\.sw[a-z]$" baseName != null 52 || 53 54 # Filter out generates files. 55 lib.hasSuffix ".o" baseName 56 || lib.hasSuffix ".so" baseName 57 || 58 # Filter out nix-build result symlinks 59 (type == "symlink" && lib.hasPrefix "result" baseName) 60 || 61 # Filter out sockets and other types of files we can't have in the store. 62 (type == "unknown") 63 ); 64 65 /** 66 Filters a source tree removing version control files and directories using cleanSourceFilter. 67 68 # Inputs 69 70 `src` 71 72 : 1\. Function argument 73 74 # Examples 75 :::{.example} 76 ## `cleanSource` usage example 77 78 ```nix 79 cleanSource ./. 80 ``` 81 82 ::: 83 */ 84 cleanSource = 85 src: 86 cleanSourceWith { 87 filter = cleanSourceFilter; 88 inherit src; 89 }; 90 91 /** 92 Like `builtins.filterSource`, except it will compose with itself, 93 allowing you to chain multiple calls together without any 94 intermediate copies being put in the nix store. 95 96 # Examples 97 :::{.example} 98 ## `cleanSourceWith` usage example 99 100 ```nix 101 lib.cleanSourceWith { 102 filter = f; 103 src = lib.cleanSourceWith { 104 filter = g; 105 src = ./.; 106 }; 107 } 108 # Succeeds! 109 110 builtins.filterSource f (builtins.filterSource g ./.) 111 # Fails! 112 ``` 113 114 ::: 115 */ 116 cleanSourceWith = 117 { 118 # A path or cleanSourceWith result to filter and/or rename. 119 src, 120 # Optional with default value: constant true (include everything) 121 # The function will be combined with the && operator such 122 # that src.filter is called lazily. 123 # For implementing a filter, see 124 # https://nixos.org/nix/manual/#builtin-filterSource 125 # Type: A function (path -> type -> bool) 126 filter ? _path: _type: true, 127 # Optional name to use as part of the store path. 128 # This defaults to `src.name` or otherwise `"source"`. 129 name ? null, 130 }: 131 let 132 orig = toSourceAttributes src; 133 in 134 fromSourceAttributes { 135 inherit (orig) origSrc; 136 filter = path: type: filter path type && orig.filter path type; 137 name = if name != null then name else orig.name; 138 }; 139 140 /** 141 Add logging to a source, for troubleshooting the filtering behavior. 142 143 # Inputs 144 145 `src` 146 147 : Source to debug. The returned source will behave like this source, but also log its filter invocations. 148 149 # Type 150 151 ``` 152 sources.trace :: sourceLike -> Source 153 ``` 154 */ 155 trace = 156 # Source to debug. The returned source will behave like this source, but also log its filter invocations. 157 src: 158 let 159 attrs = toSourceAttributes src; 160 in 161 fromSourceAttributes ( 162 attrs 163 // { 164 filter = 165 path: type: 166 let 167 r = attrs.filter path type; 168 in 169 builtins.trace "${attrs.name}.filter ${path} = ${boolToString r}" r; 170 } 171 ) 172 // { 173 satisfiesSubpathInvariant = src ? satisfiesSubpathInvariant && src.satisfiesSubpathInvariant; 174 }; 175 176 /** 177 Filter sources by a list of regular expressions. 178 179 # Inputs 180 181 `src` 182 183 : 1\. Function argument 184 185 `regexes` 186 187 : 2\. Function argument 188 189 # Examples 190 :::{.example} 191 ## `sourceByRegex` usage example 192 193 ```nix 194 src = sourceByRegex ./my-subproject [".*\.py$" "^database.sql$"] 195 ``` 196 197 ::: 198 */ 199 sourceByRegex = 200 src: regexes: 201 let 202 isFiltered = src ? _isLibCleanSourceWith; 203 origSrc = if isFiltered then src.origSrc else src; 204 in 205 lib.cleanSourceWith { 206 filter = ( 207 path: type: 208 let 209 relPath = lib.removePrefix (toString origSrc + "/") (toString path); 210 in 211 lib.any (re: match re relPath != null) regexes 212 ); 213 inherit src; 214 }; 215 216 /** 217 Get all files ending with the specified suffices from the given 218 source directory or its descendants, omitting files that do not match 219 any suffix. The result of the example below will include files like 220 `./dir/module.c` and `./dir/subdir/doc.xml` if present. 221 222 # Inputs 223 224 `src` 225 226 : Path or source containing the files to be returned 227 228 `exts` 229 230 : A list of file suffix strings 231 232 # Type 233 234 ``` 235 sourceLike -> [String] -> Source 236 ``` 237 238 # Examples 239 :::{.example} 240 ## `sourceFilesBySuffices` usage example 241 242 ```nix 243 sourceFilesBySuffices ./. [ ".xml" ".c" ] 244 ``` 245 246 ::: 247 */ 248 sourceFilesBySuffices = 249 # Path or source containing the files to be returned 250 src: 251 # A list of file suffix strings 252 exts: 253 let 254 filter = 255 name: type: 256 let 257 base = baseNameOf (toString name); 258 in 259 type == "directory" || lib.any (ext: lib.hasSuffix ext base) exts; 260 in 261 cleanSourceWith { inherit filter src; }; 262 263 pathIsGitRepo = path: (_commitIdFromGitRepoOrError path) ? value; 264 265 /** 266 Get the commit id of a git repo. 267 268 # Inputs 269 270 `path` 271 272 : 1\. Function argument 273 274 # Examples 275 :::{.example} 276 ## `commitIdFromGitRepo` usage example 277 278 ```nix 279 commitIdFromGitRepo <nixpkgs/.git> 280 ``` 281 282 ::: 283 */ 284 commitIdFromGitRepo = 285 path: 286 let 287 commitIdOrError = _commitIdFromGitRepoOrError path; 288 in 289 commitIdOrError.value or (throw commitIdOrError.error); 290 291 # Get the commit id of a git repo. 292 293 # Returns `{ value = commitHash }` or `{ error = "... message ..." }`. 294 295 # Example: commitIdFromGitRepo <nixpkgs/.git> 296 # not exported, used for commitIdFromGitRepo 297 _commitIdFromGitRepoOrError = 298 let 299 readCommitFromFile = 300 file: path: 301 let 302 fileName = path + "/${file}"; 303 packedRefsName = path + "/packed-refs"; 304 absolutePath = 305 base: path: if lib.hasPrefix "/" path then path else toString (/. + "${base}/${path}"); 306 in 307 if 308 pathIsRegularFile path 309 # Resolve git worktrees. See gitrepository-layout(5) 310 then 311 let 312 m = match "^gitdir: (.*)$" (lib.fileContents path); 313 in 314 if m == null then 315 { error = "File contains no gitdir reference: " + path; } 316 else 317 let 318 gitDir = absolutePath (dirOf path) (lib.head m); 319 commonDir'' = 320 if pathIsRegularFile "${gitDir}/commondir" then lib.fileContents "${gitDir}/commondir" else gitDir; 321 commonDir' = lib.removeSuffix "/" commonDir''; 322 commonDir = absolutePath gitDir commonDir'; 323 refFile = lib.removePrefix "${commonDir}/" "${gitDir}/${file}"; 324 in 325 readCommitFromFile refFile commonDir 326 327 else if 328 pathIsRegularFile fileName 329 # Sometimes git stores the commitId directly in the file but 330 # sometimes it stores something like: «ref: refs/heads/branch-name» 331 then 332 let 333 fileContent = lib.fileContents fileName; 334 matchRef = match "^ref: (.*)$" fileContent; 335 in 336 if matchRef == null then { value = fileContent; } else readCommitFromFile (lib.head matchRef) path 337 338 else if 339 pathIsRegularFile packedRefsName 340 # Sometimes, the file isn't there at all and has been packed away in the 341 # packed-refs file, so we have to grep through it: 342 then 343 let 344 fileContent = readFile packedRefsName; 345 matchRef = match "([a-z0-9]+) ${file}"; 346 isRef = s: isString s && (matchRef s) != null; 347 # there is a bug in libstdc++ leading to stackoverflow for long strings: 348 # https://github.com/NixOS/nix/issues/2147#issuecomment-659868795 349 refs = filter isRef (split "\n" fileContent); 350 in 351 if refs == [ ] then 352 { error = "Could not find " + file + " in " + packedRefsName; } 353 else 354 { value = lib.head (matchRef (lib.head refs)); } 355 356 else 357 { error = "Not a .git directory: " + toString path; }; 358 in 359 readCommitFromFile "HEAD"; 360 361 pathHasContext = builtins.hasContext or (lib.hasPrefix storeDir); 362 363 canCleanSource = src: src ? _isLibCleanSourceWith || !(pathHasContext (toString src)); 364 365 # -------------------------------------------------------------------------- # 366 # Internal functions 367 # 368 369 # toSourceAttributes : sourceLike -> SourceAttrs 370 # 371 # Convert any source-like object into a simple, singular representation. 372 # We don't expose this representation in order to avoid having a fifth path- 373 # like class of objects in the wild. 374 # (Existing ones being: paths, strings, sources and x//{outPath}) 375 # So instead of exposing internals, we build a library of combinator functions. 376 toSourceAttributes = 377 src: 378 let 379 isFiltered = src ? _isLibCleanSourceWith; 380 in 381 { 382 # The original path 383 origSrc = if isFiltered then src.origSrc else src; 384 filter = if isFiltered then src.filter else _: _: true; 385 name = if isFiltered then src.name else "source"; 386 }; 387 388 # fromSourceAttributes : SourceAttrs -> Source 389 # 390 # Inverse of toSourceAttributes for Source objects. 391 fromSourceAttributes = 392 { 393 origSrc, 394 filter, 395 name, 396 }: 397 { 398 _isLibCleanSourceWith = true; 399 inherit origSrc filter name; 400 outPath = builtins.path { 401 inherit filter name; 402 path = origSrc; 403 }; 404 }; 405 406in 407{ 408 409 pathType = 410 lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2305) 411 "lib.sources.pathType has been moved to lib.filesystem.pathType." 412 lib.filesystem.pathType; 413 414 pathIsDirectory = 415 lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2305) 416 "lib.sources.pathIsDirectory has been moved to lib.filesystem.pathIsDirectory." 417 lib.filesystem.pathIsDirectory; 418 419 pathIsRegularFile = 420 lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2305) 421 "lib.sources.pathIsRegularFile has been moved to lib.filesystem.pathIsRegularFile." 422 lib.filesystem.pathIsRegularFile; 423 424 inherit 425 pathIsGitRepo 426 commitIdFromGitRepo 427 428 cleanSource 429 cleanSourceWith 430 cleanSourceFilter 431 pathHasContext 432 canCleanSource 433 434 sourceByRegex 435 sourceFilesBySuffices 436 437 trace 438 ; 439}