cmDependsC.cxx 15.5 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 "cmSystemTools.h"
13

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

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

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

24
cmDependsC::cmDependsC(
25
  cmLocalGenerator* lg, const std::string& targetDir, const std::string& lang,
26 27 28
  const std::map<std::string, DependencyVector>* validDeps)
  : 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
    std::map<std::string, DependencyVector>::const_iterator tmpIt =
106
      this->ValidDeps->find(obj_i);
107
    if (tmpIt != this->ValidDeps->end()) {
108
      dependencies.insert(tmpIt->second.begin(), tmpIt->second.end());
109
      haveDeps = true;
110
    }
111
  }
112

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

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

125
    std::set<std::string> scanned;
126
    while (!this->Unscanned.empty()) {
127 128 129 130 131 132
      // 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;
133 134
      if ((srcFiles > 0) || cmSystemTools::FileIsFullPath(current.FileName)) {
        if (cmSystemTools::FileExists(current.FileName, true)) {
135
          fullName = current.FileName;
136
        }
137
      } else if (!current.QuotedLocation.empty() &&
138
                 cmSystemTools::FileExists(current.QuotedLocation, true)) {
139 140 141 142
        // 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;
143
      } else {
144
        auto headerLocationIt =
145 146 147
          this->HeaderLocationCache.find(current.FileName);
        if (headerLocationIt != this->HeaderLocationCache.end()) {
          fullName = headerLocationIt->second;
148
        } else {
149
          for (std::string const& iPath : this->IncludePath) {
150 151
            // Construct the name of the file as if it were in the current
            // include directory.  Avoid using a leading "./".
152
            std::string tmpPath =
153
              cmSystemTools::CollapseFullPath(current.FileName, iPath);
154 155

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

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

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

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

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

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

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

217 218 219 220
  // 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.
221
  std::string obj_m = cmSystemTools::ConvertToOutputPath(obj_i);
222 223
  internalDepends << obj_i << std::endl;

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

234 235 236
  return true;
}

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

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

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

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

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

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

321 322 323 324 325 326 327
  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()) {
328 329
          cacheOut << "-" << std::endl;
        } else {
330
          cacheOut << inc.QuotedLocation << std::endl;
331 332
        }
      }
333 334 335
      cacheOut << std::endl;
    }
  }
336 337
}

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

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

353
    // Match include directives.
354
    if (this->IncludeRegexLine.find(line)) {
355
      // Get the file being included.
356
      UnscannedEntry entry;
357
      entry.FileName = this->IncludeRegexLine.match(2);
358
      cmSystemTools::ConvertToUnixSlashes(entry.FileName);
359
      if (this->IncludeRegexLine.match(3) == "\"" &&
360
          !cmSystemTools::FileIsFullPath(entry.FileName)) {
361 362 363
        // 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.
364
        entry.QuotedLocation =
365
          cmSystemTools::CollapseFullPath(entry.FileName, directory);
366
      }
367

368
      // Queue the file if it has not yet been encountered and it
369 370 371 372
      // 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.
373 374 375
      // 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)
376 377
      // This kind of problem will be fixed when a more
      // preprocessor-like implementation of this scanner is created.
378
      if (this->IncludeRegexScan.find(entry.FileName)) {
Ken Martin's avatar
Ken Martin committed
379
        newCacheEntry->UnscannedEntries.push_back(entry);
380 381
        if (this->Encountered.find(entry.FileName) ==
            this->Encountered.end()) {
Ken Martin's avatar
Ken Martin committed
382 383
          this->Encountered.insert(entry.FileName);
          this->Unscanned.push(entry);
384 385 386
        }
      }
    }
387
  }
388
}
389 390 391 392 393 394

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

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

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

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("(%)=");
433
  if (pos == std::string::npos || pos == 0) {
434
    return;
435
  }
436
  std::string name = xform.substr(0, pos);
437
  std::string value = xform.substr(pos + 4);
438 439 440 441 442 443
  this->TransformRules[name] = value;
}

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

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

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