openvswitch: generalize builder
[NixPkgs.git] / lib / sources.nix
blob3ad7dc63355499402f6ef1141c6958c60faab568
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: (_commitIdFromGitRepoOrError path)?value;
171   /*
172     Get the commit id of a git repo.
174     Example: commitIdFromGitRepo <nixpkgs/.git>
175   */
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
192               then path
193               else toString (/. + "${base}/${path}");
194         in if pathIsRegularFile path
195            # Resolve git worktrees. See gitrepository-layout(5)
196            then
197              let m   = match "^gitdir: (.*)$" (lib.fileContents path);
198              in if m == null
199                 then { error = "File contains no gitdir reference: " + path; }
200                 else
201                   let gitDir      = absolutePath (dirOf path) (lib.head m);
202                       commonDir'' = if pathIsRegularFile "${gitDir}/commondir"
203                                     then lib.fileContents "${gitDir}/commondir"
204                                     else gitDir;
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»
213            then
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:
223            then
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);
230              in if refs == []
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   # -------------------------------------------------------------------------- #
242   # Internal functions
243   #
245   # toSourceAttributes : sourceLike -> SourceAttrs
246   #
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:
253     let
254       isFiltered = src ? _isLibCleanSourceWith;
255     in
256     {
257       # The original path
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";
261     };
263   # fromSourceAttributes : SourceAttrs -> Source
264   #
265   # Inverse of toSourceAttributes for Source objects.
266   fromSourceAttributes = { origSrc, filter, name }:
267     {
268       _isLibCleanSourceWith = true;
269       inherit origSrc filter name;
270       outPath = builtins.path { inherit filter name; path = origSrc; };
271     };
273 in {
274   inherit
275     pathType
276     pathIsDirectory
277     pathIsRegularFile
279     pathIsGitRepo
280     commitIdFromGitRepo
282     cleanSource
283     cleanSourceWith
284     cleanSourceFilter
285     pathHasContext
286     canCleanSource
288     sourceByRegex
289     sourceFilesBySuffices
291     trace
292     ;