1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-14 02:08:27 +08:00

Ninja: Handle depfiles with absolute paths to generated files

Ninja treats every (normalized) path as its own node.  It does not
recognize `/abs/path/to/file` in a depfile as matching `path/to/file`
even when `build.ninja` and the working directory are in `/abs/`.
See Ninja Issue 1251.  In cases where we pass absolute paths to the
compiler, it will write a depfile containing absolute paths.  If those
files are generated in the build tree by custom commands, `build.ninja`
references them by relative path in build statement outputs, so Ninja
does not hook up the dependency and rebuild the project correctly.

Add infrastructure to work around this problem by adding implicit
outputs to custom command build statements that reference the main
outputs by absolute path.  Use a `${cmake_ninja_workdir}` placeholder
to avoid repeating the base path.  For example:

    build out.txt | ${cmake_ninja_workdir}out.txt: CUSTOM_COMMAND ...

Ninja will create two nodes for the output file, one with a relative
path and one with an absolute path.  A depfile may then mention either
form of the path and Ninja will hook up the dependency.  Unfortunately
Ninja will also stat the file twice.

Issue: #13894
Fixes: #21865
This commit is contained in:
Brad King
2021-05-19 10:21:11 -04:00
parent bc40cd7a4e
commit 2725ecff38
6 changed files with 33 additions and 2 deletions

View File

@@ -222,7 +222,7 @@ void cmGlobalNinjaGenerator::WriteBuild(std::ostream& os,
}
}
// Write implicit outputs
if (!build.ImplicitOuts.empty()) {
if (!build.ImplicitOuts.empty() || !build.WorkDirOuts.empty()) {
buildStr = cmStrCat(buildStr, " |");
for (std::string const& implicitOut : build.ImplicitOuts) {
buildStr = cmStrCat(buildStr, ' ', this->EncodePath(implicitOut));
@@ -230,6 +230,14 @@ void cmGlobalNinjaGenerator::WriteBuild(std::ostream& os,
this->CombinedBuildOutputs.insert(implicitOut);
}
}
for (std::string const& workdirOut : build.WorkDirOuts) {
// Repeat some outputs, but expressed as absolute paths.
// This helps Ninja handle absolute paths found in a depfile.
// FIXME: Unfortunately this causes Ninja to stat the file twice.
// We could avoid this if Ninja Issue 1251 were fixed.
buildStr = cmStrCat(buildStr, " ${cmake_ninja_workdir}",
this->EncodePath(workdirOut));
}
}
// Write the rule.
@@ -314,6 +322,12 @@ void cmGlobalNinjaGenerator::CCOutputs::Add(
{
for (std::string const& path : paths) {
std::string out = this->GG->ConvertToNinjaPath(path);
if (this->GG->SupportsImplicitOuts() &&
!cmSystemTools::FileIsFullPath(out)) {
// This output is expressed as a relative path. Repeat it,
// but expressed as an absolute path for Ninja Issue 1251.
this->WorkDirOuts.emplace_back(out);
}
this->GG->SeenCustomCommandOutput(out);
this->ExplicitOuts.emplace_back(std::move(out));
}
@@ -340,6 +354,7 @@ void cmGlobalNinjaGenerator::WriteCustomCommandBuild(
cmNinjaBuild build("CUSTOM_COMMAND");
build.Comment = comment;
build.Outputs = std::move(outputs.ExplicitOuts);
build.WorkDirOuts = std::move(outputs.WorkDirOuts);
build.ExplicitDeps = std::move(explicitDeps);
build.OrderOnlyDeps = std::move(orderOnlyDeps);

View File

@@ -121,6 +121,7 @@ public:
}
void Add(std::vector<std::string> const& outputs);
cmNinjaDeps ExplicitOuts;
cmNinjaDeps WorkDirOuts;
};
void WriteCustomCommandBuild(std::string const& command,

View File

@@ -263,6 +263,7 @@ void cmLocalNinjaGenerator::WriteBuildFileTop()
this->GetConfigNames().front());
}
this->WriteNinjaFilesInclusionCommon(this->GetCommonFileStream());
this->WriteNinjaWorkDir(this->GetCommonFileStream());
// For the rule file.
this->WriteProjectHeader(this->GetRulesFileStream());
@@ -364,6 +365,17 @@ void cmLocalNinjaGenerator::WriteNinjaFilesInclusionCommon(std::ostream& os)
os << "\n";
}
void cmLocalNinjaGenerator::WriteNinjaWorkDir(std::ostream& os)
{
cmGlobalNinjaGenerator::WriteDivider(os);
cmGlobalNinjaGenerator::WriteComment(
os, "Logical path to working directory; prefix for absolute paths.");
cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator();
std::string ninja_workdir = this->GetBinaryDirectory();
ng->StripNinjaOutputPathPrefixAsSuffix(ninja_workdir); // Also appends '/'.
os << "cmake_ninja_workdir = " << ng->EncodePath(ninja_workdir) << "\n";
}
void cmLocalNinjaGenerator::WriteProcessedMakefile(std::ostream& os)
{
cmGlobalNinjaGenerator::WriteDivider(os);
@@ -654,6 +666,7 @@ void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
cmNinjaBuild build("phony");
build.Comment = cmStrCat("Phony custom command for ", mainOutput);
build.Outputs = std::move(ccOutputs.ExplicitOuts);
build.WorkDirOuts = std::move(ccOutputs.WorkDirOuts);
build.ExplicitDeps = std::move(ninjaDeps);
build.OrderOnlyDeps = orderOnlyDeps;
gg->WriteBuild(this->GetImplFileStream(fileConfig), build);

View File

@@ -108,6 +108,7 @@ private:
const std::string& config);
void WriteNinjaFilesInclusionConfig(std::ostream& os);
void WriteNinjaFilesInclusionCommon(std::ostream& os);
void WriteNinjaWorkDir(std::ostream& os);
void WriteProcessedMakefile(std::ostream& os);
void WritePools(std::ostream& os);

View File

@@ -53,6 +53,7 @@ public:
std::string Rule;
cmNinjaDeps Outputs;
cmNinjaDeps ImplicitOuts;
cmNinjaDeps WorkDirOuts; // For cmake_ninja_workdir.
cmNinjaDeps ExplicitDeps;
cmNinjaDeps ImplicitDeps;
cmNinjaDeps OrderOnlyDeps;

View File

@@ -183,7 +183,7 @@ if(RunCMake_GENERATOR MATCHES "Make")
endif()
if(RunCMake_GENERATOR MATCHES "^Visual Studio 9 " OR
RunCMake_GENERATOR MATCHES "Ninja")
(RunCMake_GENERATOR MATCHES "Ninja" AND ninja_version VERSION_LESS 1.7))
# This build tool misses the dependency.
set(run_BuildDepends_skip_step_2 1)
endif()