cmGeneratorTarget: detect colliding target directories

Some projects might get unlucky and have targets which collide in the
sha3-512 hash (truncated to 4) in a directory which also collides in the
same way (easily done by sharing it). Note such problems as potentially
colliding and offer potential remedies.

Also add a test which generates the error.

Closes: #27465
This commit is contained in:
Ben Boeckel
2026-02-05 12:33:56 -05:00
parent 53ae242d55
commit bc7fac6ad3
12 changed files with 112 additions and 2 deletions
@@ -94,6 +94,7 @@ std::string cmExportTryCompileFileGenerator::FindTargets(
cmTarget dummyHead("try_compile_dummy_exe", cmStateEnums::EXECUTABLE,
cmTarget::Visibility::Normal, tgt->Target->GetMakefile(),
cmTarget::PerConfig::Yes);
dummyHead.SetIsForTryCompile();
cmGeneratorTarget gDummyHead(&dummyHead, tgt->GetLocalGenerator());
+21 -2
View File
@@ -5483,8 +5483,27 @@ std::string cmGeneratorTarget::GetSupportDirectory(
cmStateEnums::IntermediateDirKind kind) const
{
cmLocalGenerator* lg = this->GetLocalGenerator();
return cmStrCat(lg->GetObjectOutputRoot(kind), '/',
lg->GetTargetDirectory(this, kind));
auto targetDir = cmStrCat(lg->GetObjectOutputRoot(kind), '/',
lg->GetTargetDirectory(this, kind));
#ifndef CMAKE_BOOTSTRAP
auto& tdr =
this->GetGlobalGenerator()->RegisterTargetDirectory(this, targetDir);
if (tdr.CollidesWith && !tdr.Warned) {
this->Makefile->IssueMessage(
MessageType::WARNING,
cmStrCat("The '", tdr.CollidesWith->GetName(), "' and '",
this->GetName(),
"' targets share an intermediate directory\n ", targetDir,
"\nwhich may cause problems with the build graph. This project "
"is not compatible with the `SHORT` target intermediate "
"directory strategy. Possible remedies include: moving the "
"target into different directories or renaming a target."));
tdr.Warned = true;
}
#endif
return targetDir;
}
std::string cmGeneratorTarget::GetCMFSupportDirectory(
+29
View File
@@ -2054,6 +2054,35 @@ std::string cmGlobalGenerator::ComputeTargetShortName(
return cmStrCat(tgtHash, dirHash);
}
cmGlobalGenerator::TargetDirectoryRegistration&
cmGlobalGenerator::RegisterTargetDirectory(cmGeneratorTarget const* tgt,
std::string const& targetDir) const
{
if (!tgt->IsNormal() || tgt->GetType() == cmStateEnums::GLOBAL_TARGET ||
tgt->Target->IsForTryCompile()) {
static TargetDirectoryRegistration utilityRegistration(nullptr, true);
return utilityRegistration;
}
// Get the registration instance for the target.
#if __cplusplus >= 201703L
auto registration = this->TargetDirectoryRegistrations.try_emplace(tgt);
#else
auto registration = this->TargetDirectoryRegistrations.insert(
std::make_pair(tgt, TargetDirectoryRegistration()));
#endif
// If it was just inserted, search for a `CollidesWith` possibility.
if (registration.second) {
auto& otherTargets = this->TargetDirectories[targetDir];
if (!otherTargets.empty()) {
registration.first->second.CollidesWith = *otherTargets.begin();
}
otherTargets.insert(tgt);
}
return registration.first->second;
}
void cmGlobalGenerator::ComputeTargetObjectDirectory(
cmGeneratorTarget* /*unused*/) const
{
+23
View File
@@ -648,6 +648,20 @@ public:
virtual std::string GetShortBinaryOutputDir() const;
std::string ComputeTargetShortName(std::string const& bindir,
std::string const& targetName) const;
struct TargetDirectoryRegistration
{
TargetDirectoryRegistration() = default;
TargetDirectoryRegistration(cmGeneratorTarget const* t, bool w)
: CollidesWith(t)
, Warned(w)
{
}
cmGeneratorTarget const* CollidesWith = nullptr;
bool Warned = false;
};
TargetDirectoryRegistration& RegisterTargetDirectory(
cmGeneratorTarget const* tgt, std::string const& targetDir) const;
virtual void ComputeTargetObjectDirectory(cmGeneratorTarget* gt) const;
@@ -823,12 +837,21 @@ private:
std::unordered_map<std::string, cmGeneratorTarget*>;
using MakefileMap = std::unordered_map<std::string, cmMakefile*>;
using LocalGeneratorMap = std::unordered_map<std::string, cmLocalGenerator*>;
using TargetDirectoryRegistrationMap =
std::map<cmGeneratorTarget const*, TargetDirectoryRegistration>;
using TargetDirectoryMap =
std::unordered_map<std::string, std::set<cmGeneratorTarget const*>>;
// Map efficiently from target name to cmTarget instance.
// Do not use this structure for looping over all targets.
// It contains both normal and globally visible imported targets.
TargetMap TargetSearchIndex;
GeneratorTargetMap GeneratorTargetSearchIndex;
// Map from target to a directory registration.
mutable TargetDirectoryRegistrationMap TargetDirectoryRegistrations;
// Map from target directories to targets using it.
mutable TargetDirectoryMap TargetDirectories;
// Map efficiently from source directory path to cmMakefile instance.
// Do not use this structure for looping over all directories.
// It may not contain all of them (see note in IndexMakefile method).
+11
View File
@@ -609,6 +609,7 @@ public:
bool BuildInterfaceIncludesAppended;
bool PerConfig;
bool IsSymbolic;
bool IsForTryCompile{ false };
cmTarget::Visibility TargetVisibility;
std::set<BT<std::pair<std::string, bool>>> Utilities;
std::set<std::string> CodegenDependencies;
@@ -2906,6 +2907,16 @@ bool cmTarget::CanCompileSources() const
return false;
}
void cmTarget::SetIsForTryCompile()
{
this->impl->IsForTryCompile = true;
}
bool cmTarget::IsForTryCompile() const
{
return this->impl->IsForTryCompile;
}
char const* cmTarget::GetSuffixVariableInternal(
cmStateEnums::ArtifactType artifact) const
{
+2
View File
@@ -237,6 +237,8 @@ public:
bool IsRuntimeBinary() const;
bool IsSymbolic() const;
bool CanCompileSources() const;
void SetIsForTryCompile();
bool IsForTryCompile() const;
bool GetMappedConfig(std::string const& desiredConfig, cmValue& loc,
cmValue& imp, std::string& suffix) const;
@@ -0,0 +1,10 @@
CMake Warning in collides/CMakeLists.txt:
The 'CubeTestPluginLogic' and 'TeamworkServerBlobStoreUploadSessionStore'
targets share an intermediate directory
.*/Tests/RunCMake/IntermediateDirStrategy/Collides-build/(collides/)?.o/039c43fd
which may cause problems with the build graph. This project is not
compatible with the `SHORT` target intermediate directory strategy.
Possible remedies include: moving the target into different directories or
renaming a target.
@@ -0,0 +1,4 @@
set(CMAKE_INTERMEDIATE_DIR_STRATEGY SHORT CACHE STRING "" FORCE)
enable_language(C)
add_subdirectory(collides)
@@ -47,5 +47,6 @@ function(run_install_test case)
endfunction()
if (RunCMake_GENERATOR MATCHES "(Ninja|Makefiles|Visual Studio)")
run_cmake(Collides)
run_install_test(ShortObjectDoesntChangeInstall)
endif ()
@@ -0,0 +1,2 @@
add_library(CubeTestPluginLogic STATIC lib1.c)
add_library(TeamworkServerBlobStoreUploadSessionStore STATIC lib2.c)
@@ -0,0 +1,4 @@
int f(int a)
{
return a;
}
@@ -0,0 +1,4 @@
int f(int a)
{
return a;
}