Coverage Report

Created: 2026-06-15 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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.insert(inc.FileName).second) {
180
0
              this->Unscanned.push(inc);
181
0
            }
182
0
          }
183
0
        } else {
184
185
          // Try to scan the file.  Just leave it out if we cannot find
186
          // it.
187
0
          cmsys::ifstream fin(fullName.c_str());
188
0
          if (fin) {
189
0
            cmsys::FStream::BOM bom = cmsys::FStream::ReadBOM(fin);
190
0
            if (bom == cmsys::FStream::BOM_None ||
191
0
                bom == cmsys::FStream::BOM_UTF8) {
192
              // Add this file as a dependency.
193
0
              dependencies.insert(fullName);
194
195
              // Scan this file for new dependencies.  Pass the directory
196
              // containing the file to handle double-quote includes.
197
0
              std::string dir = cmSystemTools::GetFilenamePath(fullName);
198
0
              this->Scan(fin, dir, fullName);
199
0
            } else {
200
              // Skip file with encoding we do not implement.
201
0
            }
202
0
          }
203
0
        }
204
0
      }
205
206
0
      srcFiles--;
207
0
    }
208
0
  }
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
0
  std::string obj_m = this->LocalGenerator->ConvertToMakefilePath(obj_i);
215
0
  internalDepends << obj_i << '\n';
216
0
  if (!dependencies.empty()) {
217
0
    auto const& lineContinue = static_cast<cmGlobalUnixMakefileGenerator3*>(
218
0
                                 this->LocalGenerator->GetGlobalGenerator())
219
0
                                 ->LineContinueDirective;
220
0
    bool supportLongLineDepend = static_cast<cmGlobalUnixMakefileGenerator3*>(
221
0
                                   this->LocalGenerator->GetGlobalGenerator())
222
0
                                   ->SupportsLongLineDependencies();
223
0
    if (supportLongLineDepend) {
224
0
      makeDepends << obj_m << ':';
225
0
    }
226
0
    for (std::string const& dep : dependencies) {
227
0
      std::string dependee = this->LocalGenerator->ConvertToMakefilePath(
228
0
        this->LocalGenerator->MaybeRelativeToTopBinDir(dep));
229
0
      if (supportLongLineDepend) {
230
0
        makeDepends << ' ' << lineContinue << ' ' << dependee;
231
0
      } else {
232
0
        makeDepends << obj_m << ": " << dependee << '\n';
233
0
      }
234
0
      internalDepends << ' ' << dep << '\n';
235
0
    }
236
0
    makeDepends << '\n';
237
0
  }
238
239
0
  return true;
240
0
}
241
242
void cmDependsC::ReadCacheFile()
243
0
{
244
0
  if (this->CacheFileName.empty()) {
245
0
    return;
246
0
  }
247
0
  cmsys::ifstream fin(this->CacheFileName.c_str());
248
0
  if (!fin) {
249
0
    return;
250
0
  }
251
252
0
  std::string line;
253
0
  cmIncludeLines* cacheEntry = nullptr;
254
0
  bool haveFileName = false;
255
256
0
  cmFileTime cacheFileTime;
257
0
  bool const cacheFileTimeGood = cacheFileTime.Load(this->CacheFileName);
258
0
  while (cmSystemTools::GetLineFromStream(fin, line)) {
259
0
    if (line.empty()) {
260
0
      cacheEntry = nullptr;
261
0
      haveFileName = false;
262
0
      continue;
263
0
    }
264
    // the first line after an empty line is the name of the parsed file
265
0
    if (!haveFileName) {
266
0
      haveFileName = true;
267
268
0
      cmFileTime fileTime;
269
0
      bool const res = cacheFileTimeGood && fileTime.Load(line);
270
0
      bool const newer = res && cacheFileTime.Newer(fileTime);
271
272
0
      if (res && newer) // cache is newer than the parsed file
273
0
      {
274
0
        cacheEntry = &this->FileCache[line];
275
0
      }
276
      // file doesn't exist, check that the regular expressions
277
      // haven't changed
278
0
      else if (!res) {
279
0
        if (cmHasLiteralPrefix(line, INCLUDE_REGEX_LINE_MARKER)) {
280
0
          if (line != this->IncludeRegexLineString) {
281
0
            return;
282
0
          }
283
0
        } else if (cmHasLiteralPrefix(line, INCLUDE_REGEX_SCAN_MARKER)) {
284
0
          if (line != this->IncludeRegexScanString) {
285
0
            return;
286
0
          }
287
0
        } else if (cmHasLiteralPrefix(line, INCLUDE_REGEX_COMPLAIN_MARKER)) {
288
0
          if (line != this->IncludeRegexComplainString) {
289
0
            return;
290
0
          }
291
0
        } else if (cmHasLiteralPrefix(line, INCLUDE_REGEX_TRANSFORM_MARKER)) {
292
0
          if (line != this->IncludeRegexTransformString) {
293
0
            return;
294
0
          }
295
0
        }
296
0
      }
297
0
    } else if (cacheEntry) {
298
0
      UnscannedEntry entry;
299
0
      entry.FileName = line;
300
0
      if (cmSystemTools::GetLineFromStream(fin, line)) {
301
0
        if (line != "-") {
302
0
          entry.QuotedLocation = line;
303
0
        }
304
0
        cacheEntry->UnscannedEntries.push_back(std::move(entry));
305
0
      }
306
0
    }
307
0
  }
308
0
}
309
310
void cmDependsC::WriteCacheFile() const
311
0
{
312
0
  if (this->CacheFileName.empty()) {
313
0
    return;
314
0
  }
315
0
  cmsys::ofstream cacheOut(this->CacheFileName.c_str());
316
0
  if (!cacheOut) {
317
0
    return;
318
0
  }
319
320
0
  cacheOut << this->IncludeRegexLineString << "\n\n";
321
0
  cacheOut << this->IncludeRegexScanString << "\n\n";
322
0
  cacheOut << this->IncludeRegexComplainString << "\n\n";
323
0
  cacheOut << this->IncludeRegexTransformString << "\n\n";
324
325
0
  for (auto const& fileIt : this->FileCache) {
326
0
    if (fileIt.second.Used) {
327
0
      cacheOut << fileIt.first << '\n';
328
329
0
      for (UnscannedEntry const& inc : fileIt.second.UnscannedEntries) {
330
0
        cacheOut << inc.FileName << '\n';
331
0
        if (inc.QuotedLocation.empty()) {
332
0
          cacheOut << '-' << '\n';
333
0
        } else {
334
0
          cacheOut << inc.QuotedLocation << '\n';
335
0
        }
336
0
      }
337
0
      cacheOut << '\n';
338
0
    }
339
0
  }
340
0
}
341
342
void cmDependsC::Scan(std::istream& is, std::string const& directory,
343
                      std::string const& fullName)
344
0
{
345
0
  cmIncludeLines& newCacheEntry = this->FileCache[fullName];
346
0
  newCacheEntry.Used = true;
347
348
  // Read one line at a time.
349
0
  std::string line;
350
0
  while (cmSystemTools::GetLineFromStream(is, line)) {
351
    // Transform the line content first.
352
0
    if (!this->TransformRules.empty()) {
353
0
      this->TransformLine(line);
354
0
    }
355
356
    // Match include directives.
357
0
    if (this->IncludeRegexLine.find(line)) {
358
      // Get the file being included.
359
0
      UnscannedEntry entry;
360
0
      entry.FileName = this->IncludeRegexLine.match(2);
361
0
      cmSystemTools::ConvertToUnixSlashes(entry.FileName);
362
0
      if (this->IncludeRegexLine.match(3) == "\"" &&
363
0
          !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
0
        entry.QuotedLocation =
368
0
          cmSystemTools::CollapseFullPath(entry.FileName, directory);
369
0
      }
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
0
      if (this->IncludeRegexScan.find(entry.FileName)) {
382
0
        newCacheEntry.UnscannedEntries.push_back(entry);
383
0
        if (this->Encountered.insert(entry.FileName).second) {
384
0
          this->Unscanned.push(entry);
385
0
        }
386
0
      }
387
0
    }
388
0
  }
389
0
}
390
391
void cmDependsC::SetupTransforms()
392
0
{
393
  // Get the transformation rules.
394
0
  cmMakefile* mf = this->LocalGenerator->GetMakefile();
395
0
  cmList transformRules{ mf->GetDefinition("CMAKE_INCLUDE_TRANSFORMS"),
396
0
                         cmList::EmptyElements::Yes };
397
0
  for (auto const& tr : transformRules) {
398
0
    this->ParseTransform(tr);
399
0
  }
400
401
0
  this->IncludeRegexTransformString = INCLUDE_REGEX_TRANSFORM_MARKER;
402
0
  if (!this->TransformRules.empty()) {
403
    // Construct the regular expression to match lines to be
404
    // transformed.
405
0
    std::string xform = "^([ \t]*[#%][ \t]*(include|import)[ \t]*)(";
406
0
    char const* sep = "";
407
0
    for (auto const& tr : this->TransformRules) {
408
0
      xform += sep;
409
0
      xform += tr.first;
410
0
      sep = "|";
411
0
    }
412
0
    xform += ")[ \t]*\\(([^),]*)\\)";
413
0
    this->IncludeRegexTransform.compile(xform);
414
415
    // Build a string that encodes all transformation rules and will
416
    // change when rules are changed.
417
0
    this->IncludeRegexTransformString += xform;
418
0
    for (auto const& tr : this->TransformRules) {
419
0
      this->IncludeRegexTransformString += " ";
420
0
      this->IncludeRegexTransformString += tr.first;
421
0
      this->IncludeRegexTransformString += "(%)=";
422
0
      this->IncludeRegexTransformString += tr.second;
423
0
    }
424
0
  }
425
0
}
426
427
void cmDependsC::ParseTransform(std::string const& xform)
428
0
{
429
  // A transform rule is of the form SOME_MACRO(%)=value-with-%
430
  // We can simply separate with "(%)=".
431
0
  std::string::size_type pos = xform.find("(%)=");
432
0
  if (pos == std::string::npos || pos == 0) {
433
0
    return;
434
0
  }
435
0
  std::string name = xform.substr(0, pos);
436
0
  std::string value = xform.substr(pos + 4);
437
0
  this->TransformRules[name] = value;
438
0
}
439
440
void cmDependsC::TransformLine(std::string& line)
441
0
{
442
  // Check for a transform rule match.  Return if none.
443
0
  if (!this->IncludeRegexTransform.find(line)) {
444
0
    return;
445
0
  }
446
0
  auto tri = this->TransformRules.find(this->IncludeRegexTransform.match(3));
447
0
  if (tri == this->TransformRules.end()) {
448
0
    return;
449
0
  }
450
451
  // Construct the transformed line.
452
0
  std::string newline = this->IncludeRegexTransform.match(1);
453
0
  std::string arg = this->IncludeRegexTransform.match(4);
454
0
  for (char c : tri->second) {
455
0
    if (c == '%') {
456
0
      newline += arg;
457
0
    } else {
458
0
      newline += c;
459
0
    }
460
0
  }
461
462
  // Return the transformed line.
463
0
  line = newline;
464
0
}