cmDependsC.cxx 15.4 KB
Newer Older
1 2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
3 4
#include "cmDependsC.h"

5
#include "cmsys/FStream.hxx"
6 7
#include <utility>

8
#include "cmAlgorithms.h"
9
#include "cmFileTime.h"
10
#include "cmLocalGenerator.h"
11
#include "cmMakefile.h"
12
#include "cmStringAlgorithms.h"
13
#include "cmSystemTools.h"
14

15
#define INCLUDE_REGEX_LINE                                                    \
16
  "^[ \t]*[#%][ \t]*(include|import)[ \t]*[<\"]([^\">]+)([\">])"
17 18 19 20

#define INCLUDE_REGEX_LINE_MARKER "#IncludeRegexLine: "
#define INCLUDE_REGEX_SCAN_MARKER "#IncludeRegexScan: "
#define INCLUDE_REGEX_COMPLAIN_MARKER "#IncludeRegexComplain: "
21
#define INCLUDE_REGEX_TRANSFORM_MARKER "#IncludeRegexTransform: "
22

wahikihiki's avatar
wahikihiki committed
23
cmDependsC::cmDependsC() = default;
24

25 26
cmDependsC::cmDependsC(cmLocalGenerator* lg, const std::string& targetDir,
                       const std::string& lang, const DependencyMap* validDeps)
27 28
  : cmDepends(lg, targetDir)
  , ValidDeps(validDeps)
29
{
30 31 32 33 34 35 36 37 38
  cmMakefile* mf = lg->GetMakefile();

  // Configure the include file search path.
  this->SetIncludePathFromLanguage(lang);

  // Configure regular expressions.
  std::string scanRegex = "^.*$";
  std::string complainRegex = "^$";
  {
39 40 41 42 43
    std::string scanRegexVar = "CMAKE_";
    scanRegexVar += lang;
    scanRegexVar += "_INCLUDE_REGEX_SCAN";
    if (const char* sr = mf->GetDefinition(scanRegexVar)) {
      scanRegex = sr;
44
    }
45 46 47 48 49
    std::string complainRegexVar = "CMAKE_";
    complainRegexVar += lang;
    complainRegexVar += "_INCLUDE_REGEX_COMPLAIN";
    if (const char* cr = mf->GetDefinition(complainRegexVar)) {
      complainRegex = cr;
50 51 52 53
    }
  }

  this->IncludeRegexLine.compile(INCLUDE_REGEX_LINE);
54 55
  this->IncludeRegexScan.compile(scanRegex);
  this->IncludeRegexComplain.compile(complainRegex);
56 57 58 59 60 61
  this->IncludeRegexLineString = INCLUDE_REGEX_LINE_MARKER INCLUDE_REGEX_LINE;
  this->IncludeRegexScanString = INCLUDE_REGEX_SCAN_MARKER;
  this->IncludeRegexScanString += scanRegex;
  this->IncludeRegexComplainString = INCLUDE_REGEX_COMPLAIN_MARKER;
  this->IncludeRegexComplainString += complainRegex;

62 63
  this->SetupTransforms();

64 65 66 67 68
  this->CacheFileName = this->TargetDirectory;
  this->CacheFileName += "/";
  this->CacheFileName += lang;
  this->CacheFileName += ".includecache";

69
  this->ReadCacheFile();
70 71 72 73
}

cmDependsC::~cmDependsC()
{
74
  this->WriteCacheFile();
75
  cmDeleteAll(this->FileCache);
76 77
}

78 79 80 81
bool cmDependsC::WriteDependencies(const std::set<std::string>& sources,
                                   const std::string& obj,
                                   std::ostream& makeDepends,
                                   std::ostream& internalDepends)
82 83
{
  // Make sure this is a scanning instance.
84
  if (sources.empty() || sources.begin()->empty()) {
85 86
    cmSystemTools::Error("Cannot scan dependencies without a source file.");
    return false;
87 88
  }
  if (obj.empty()) {
89
    cmSystemTools::Error("Cannot scan dependencies without an object file.");
90
    return false;
91
  }
92

93
  std::set<std::string> dependencies;
94 95
  bool haveDeps = false;

96 97 98 99 100
  std::string binDir = this->LocalGenerator->GetBinaryDirectory();

  // Compute a path to the object file to write to the internal depend file.
  // Any existing content of the internal depend file has already been
  // loaded in ValidDeps with this path as a key.
101 102
  std::string obj_i =
    this->LocalGenerator->MaybeConvertToRelativePath(binDir, obj);
103

Daniel Pfeifer's avatar
Daniel Pfeifer committed
104
  if (this->ValidDeps != nullptr) {
105
    auto const tmpIt = this->ValidDeps->find(obj_i);
106
    if (tmpIt != this->ValidDeps->end()) {
107
      dependencies.insert(tmpIt->second.begin(), tmpIt->second.end());
108
      haveDeps = true;
109
    }
110
  }
111

112
  if (!haveDeps) {
113
    // Walk the dependency graph starting with the source file.
Daniel Pfeifer's avatar
Daniel Pfeifer committed
114
    int srcFiles = static_cast<int>(sources.size());
115
    this->Encountered.clear();
116

117
    for (std::string const& src : sources) {
118
      UnscannedEntry root;
119
      root.FileName = src;
120
      this->Unscanned.push(root);
121
      this->Encountered.insert(src);
122
    }
123

124
    std::set<std::string> scanned;
125
    while (!this->Unscanned.empty()) {
126 127 128 129 130 131
      // Get the next file to scan.
      UnscannedEntry current = this->Unscanned.front();
      this->Unscanned.pop();

      // If not a full path, find the file in the include path.
      std::string fullName;
132 133
      if ((srcFiles > 0) || cmSystemTools::FileIsFullPath(current.FileName)) {
        if (cmSystemTools::FileExists(current.FileName, true)) {
134
          fullName = current.FileName;
135
        }
136
      } else if (!current.QuotedLocation.empty() &&
137
                 cmSystemTools::FileExists(current.QuotedLocation, true)) {
138 139 140 141
        // The include statement producing this entry was a double-quote
        // include and the included file is present in the directory of
        // the source containing the include statement.
        fullName = current.QuotedLocation;
142
      } else {
143
        auto headerLocationIt =
144 145 146
          this->HeaderLocationCache.find(current.FileName);
        if (headerLocationIt != this->HeaderLocationCache.end()) {
          fullName = headerLocationIt->second;
147
        } else {
148
          for (std::string const& iPath : this->IncludePath) {
149 150
            // Construct the name of the file as if it were in the current
            // include directory.  Avoid using a leading "./".
151
            std::string tmpPath =
152
              cmSystemTools::CollapseFullPath(current.FileName, iPath);
153 154

            // Look for the file in this location.
155 156 157
            if (cmSystemTools::FileExists(tmpPath, true)) {
              fullName = tmpPath;
              this->HeaderLocationCache[current.FileName] = std::move(tmpPath);
158
              break;
159
            }
160
          }
161
        }
162
      }
163

164 165
      // Complain if the file cannot be found and matches the complain
      // regex.
166
      if (fullName.empty() &&
167
          this->IncludeRegexComplain.find(current.FileName)) {
168
        cmSystemTools::Error("Cannot find file \"" + current.FileName + "\".");
169
        return false;
170
      }
171

172
      // Scan the file if it was found and has not been scanned already.
173
      if (!fullName.empty() && (scanned.find(fullName) == scanned.end())) {
174 175 176 177
        // Record scanned files.
        scanned.insert(fullName);

        // Check whether this file is already in the cache
178
        auto fileIt = this->FileCache.find(fullName);
179 180
        if (fileIt != this->FileCache.end()) {
          fileIt->second->Used = true;
181
          dependencies.insert(fullName);
182 183
          for (UnscannedEntry const& inc : fileIt->second->UnscannedEntries) {
            if (this->Encountered.find(inc.FileName) ==
184
                this->Encountered.end()) {
185 186
              this->Encountered.insert(inc.FileName);
              this->Unscanned.push(inc);
187 188
            }
          }
189
        } else {
Andy Cedilnik's avatar
Andy Cedilnik committed
190

191 192
          // Try to scan the file.  Just leave it out if we cannot find
          // it.
193
          cmsys::ifstream fin(fullName.c_str());
194
          if (fin) {
195
            cmsys::FStream::BOM bom = cmsys::FStream::ReadBOM(fin);
196 197
            if (bom == cmsys::FStream::BOM_None ||
                bom == cmsys::FStream::BOM_UTF8) {
198 199
              // Add this file as a dependency.
              dependencies.insert(fullName);
200

201 202 203
              // Scan this file for new dependencies.  Pass the directory
              // containing the file to handle double-quote includes.
              std::string dir = cmSystemTools::GetFilenamePath(fullName);
204
              this->Scan(fin, dir, fullName);
205
            } else {
206
              // Skip file with encoding we do not implement.
207
            }
208
          }
209
        }
210
      }
Andy Cedilnik's avatar
Andy Cedilnik committed
211

212
      srcFiles--;
213
    }
214
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
215

216 217 218 219
  // Write the dependencies to the output stream.  Makefile rules
  // written by the original local generator for this directory
  // convert the dependencies to paths relative to the home output
  // directory.  We must do the same here.
220
  std::string obj_m = cmSystemTools::ConvertToOutputPath(obj_i);
221 222
  internalDepends << obj_i << std::endl;

223
  for (std::string const& dep : dependencies) {
224 225
    makeDepends << obj_m << ": "
                << cmSystemTools::ConvertToOutputPath(
226 227
                     this->LocalGenerator->MaybeConvertToRelativePath(binDir,
                                                                      dep))
228
                << std::endl;
229
    internalDepends << " " << dep << std::endl;
230
  }
231
  makeDepends << std::endl;
Andy Cedilnik's avatar
Andy Cedilnik committed
232

233 234 235
  return true;
}

236
void cmDependsC::ReadCacheFile()
237
{
238
  if (this->CacheFileName.empty()) {
239
    return;
240
  }
241
  cmsys::ifstream fin(this->CacheFileName.c_str());
242
  if (!fin) {
243
    return;
244
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
245

246
  std::string line;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
247
  cmIncludeLines* cacheEntry = nullptr;
248
  bool haveFileName = false;
Andy Cedilnik's avatar
Andy Cedilnik committed
249

250 251
  cmFileTime cacheFileTime;
  bool const cacheFileTimeGood = cacheFileTime.Load(this->CacheFileName);
252 253
  while (cmSystemTools::GetLineFromStream(fin, line)) {
    if (line.empty()) {
Daniel Pfeifer's avatar
Daniel Pfeifer committed
254
      cacheEntry = nullptr;
255
      haveFileName = false;
256
      continue;
257 258
    }
    // the first line after an empty line is the name of the parsed file
259
    if (!haveFileName) {
260
      haveFileName = true;
261

262 263 264 265 266
      cmFileTime fileTime;
      bool const res = cacheFileTimeGood && fileTime.Load(line);
      bool const newer = res && cacheFileTime.Newer(fileTime);

      if (res && newer) // cache is newer than the parsed file
267 268 269 270
      {
        cacheEntry = new cmIncludeLines;
        this->FileCache[line] = cacheEntry;
      }
Brad King's avatar
Brad King committed
271 272
      // file doesn't exist, check that the regular expressions
      // haven't changed
273
      else if (!res) {
274 275
        if (line.find(INCLUDE_REGEX_LINE_MARKER) == 0) {
          if (line != this->IncludeRegexLineString) {
276 277
            return;
          }
278 279
        } else if (line.find(INCLUDE_REGEX_SCAN_MARKER) == 0) {
          if (line != this->IncludeRegexScanString) {
280 281
            return;
          }
282 283
        } else if (line.find(INCLUDE_REGEX_COMPLAIN_MARKER) == 0) {
          if (line != this->IncludeRegexComplainString) {
284 285
            return;
          }
286 287
        } else if (line.find(INCLUDE_REGEX_TRANSFORM_MARKER) == 0) {
          if (line != this->IncludeRegexTransformString) {
288 289
            return;
          }
290
        }
291
      }
Daniel Pfeifer's avatar
Daniel Pfeifer committed
292
    } else if (cacheEntry != nullptr) {
293 294
      UnscannedEntry entry;
      entry.FileName = line;
295 296 297
      if (cmSystemTools::GetLineFromStream(fin, line)) {
        if (line != "-") {
          entry.QuotedLocation = line;
298
        }
299
        cacheEntry->UnscannedEntries.push_back(std::move(entry));
300 301
      }
    }
302
  }
303 304 305 306
}

void cmDependsC::WriteCacheFile() const
{
307
  if (this->CacheFileName.empty()) {
308
    return;
309
  }
310
  cmsys::ofstream cacheOut(this->CacheFileName.c_str());
311
  if (!cacheOut) {
312
    return;
313
  }
314

315 316 317
  cacheOut << this->IncludeRegexLineString << "\n\n";
  cacheOut << this->IncludeRegexScanString << "\n\n";
  cacheOut << this->IncludeRegexComplainString << "\n\n";
318
  cacheOut << this->IncludeRegexTransformString << "\n\n";
319

320 321 322 323 324 325 326
  for (auto const& fileIt : this->FileCache) {
    if (fileIt.second->Used) {
      cacheOut << fileIt.first << std::endl;

      for (UnscannedEntry const& inc : fileIt.second->UnscannedEntries) {
        cacheOut << inc.FileName << std::endl;
        if (inc.QuotedLocation.empty()) {
327 328
          cacheOut << "-" << std::endl;
        } else {
329
          cacheOut << inc.QuotedLocation << std::endl;
330 331
        }
      }
332 333 334
      cacheOut << std::endl;
    }
  }
335 336
}

337
void cmDependsC::Scan(std::istream& is, const std::string& directory,
338
                      const std::string& fullName)
339
{
340 341 342
  cmIncludeLines* newCacheEntry = new cmIncludeLines;
  newCacheEntry->Used = true;
  this->FileCache[fullName] = newCacheEntry;
343

344 345
  // Read one line at a time.
  std::string line;
346
  while (cmSystemTools::GetLineFromStream(is, line)) {
347
    // Transform the line content first.
348
    if (!this->TransformRules.empty()) {
349
      this->TransformLine(line);
350
    }
351

352
    // Match include directives.
353
    if (this->IncludeRegexLine.find(line)) {
354
      // Get the file being included.
355
      UnscannedEntry entry;
356
      entry.FileName = this->IncludeRegexLine.match(2);
357
      cmSystemTools::ConvertToUnixSlashes(entry.FileName);
358
      if (this->IncludeRegexLine.match(3) == "\"" &&
359
          !cmSystemTools::FileIsFullPath(entry.FileName)) {
360 361 362
        // This was a double-quoted include with a relative path.  We
        // must check for the file in the directory containing the
        // file we are scanning.
363
        entry.QuotedLocation =
364
          cmSystemTools::CollapseFullPath(entry.FileName, directory);
365
      }
366

367
      // Queue the file if it has not yet been encountered and it
368 369 370 371
      // matches the regular expression for recursive scanning.  Note
      // that this check does not account for the possibility of two
      // headers with the same name in different directories when one
      // is included by double-quotes and the other by angle brackets.
372 373 374
      // It also does not work properly if two header files with the same
      // name exist in different directories, and both are included from a
      // file their own directory by simply using "filename.h" (#12619)
375 376
      // This kind of problem will be fixed when a more
      // preprocessor-like implementation of this scanner is created.
377
      if (this->IncludeRegexScan.find(entry.FileName)) {
378
        newCacheEntry->UnscannedEntries.push_back(entry);
379 380
        if (this->Encountered.find(entry.FileName) ==
            this->Encountered.end()) {
381 382
          this->Encountered.insert(entry.FileName);
          this->Unscanned.push(entry);
383 384 385
        }
      }
    }
386
  }
387
}
388 389 390 391 392 393

void cmDependsC::SetupTransforms()
{
  // Get the transformation rules.
  std::vector<std::string> transformRules;
  cmMakefile* mf = this->LocalGenerator->GetMakefile();
394
  if (const char* xform = mf->GetDefinition("CMAKE_INCLUDE_TRANSFORMS")) {
395
    cmExpandList(xform, transformRules, true);
396
  }
397 398
  for (std::string const& tr : transformRules) {
    this->ParseTransform(tr);
399
  }
400 401

  this->IncludeRegexTransformString = INCLUDE_REGEX_TRANSFORM_MARKER;
402
  if (!this->TransformRules.empty()) {
403 404
    // Construct the regular expression to match lines to be
    // transformed.
405
    std::string xform = "^([ \t]*[#%][ \t]*(include|import)[ \t]*)(";
406
    const char* sep = "";
407
    for (auto const& tr : this->TransformRules) {
408
      xform += sep;
409
      xform += tr.first;
410
      sep = "|";
411
    }
412
    xform += ")[ \t]*\\(([^),]*)\\)";
413
    this->IncludeRegexTransform.compile(xform);
414 415 416 417

    // Build a string that encodes all transformation rules and will
    // change when rules are changed.
    this->IncludeRegexTransformString += xform;
418
    for (auto const& tr : this->TransformRules) {
419
      this->IncludeRegexTransformString += " ";
420
      this->IncludeRegexTransformString += tr.first;
421
      this->IncludeRegexTransformString += "(%)=";
422
      this->IncludeRegexTransformString += tr.second;
423
    }
424
  }
425 426 427 428 429 430 431
}

void cmDependsC::ParseTransform(std::string const& xform)
{
  // A transform rule is of the form SOME_MACRO(%)=value-with-%
  // We can simply separate with "(%)=".
  std::string::size_type pos = xform.find("(%)=");
432
  if (pos == std::string::npos || pos == 0) {
433
    return;
434
  }
435
  std::string name = xform.substr(0, pos);
436
  std::string value = xform.substr(pos + 4);
437 438 439 440 441 442
  this->TransformRules[name] = value;
}

void cmDependsC::TransformLine(std::string& line)
{
  // Check for a transform rule match.  Return if none.
443
  if (!this->IncludeRegexTransform.find(line)) {
444
    return;
445
  }
446 447
  TransformRulesType::const_iterator tri =
    this->TransformRules.find(this->IncludeRegexTransform.match(3));
448
  if (tri == this->TransformRules.end()) {
449
    return;
450
  }
451 452 453 454

  // Construct the transformed line.
  std::string newline = this->IncludeRegexTransform.match(1);
  std::string arg = this->IncludeRegexTransform.match(4);
455 456
  for (char c : tri->second) {
    if (c == '%') {
457
      newline += arg;
458
    } else {
459
      newline += c;
460
    }
461
  }
462 463 464 465

  // Return the transformed line.
  line = newline;
}