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: (tryEval (commitIdFromGitRepo path)).success;
172 Get the commit id of a git repo.
174 Example: commitIdFromGitRepo <nixpkgs/.git>
176 commitIdFromGitRepo =
177 let readCommitFromFile = file: path:
178 let fileName = toString path + "/" + file;
179 packedRefsName = toString path + "/packed-refs";
180 absolutePath = base: path:
181 if lib.hasPrefix "/" path
183 else toString (/. + "${base}/${path}");
184 in if pathIsRegularFile path
185 # Resolve git worktrees. See gitrepository-layout(5)
187 let m = match "^gitdir: (.*)$" (lib.fileContents path);
189 then throw ("File contains no gitdir reference: " + path)
191 let gitDir = absolutePath (dirOf path) (lib.head m);
192 commonDir'' = if pathIsRegularFile "${gitDir}/commondir"
193 then lib.fileContents "${gitDir}/commondir"
195 commonDir' = lib.removeSuffix "/" commonDir'';
196 commonDir = absolutePath gitDir commonDir';
197 refFile = lib.removePrefix "${commonDir}/" "${gitDir}/${file}";
198 in readCommitFromFile refFile commonDir
200 else if pathIsRegularFile fileName
201 # Sometimes git stores the commitId directly in the file but
202 # sometimes it stores something like: «ref: refs/heads/branch-name»
204 let fileContent = lib.fileContents fileName;
205 matchRef = match "^ref: (.*)$" fileContent;
206 in if matchRef == null
208 else readCommitFromFile (lib.head matchRef) path
210 else if pathIsRegularFile packedRefsName
211 # Sometimes, the file isn't there at all and has been packed away in the
212 # packed-refs file, so we have to grep through it:
214 let fileContent = readFile packedRefsName;
215 matchRef = match "([a-z0-9]+) ${file}";
216 isRef = s: isString s && (matchRef s) != null;
217 # there is a bug in libstdc++ leading to stackoverflow for long strings:
218 # https://github.com/NixOS/nix/issues/2147#issuecomment-659868795
219 refs = filter isRef (split "\n" fileContent);
221 then throw ("Could not find " + file + " in " + packedRefsName)
222 else lib.head (matchRef (lib.head refs))
224 else throw ("Not a .git directory: " + path);
225 in readCommitFromFile "HEAD";
227 pathHasContext = builtins.hasContext or (lib.hasPrefix storeDir);
229 canCleanSource = src: src ? _isLibCleanSourceWith || !(pathHasContext (toString src));
231 # -------------------------------------------------------------------------- #
235 # toSourceAttributes : sourceLike -> SourceAttrs
237 # Convert any source-like object into a simple, singular representation.
238 # We don't expose this representation in order to avoid having a fifth path-
239 # like class of objects in the wild.
240 # (Existing ones being: paths, strings, sources and x//{outPath})
241 # So instead of exposing internals, we build a library of combinator functions.
242 toSourceAttributes = src:
244 isFiltered = src ? _isLibCleanSourceWith;
248 origSrc = if isFiltered then src.origSrc else src;
249 filter = if isFiltered then src.filter else _: _: true;
250 name = if isFiltered then src.name else "source";
253 # fromSourceAttributes : SourceAttrs -> Source
255 # Inverse of toSourceAttributes for Source objects.
256 fromSourceAttributes = { origSrc, filter, name }:
258 _isLibCleanSourceWith = true;
259 inherit origSrc filter name;
260 outPath = builtins.path { inherit filter name; path = origSrc; };
279 sourceFilesBySuffices