summaryrefslogtreecommitdiffstats
path: root/Source/cmDependsC.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'Source/cmDependsC.cxx')
-rw-r--r--Source/cmDependsC.cxx495
1 files changed, 495 insertions, 0 deletions
diff --git a/Source/cmDependsC.cxx b/Source/cmDependsC.cxx
new file mode 100644
index 0000000..18e123e
--- /dev/null
+++ b/Source/cmDependsC.cxx
@@ -0,0 +1,495 @@
+/*============================================================================
+ CMake - Cross Platform Makefile Generator
+ Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+
+ Distributed under the OSI-approved BSD License (the "License");
+ see accompanying file Copyright.txt for details.
+
+ This software is distributed WITHOUT ANY WARRANTY; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the License for more information.
+============================================================================*/
+#include "cmDependsC.h"
+
+#include "cmAlgorithms.h"
+#include "cmFileTimeComparison.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmSystemTools.h"
+#include <cmsys/FStream.hxx>
+
+#include <ctype.h> // isspace
+
+#define INCLUDE_REGEX_LINE \
+ "^[ \t]*#[ \t]*(include|import)[ \t]*[<\"]([^\">]+)([\">])"
+
+#define INCLUDE_REGEX_LINE_MARKER "#IncludeRegexLine: "
+#define INCLUDE_REGEX_SCAN_MARKER "#IncludeRegexScan: "
+#define INCLUDE_REGEX_COMPLAIN_MARKER "#IncludeRegexComplain: "
+#define INCLUDE_REGEX_TRANSFORM_MARKER "#IncludeRegexTransform: "
+
+cmDependsC::cmDependsC()
+ : ValidDeps(CM_NULLPTR)
+{
+}
+
+cmDependsC::cmDependsC(
+ cmLocalGenerator* lg, const char* targetDir, const std::string& lang,
+ const std::map<std::string, DependencyVector>* validDeps)
+ : cmDepends(lg, targetDir)
+ , ValidDeps(validDeps)
+{
+ cmMakefile* mf = lg->GetMakefile();
+
+ // Configure the include file search path.
+ this->SetIncludePathFromLanguage(lang);
+
+ // Configure regular expressions.
+ std::string scanRegex = "^.*$";
+ std::string complainRegex = "^$";
+ {
+ std::string scanRegexVar = "CMAKE_";
+ scanRegexVar += lang;
+ scanRegexVar += "_INCLUDE_REGEX_SCAN";
+ if (const char* sr = mf->GetDefinition(scanRegexVar)) {
+ scanRegex = sr;
+ }
+ std::string complainRegexVar = "CMAKE_";
+ complainRegexVar += lang;
+ complainRegexVar += "_INCLUDE_REGEX_COMPLAIN";
+ if (const char* cr = mf->GetDefinition(complainRegexVar)) {
+ complainRegex = cr;
+ }
+ }
+
+ this->IncludeRegexLine.compile(INCLUDE_REGEX_LINE);
+ this->IncludeRegexScan.compile(scanRegex.c_str());
+ this->IncludeRegexComplain.compile(complainRegex.c_str());
+ 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;
+
+ this->SetupTransforms();
+
+ this->CacheFileName = this->TargetDirectory;
+ this->CacheFileName += "/";
+ this->CacheFileName += lang;
+ this->CacheFileName += ".includecache";
+
+ this->ReadCacheFile();
+}
+
+cmDependsC::~cmDependsC()
+{
+ this->WriteCacheFile();
+ cmDeleteAll(this->FileCache);
+}
+
+bool cmDependsC::WriteDependencies(const std::set<std::string>& sources,
+ const std::string& obj,
+ std::ostream& makeDepends,
+ std::ostream& internalDepends)
+{
+ // Make sure this is a scanning instance.
+ if (sources.empty() || sources.begin()->empty()) {
+ cmSystemTools::Error("Cannot scan dependencies without a source file.");
+ return false;
+ }
+ if (obj.empty()) {
+ cmSystemTools::Error("Cannot scan dependencies without an object file.");
+ return false;
+ }
+
+ std::set<std::string> dependencies;
+ bool haveDeps = false;
+
+ if (this->ValidDeps != CM_NULLPTR) {
+ std::map<std::string, DependencyVector>::const_iterator tmpIt =
+ this->ValidDeps->find(obj);
+ if (tmpIt != this->ValidDeps->end()) {
+ dependencies.insert(tmpIt->second.begin(), tmpIt->second.end());
+ haveDeps = true;
+ }
+ }
+
+ if (!haveDeps) {
+ // Walk the dependency graph starting with the source file.
+ int srcFiles = (int)sources.size();
+ this->Encountered.clear();
+
+ for (std::set<std::string>::const_iterator srcIt = sources.begin();
+ srcIt != sources.end(); ++srcIt) {
+ UnscannedEntry root;
+ root.FileName = *srcIt;
+ this->Unscanned.push(root);
+ this->Encountered.insert(*srcIt);
+ }
+
+ std::set<std::string> scanned;
+
+ // Use reserve to allocate enough memory for tempPathStr
+ // so that during the loops no memory is allocated or freed
+ std::string tempPathStr;
+ tempPathStr.reserve(4 * 1024);
+
+ while (!this->Unscanned.empty()) {
+ // 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;
+ if ((srcFiles > 0) ||
+ cmSystemTools::FileIsFullPath(current.FileName.c_str())) {
+ if (cmSystemTools::FileExists(current.FileName.c_str(), true)) {
+ fullName = current.FileName;
+ }
+ } else if (!current.QuotedLocation.empty() &&
+ cmSystemTools::FileExists(current.QuotedLocation.c_str(),
+ true)) {
+ // 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;
+ } else {
+ std::map<std::string, std::string>::iterator headerLocationIt =
+ this->HeaderLocationCache.find(current.FileName);
+ if (headerLocationIt != this->HeaderLocationCache.end()) {
+ fullName = headerLocationIt->second;
+ } else {
+ for (std::vector<std::string>::const_iterator i =
+ this->IncludePath.begin();
+ i != this->IncludePath.end(); ++i) {
+ // Construct the name of the file as if it were in the current
+ // include directory. Avoid using a leading "./".
+
+ tempPathStr =
+ cmSystemTools::CollapseCombinedPath(*i, current.FileName);
+
+ // Look for the file in this location.
+ if (cmSystemTools::FileExists(tempPathStr.c_str(), true)) {
+ fullName = tempPathStr;
+ HeaderLocationCache[current.FileName] = fullName;
+ break;
+ }
+ }
+ }
+ }
+
+ // Complain if the file cannot be found and matches the complain
+ // regex.
+ if (fullName.empty() &&
+ this->IncludeRegexComplain.find(current.FileName.c_str())) {
+ cmSystemTools::Error("Cannot find file \"", current.FileName.c_str(),
+ "\".");
+ return false;
+ }
+
+ // Scan the file if it was found and has not been scanned already.
+ if (!fullName.empty() && (scanned.find(fullName) == scanned.end())) {
+ // Record scanned files.
+ scanned.insert(fullName);
+
+ // Check whether this file is already in the cache
+ std::map<std::string, cmIncludeLines*>::iterator fileIt =
+ this->FileCache.find(fullName);
+ if (fileIt != this->FileCache.end()) {
+ fileIt->second->Used = true;
+ dependencies.insert(fullName);
+ for (std::vector<UnscannedEntry>::const_iterator incIt =
+ fileIt->second->UnscannedEntries.begin();
+ incIt != fileIt->second->UnscannedEntries.end(); ++incIt) {
+ if (this->Encountered.find(incIt->FileName) ==
+ this->Encountered.end()) {
+ this->Encountered.insert(incIt->FileName);
+ this->Unscanned.push(*incIt);
+ }
+ }
+ } else {
+
+ // Try to scan the file. Just leave it out if we cannot find
+ // it.
+ cmsys::ifstream fin(fullName.c_str());
+ if (fin) {
+ cmsys::FStream::BOM bom = cmsys::FStream::ReadBOM(fin);
+ if (bom == cmsys::FStream::BOM_None ||
+ bom == cmsys::FStream::BOM_UTF8) {
+ // Add this file as a dependency.
+ dependencies.insert(fullName);
+
+ // Scan this file for new dependencies. Pass the directory
+ // containing the file to handle double-quote includes.
+ std::string dir = cmSystemTools::GetFilenamePath(fullName);
+ this->Scan(fin, dir.c_str(), fullName);
+ } else {
+ // Skip file with encoding we do not implement.
+ }
+ }
+ }
+ }
+
+ srcFiles--;
+ }
+ }
+
+ // 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.
+ std::string obj_i =
+ this->LocalGenerator->Convert(obj, cmOutputConverter::HOME_OUTPUT);
+ std::string obj_m = this->LocalGenerator->ConvertToOutputFormat(
+ obj_i, cmOutputConverter::MAKERULE);
+ internalDepends << obj_i << std::endl;
+
+ for (std::set<std::string>::const_iterator i = dependencies.begin();
+ i != dependencies.end(); ++i) {
+ makeDepends << obj_m << ": "
+ << this->LocalGenerator->Convert(
+ *i, cmOutputConverter::HOME_OUTPUT,
+ cmOutputConverter::MAKERULE)
+ << std::endl;
+ internalDepends << " " << *i << std::endl;
+ }
+ makeDepends << std::endl;
+
+ return true;
+}
+
+void cmDependsC::ReadCacheFile()
+{
+ if (this->CacheFileName.empty()) {
+ return;
+ }
+ cmsys::ifstream fin(this->CacheFileName.c_str());
+ if (!fin) {
+ return;
+ }
+
+ std::string line;
+ cmIncludeLines* cacheEntry = CM_NULLPTR;
+ bool haveFileName = false;
+
+ while (cmSystemTools::GetLineFromStream(fin, line)) {
+ if (line.empty()) {
+ cacheEntry = CM_NULLPTR;
+ haveFileName = false;
+ continue;
+ }
+ // the first line after an empty line is the name of the parsed file
+ if (!haveFileName) {
+ haveFileName = true;
+ int newer = 0;
+ cmFileTimeComparison comp;
+ bool res = comp.FileTimeCompare(this->CacheFileName.c_str(),
+ line.c_str(), &newer);
+
+ if (res && newer == 1) // cache is newer than the parsed file
+ {
+ cacheEntry = new cmIncludeLines;
+ this->FileCache[line] = cacheEntry;
+ }
+ // file doesn't exist, check that the regular expressions
+ // haven't changed
+ else if (!res) {
+ if (line.find(INCLUDE_REGEX_LINE_MARKER) == 0) {
+ if (line != this->IncludeRegexLineString) {
+ return;
+ }
+ } else if (line.find(INCLUDE_REGEX_SCAN_MARKER) == 0) {
+ if (line != this->IncludeRegexScanString) {
+ return;
+ }
+ } else if (line.find(INCLUDE_REGEX_COMPLAIN_MARKER) == 0) {
+ if (line != this->IncludeRegexComplainString) {
+ return;
+ }
+ } else if (line.find(INCLUDE_REGEX_TRANSFORM_MARKER) == 0) {
+ if (line != this->IncludeRegexTransformString) {
+ return;
+ }
+ }
+ }
+ } else if (cacheEntry != CM_NULLPTR) {
+ UnscannedEntry entry;
+ entry.FileName = line;
+ if (cmSystemTools::GetLineFromStream(fin, line)) {
+ if (line != "-") {
+ entry.QuotedLocation = line;
+ }
+ cacheEntry->UnscannedEntries.push_back(entry);
+ }
+ }
+ }
+}
+
+void cmDependsC::WriteCacheFile() const
+{
+ if (this->CacheFileName.empty()) {
+ return;
+ }
+ cmsys::ofstream cacheOut(this->CacheFileName.c_str());
+ if (!cacheOut) {
+ return;
+ }
+
+ cacheOut << this->IncludeRegexLineString << "\n\n";
+ cacheOut << this->IncludeRegexScanString << "\n\n";
+ cacheOut << this->IncludeRegexComplainString << "\n\n";
+ cacheOut << this->IncludeRegexTransformString << "\n\n";
+
+ for (std::map<std::string, cmIncludeLines*>::const_iterator fileIt =
+ this->FileCache.begin();
+ fileIt != this->FileCache.end(); ++fileIt) {
+ if (fileIt->second->Used) {
+ cacheOut << fileIt->first << std::endl;
+
+ for (std::vector<UnscannedEntry>::const_iterator incIt =
+ fileIt->second->UnscannedEntries.begin();
+ incIt != fileIt->second->UnscannedEntries.end(); ++incIt) {
+ cacheOut << incIt->FileName << std::endl;
+ if (incIt->QuotedLocation.empty()) {
+ cacheOut << "-" << std::endl;
+ } else {
+ cacheOut << incIt->QuotedLocation << std::endl;
+ }
+ }
+ cacheOut << std::endl;
+ }
+ }
+}
+
+void cmDependsC::Scan(std::istream& is, const char* directory,
+ const std::string& fullName)
+{
+ cmIncludeLines* newCacheEntry = new cmIncludeLines;
+ newCacheEntry->Used = true;
+ this->FileCache[fullName] = newCacheEntry;
+
+ // Read one line at a time.
+ std::string line;
+ while (cmSystemTools::GetLineFromStream(is, line)) {
+ // Transform the line content first.
+ if (!this->TransformRules.empty()) {
+ this->TransformLine(line);
+ }
+
+ // Match include directives.
+ if (this->IncludeRegexLine.find(line.c_str())) {
+ // Get the file being included.
+ UnscannedEntry entry;
+ entry.FileName = this->IncludeRegexLine.match(2);
+ cmSystemTools::ConvertToUnixSlashes(entry.FileName);
+ if (this->IncludeRegexLine.match(3) == "\"" &&
+ !cmSystemTools::FileIsFullPath(entry.FileName.c_str())) {
+ // 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.
+ entry.QuotedLocation =
+ cmSystemTools::CollapseCombinedPath(directory, entry.FileName);
+ }
+
+ // Queue the file if it has not yet been encountered and it
+ // 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.
+ // 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)
+ // This kind of problem will be fixed when a more
+ // preprocessor-like implementation of this scanner is created.
+ if (this->IncludeRegexScan.find(entry.FileName.c_str())) {
+ newCacheEntry->UnscannedEntries.push_back(entry);
+ if (this->Encountered.find(entry.FileName) ==
+ this->Encountered.end()) {
+ this->Encountered.insert(entry.FileName);
+ this->Unscanned.push(entry);
+ }
+ }
+ }
+ }
+}
+
+void cmDependsC::SetupTransforms()
+{
+ // Get the transformation rules.
+ std::vector<std::string> transformRules;
+ cmMakefile* mf = this->LocalGenerator->GetMakefile();
+ if (const char* xform = mf->GetDefinition("CMAKE_INCLUDE_TRANSFORMS")) {
+ cmSystemTools::ExpandListArgument(xform, transformRules, true);
+ }
+ for (std::vector<std::string>::const_iterator tri = transformRules.begin();
+ tri != transformRules.end(); ++tri) {
+ this->ParseTransform(*tri);
+ }
+
+ this->IncludeRegexTransformString = INCLUDE_REGEX_TRANSFORM_MARKER;
+ if (!this->TransformRules.empty()) {
+ // Construct the regular expression to match lines to be
+ // transformed.
+ std::string xform = "^([ \t]*#[ \t]*(include|import)[ \t]*)(";
+ const char* sep = "";
+ for (TransformRulesType::const_iterator tri = this->TransformRules.begin();
+ tri != this->TransformRules.end(); ++tri) {
+ xform += sep;
+ xform += tri->first;
+ sep = "|";
+ }
+ xform += ")[ \t]*\\(([^),]*)\\)";
+ this->IncludeRegexTransform.compile(xform.c_str());
+
+ // Build a string that encodes all transformation rules and will
+ // change when rules are changed.
+ this->IncludeRegexTransformString += xform;
+ for (TransformRulesType::const_iterator tri = this->TransformRules.begin();
+ tri != this->TransformRules.end(); ++tri) {
+ this->IncludeRegexTransformString += " ";
+ this->IncludeRegexTransformString += tri->first;
+ this->IncludeRegexTransformString += "(%)=";
+ this->IncludeRegexTransformString += tri->second;
+ }
+ }
+}
+
+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("(%)=");
+ if (pos == xform.npos || pos == 0) {
+ return;
+ }
+ std::string name = xform.substr(0, pos);
+ std::string value = xform.substr(pos + 4, xform.npos);
+ this->TransformRules[name] = value;
+}
+
+void cmDependsC::TransformLine(std::string& line)
+{
+ // Check for a transform rule match. Return if none.
+ if (!this->IncludeRegexTransform.find(line.c_str())) {
+ return;
+ }
+ TransformRulesType::const_iterator tri =
+ this->TransformRules.find(this->IncludeRegexTransform.match(3));
+ if (tri == this->TransformRules.end()) {
+ return;
+ }
+
+ // Construct the transformed line.
+ std::string newline = this->IncludeRegexTransform.match(1);
+ std::string arg = this->IncludeRegexTransform.match(4);
+ for (const char* c = tri->second.c_str(); *c; ++c) {
+ if (*c == '%') {
+ newline += arg;
+ } else {
+ newline += *c;
+ }
+ }
+
+ // Return the transformed line.
+ line = newline;
+}