at 24.11-pre 9.9 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 cleanSourceFilter = name: type: let baseName = baseNameOf (toString name); in ! ( 27 # Filter out version control software files/directories 28 (baseName == ".git" || type == "directory" && (baseName == ".svn" || baseName == "CVS" || baseName == ".hg")) || 29 # Filter out editor backup / swap files. 30 lib.hasSuffix "~" baseName || 31 match "^\\.sw[a-z]$" baseName != null || 32 match "^\\..*\\.sw[a-z]$" baseName != null || 33 34 # Filter out generates files. 35 lib.hasSuffix ".o" baseName || 36 lib.hasSuffix ".so" baseName || 37 # Filter out nix-build result symlinks 38 (type == "symlink" && lib.hasPrefix "result" baseName) || 39 # Filter out sockets and other types of files we can't have in the store. 40 (type == "unknown") 41 ); 42 43 /* 44 Filters a source tree removing version control files and directories using cleanSourceFilter. 45 46 Example: 47 cleanSource ./. 48 */ 49 cleanSource = src: cleanSourceWith { filter = cleanSourceFilter; inherit src; }; 50 51 /* 52 Like `builtins.filterSource`, except it will compose with itself, 53 allowing you to chain multiple calls together without any 54 intermediate copies being put in the nix store. 55 56 Example: 57 lib.cleanSourceWith { 58 filter = f; 59 src = lib.cleanSourceWith { 60 filter = g; 61 src = ./.; 62 }; 63 } 64 # Succeeds! 65 66 builtins.filterSource f (builtins.filterSource g ./.) 67 # Fails! 68 69 */ 70 cleanSourceWith = 71 { 72 # A path or cleanSourceWith result to filter and/or rename. 73 src, 74 # Optional with default value: constant true (include everything) 75 # The function will be combined with the && operator such 76 # that src.filter is called lazily. 77 # For implementing a filter, see 78 # https://nixos.org/nix/manual/#builtin-filterSource 79 # Type: A function (path -> type -> bool) 80 filter ? _path: _type: true, 81 # Optional name to use as part of the store path. 82 # This defaults to `src.name` or otherwise `"source"`. 83 name ? null 84 }: 85 let 86 orig = toSourceAttributes src; 87 in fromSourceAttributes { 88 inherit (orig) origSrc; 89 filter = path: type: filter path type && orig.filter path type; 90 name = if name != null then name else orig.name; 91 }; 92 93 /* 94 Add logging to a source, for troubleshooting the filtering behavior. 95 Type: 96 sources.trace :: sourceLike -> Source 97 */ 98 trace = 99 # Source to debug. The returned source will behave like this source, but also log its filter invocations. 100 src: 101 let 102 attrs = toSourceAttributes src; 103 in 104 fromSourceAttributes ( 105 attrs // { 106 filter = path: type: 107 let 108 r = attrs.filter path type; 109 in 110 builtins.trace "${attrs.name}.filter ${path} = ${boolToString r}" r; 111 } 112 ) // { 113 satisfiesSubpathInvariant = src ? satisfiesSubpathInvariant && src.satisfiesSubpathInvariant; 114 }; 115 116 /* 117 Filter sources by a list of regular expressions. 118 119 Example: src = sourceByRegex ./my-subproject [".*\.py$" "^database.sql$"] 120 */ 121 sourceByRegex = src: regexes: 122 let 123 isFiltered = src ? _isLibCleanSourceWith; 124 origSrc = if isFiltered then src.origSrc else src; 125 in lib.cleanSourceWith { 126 filter = (path: type: 127 let relPath = lib.removePrefix (toString origSrc + "/") (toString path); 128 in lib.any (re: match re relPath != null) regexes); 129 inherit src; 130 }; 131 132 /* 133 Get all files ending with the specified suffices from the given 134 source directory or its descendants, omitting files that do not match 135 any suffix. The result of the example below will include files like 136 `./dir/module.c` and `./dir/subdir/doc.xml` if present. 137 138 Type: sourceLike -> [String] -> Source 139 140 Example: 141 sourceFilesBySuffices ./. [ ".xml" ".c" ] 142 */ 143 sourceFilesBySuffices = 144 # Path or source containing the files to be returned 145 src: 146 # A list of file suffix strings 147 exts: 148 let filter = name: type: 149 let base = baseNameOf (toString name); 150 in type == "directory" || lib.any (ext: lib.hasSuffix ext base) exts; 151 in cleanSourceWith { inherit filter src; }; 152 153 pathIsGitRepo = path: (_commitIdFromGitRepoOrError path)?value; 154 155 /* 156 Get the commit id of a git repo. 157 158 Example: commitIdFromGitRepo <nixpkgs/.git> 159 */ 160 commitIdFromGitRepo = path: 161 let commitIdOrError = _commitIdFromGitRepoOrError path; 162 in commitIdOrError.value or (throw commitIdOrError.error); 163 164 # Get the commit id of a git repo. 165 166 # Returns `{ value = commitHash }` or `{ error = "... message ..." }`. 167 168 # Example: commitIdFromGitRepo <nixpkgs/.git> 169 # not exported, used for commitIdFromGitRepo 170 _commitIdFromGitRepoOrError = 171 let readCommitFromFile = file: path: 172 let fileName = path + "/${file}"; 173 packedRefsName = path + "/packed-refs"; 174 absolutePath = base: path: 175 if lib.hasPrefix "/" path 176 then path 177 else toString (/. + "${base}/${path}"); 178 in if pathIsRegularFile path 179 # Resolve git worktrees. See gitrepository-layout(5) 180 then 181 let m = match "^gitdir: (.*)$" (lib.fileContents path); 182 in if m == null 183 then { error = "File contains no gitdir reference: " + path; } 184 else 185 let gitDir = absolutePath (dirOf path) (lib.head m); 186 commonDir'' = if pathIsRegularFile "${gitDir}/commondir" 187 then lib.fileContents "${gitDir}/commondir" 188 else gitDir; 189 commonDir' = lib.removeSuffix "/" commonDir''; 190 commonDir = absolutePath gitDir commonDir'; 191 refFile = lib.removePrefix "${commonDir}/" "${gitDir}/${file}"; 192 in readCommitFromFile refFile commonDir 193 194 else if pathIsRegularFile fileName 195 # Sometimes git stores the commitId directly in the file but 196 # sometimes it stores something like: «ref: refs/heads/branch-name» 197 then 198 let fileContent = lib.fileContents fileName; 199 matchRef = match "^ref: (.*)$" fileContent; 200 in if matchRef == null 201 then { value = fileContent; } 202 else readCommitFromFile (lib.head matchRef) path 203 204 else if pathIsRegularFile packedRefsName 205 # Sometimes, the file isn't there at all and has been packed away in the 206 # packed-refs file, so we have to grep through it: 207 then 208 let fileContent = readFile packedRefsName; 209 matchRef = match "([a-z0-9]+) ${file}"; 210 isRef = s: isString s && (matchRef s) != null; 211 # there is a bug in libstdc++ leading to stackoverflow for long strings: 212 # https://github.com/NixOS/nix/issues/2147#issuecomment-659868795 213 refs = filter isRef (split "\n" fileContent); 214 in if refs == [] 215 then { error = "Could not find " + file + " in " + packedRefsName; } 216 else { value = lib.head (matchRef (lib.head refs)); } 217 218 else { error = "Not a .git directory: " + toString path; }; 219 in readCommitFromFile "HEAD"; 220 221 pathHasContext = builtins.hasContext or (lib.hasPrefix storeDir); 222 223 canCleanSource = src: src ? _isLibCleanSourceWith || !(pathHasContext (toString src)); 224 225 # -------------------------------------------------------------------------- # 226 # Internal functions 227 # 228 229 # toSourceAttributes : sourceLike -> SourceAttrs 230 # 231 # Convert any source-like object into a simple, singular representation. 232 # We don't expose this representation in order to avoid having a fifth path- 233 # like class of objects in the wild. 234 # (Existing ones being: paths, strings, sources and x//{outPath}) 235 # So instead of exposing internals, we build a library of combinator functions. 236 toSourceAttributes = src: 237 let 238 isFiltered = src ? _isLibCleanSourceWith; 239 in 240 { 241 # The original path 242 origSrc = if isFiltered then src.origSrc else src; 243 filter = if isFiltered then src.filter else _: _: true; 244 name = if isFiltered then src.name else "source"; 245 }; 246 247 # fromSourceAttributes : SourceAttrs -> Source 248 # 249 # Inverse of toSourceAttributes for Source objects. 250 fromSourceAttributes = { origSrc, filter, name }: 251 { 252 _isLibCleanSourceWith = true; 253 inherit origSrc filter name; 254 outPath = builtins.path { inherit filter name; path = origSrc; }; 255 }; 256 257in { 258 259 pathType = lib.warnIf (lib.isInOldestRelease 2305) 260 "lib.sources.pathType has been moved to lib.filesystem.pathType." 261 lib.filesystem.pathType; 262 263 pathIsDirectory = lib.warnIf (lib.isInOldestRelease 2305) 264 "lib.sources.pathIsDirectory has been moved to lib.filesystem.pathIsDirectory." 265 lib.filesystem.pathIsDirectory; 266 267 pathIsRegularFile = lib.warnIf (lib.isInOldestRelease 2305) 268 "lib.sources.pathIsRegularFile has been moved to lib.filesystem.pathIsRegularFile." 269 lib.filesystem.pathIsRegularFile; 270 271 inherit 272 pathIsGitRepo 273 commitIdFromGitRepo 274 275 cleanSource 276 cleanSourceWith 277 cleanSourceFilter 278 pathHasContext 279 canCleanSource 280 281 sourceByRegex 282 sourceFilesBySuffices 283 284 trace 285 ; 286}