Coverage Report

Created: 2026-03-12 06:35

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.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
}