From bc7fac6ad390ef1a9bb326100b07c008792bce89 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Wed, 4 Feb 2026 16:53:16 -0500 Subject: [PATCH] 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 --- Source/cmExportTryCompileFileGenerator.cxx | 1 + Source/cmGeneratorTarget.cxx | 23 +++++++++++++-- Source/cmGlobalGenerator.cxx | 29 +++++++++++++++++++ Source/cmGlobalGenerator.h | 23 +++++++++++++++ Source/cmTarget.cxx | 11 +++++++ Source/cmTarget.h | 2 ++ .../Collides-stderr.txt | 10 +++++++ .../IntermediateDirStrategy/Collides.cmake | 4 +++ .../RunCMakeTest.cmake | 1 + .../collides/CMakeLists.txt | 2 ++ .../IntermediateDirStrategy/collides/lib1.c | 4 +++ .../IntermediateDirStrategy/collides/lib2.c | 4 +++ 12 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 Tests/RunCMake/IntermediateDirStrategy/Collides-stderr.txt create mode 100644 Tests/RunCMake/IntermediateDirStrategy/Collides.cmake create mode 100644 Tests/RunCMake/IntermediateDirStrategy/collides/CMakeLists.txt create mode 100644 Tests/RunCMake/IntermediateDirStrategy/collides/lib1.c create mode 100644 Tests/RunCMake/IntermediateDirStrategy/collides/lib2.c diff --git a/Source/cmExportTryCompileFileGenerator.cxx b/Source/cmExportTryCompileFileGenerator.cxx index 456e6b9453..f78eaabeae 100644 --- a/Source/cmExportTryCompileFileGenerator.cxx +++ b/Source/cmExportTryCompileFileGenerator.cxx @@ -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()); diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index a59186c0c4..22f1e17508 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -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( diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 4c40e9316d..2fa8032525 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -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 { diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index 5771256ea6..02875b60ad 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -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; using MakefileMap = std::unordered_map; using LocalGeneratorMap = std::unordered_map; + using TargetDirectoryRegistrationMap = + std::map; + using TargetDirectoryMap = + std::unordered_map>; // 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). diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index ad37fe0ae0..8539948685 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -609,6 +609,7 @@ public: bool BuildInterfaceIncludesAppended; bool PerConfig; bool IsSymbolic; + bool IsForTryCompile{ false }; cmTarget::Visibility TargetVisibility; std::set>> Utilities; std::set 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 { diff --git a/Source/cmTarget.h b/Source/cmTarget.h index 96a067b4d7..b275e47329 100644 --- a/Source/cmTarget.h +++ b/Source/cmTarget.h @@ -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; diff --git a/Tests/RunCMake/IntermediateDirStrategy/Collides-stderr.txt b/Tests/RunCMake/IntermediateDirStrategy/Collides-stderr.txt new file mode 100644 index 0000000000..d1a775dd63 --- /dev/null +++ b/Tests/RunCMake/IntermediateDirStrategy/Collides-stderr.txt @@ -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. diff --git a/Tests/RunCMake/IntermediateDirStrategy/Collides.cmake b/Tests/RunCMake/IntermediateDirStrategy/Collides.cmake new file mode 100644 index 0000000000..073ec18a0b --- /dev/null +++ b/Tests/RunCMake/IntermediateDirStrategy/Collides.cmake @@ -0,0 +1,4 @@ +set(CMAKE_INTERMEDIATE_DIR_STRATEGY SHORT CACHE STRING "" FORCE) +enable_language(C) + +add_subdirectory(collides) diff --git a/Tests/RunCMake/IntermediateDirStrategy/RunCMakeTest.cmake b/Tests/RunCMake/IntermediateDirStrategy/RunCMakeTest.cmake index eb2a5f5c03..bc0d736073 100644 --- a/Tests/RunCMake/IntermediateDirStrategy/RunCMakeTest.cmake +++ b/Tests/RunCMake/IntermediateDirStrategy/RunCMakeTest.cmake @@ -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 () diff --git a/Tests/RunCMake/IntermediateDirStrategy/collides/CMakeLists.txt b/Tests/RunCMake/IntermediateDirStrategy/collides/CMakeLists.txt new file mode 100644 index 0000000000..446c53d4af --- /dev/null +++ b/Tests/RunCMake/IntermediateDirStrategy/collides/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(CubeTestPluginLogic STATIC lib1.c) +add_library(TeamworkServerBlobStoreUploadSessionStore STATIC lib2.c) diff --git a/Tests/RunCMake/IntermediateDirStrategy/collides/lib1.c b/Tests/RunCMake/IntermediateDirStrategy/collides/lib1.c new file mode 100644 index 0000000000..a482372785 --- /dev/null +++ b/Tests/RunCMake/IntermediateDirStrategy/collides/lib1.c @@ -0,0 +1,4 @@ +int f(int a) +{ + return a; +} diff --git a/Tests/RunCMake/IntermediateDirStrategy/collides/lib2.c b/Tests/RunCMake/IntermediateDirStrategy/collides/lib2.c new file mode 100644 index 0000000000..a482372785 --- /dev/null +++ b/Tests/RunCMake/IntermediateDirStrategy/collides/lib2.c @@ -0,0 +1,4 @@ +int f(int a) +{ + return a; +}