1 # Functions for copying sources to the Nix store.
4 # Tested in lib/tests/sources.sh
23 Returns the type of a path: regular (for file), symlink, or directory.
25 pathType = path: getAttr (baseNameOf path) (readDir (dirOf path));
28 Returns true if the path exists and is a directory, false otherwise.
30 pathIsDirectory = path: if pathExists path then (pathType path) == "directory" else false;
33 Returns true if the path exists and is a regular file, false otherwise.
35 pathIsRegularFile = path: if pathExists path then (pathType path) == "regular" else false;
38 A basic filter for `cleanSourceWith` that removes
39 directories of version control system, backup files (*~)
40 and some generated files.
42 cleanSourceFilter = name: type: let baseName = baseNameOf (toString name); in ! (
43 # Filter out version control software files/directories
44 (baseName == ".git" || type == "directory" && (baseName == ".svn" || baseName == "CVS" || baseName == ".hg")) ||
45 # Filter out editor backup / swap files.
46 lib.hasSuffix "~" baseName ||
47 match "^\\.sw[a-z]$" baseName != null ||
48 match "^\\..*\\.sw[a-z]$" baseName != null ||
50 # Filter out generates files.
51 lib.hasSuffix ".o" baseName ||
52 lib.hasSuffix ".so" baseName ||
53 # Filter out nix-build result symlinks
54 (type == "symlink" && lib.hasPrefix "result" baseName) ||
55 # Filter out sockets and other types of files we can't have in the store.
60 Filters a source tree removing version control files and directories using cleanSourceFilter.
65 cleanSource = src: cleanSourceWith { filter = cleanSourceFilter; inherit src; };
68 Like `builtins.filterSource`, except it will compose with itself,
69 allowing you to chain multiple calls together without any
70 intermediate copies being put in the nix store.
75 src = lib.cleanSourceWith {
82 builtins.filterSource f (builtins.filterSource g ./.)
88 # A path or cleanSourceWith result to filter and/or rename.
90 # Optional with default value: constant true (include everything)
91 # The function will be combined with the && operator such
92 # that src.filter is called lazily.
93 # For implementing a filter, see
94 # https://nixos.org/nix/manual/#builtin-filterSource
95 # Type: A function (path -> type -> bool)
96 filter ? _path: _type: true,
97 # Optional name to use as part of the store path.
98 # This defaults to `src.name` or otherwise `"source"`.
102 orig = toSourceAttributes src;
103 in fromSourceAttributes {
104 inherit (orig) origSrc;
105 filter = path: type: filter path type && orig.filter path type;
106 name = if name != null then name else orig.name;
110 Add logging to a source, for troubleshooting the filtering behavior.
112 sources.trace :: sourceLike -> Source
115 # Source to debug. The returned source will behave like this source, but also log its filter invocations.
118 attrs = toSourceAttributes src;
120 fromSourceAttributes (
124 r = attrs.filter path type;
126 builtins.trace "${attrs.name}.filter ${path} = ${boolToString r}" r;
129 satisfiesSubpathInvariant = src ? satisfiesSubpathInvariant && src.satisfiesSubpathInvariant;
133 Filter sources by a list of regular expressions.
135 Example: src = sourceByRegex ./my-subproject [".*\.py$" "^database.sql$"]
137 sourceByRegex = src: regexes:
139 isFiltered = src ? _isLibCleanSourceWith;
140 origSrc = if isFiltered then src.origSrc else src;
141 in lib.cleanSourceWith {
142 filter = (path: type:
143 let relPath = lib.removePrefix (toString origSrc + "/") (toString path);
144 in lib.any (re: match re relPath != null) regexes);
149 Get all files ending with the specified suffices from the given
150 source directory or its descendants, omitting files that do not match
151 any suffix. The result of the example below will include files like
152 `./dir/module.c` and `./dir/subdir/doc.xml` if present.
154 Type: sourceLike -> [String] -> Source
157 sourceFilesBySuffices ./. [ ".xml" ".c" ]
159 sourceFilesBySuffices =
160 # Path or source containing the files to be returned
162 # A list of file suffix strings
164 let filter = name: type:
165 let base = baseNameOf (toString name);
166 in type == "directory" || lib.any (ext: lib.hasSuffix ext base) exts;
167 in cleanSourceWith { inherit filter src; };
169 pathIsGitRepo = path: (_commitIdFromGitRepoOrError path)?value;
172 Get the commit id of a git repo.
174 Example: commitIdFromGitRepo <nixpkgs/.git>
176 commitIdFromGitRepo = path:
177 let commitIdOrError = _commitIdFromGitRepoOrError path;
178 in commitIdOrError.value or (throw commitIdOrError.error);
180 # Get the commit id of a git repo.
182 # Returns `{ value = commitHash }` or `{ error = "... message ..." }`.
184 # Example: commitIdFromGitRepo <nixpkgs/.git>
185 # not exported, used for commitIdFromGitRepo
186 _commitIdFromGitRepoOrError =
187 let readCommitFromFile = file: path:
188 let fileName = path + "/${file}";
189 packedRefsName = path + "/packed-refs";
190 absolutePath = base: path:
191 if lib.hasPrefix "/" path
193 else toString (/. + "${base}/${path}");
194 in if pathIsRegularFile path
195 # Resolve git worktrees. See gitrepository-layout(5)
197 let m = match "^gitdir: (.*)$" (lib.fileContents path);
199 then { error = "File contains no gitdir reference: " + path; }
201 let gitDir = absolutePath (dirOf path) (lib.head m);
202 commonDir'' = if pathIsRegularFile "${gitDir}/commondir"
203 then lib.fileContents "${gitDir}/commondir"
205 commonDir' = lib.removeSuffix "/" commonDir'';
206 commonDir = absolutePath gitDir commonDir';
207 refFile = lib.removePrefix "${commonDir}/" "${gitDir}/${file}";
208 in readCommitFromFile refFile commonDir
210 else if pathIsRegularFile fileName
211 # Sometimes git stores the commitId directly in the file but
212 # sometimes it stores something like: «ref: refs/heads/branch-name»
214 let fileContent = lib.fileContents fileName;
215 matchRef = match "^ref: (.*)$" fileContent;
216 in if matchRef == null
217 then { value = fileContent; }
218 else readCommitFromFile (lib.head matchRef) path
220 else if pathIsRegularFile packedRefsName
221 # Sometimes, the file isn't there at all and has been packed away in the
222 # packed-refs file, so we have to grep through it:
224 let fileContent = readFile packedRefsName;
225 matchRef = match "([a-z0-9]+) ${file}";
226 isRef = s: isString s && (matchRef s) != null;
227 # there is a bug in libstdc++ leading to stackoverflow for long strings:
228 # https://github.com/NixOS/nix/issues/2147#issuecomment-659868795
229 refs = filter isRef (split "\n" fileContent);
231 then { error = "Could not find " + file + " in " + packedRefsName; }
232 else { value = lib.head (matchRef (lib.head refs)); }
234 else { error = "Not a .git directory: " + toString path; };
235 in readCommitFromFile "HEAD";
237 pathHasContext = builtins.hasContext or (lib.hasPrefix storeDir);
239 canCleanSource = src: src ? _isLibCleanSourceWith || !(pathHasContext (toString src));
241 # -------------------------------------------------------------------------- #
245 # toSourceAttributes : sourceLike -> SourceAttrs
247 # Convert any source-like object into a simple, singular representation.
248 # We don't expose this representation in order to avoid having a fifth path-
249 # like class of objects in the wild.
250 # (Existing ones being: paths, strings, sources and x//{outPath})
251 # So instead of exposing internals, we build a library of combinator functions.
252 toSourceAttributes = src:
254 isFiltered = src ? _isLibCleanSourceWith;
258 origSrc = if isFiltered then src.origSrc else src;
259 filter = if isFiltered then src.filter else _: _: true;
260 name = if isFiltered then src.name else "source";
263 # fromSourceAttributes : SourceAttrs -> Source
265 # Inverse of toSourceAttributes for Source objects.
266 fromSourceAttributes = { origSrc, filter, name }:
268 _isLibCleanSourceWith = true;
269 inherit origSrc filter name;
270 outPath = builtins.path { inherit filter name; path = origSrc; };
289 sourceFilesBySuffices