blob: 27f328d0b9d877d63b19dc82c418558ad585c28c [file] [log] [blame]
/* 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 <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 && !source.empty()) {
// 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 (!source.empty()) {
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);
if (!source.empty()) {
// 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);
}
}