/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmDependsCompiler.h" #include #include #include #include #include #include #include #include #include "cmsys/FStream.hxx" #include "cmFileTime.h" #include "cmGlobalUnixMakefileGenerator3.h" #include "cmLocalUnixMakefileGenerator3.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" namespace { std::string& ReplaceAll(std::string& data, const std::string& toSearch, const std::string& replaceStr) { // Get the first occurrence auto pos = data.find(toSearch); // Repeat until the end is reached while (pos != std::string::npos) { // Replace this occurrence of Sub String data.replace(pos, toSearch.size(), replaceStr); // Get the next occurrence from the current position pos = data.find(toSearch, pos + replaceStr.size()); } return data; } std::string& NormalizePath(std::string& item) { ReplaceAll(item, "$$", "$"); ReplaceAll(item, "\\ ", " "); ReplaceAll(item, "\\#", "#"); ReplaceAll(item, "\\", "/"); return item; } void ParseLine(const std::string& line, std::vector& depends) { auto start = line.find_first_not_of(' '); if (start == std::string::npos || line[start] == '#') { return; } auto index = start; while ((index = line.find(' ', index)) != std::string::npos) { if (line[index - 1] == '\\') { index += 1; continue; } auto item = line.substr(start, index - start); if (item.back() != ':') { // check that ':' is not present after some spaces auto index2 = line.find_first_not_of(' ', index + 1); if (index2 == std::string::npos || line[index2] != ':') { // this is a dependency, add it depends.emplace_back(std::move(NormalizePath(item))); } else { index = index2; } } start = line.find_first_not_of(' ', index + 1); index = start; } if (start != std::string::npos) { auto item = line.substr(start); if (line.back() != ':') { // this is a dependency, add it depends.emplace_back(std::move(NormalizePath(item))); } } } } bool cmDependsCompiler::CheckDependencies( const std::string& internalDepFile, const std::vector& depFiles, cmDepends::DependencyMap& dependencies, const std::function& isValidPath) { bool status = true; bool forceReadDeps = true; cmFileTime internalDepFileTime; // read cached dependencies stored in internal file if (cmSystemTools::FileExists(internalDepFile)) { internalDepFileTime.Load(internalDepFile); forceReadDeps = false; // read current dependencies cmsys::ifstream fin(internalDepFile.c_str()); if (fin) { std::string line; std::string depender; std::vector* currentDependencies = nullptr; while (std::getline(fin, line)) { if (line.empty() || line.front() == '#') { continue; } // Drop carriage return character at the end if (line.back() == '\r') { line.pop_back(); if (line.empty()) { continue; } } // Check if this a depender line if (line.front() != ' ') { depender = std::move(line); currentDependencies = &dependencies[depender]; continue; } // This is a dependee line if (currentDependencies != nullptr) { currentDependencies->emplace_back(line.substr(1)); } } fin.close(); } } // Now, update dependencies map with all new compiler generated // dependencies files cmFileTime depFileTime; for (auto dep = depFiles.begin(); dep != depFiles.end(); dep++) { const auto& source = *dep++; const auto& target = *dep++; const auto& format = *dep++; const auto& depFile = *dep; if (!cmSystemTools::FileExists(depFile)) { continue; } if (!forceReadDeps) { depFileTime.Load(depFile); } if (forceReadDeps || depFileTime.Newer(internalDepFileTime)) { status = false; if (this->Verbose) { cmSystemTools::Stdout(cmStrCat("Dependencies file \"", depFile, "\" is newer than depends file \"", internalDepFile, "\".\n")); } cmsys::ifstream fin(depFile.c_str()); if (!fin) { continue; } std::vector depends; std::string line; if (format == "msvc"_s) { if (!isValidPath) { // insert source as first dependency depends.push_back(source); } while (cmSystemTools::GetLineFromStream(fin, line)) { depends.emplace_back(std::move(line)); } } else { while (cmSystemTools::GetLineFromStream(fin, line)) { if (line.empty()) { continue; } if (line.back() == '\\') { line.pop_back(); } ParseLine(line, depends); } if (depends.empty()) { // unexpectedly empty, ignore it and continue continue; } // depending of the effective format of the dependencies file generated // by the compiler, the target can be wrongly identified as a // dependency so remove it from the list if (depends.front() == target) { depends.erase(depends.begin()); } // ensure source file is the first dependency if (depends.front() != source) { cm::erase(depends, source); if (!isValidPath) { depends.insert(depends.begin(), source); } } else if (isValidPath) { // remove first dependency because it must not be filtered out depends.erase(depends.begin()); } } if (isValidPath) { cm::erase_if(depends, isValidPath); // insert source as first dependency depends.insert(depends.begin(), source); } dependencies[target] = std::move(depends); } } return status; } void cmDependsCompiler::WriteDependencies( const cmDepends::DependencyMap& dependencies, std::ostream& makeDepends, std::ostream& internalDepends) { // dependencies file consumed by make tool const auto& lineContinue = static_cast( this->LocalGenerator->GetGlobalGenerator()) ->LineContinueDirective; const auto& binDir = this->LocalGenerator->GetBinaryDirectory(); cmDepends::DependencyMap makeDependencies(dependencies); std::unordered_set phonyTargets; // external dependencies file for (auto& node : makeDependencies) { auto& deps = node.second; std::transform( deps.cbegin(), deps.cend(), deps.begin(), [this, &binDir](const std::string& dep) { return LocalGenerator->ConvertToMakefilePath( this->LocalGenerator->MaybeConvertToRelativePath(binDir, dep)); }); makeDepends << this->LocalGenerator->ConvertToMakefilePath(node.first) << ": " << deps.front(); // first dependency is the source, remove it because should not be declared // as phony target deps.erase(deps.begin()); for (const auto& dep : deps) { makeDepends << ' ' << lineContinue << " " << dep; phonyTargets.emplace(dep.data(), dep.length()); } makeDepends << std::endl << std::endl; } // add phony targets for (const auto& target : phonyTargets) { makeDepends << std::endl << target << ':' << std::endl; } // internal dependencies file for (const auto& node : dependencies) { internalDepends << node.first << std::endl; for (const auto& dep : node.second) { internalDepends << ' ' << dep << std::endl; } internalDepends << std::endl; } } void cmDependsCompiler::ClearDependencies( const std::vector& depFiles) { for (auto dep = depFiles.begin(); dep != depFiles.end(); dep++) { dep += 3; cmSystemTools::RemoveFile(*dep); } }