/src/CMake/Source/cmDependsC.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | #include "cmDependsC.h" |
4 | | |
5 | | #include <utility> |
6 | | |
7 | | #include "cmsys/FStream.hxx" |
8 | | |
9 | | #include "cmFileTime.h" |
10 | | #include "cmGlobalUnixMakefileGenerator3.h" |
11 | | #include "cmList.h" |
12 | | #include "cmLocalUnixMakefileGenerator3.h" |
13 | | #include "cmMakefile.h" |
14 | | #include "cmStringAlgorithms.h" |
15 | | #include "cmSystemTools.h" |
16 | | #include "cmValue.h" |
17 | | |
18 | | #define INCLUDE_REGEX_LINE \ |
19 | 0 | "^[ \t]*[#%][ \t]*(include|import)[ \t]*[<\"]([^\">]+)([\">])" |
20 | | |
21 | 0 | #define INCLUDE_REGEX_LINE_MARKER "#IncludeRegexLine: " |
22 | 0 | #define INCLUDE_REGEX_SCAN_MARKER "#IncludeRegexScan: " |
23 | 0 | #define INCLUDE_REGEX_COMPLAIN_MARKER "#IncludeRegexComplain: " |
24 | 0 | #define INCLUDE_REGEX_TRANSFORM_MARKER "#IncludeRegexTransform: " |
25 | | |
26 | 0 | cmDependsC::cmDependsC() = default; |
27 | | |
28 | | cmDependsC::cmDependsC(cmLocalUnixMakefileGenerator3* lg, |
29 | | std::string const& targetDir, std::string const& lang, |
30 | | DependencyMap const* validDeps) |
31 | 0 | : cmDepends(lg, targetDir) |
32 | 0 | , ValidDeps(validDeps) |
33 | 0 | { |
34 | 0 | cmMakefile* mf = lg->GetMakefile(); |
35 | | |
36 | | // Configure the include file search path. |
37 | 0 | this->SetIncludePathFromLanguage(lang); |
38 | | |
39 | | // Configure regular expressions. |
40 | 0 | std::string scanRegex = "^.*$"; |
41 | 0 | std::string complainRegex = "^$"; |
42 | 0 | { |
43 | 0 | std::string scanRegexVar = cmStrCat("CMAKE_", lang, "_INCLUDE_REGEX_SCAN"); |
44 | 0 | if (cmValue sr = mf->GetDefinition(scanRegexVar)) { |
45 | 0 | scanRegex = *sr; |
46 | 0 | } |
47 | 0 | std::string complainRegexVar = |
48 | 0 | cmStrCat("CMAKE_", lang, "_INCLUDE_REGEX_COMPLAIN"); |
49 | 0 | if (cmValue cr = mf->GetDefinition(complainRegexVar)) { |
50 | 0 | complainRegex = *cr; |
51 | 0 | } |
52 | 0 | } |
53 | |
|
54 | 0 | this->IncludeRegexLine.compile(INCLUDE_REGEX_LINE); |
55 | 0 | this->IncludeRegexScan.compile(scanRegex); |
56 | 0 | this->IncludeRegexComplain.compile(complainRegex); |
57 | 0 | this->IncludeRegexLineString = INCLUDE_REGEX_LINE_MARKER INCLUDE_REGEX_LINE; |
58 | 0 | this->IncludeRegexScanString = |
59 | 0 | cmStrCat(INCLUDE_REGEX_SCAN_MARKER, scanRegex); |
60 | 0 | this->IncludeRegexComplainString = |
61 | 0 | cmStrCat(INCLUDE_REGEX_COMPLAIN_MARKER, complainRegex); |
62 | |
|
63 | 0 | this->SetupTransforms(); |
64 | |
|
65 | 0 | this->CacheFileName = |
66 | 0 | cmStrCat(this->TargetDirectory, '/', lang, ".includecache"); |
67 | |
|
68 | 0 | this->ReadCacheFile(); |
69 | 0 | } |
70 | | |
71 | | cmDependsC::~cmDependsC() |
72 | 0 | { |
73 | 0 | this->WriteCacheFile(); |
74 | 0 | } |
75 | | |
76 | | bool cmDependsC::WriteDependencies(std::set<std::string> const& sources, |
77 | | std::string const& obj, |
78 | | std::ostream& makeDepends, |
79 | | std::ostream& internalDepends) |
80 | 0 | { |
81 | | // Make sure this is a scanning instance. |
82 | 0 | if (sources.empty() || sources.begin()->empty()) { |
83 | 0 | cmSystemTools::Error("Cannot scan dependencies without a source file."); |
84 | 0 | return false; |
85 | 0 | } |
86 | 0 | if (obj.empty()) { |
87 | 0 | cmSystemTools::Error("Cannot scan dependencies without an object file."); |
88 | 0 | return false; |
89 | 0 | } |
90 | | |
91 | 0 | std::set<std::string> dependencies; |
92 | 0 | bool haveDeps = false; |
93 | | |
94 | | // Compute a path to the object file to write to the internal depend file. |
95 | | // Any existing content of the internal depend file has already been |
96 | | // loaded in ValidDeps with this path as a key. |
97 | 0 | std::string obj_i = this->LocalGenerator->MaybeRelativeToTopBinDir(obj); |
98 | |
|
99 | 0 | if (this->ValidDeps) { |
100 | 0 | auto const tmpIt = this->ValidDeps->find(obj_i); |
101 | 0 | if (tmpIt != this->ValidDeps->end()) { |
102 | 0 | dependencies.insert(tmpIt->second.begin(), tmpIt->second.end()); |
103 | 0 | haveDeps = true; |
104 | 0 | } |
105 | 0 | } |
106 | |
|
107 | 0 | if (!haveDeps) { |
108 | | // Walk the dependency graph starting with the source file. |
109 | 0 | int srcFiles = static_cast<int>(sources.size()); |
110 | 0 | this->Encountered.clear(); |
111 | |
|
112 | 0 | for (std::string const& src : sources) { |
113 | 0 | UnscannedEntry root; |
114 | 0 | root.FileName = src; |
115 | 0 | this->Unscanned.push(root); |
116 | 0 | this->Encountered.insert(src); |
117 | 0 | } |
118 | |
|
119 | 0 | std::set<std::string> scanned; |
120 | 0 | while (!this->Unscanned.empty()) { |
121 | | // Get the next file to scan. |
122 | 0 | UnscannedEntry current = this->Unscanned.front(); |
123 | 0 | this->Unscanned.pop(); |
124 | | |
125 | | // If not a full path, find the file in the include path. |
126 | 0 | std::string fullName; |
127 | 0 | if ((srcFiles > 0) || cmSystemTools::FileIsFullPath(current.FileName)) { |
128 | 0 | if (cmSystemTools::FileExists(current.FileName, true)) { |
129 | 0 | fullName = current.FileName; |
130 | 0 | } |
131 | 0 | } else if (!current.QuotedLocation.empty() && |
132 | 0 | cmSystemTools::FileExists(current.QuotedLocation, true)) { |
133 | | // The include statement producing this entry was a double-quote |
134 | | // include and the included file is present in the directory of |
135 | | // the source containing the include statement. |
136 | 0 | fullName = current.QuotedLocation; |
137 | 0 | } else { |
138 | 0 | auto headerLocationIt = |
139 | 0 | this->HeaderLocationCache.find(current.FileName); |
140 | 0 | if (headerLocationIt != this->HeaderLocationCache.end()) { |
141 | 0 | fullName = headerLocationIt->second; |
142 | 0 | } else { |
143 | 0 | for (std::string const& iPath : this->IncludePath) { |
144 | | // Construct the name of the file as if it were in the current |
145 | | // include directory. Avoid using a leading "./". |
146 | 0 | std::string tmpPath = |
147 | 0 | cmSystemTools::CollapseFullPath(current.FileName, iPath); |
148 | | |
149 | | // Look for the file in this location. |
150 | 0 | if (cmSystemTools::FileExists(tmpPath, true)) { |
151 | 0 | fullName = tmpPath; |
152 | 0 | this->HeaderLocationCache[current.FileName] = std::move(tmpPath); |
153 | 0 | break; |
154 | 0 | } |
155 | 0 | } |
156 | 0 | } |
157 | 0 | } |
158 | | |
159 | | // Complain if the file cannot be found and matches the complain |
160 | | // regex. |
161 | 0 | if (fullName.empty() && |
162 | 0 | this->IncludeRegexComplain.find(current.FileName)) { |
163 | 0 | cmSystemTools::Error( |
164 | 0 | cmStrCat("Cannot find file \"", current.FileName, "\".")); |
165 | 0 | return false; |
166 | 0 | } |
167 | | |
168 | | // Scan the file if it was found and has not been scanned already. |
169 | 0 | if (!fullName.empty() && (scanned.find(fullName) == scanned.end())) { |
170 | | // Record scanned files. |
171 | 0 | scanned.insert(fullName); |
172 | | |
173 | | // Check whether this file is already in the cache |
174 | 0 | auto fileIt = this->FileCache.find(fullName); |
175 | 0 | if (fileIt != this->FileCache.end()) { |
176 | 0 | fileIt->second.Used = true; |
177 | 0 | dependencies.insert(fullName); |
178 | 0 | for (UnscannedEntry const& inc : fileIt->second.UnscannedEntries) { |
179 | 0 | if (this->Encountered.find(inc.FileName) == |
180 | 0 | this->Encountered.end()) { |
181 | 0 | this->Encountered.insert(inc.FileName); |
182 | 0 | this->Unscanned.push(inc); |
183 | 0 | } |
184 | 0 | } |
185 | 0 | } else { |
186 | | |
187 | | // Try to scan the file. Just leave it out if we cannot find |
188 | | // it. |
189 | 0 | cmsys::ifstream fin(fullName.c_str()); |
190 | 0 | if (fin) { |
191 | 0 | cmsys::FStream::BOM bom = cmsys::FStream::ReadBOM(fin); |
192 | 0 | if (bom == cmsys::FStream::BOM_None || |
193 | 0 | bom == cmsys::FStream::BOM_UTF8) { |
194 | | // Add this file as a dependency. |
195 | 0 | dependencies.insert(fullName); |
196 | | |
197 | | // Scan this file for new dependencies. Pass the directory |
198 | | // containing the file to handle double-quote includes. |
199 | 0 | std::string dir = cmSystemTools::GetFilenamePath(fullName); |
200 | 0 | this->Scan(fin, dir, fullName); |
201 | 0 | } else { |
202 | | // Skip file with encoding we do not implement. |
203 | 0 | } |
204 | 0 | } |
205 | 0 | } |
206 | 0 | } |
207 | |
|
208 | 0 | srcFiles--; |
209 | 0 | } |
210 | 0 | } |
211 | | |
212 | | // Write the dependencies to the output stream. Makefile rules |
213 | | // written by the original local generator for this directory |
214 | | // convert the dependencies to paths relative to the home output |
215 | | // directory. We must do the same here. |
216 | 0 | std::string obj_m = this->LocalGenerator->ConvertToMakefilePath(obj_i); |
217 | 0 | internalDepends << obj_i << '\n'; |
218 | 0 | if (!dependencies.empty()) { |
219 | 0 | auto const& lineContinue = static_cast<cmGlobalUnixMakefileGenerator3*>( |
220 | 0 | this->LocalGenerator->GetGlobalGenerator()) |
221 | 0 | ->LineContinueDirective; |
222 | 0 | bool supportLongLineDepend = static_cast<cmGlobalUnixMakefileGenerator3*>( |
223 | 0 | this->LocalGenerator->GetGlobalGenerator()) |
224 | 0 | ->SupportsLongLineDependencies(); |
225 | 0 | if (supportLongLineDepend) { |
226 | 0 | makeDepends << obj_m << ':'; |
227 | 0 | } |
228 | 0 | for (std::string const& dep : dependencies) { |
229 | 0 | std::string dependee = this->LocalGenerator->ConvertToMakefilePath( |
230 | 0 | this->LocalGenerator->MaybeRelativeToTopBinDir(dep)); |
231 | 0 | if (supportLongLineDepend) { |
232 | 0 | makeDepends << ' ' << lineContinue << ' ' << dependee; |
233 | 0 | } else { |
234 | 0 | makeDepends << obj_m << ": " << dependee << '\n'; |
235 | 0 | } |
236 | 0 | internalDepends << ' ' << dep << '\n'; |
237 | 0 | } |
238 | 0 | makeDepends << '\n'; |
239 | 0 | } |
240 | |
|
241 | 0 | return true; |
242 | 0 | } |
243 | | |
244 | | void cmDependsC::ReadCacheFile() |
245 | 0 | { |
246 | 0 | if (this->CacheFileName.empty()) { |
247 | 0 | return; |
248 | 0 | } |
249 | 0 | cmsys::ifstream fin(this->CacheFileName.c_str()); |
250 | 0 | if (!fin) { |
251 | 0 | return; |
252 | 0 | } |
253 | | |
254 | 0 | std::string line; |
255 | 0 | cmIncludeLines* cacheEntry = nullptr; |
256 | 0 | bool haveFileName = false; |
257 | |
|
258 | 0 | cmFileTime cacheFileTime; |
259 | 0 | bool const cacheFileTimeGood = cacheFileTime.Load(this->CacheFileName); |
260 | 0 | while (cmSystemTools::GetLineFromStream(fin, line)) { |
261 | 0 | if (line.empty()) { |
262 | 0 | cacheEntry = nullptr; |
263 | 0 | haveFileName = false; |
264 | 0 | continue; |
265 | 0 | } |
266 | | // the first line after an empty line is the name of the parsed file |
267 | 0 | if (!haveFileName) { |
268 | 0 | haveFileName = true; |
269 | |
|
270 | 0 | cmFileTime fileTime; |
271 | 0 | bool const res = cacheFileTimeGood && fileTime.Load(line); |
272 | 0 | bool const newer = res && cacheFileTime.Newer(fileTime); |
273 | |
|
274 | 0 | if (res && newer) // cache is newer than the parsed file |
275 | 0 | { |
276 | 0 | cacheEntry = &this->FileCache[line]; |
277 | 0 | } |
278 | | // file doesn't exist, check that the regular expressions |
279 | | // haven't changed |
280 | 0 | else if (!res) { |
281 | 0 | if (cmHasLiteralPrefix(line, INCLUDE_REGEX_LINE_MARKER)) { |
282 | 0 | if (line != this->IncludeRegexLineString) { |
283 | 0 | return; |
284 | 0 | } |
285 | 0 | } else if (cmHasLiteralPrefix(line, INCLUDE_REGEX_SCAN_MARKER)) { |
286 | 0 | if (line != this->IncludeRegexScanString) { |
287 | 0 | return; |
288 | 0 | } |
289 | 0 | } else if (cmHasLiteralPrefix(line, INCLUDE_REGEX_COMPLAIN_MARKER)) { |
290 | 0 | if (line != this->IncludeRegexComplainString) { |
291 | 0 | return; |
292 | 0 | } |
293 | 0 | } else if (cmHasLiteralPrefix(line, INCLUDE_REGEX_TRANSFORM_MARKER)) { |
294 | 0 | if (line != this->IncludeRegexTransformString) { |
295 | 0 | return; |
296 | 0 | } |
297 | 0 | } |
298 | 0 | } |
299 | 0 | } else if (cacheEntry) { |
300 | 0 | UnscannedEntry entry; |
301 | 0 | entry.FileName = line; |
302 | 0 | if (cmSystemTools::GetLineFromStream(fin, line)) { |
303 | 0 | if (line != "-") { |
304 | 0 | entry.QuotedLocation = line; |
305 | 0 | } |
306 | 0 | cacheEntry->UnscannedEntries.push_back(std::move(entry)); |
307 | 0 | } |
308 | 0 | } |
309 | 0 | } |
310 | 0 | } |
311 | | |
312 | | void cmDependsC::WriteCacheFile() const |
313 | 0 | { |
314 | 0 | if (this->CacheFileName.empty()) { |
315 | 0 | return; |
316 | 0 | } |
317 | 0 | cmsys::ofstream cacheOut(this->CacheFileName.c_str()); |
318 | 0 | if (!cacheOut) { |
319 | 0 | return; |
320 | 0 | } |
321 | | |
322 | 0 | cacheOut << this->IncludeRegexLineString << "\n\n"; |
323 | 0 | cacheOut << this->IncludeRegexScanString << "\n\n"; |
324 | 0 | cacheOut << this->IncludeRegexComplainString << "\n\n"; |
325 | 0 | cacheOut << this->IncludeRegexTransformString << "\n\n"; |
326 | |
|
327 | 0 | for (auto const& fileIt : this->FileCache) { |
328 | 0 | if (fileIt.second.Used) { |
329 | 0 | cacheOut << fileIt.first << '\n'; |
330 | |
|
331 | 0 | for (UnscannedEntry const& inc : fileIt.second.UnscannedEntries) { |
332 | 0 | cacheOut << inc.FileName << '\n'; |
333 | 0 | if (inc.QuotedLocation.empty()) { |
334 | 0 | cacheOut << '-' << '\n'; |
335 | 0 | } else { |
336 | 0 | cacheOut << inc.QuotedLocation << '\n'; |
337 | 0 | } |
338 | 0 | } |
339 | 0 | cacheOut << '\n'; |
340 | 0 | } |
341 | 0 | } |
342 | 0 | } |
343 | | |
344 | | void cmDependsC::Scan(std::istream& is, std::string const& directory, |
345 | | std::string const& fullName) |
346 | 0 | { |
347 | 0 | cmIncludeLines& newCacheEntry = this->FileCache[fullName]; |
348 | 0 | newCacheEntry.Used = true; |
349 | | |
350 | | // Read one line at a time. |
351 | 0 | std::string line; |
352 | 0 | while (cmSystemTools::GetLineFromStream(is, line)) { |
353 | | // Transform the line content first. |
354 | 0 | if (!this->TransformRules.empty()) { |
355 | 0 | this->TransformLine(line); |
356 | 0 | } |
357 | | |
358 | | // Match include directives. |
359 | 0 | if (this->IncludeRegexLine.find(line)) { |
360 | | // Get the file being included. |
361 | 0 | UnscannedEntry entry; |
362 | 0 | entry.FileName = this->IncludeRegexLine.match(2); |
363 | 0 | cmSystemTools::ConvertToUnixSlashes(entry.FileName); |
364 | 0 | if (this->IncludeRegexLine.match(3) == "\"" && |
365 | 0 | !cmSystemTools::FileIsFullPath(entry.FileName)) { |
366 | | // This was a double-quoted include with a relative path. We |
367 | | // must check for the file in the directory containing the |
368 | | // file we are scanning. |
369 | 0 | entry.QuotedLocation = |
370 | 0 | cmSystemTools::CollapseFullPath(entry.FileName, directory); |
371 | 0 | } |
372 | | |
373 | | // Queue the file if it has not yet been encountered and it |
374 | | // matches the regular expression for recursive scanning. Note |
375 | | // that this check does not account for the possibility of two |
376 | | // headers with the same name in different directories when one |
377 | | // is included by double-quotes and the other by angle brackets. |
378 | | // It also does not work properly if two header files with the same |
379 | | // name exist in different directories, and both are included from a |
380 | | // file their own directory by simply using "filename.h" (#12619) |
381 | | // This kind of problem will be fixed when a more |
382 | | // preprocessor-like implementation of this scanner is created. |
383 | 0 | if (this->IncludeRegexScan.find(entry.FileName)) { |
384 | 0 | newCacheEntry.UnscannedEntries.push_back(entry); |
385 | 0 | if (this->Encountered.find(entry.FileName) == |
386 | 0 | this->Encountered.end()) { |
387 | 0 | this->Encountered.insert(entry.FileName); |
388 | 0 | this->Unscanned.push(entry); |
389 | 0 | } |
390 | 0 | } |
391 | 0 | } |
392 | 0 | } |
393 | 0 | } |
394 | | |
395 | | void cmDependsC::SetupTransforms() |
396 | 0 | { |
397 | | // Get the transformation rules. |
398 | 0 | cmMakefile* mf = this->LocalGenerator->GetMakefile(); |
399 | 0 | cmList transformRules{ mf->GetDefinition("CMAKE_INCLUDE_TRANSFORMS"), |
400 | 0 | cmList::EmptyElements::Yes }; |
401 | 0 | for (auto const& tr : transformRules) { |
402 | 0 | this->ParseTransform(tr); |
403 | 0 | } |
404 | |
|
405 | 0 | this->IncludeRegexTransformString = INCLUDE_REGEX_TRANSFORM_MARKER; |
406 | 0 | if (!this->TransformRules.empty()) { |
407 | | // Construct the regular expression to match lines to be |
408 | | // transformed. |
409 | 0 | std::string xform = "^([ \t]*[#%][ \t]*(include|import)[ \t]*)("; |
410 | 0 | char const* sep = ""; |
411 | 0 | for (auto const& tr : this->TransformRules) { |
412 | 0 | xform += sep; |
413 | 0 | xform += tr.first; |
414 | 0 | sep = "|"; |
415 | 0 | } |
416 | 0 | xform += ")[ \t]*\\(([^),]*)\\)"; |
417 | 0 | this->IncludeRegexTransform.compile(xform); |
418 | | |
419 | | // Build a string that encodes all transformation rules and will |
420 | | // change when rules are changed. |
421 | 0 | this->IncludeRegexTransformString += xform; |
422 | 0 | for (auto const& tr : this->TransformRules) { |
423 | 0 | this->IncludeRegexTransformString += " "; |
424 | 0 | this->IncludeRegexTransformString += tr.first; |
425 | 0 | this->IncludeRegexTransformString += "(%)="; |
426 | 0 | this->IncludeRegexTransformString += tr.second; |
427 | 0 | } |
428 | 0 | } |
429 | 0 | } |
430 | | |
431 | | void cmDependsC::ParseTransform(std::string const& xform) |
432 | 0 | { |
433 | | // A transform rule is of the form SOME_MACRO(%)=value-with-% |
434 | | // We can simply separate with "(%)=". |
435 | 0 | std::string::size_type pos = xform.find("(%)="); |
436 | 0 | if (pos == std::string::npos || pos == 0) { |
437 | 0 | return; |
438 | 0 | } |
439 | 0 | std::string name = xform.substr(0, pos); |
440 | 0 | std::string value = xform.substr(pos + 4); |
441 | 0 | this->TransformRules[name] = value; |
442 | 0 | } |
443 | | |
444 | | void cmDependsC::TransformLine(std::string& line) |
445 | 0 | { |
446 | | // Check for a transform rule match. Return if none. |
447 | 0 | if (!this->IncludeRegexTransform.find(line)) { |
448 | 0 | return; |
449 | 0 | } |
450 | 0 | auto tri = this->TransformRules.find(this->IncludeRegexTransform.match(3)); |
451 | 0 | if (tri == this->TransformRules.end()) { |
452 | 0 | return; |
453 | 0 | } |
454 | | |
455 | | // Construct the transformed line. |
456 | 0 | std::string newline = this->IncludeRegexTransform.match(1); |
457 | 0 | std::string arg = this->IncludeRegexTransform.match(4); |
458 | 0 | for (char c : tri->second) { |
459 | 0 | if (c == '%') { |
460 | 0 | newline += arg; |
461 | 0 | } else { |
462 | 0 | newline += c; |
463 | 0 | } |
464 | 0 | } |
465 | | |
466 | | // Return the transformed line. |
467 | 0 | line = newline; |
468 | 0 | } |