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