| /* 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 <algorithm> |
| #include <iterator> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <unordered_set> |
| #include <utility> |
| |
| #include <cm/optional> |
| #include <cm/string_view> |
| #include <cm/vector> |
| #include <cmext/string_view> |
| |
| #include "cmsys/FStream.hxx" |
| |
| #include "cmFileTime.h" |
| #include "cmGccDepfileReader.h" |
| #include "cmGccDepfileReaderTypes.h" |
| #include "cmGlobalUnixMakefileGenerator3.h" |
| #include "cmLocalUnixMakefileGenerator3.h" |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.h" |
| |
| bool cmDependsCompiler::CheckDependencies( |
| const std::string& internalDepFile, const std::vector<std::string>& depFiles, |
| cmDepends::DependencyMap& dependencies, |
| const std::function<bool(const std::string&)>& 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<std::string>* 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.Compare(internalDepFileTime) >= 0) { |
| status = false; |
| if (this->Verbose) { |
| cmSystemTools::Stdout(cmStrCat("Dependencies file \"", depFile, |
| "\" is newer than depends file \"", |
| internalDepFile, "\".\n")); |
| } |
| |
| std::vector<std::string> depends; |
| if (format == "custom"_s) { |
| auto deps = cmReadGccDepfile( |
| depFile.c_str(), this->LocalGenerator->GetCurrentBinaryDirectory()); |
| if (!deps) { |
| continue; |
| } |
| |
| for (auto& entry : *deps) { |
| depends = std::move(entry.paths); |
| if (isValidPath) { |
| cm::erase_if(depends, isValidPath); |
| } |
| // copy depends for each target, except first one, which can be |
| // moved |
| for (auto index = entry.rules.size() - 1; index > 0; --index) { |
| auto& rule_deps = dependencies[entry.rules[index]]; |
| rule_deps.insert(rule_deps.end(), depends.cbegin(), |
| depends.cend()); |
| } |
| auto& rule_deps = dependencies[entry.rules.front()]; |
| std::move(depends.cbegin(), depends.cend(), |
| std::back_inserter(rule_deps)); |
| } |
| } else { |
| if (format == "msvc"_s) { |
| cmsys::ifstream fin(depFile.c_str()); |
| if (!fin) { |
| continue; |
| } |
| |
| std::string line; |
| if (!isValidPath) { |
| // insert source as first dependency |
| depends.push_back(source); |
| } |
| while (cmSystemTools::GetLineFromStream(fin, line)) { |
| depends.emplace_back(std::move(line)); |
| } |
| } else if (format == "gcc"_s) { |
| auto deps = cmReadGccDepfile( |
| depFile.c_str(), this->LocalGenerator->GetCurrentBinaryDirectory(), |
| GccDepfilePrependPaths::Deps); |
| if (!deps) { |
| continue; |
| } |
| |
| // dependencies generated by the compiler contains only one target |
| depends = std::move(deps->front().paths); |
| 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()); |
| } |
| } else { |
| // unknown format, ignore it |
| continue; |
| } |
| |
| 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<cmGlobalUnixMakefileGenerator3*>( |
| this->LocalGenerator->GetGlobalGenerator()) |
| ->LineContinueDirective; |
| bool supportLongLineDepend = static_cast<cmGlobalUnixMakefileGenerator3*>( |
| this->LocalGenerator->GetGlobalGenerator()) |
| ->SupportsLongLineDependencies(); |
| cmDepends::DependencyMap makeDependencies(dependencies); |
| std::unordered_set<cm::string_view> phonyTargets; |
| |
| // external dependencies file |
| for (auto& node : makeDependencies) { |
| auto target = this->LocalGenerator->ConvertToMakefilePath( |
| this->LocalGenerator->MaybeRelativeToTopBinDir(node.first)); |
| auto& deps = node.second; |
| std::transform(deps.cbegin(), deps.cend(), deps.begin(), |
| [this](const std::string& dep) { |
| return this->LocalGenerator->ConvertToMakefilePath( |
| this->LocalGenerator->MaybeRelativeToTopBinDir(dep)); |
| }); |
| |
| bool first_dep = true; |
| if (supportLongLineDepend) { |
| makeDepends << target << ": "; |
| } |
| for (const auto& dep : deps) { |
| if (supportLongLineDepend) { |
| if (first_dep) { |
| first_dep = false; |
| makeDepends << dep; |
| } else { |
| makeDepends << ' ' << lineContinue << " " << dep; |
| } |
| } else { |
| makeDepends << target << ": " << dep << std::endl; |
| } |
| |
| 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<std::string>& depFiles) |
| { |
| for (auto dep = depFiles.begin(); dep != depFiles.end(); dep++) { |
| dep += 3; |
| cmSystemTools::RemoveFile(*dep); |
| } |
| } |