python310Packages.pydeconz: 104 -> 105
[NixPkgs.git] / lib / sources.nix
blobcec395c9bb180ae3e9fd397365552d94f86a123c
1 # Functions for copying sources to the Nix store.
2 { lib }:
4 # Tested in lib/tests/sources.sh
5 let
6   inherit (builtins)
7     match
8     readDir
9     split
10     storeDir
11     tryEval
12     ;
13   inherit (lib)
14     boolToString
15     filter
16     getAttr
17     isString
18     pathExists
19     readFile
20     ;
22   /*
23     Returns the type of a path: regular (for file), symlink, or directory.
24   */
25   pathType = path: getAttr (baseNameOf path) (readDir (dirOf path));
27   /*
28     Returns true if the path exists and is a directory, false otherwise.
29   */
30   pathIsDirectory = path: if pathExists path then (pathType path) == "directory" else false;
32   /*
33     Returns true if the path exists and is a regular file, false otherwise.
34   */
35   pathIsRegularFile = path: if pathExists path then (pathType path) == "regular" else false;
37   /*
38     A basic filter for `cleanSourceWith` that removes
39     directories of version control system, backup files (*~)
40     and some generated files.
41   */
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.
56     (type == "unknown")
57   );
59   /*
60     Filters a source tree removing version control files and directories using cleanSourceFilter.
62     Example:
63              cleanSource ./.
64   */
65   cleanSource = src: cleanSourceWith { filter = cleanSourceFilter; inherit src; };
67   /*
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.
72     Example:
73         lib.cleanSourceWith {
74           filter = f;
75           src = lib.cleanSourceWith {
76             filter = g;
77             src = ./.;
78           };
79         }
80         # Succeeds!
82         builtins.filterSource f (builtins.filterSource g ./.)
83         # Fails!
85   */
86   cleanSourceWith =
87     {
88       # A path or cleanSourceWith result to filter and/or rename.
89       src,
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"`.
99       name ? null
100     }:
101     let
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;
107     };
109   /*
110     Add logging to a source, for troubleshooting the filtering behavior.
111     Type:
112       sources.trace :: sourceLike -> Source
113   */
114   trace =
115     # Source to debug. The returned source will behave like this source, but also log its filter invocations.
116     src:
117     let
118       attrs = toSourceAttributes src;
119     in
120       fromSourceAttributes (
121         attrs // {
122           filter = path: type:
123             let
124               r = attrs.filter path type;
125             in
126               builtins.trace "${attrs.name}.filter ${path} = ${boolToString r}" r;
127         }
128       ) // {
129         satisfiesSubpathInvariant = src ? satisfiesSubpathInvariant && src.satisfiesSubpathInvariant;
130       };
132   /*
133     Filter sources by a list of regular expressions.
135     Example: src = sourceByRegex ./my-subproject [".*\.py$" "^database.sql$"]
136   */
137   sourceByRegex = src: regexes:
138     let
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);
145       inherit src;
146     };
148   /*
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
156     Example:
157       sourceFilesBySuffices ./. [ ".xml" ".c" ]
158   */
159   sourceFilesBySuffices =
160     # Path or source containing the files to be returned
161     src:
162     # A list of file suffix strings
163     exts:
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;
171   /*
172     Get the commit id of a git repo.
174     Example: commitIdFromGitRepo <nixpkgs/.git>
175   */
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
182               then path
183               else toString (/. + "${base}/${path}");
184         in if pathIsRegularFile path
185            # Resolve git worktrees. See gitrepository-layout(5)
186            then
187              let m   = match "^gitdir: (.*)$" (lib.fileContents path);
188              in if m == null
189                 then throw ("File contains no gitdir reference: " + path)
190                 else
191                   let gitDir      = absolutePath (dirOf path) (lib.head m);
192                       commonDir'' = if pathIsRegularFile "${gitDir}/commondir"
193                                     then lib.fileContents "${gitDir}/commondir"
194                                     else gitDir;
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»
203            then
204              let fileContent = lib.fileContents fileName;
205                  matchRef    = match "^ref: (.*)$" fileContent;
206              in if  matchRef == null
207                 then fileContent
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:
213            then
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);
220              in if refs == []
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   # -------------------------------------------------------------------------- #
232   # Internal functions
233   #
235   # toSourceAttributes : sourceLike -> SourceAttrs
236   #
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:
243     let
244       isFiltered = src ? _isLibCleanSourceWith;
245     in
246     {
247       # The original path
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";
251     };
253   # fromSourceAttributes : SourceAttrs -> Source
254   #
255   # Inverse of toSourceAttributes for Source objects.
256   fromSourceAttributes = { origSrc, filter, name }:
257     {
258       _isLibCleanSourceWith = true;
259       inherit origSrc filter name;
260       outPath = builtins.path { inherit filter name; path = origSrc; };
261     };
263 in {
264   inherit
265     pathType
266     pathIsDirectory
267     pathIsRegularFile
269     pathIsGitRepo
270     commitIdFromGitRepo
272     cleanSource
273     cleanSourceWith
274     cleanSourceFilter
275     pathHasContext
276     canCleanSource
278     sourceByRegex
279     sourceFilesBySuffices
281     trace
282     ;