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"
7 #include "cmsys/FStream.hxx"
9 #include "cmFileTime.h"
10 #include "cmGlobalUnixMakefileGenerator3.h"
11 #include "cmLocalUnixMakefileGenerator3.h"
12 #include "cmMakefile.h"
13 #include "cmStringAlgorithms.h"
14 #include "cmSystemTools.h"
17 #define INCLUDE_REGEX_LINE \
18 "^[ \t]*[#%][ \t]*(include|import)[ \t]*[<\"]([^\">]+)([\">])"
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: "
25 cmDependsC::cmDependsC() = default;
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)
33 cmMakefile* mf = lg->GetMakefile();
35 // Configure the include file search path.
36 this->SetIncludePathFromLanguage(lang);
38 // Configure regular expressions.
39 std::string scanRegex = "^.*$";
40 std::string complainRegex = "^$";
42 std::string scanRegexVar = cmStrCat("CMAKE_", lang, "_INCLUDE_REGEX_SCAN");
43 if (cmValue sr = mf->GetDefinition(scanRegexVar)) {
46 std::string complainRegexVar =
47 cmStrCat("CMAKE_", lang, "_INCLUDE_REGEX_COMPLAIN");
48 if (cmValue cr = mf->GetDefinition(complainRegexVar)) {
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);
62 this->SetupTransforms();
65 cmStrCat(this->TargetDirectory, '/', lang, ".includecache");
67 this->ReadCacheFile();
70 cmDependsC::~cmDependsC()
72 this->WriteCacheFile();
75 bool cmDependsC::WriteDependencies(const std::set<std::string>& sources,
76 const std::string& obj,
77 std::ostream& makeDepends,
78 std::ostream& internalDepends)
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.");
86 cmSystemTools::Error("Cannot scan dependencies without an object file.");
90 std::set<std::string> dependencies;
91 bool haveDeps = false;
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);
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());
107 // Walk the dependency graph starting with the source file.
108 int srcFiles = static_cast<int>(sources.size());
109 this->Encountered.clear();
111 for (std::string const& src : sources) {
114 this->Unscanned.push(root);
115 this->Encountered.insert(src);
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();
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;
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;
137 auto headerLocationIt =
138 this->HeaderLocationCache.find(current.FileName);
139 if (headerLocationIt != this->HeaderLocationCache.end()) {
140 fullName = headerLocationIt->second;
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);
148 // Look for the file in this location.
149 if (cmSystemTools::FileExists(tmpPath, true)) {
151 this->HeaderLocationCache[current.FileName] = std::move(tmpPath);
158 // Complain if the file cannot be found and matches the complain
160 if (fullName.empty() &&
161 this->IncludeRegexComplain.find(current.FileName)) {
162 cmSystemTools::Error("Cannot find file \"" + current.FileName + "\".");
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);
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);
185 // Try to scan the file. Just leave it out if we cannot find
187 cmsys::ifstream fin(fullName.c_str());
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);
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);
200 // Skip file with encoding we do not implement.
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 << ':';
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;
232 makeDepends << obj_m << ": " << dependee << '\n';
234 internalDepends << ' ' << dep << '\n';
242 void cmDependsC::ReadCacheFile()
244 if (this->CacheFileName.empty()) {
247 cmsys::ifstream fin(this->CacheFileName.c_str());
253 cmIncludeLines* cacheEntry = nullptr;
254 bool haveFileName = false;
256 cmFileTime cacheFileTime;
257 bool const cacheFileTimeGood = cacheFileTime.Load(this->CacheFileName);
258 while (cmSystemTools::GetLineFromStream(fin, line)) {
260 cacheEntry = nullptr;
261 haveFileName = false;
264 // the first line after an empty line is the name of the parsed file
269 bool const res = cacheFileTimeGood && fileTime.Load(line);
270 bool const newer = res && cacheFileTime.Newer(fileTime);
272 if (res && newer) // cache is newer than the parsed file
274 cacheEntry = &this->FileCache[line];
276 // file doesn't exist, check that the regular expressions
279 if (cmHasLiteralPrefix(line, INCLUDE_REGEX_LINE_MARKER)) {
280 if (line != this->IncludeRegexLineString) {
283 } else if (cmHasLiteralPrefix(line, INCLUDE_REGEX_SCAN_MARKER)) {
284 if (line != this->IncludeRegexScanString) {
287 } else if (cmHasLiteralPrefix(line, INCLUDE_REGEX_COMPLAIN_MARKER)) {
288 if (line != this->IncludeRegexComplainString) {
291 } else if (cmHasLiteralPrefix(line, INCLUDE_REGEX_TRANSFORM_MARKER)) {
292 if (line != this->IncludeRegexTransformString) {
297 } else if (cacheEntry != nullptr) {
298 UnscannedEntry entry;
299 entry.FileName = line;
300 if (cmSystemTools::GetLineFromStream(fin, line)) {
302 entry.QuotedLocation = line;
304 cacheEntry->UnscannedEntries.push_back(std::move(entry));
310 void cmDependsC::WriteCacheFile() const
312 if (this->CacheFileName.empty()) {
315 cmsys::ofstream cacheOut(this->CacheFileName.c_str());
320 cacheOut << this->IncludeRegexLineString << "\n\n";
321 cacheOut << this->IncludeRegexScanString << "\n\n";
322 cacheOut << this->IncludeRegexComplainString << "\n\n";
323 cacheOut << this->IncludeRegexTransformString << "\n\n";
325 for (auto const& fileIt : this->FileCache) {
326 if (fileIt.second.Used) {
327 cacheOut << fileIt.first << '\n';
329 for (UnscannedEntry const& inc : fileIt.second.UnscannedEntries) {
330 cacheOut << inc.FileName << '\n';
331 if (inc.QuotedLocation.empty()) {
332 cacheOut << '-' << '\n';
334 cacheOut << inc.QuotedLocation << '\n';
342 void cmDependsC::Scan(std::istream& is, const std::string& directory,
343 const std::string& fullName)
345 cmIncludeLines& newCacheEntry = this->FileCache[fullName];
346 newCacheEntry.Used = true;
348 // Read one line at a time.
350 while (cmSystemTools::GetLineFromStream(is, line)) {
351 // Transform the line content first.
352 if (!this->TransformRules.empty()) {
353 this->TransformLine(line);
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);
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);
393 void cmDependsC::SetupTransforms()
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);
403 this->IncludeRegexTransformString = INCLUDE_REGEX_TRANSFORM_MARKER;
404 if (!this->TransformRules.empty()) {
405 // Construct the regular expression to match lines to be
407 std::string xform = "^([ \t]*[#%][ \t]*(include|import)[ \t]*)(";
408 const char* sep = "";
409 for (auto const& tr : this->TransformRules) {
414 xform += ")[ \t]*\\(([^),]*)\\)";
415 this->IncludeRegexTransform.compile(xform);
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;
429 void cmDependsC::ParseTransform(std::string const& xform)
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) {
437 std::string name = xform.substr(0, pos);
438 std::string value = xform.substr(pos + 4);
439 this->TransformRules[name] = value;
442 void cmDependsC::TransformLine(std::string& line)
444 // Check for a transform rule match. Return if none.
445 if (!this->IncludeRegexTransform.find(line)) {
448 auto tri = this->TransformRules.find(this->IncludeRegexTransform.match(3));
449 if (tri == this->TransformRules.end()) {
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) {
464 // Return the transformed line.