pchreuse: defer target existence enforcement to generation time

Now that generation can work with any way the state gets to the way it
is, just do the target enforcement at generation time. This allows PCH
reuse targets to be declared before or after targets which use them.

Also update `cmLocalGenerator` to use the methods now that they reliably
provide values rather than parallel construction.
This commit is contained in:
Ben Boeckel
2025-06-16 14:30:16 +02:00
parent 3ef773490d
commit f78f592b78
14 changed files with 225 additions and 90 deletions

View File

@@ -1296,6 +1296,8 @@ std::string cmGeneratorTarget::GetCompilePDBName(
return components.prefix + pdbName + ".pdb";
}
// If the target is PCH-reused, we need a stable name for the PDB file so
// that reusing targets can construct a stable name for it.
if (this->PchReused) {
NameComponents const& components = GetFullNameInternalComponents(
config, cmStateEnums::RuntimeBinaryArtifact);
@@ -2811,12 +2813,45 @@ std::string cmGeneratorTarget::GetClangTidyExportFixesDirectory(
return cmSystemTools::CollapseFullPath(path);
}
struct CycleWatcher
{
CycleWatcher(bool& flag)
: Flag(flag)
{
this->Flag = true;
}
~CycleWatcher() { this->Flag = false; }
bool& Flag;
};
cmGeneratorTarget const* cmGeneratorTarget::GetPchReuseTarget() const
{
if (this->ComputingPchReuse) {
// TODO: Get the full cycle.
if (!this->PchReuseCycleDetected) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Circular PCH reuse target involving '", this->GetName(),
'\''));
}
this->PchReuseCycleDetected = true;
return nullptr;
}
CycleWatcher watch(this->ComputingPchReuse);
(void)watch;
cmValue pchReuseFrom = this->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
if (!pchReuseFrom) {
return nullptr;
}
cmGeneratorTarget const* generatorTarget =
this->GetGlobalGenerator()->FindGeneratorTarget(*pchReuseFrom);
if (!generatorTarget) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat(
"Target \"", *pchReuseFrom, "\" for the \"", this->GetName(),
R"(" target's "PRECOMPILE_HEADERS_REUSE_FROM" property does not exist.)"));
}
if (this->GetProperty("PRECOMPILE_HEADERS").IsOn()) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
@@ -2824,16 +2859,43 @@ cmGeneratorTarget const* cmGeneratorTarget::GetPchReuseTarget() const
this->GetName(), "\")\n"));
}
// Guaranteed to exist because `SetProperty` does a target lookup.
return this->GetGlobalGenerator()->FindGeneratorTarget(*pchReuseFrom);
if (generatorTarget) {
if (auto const* recurseReuseTarget =
generatorTarget->GetPchReuseTarget()) {
return recurseReuseTarget;
}
}
return generatorTarget;
}
cmGeneratorTarget* cmGeneratorTarget::GetPchReuseTarget()
{
if (this->ComputingPchReuse) {
// TODO: Get the full cycle.
if (!this->PchReuseCycleDetected) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Circular PCH reuse target involving '", this->GetName(),
'\''));
}
this->PchReuseCycleDetected = true;
return nullptr;
}
CycleWatcher watch(this->ComputingPchReuse);
(void)watch;
cmValue pchReuseFrom = this->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
if (!pchReuseFrom) {
return nullptr;
}
cmGeneratorTarget* generatorTarget =
this->GetGlobalGenerator()->FindGeneratorTarget(*pchReuseFrom);
if (!generatorTarget) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat(
"Target \"", *pchReuseFrom, "\" for the \"", this->GetName(),
R"(" target's "PRECOMPILE_HEADERS_REUSE_FROM" property does not exist.)"));
}
if (this->GetProperty("PRECOMPILE_HEADERS").IsOn()) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
@@ -2841,8 +2903,12 @@ cmGeneratorTarget* cmGeneratorTarget::GetPchReuseTarget()
this->GetName(), "\")\n"));
}
// Guaranteed to exist because `SetProperty` does a target lookup.
return this->GetGlobalGenerator()->FindGeneratorTarget(*pchReuseFrom);
if (generatorTarget) {
if (auto* recurseReuseTarget = generatorTarget->GetPchReuseTarget()) {
return recurseReuseTarget;
}
}
return generatorTarget;
}
std::vector<std::string> cmGeneratorTarget::GetPchArchs(
@@ -2873,6 +2939,7 @@ std::string cmGeneratorTarget::GetPchHeader(std::string const& config,
}
cmGeneratorTarget const* generatorTarget = this;
cmGeneratorTarget const* reuseTarget = this->GetPchReuseTarget();
bool const haveReuseTarget = reuseTarget && reuseTarget != this;
if (reuseTarget) {
generatorTarget = reuseTarget;
}
@@ -2882,7 +2949,7 @@ std::string cmGeneratorTarget::GetPchHeader(std::string const& config,
if (inserted.second) {
std::vector<BT<std::string>> const headers =
this->GetPrecompileHeaders(config, language);
if (headers.empty() && !reuseTarget) {
if (headers.empty() && !haveReuseTarget) {
return std::string();
}
std::string& filename = inserted.first->second;
@@ -2905,7 +2972,7 @@ std::string cmGeneratorTarget::GetPchHeader(std::string const& config,
languageToExtension.at(language));
std::string const filename_tmp = cmStrCat(filename, ".tmp");
if (!reuseTarget) {
if (!haveReuseTarget) {
cmValue pchPrologue =
this->Makefile->GetDefinition("CMAKE_PCH_PROLOGUE");
cmValue pchEpilogue =
@@ -2981,6 +3048,7 @@ std::string cmGeneratorTarget::GetPchSource(std::string const& config,
cmGeneratorTarget const* generatorTarget = this;
cmGeneratorTarget const* reuseTarget = this->GetPchReuseTarget();
bool const haveReuseTarget = reuseTarget && reuseTarget != this;
if (reuseTarget) {
generatorTarget = reuseTarget;
}
@@ -3009,7 +3077,7 @@ std::string cmGeneratorTarget::GetPchSource(std::string const& config,
}
std::string const filename_tmp = cmStrCat(filename, ".tmp");
if (!reuseTarget) {
if (!haveReuseTarget) {
{
cmGeneratedFileStream file(filename_tmp);
file << "/* generated by CMake */\n";
@@ -4467,11 +4535,9 @@ bool cmGeneratorTarget::ComputePDBOutputDir(std::string const& kind,
}
}
if (out.empty()) {
// A target which is PCH-reused must have a stable PDB output directory so
// that the PDB can be stably referred to when consuming the PCH file.
if (this->PchReused) {
out = cmStrCat(this->GetLocalGenerator()->GetCurrentBinaryDirectory(),
'/', this->GetName(), ".dir/");
// Compile output should always have a path.
if (kind == "COMPILE_PDB"_s) {
out = this->GetSupportDirectory();
} else {
return false;
}

View File

@@ -1522,6 +1522,8 @@ private:
};
mutable std::map<std::string, InfoByConfig> Configs;
bool PchReused = false;
mutable bool ComputingPchReuse = false;
mutable bool PchReuseCycleDetected = false;
};
class cmGeneratorTarget::TargetPropertyEntry

View File

@@ -2779,8 +2779,8 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
continue;
}
cmValue ReuseFrom =
target->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
auto* reuseTarget = target->GetPchReuseTarget();
bool const haveReuseTarget = reuseTarget && reuseTarget != target;
auto* pch_sf = this->Makefile->GetOrCreateSource(
pchSource, false, cmSourceFileLocationKind::Known);
@@ -2789,7 +2789,7 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
pch_sf->SetProperty("CXX_SCAN_FOR_MODULES", "0");
if (!this->GetGlobalGenerator()->IsXcode()) {
if (!ReuseFrom) {
if (!haveReuseTarget) {
target->AddSource(pchSource, true);
}
@@ -2797,14 +2797,11 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
// Exclude the pch files from linking
if (this->Makefile->IsOn("CMAKE_LINK_PCH")) {
if (!ReuseFrom) {
if (!haveReuseTarget) {
pch_sf->AppendProperty(
"OBJECT_OUTPUTS",
cmStrCat("$<$<CONFIG:", config, ">:", pchFile, '>'));
} else {
auto* reuseTarget =
this->GlobalGenerator->FindGeneratorTarget(*ReuseFrom);
if (this->Makefile->IsOn("CMAKE_PCH_COPY_COMPILE_PDB")) {
std::string const compilerId =
@@ -2850,11 +2847,11 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
}
if (editAndContinueDebugInfo || msvc2008OrLess) {
this->CopyPchCompilePdb(config, lang, target, *ReuseFrom,
reuseTarget, { ".pdb", ".idb" });
this->CopyPchCompilePdb(config, lang, target, reuseTarget,
{ ".pdb", ".idb" });
} else if (programDatabaseDebugInfo) {
this->CopyPchCompilePdb(config, lang, target, *ReuseFrom,
reuseTarget, { ".pdb" });
this->CopyPchCompilePdb(config, lang, target, reuseTarget,
{ ".pdb" });
}
}
@@ -2906,18 +2903,11 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
void cmLocalGenerator::CopyPchCompilePdb(
std::string const& config, std::string const& language,
cmGeneratorTarget* target, std::string const& ReuseFrom,
cmGeneratorTarget* reuseTarget, std::vector<std::string> const& extensions)
cmGeneratorTarget* target, cmGeneratorTarget* reuseTarget,
std::vector<std::string> const& extensions)
{
std::string const pdb_prefix =
this->GetGlobalGenerator()->IsMultiConfig() ? cmStrCat(config, '/') : "";
std::string const target_compile_pdb_dir =
cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
target->GetName(), ".dir/");
std::string const copy_script =
cmStrCat(target_compile_pdb_dir, "copy_idb_pdb_", config, ".cmake");
std::string const copy_script = cmStrCat(target->GetSupportDirectory(),
"/copy_idb_pdb_", config, ".cmake");
cmGeneratedFileStream file(copy_script);
file << "# CMake generated file\n";
@@ -2926,28 +2916,37 @@ void cmLocalGenerator::CopyPchCompilePdb(
<< "# by mspdbsrv. The foreach retry loop is needed to make sure\n"
<< "# the pdb file is ready to be copied.\n\n";
auto configGenex = [&](cm::string_view expr) -> std::string {
if (this->GetGlobalGenerator()->IsMultiConfig()) {
return cmStrCat("$<$<CONFIG:", config, ">:", expr, '>');
}
return std::string(expr);
};
std::vector<std::string> outputs;
auto replaceExtension = [](std::string const& path,
std::string const& ext) -> std::string {
auto const dir = cmSystemTools::GetFilenamePath(path);
auto const base = cmSystemTools::GetFilenameWithoutLastExtension(path);
if (dir.empty()) {
return cmStrCat(base, ext);
}
return cmStrCat(dir, '/', base, ext);
};
for (auto const& extension : extensions) {
std::string const from_file =
cmStrCat(reuseTarget->GetLocalGenerator()->GetCurrentBinaryDirectory(),
'/', ReuseFrom, ".dir/${PDB_PREFIX}", ReuseFrom, extension);
std::string const to_dir =
cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
target->GetName(), ".dir/${PDB_PREFIX}");
std::string const to_file = cmStrCat(to_dir, ReuseFrom, extension);
std::string dest_file = to_file;
std::string const& prefix = target->GetSafeProperty("PREFIX");
if (!prefix.empty()) {
dest_file = cmStrCat(to_dir, prefix, ReuseFrom, extension);
}
replaceExtension(reuseTarget->GetCompilePDBPath(config), extension);
std::string const to_dir = target->GetCompilePDBDirectory(config);
std::string const to_file = cmStrCat(
replaceExtension(reuseTarget->GetCompilePDBName(config), extension),
'/');
std::string const dest_file = cmStrCat(to_dir, to_file);
file << "foreach(retry RANGE 1 30)\n";
file << " if (EXISTS \"" << from_file << "\" AND (NOT EXISTS \""
<< dest_file << "\" OR NOT \"" << dest_file << "\" IS_NEWER_THAN \""
<< from_file << "\"))\n";
file << " file(MAKE_DIRECTORY \"" << to_dir << "\")\n";
file << " execute_process(COMMAND ${CMAKE_COMMAND} -E copy";
file << " \"" << from_file << "\""
<< " \"" << to_dir << "\" RESULT_VARIABLE result "
@@ -2956,10 +2955,6 @@ void cmLocalGenerator::CopyPchCompilePdb(
<< " execute_process(COMMAND ${CMAKE_COMMAND}"
<< " -E sleep 1)\n"
<< " else()\n";
if (!prefix.empty()) {
file << " file(REMOVE \"" << dest_file << "\")\n";
file << " file(RENAME \"" << to_file << "\" \"" << dest_file << "\")\n";
}
file << " break()\n"
<< " endif()\n";
file << " elseif(NOT EXISTS \"" << from_file << "\")\n"
@@ -2967,29 +2962,18 @@ void cmLocalGenerator::CopyPchCompilePdb(
<< " -E sleep 1)\n"
<< " endif()\n";
file << "endforeach()\n";
outputs.push_back(configGenex(dest_file));
}
auto configGenex = [&](cm::string_view expr) -> std::string {
if (this->GetGlobalGenerator()->IsMultiConfig()) {
return cmStrCat("$<$<CONFIG:", config, ">:", expr, '>');
}
return std::string(expr);
};
cmCustomCommandLines commandLines = cmMakeSingleCommandLine(
{ configGenex(cmSystemTools::GetCMakeCommand()),
configGenex(cmStrCat("-DPDB_PREFIX=", pdb_prefix)), configGenex("-P"),
configGenex(copy_script) });
cmCustomCommandLines commandLines =
cmMakeSingleCommandLine({ configGenex(cmSystemTools::GetCMakeCommand()),
configGenex("-P"), configGenex(copy_script) });
auto const comment =
cmStrCat("Copying PDB for PCH reuse from ", reuseTarget->GetName(),
" for ", target->GetName());
;
std::vector<std::string> outputs;
outputs.push_back(configGenex(
cmStrCat(target_compile_pdb_dir, pdb_prefix, ReuseFrom, ".pdb")));
auto cc = cm::make_unique<cmCustomCommand>();
cc->SetCommandLines(commandLines);
cc->SetComment(comment.c_str());
@@ -3011,9 +2995,6 @@ void cmLocalGenerator::CopyPchCompilePdb(
target->AddSource(copy_rule->ResolveFullPath());
}
}
target->Target->SetProperty("COMPILE_PDB_OUTPUT_DIRECTORY",
target_compile_pdb_dir);
}
cm::optional<std::string> cmLocalGenerator::GetMSVCDebugFormatName(

View File

@@ -640,7 +640,6 @@ private:
void CopyPchCompilePdb(std::string const& config,
std::string const& language,
cmGeneratorTarget* target,
std::string const& ReuseFrom,
cmGeneratorTarget* reuseTarget,
std::vector<std::string> const& extensions);

View File

@@ -2148,24 +2148,10 @@ void cmTarget::SetProperty(std::string const& prop, cmValue value)
return;
}
} else if (prop == propPRECOMPILE_HEADERS_REUSE_FROM) {
auto* reusedTarget = this->impl->Makefile->GetCMakeInstance()
->GetGlobalGenerator()
->FindTarget(value);
if (!reusedTarget) {
std::string const e(
"PRECOMPILE_HEADERS_REUSE_FROM set with non existing target");
this->impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e);
return;
this->impl->Properties.SetProperty(prop, value);
if (value) {
this->AddUtility(*value, false, this->impl->Makefile);
}
std::string reusedFrom = reusedTarget->GetSafeProperty(prop);
if (reusedFrom.empty()) {
reusedFrom = *value;
}
this->impl->Properties.SetProperty(prop, reusedFrom);
this->AddUtility(reusedFrom, false, this->impl->Makefile);
} else if (prop == propC_STANDARD || prop == propCXX_STANDARD ||
prop == propCUDA_STANDARD || prop == propHIP_STANDARD ||
prop == propOBJC_STANDARD || prop == propOBJCXX_STANDARD) {

View File

@@ -76,16 +76,27 @@ endif()
set(pdbs "")
foreach(t ${my_targets})
set(with_compile 0)
get_property(pdb_name TARGET ${t} PROPERTY PDB_NAME)
get_property(pdb_dir TARGET ${t} PROPERTY PDB_OUTPUT_DIRECTORY)
if(NOT pdb_name)
set(with_compile 1)
get_property(pdb_name TARGET ${t} PROPERTY COMPILE_PDB_NAME)
endif()
if(NOT pdb_dir)
get_property(pdb_dir TARGET ${t} PROPERTY COMPILE_PDB_OUTPUT_DIRECTORY)
endif()
if(NOT pdb_dir)
set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR})
if (NOT with_compile)
set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR})
elseif (CMAKE_GENERATOR MATCHES "Ninja" OR
CMAKE_GENERATOR MATCHES "Makefiles")
set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${t}.dir)
elseif (CMAKE_GENERATOR MATCHES "Visual Studio")
set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR}/${t}.dir)
else ()
set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR}/${t}.dir)
endif ()
endif()
if (pdb_dir MATCHES "\\$<.*>")
# Skip per-configuration subdirectory if the value contained

View File

@@ -0,0 +1,2 @@
((Classic Intel)?Warning #672: the command line options do not match those used[^
]*)?

View File

@@ -54,12 +54,24 @@ file(WRITE ${CMAKE_BINARY_DIR}/message.hxx [=[
const char* message();
]=])
add_library(pch_before_reuse_pch ${CMAKE_BINARY_DIR}/message.cxx)
target_precompile_headers(pch_before_reuse_pch PRIVATE "${CMAKE_BINARY_DIR}/string.hxx")
target_precompile_headers(pch_before_reuse_pch REUSE_FROM pch-generator)
set_property(TARGET pch_before_reuse_pch PROPERTY PRECOMPILE_HEADERS_REUSE_FROM)
target_include_directories(pch_before_reuse_pch PRIVATE ${CMAKE_BINARY_DIR})
add_library(pch_before_reuse_reuse ${CMAKE_BINARY_DIR}/message.cxx)
target_precompile_headers(pch_before_reuse_reuse PRIVATE "${CMAKE_BINARY_DIR}/string.hxx")
target_precompile_headers(pch_before_reuse_reuse REUSE_FROM pch-generator)
set_property(TARGET pch_before_reuse_reuse PROPERTY PRECOMPILE_HEADERS "")
target_include_directories(pch_before_reuse_reuse PRIVATE ${CMAKE_BINARY_DIR})
add_library(reuse_before_pch_pch ${CMAKE_BINARY_DIR}/message.cxx)
target_precompile_headers(reuse_before_pch_pch REUSE_FROM pch-generator)
target_precompile_headers(reuse_before_pch_pch PRIVATE "${CMAKE_BINARY_DIR}/string.hxx")
set_property(TARGET reuse_before_pch_pch PROPERTY PRECOMPILE_HEADERS_REUSE_FROM)
target_include_directories(reuse_before_pch_pch PRIVATE ${CMAKE_BINARY_DIR})
add_library(reuse_before_pch_reuse ${CMAKE_BINARY_DIR}/message.cxx)
target_precompile_headers(reuse_before_pch_reuse REUSE_FROM pch-generator)
target_precompile_headers(reuse_before_pch_reuse PRIVATE "${CMAKE_BINARY_DIR}/string.hxx")

View File

@@ -0,0 +1,2 @@
((Classic Intel)?Warning #672: the command line options do not match those used[^
]*)?

View File

@@ -0,0 +1,59 @@
enable_language(CXX)
if(CMAKE_CXX_COMPILE_OPTIONS_USE_PCH)
add_definitions(-DHAVE_PCH_SUPPORT)
endif()
######################################################################
file(WRITE ${CMAKE_BINARY_DIR}/message.cxx [=[
#include "message.hxx"
#ifndef HAVE_PCH_SUPPORT
#include "string.hxx"
#endif
const char* message()
{
static std::string greeting("hi there");
return greeting.c_str();
}
]=])
file(WRITE ${CMAKE_BINARY_DIR}/message.hxx [=[
const char* message();
]=])
add_library(reuse_decl_order ${CMAKE_BINARY_DIR}/message.cxx)
target_precompile_headers(reuse_decl_order REUSE_FROM pch-generator)
target_include_directories(reuse_decl_order PRIVATE ${CMAKE_BINARY_DIR})
######################################################################
file(WRITE ${CMAKE_BINARY_DIR}/pch.cxx [=[
void nothing()
{
}
]=])
file(WRITE ${CMAKE_BINARY_DIR}/string.hxx [=[
#include <string.h>
namespace std {
struct string
{
char storage[20];
string(const char* s) {
strcpy(storage, s);
}
const char* c_str() const {
return storage;
}
};
}
]=])
add_library(pch-generator ${CMAKE_BINARY_DIR}/pch.cxx)
target_precompile_headers(pch-generator PRIVATE ${CMAKE_BINARY_DIR}/string.hxx)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,2 @@
CMake Error in CMakeLists.txt:
Circular PCH reuse target involving 'first'

View File

@@ -0,0 +1,10 @@
enable_language(C)
add_library(first foo.c)
target_precompile_headers(first REUSE_FROM third)
add_library(second foo.c)
target_precompile_headers(second REUSE_FROM first)
add_library(third foo.c)
target_precompile_headers(third REUSE_FROM second)

View File

@@ -32,7 +32,9 @@ run_test(PchReuseFromSubdir)
run_build_verbose(PchReuseFromIgnoreOwnProps)
run_build_verbose(PchReuseFromUseUpdatedProps)
run_build_verbose(PchReuseConsistency)
run_cmake(PchReuseFromCycle)
run_cmake(PchMultilanguage)
run_build_verbose(PchReuseDeclarationOrder)
if(RunCMake_GENERATOR MATCHES "Make|Ninja")
run_cmake(PchWarnInvalid)