1 /* Functions for copying sources to the Nix store. */
4 # Tested in lib/tests/sources.sh
17 inherit (lib.filesystem)
22 A basic filter for `cleanSourceWith` that removes
23 directories of version control system, backup files (*~)
24 and some generated files.
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 ||
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.
44 Filters a source tree removing version control files and directories using cleanSourceFilter.
49 cleanSource = src: cleanSourceWith { filter = cleanSourceFilter; inherit src; };
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.
59 src = lib.cleanSourceWith {
66 builtins.filterSource f (builtins.filterSource g ./.)
72 # A path or cleanSourceWith result to filter and/or rename.
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"`.
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;
94 Add logging to a source, for troubleshooting the filtering behavior.
96 sources.trace :: sourceLike -> Source
99 # Source to debug. The returned source will behave like this source, but also log its filter invocations.
102 attrs = toSourceAttributes src;
104 fromSourceAttributes (
108 r = attrs.filter path type;
110 builtins.trace "${attrs.name}.filter ${path} = ${boolToString r}" r;
113 satisfiesSubpathInvariant = src ? satisfiesSubpathInvariant && src.satisfiesSubpathInvariant;
117 Filter sources by a list of regular expressions.
119 Example: src = sourceByRegex ./my-subproject [".*\.py$" "^database.sql$"]
121 sourceByRegex = src: regexes:
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);
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.
138 Type: sourceLike -> [String] -> Source
141 sourceFilesBySuffices ./. [ ".xml" ".c" ]
143 sourceFilesBySuffices =
144 # Path or source containing the files to be returned
146 # A list of file suffix strings
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; };
153 pathIsGitRepo = path: (_commitIdFromGitRepoOrError path)?value;
156 Get the commit id of a git repo.
158 Example: commitIdFromGitRepo <nixpkgs/.git>
160 commitIdFromGitRepo = path:
161 let commitIdOrError = _commitIdFromGitRepoOrError path;
162 in commitIdOrError.value or (throw commitIdOrError.error);
164 # Get the commit id of a git repo.
166 # Returns `{ value = commitHash }` or `{ error = "... message ..." }`.
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
177 else toString (/. + "${base}/${path}");
178 in if pathIsRegularFile path
179 # Resolve git worktrees. See gitrepository-layout(5)
181 let m = match "^gitdir: (.*)$" (lib.fileContents path);
183 then { error = "File contains no gitdir reference: " + path; }
185 let gitDir = absolutePath (dirOf path) (lib.head m);
186 commonDir'' = if pathIsRegularFile "${gitDir}/commondir"
187 then lib.fileContents "${gitDir}/commondir"
189 commonDir' = lib.removeSuffix "/" commonDir'';
190 commonDir = absolutePath gitDir commonDir';
191 refFile = lib.removePrefix "${commonDir}/" "${gitDir}/${file}";
192 in readCommitFromFile refFile commonDir
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»
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
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:
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);
215 then { error = "Could not find " + file + " in " + packedRefsName; }
216 else { value = lib.head (matchRef (lib.head refs)); }
218 else { error = "Not a .git directory: " + toString path; };
219 in readCommitFromFile "HEAD";
221 pathHasContext = builtins.hasContext or (lib.hasPrefix storeDir);
223 canCleanSource = src: src ? _isLibCleanSourceWith || !(pathHasContext (toString src));
225 # -------------------------------------------------------------------------- #
229 # toSourceAttributes : sourceLike -> SourceAttrs
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:
238 isFiltered = src ? _isLibCleanSourceWith;
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";
247 # fromSourceAttributes : SourceAttrs -> Source
249 # Inverse of toSourceAttributes for Source objects.
250 fromSourceAttributes = { origSrc, filter, name }:
252 _isLibCleanSourceWith = true;
253 inherit origSrc filter name;
254 outPath = builtins.path { inherit filter name; path = origSrc; };
259 pathType = lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2305)
260 "lib.sources.pathType has been moved to lib.filesystem.pathType."
261 lib.filesystem.pathType;
263 pathIsDirectory = lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2305)
264 "lib.sources.pathIsDirectory has been moved to lib.filesystem.pathIsDirectory."
265 lib.filesystem.pathIsDirectory;
267 pathIsRegularFile = lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2305)
268 "lib.sources.pathIsRegularFile has been moved to lib.filesystem.pathIsRegularFile."
269 lib.filesystem.pathIsRegularFile;
282 sourceFilesBySuffices