NMake,JOM,Borland: Avoid case collisions for test_prep targets

These make tools match target names case-insensitively, so generating a
separate rule for tests whose names differ only in case produces a
duplicate rule, and a test named e.g. "All" collides with the
"test_prep/all" rule and forms a dependency cycle.

Key the "test_prep/" target map on the lowercased name when the
generator matches target names case-insensitively, keeping the
first spelling seen for the user-facing target name.

Amends commit 503bdb9777 (Makefile: Generate test_prep targets for
CMAKE_TEST_BUILD_DEPENDS, 2026-06-24).
This commit is contained in:
Tyler Yankee committed 2026-10-06 15:30:51 -04:00
1 parent 88c123af9a
commit ac77c23f9e
8 files changed
+68 -1

No files matched your search

@@ -56,6 +56,8 @@ public:
bool IsGNUMakeJobServerAware() const override { return false; }
bool TargetNamesAreCaseInsensitive() const override { return true; }
std::string GetShortBinaryOutputDir() const override;
protected:
+2
View File
@@ -49,6 +49,8 @@ public:
bool IsGNUMakeJobServerAware() const override { return false; }
bool TargetNamesAreCaseInsensitive() const override { return true; }
protected:
std::vector<GeneratedMakeCommand> GenerateBuildCommand(
std::string const& makeProgram, std::string const& projectName,
+2
View File
@@ -56,6 +56,8 @@ public:
bool IsGNUMakeJobServerAware() const override { return false; }
bool TargetNamesAreCaseInsensitive() const override { return true; }
protected:
std::vector<GeneratedMakeCommand> GenerateBuildCommand(
std::string const& makeProgram, std::string const& projectName,
+16 -1
View File
@@ -1101,7 +1101,12 @@ void cmGlobalUnixMakefileGenerator3::ComputeTestPrepTargets()
cmGlobalUnixMakefileGenerator3::TestPrepTarget&
cmGlobalUnixMakefileGenerator3::GetTestPrepTarget(std::string const& name)
{
TestPrepTarget& testPrepTarget = this->TestPrepTargets[name];
// Merge targets whose names differ only in case where the make tool does
// not distinguish them. Keep the first spelling for the user-facing name.
TestPrepTarget& testPrepTarget =
this->TestPrepTargets[this->TargetNamesAreCaseInsensitive()
? cmSystemTools::LowerCase(name)
: name];
if (testPrepTarget.Name.empty()) {
testPrepTarget.Name = name;
}
@@ -1160,6 +1165,12 @@ void cmGlobalUnixMakefileGenerator3::WriteTestPrepRules(
std::vector<std::string> no_commands;
std::vector<std::string> allDeps;
for (auto const& entry : this->TestPrepTargets) {
// A test whose target name matches "test_prep/all" is prepared by the
// rule for all tests.
if (entry.first == "test_prep/all") {
cm::append(allDeps, entry.second.Rules);
continue;
}
std::vector<std::string> depends = entry.second.Rules;
if (depends.empty() && !this->EmptyRuleHackDepends.empty()) {
depends.push_back(this->EmptyRuleHackDepends);
@@ -1196,6 +1207,10 @@ void cmGlobalUnixMakefileGenerator3::WriteTestPrepConvenienceRules(
ruleFileStream << "# Convenience rules to build test dependencies.\n\n";
for (auto const& entry : this->TestPrepTargets) {
// The "test_prep/all" rule is written below.
if (entry.first == "test_prep/all") {
continue;
}
depends.clear();
if (regenerate) {
depends.emplace_back("cmake_check_build_system");
+7
View File
@@ -121,6 +121,13 @@ public:
bool IsGNUMakeJobServerAware() const override { return true; }
/**
* Utilized to determine if the make tool matches target names
* case-insensitively, so that generated target names may not differ
* only in case.
*/
virtual bool TargetNamesAreCaseInsensitive() const { return false; }
/**
* Generate the all required files for building this project/tree. This
* basically creates a series of LocalGenerators for each directory and
@@ -0,0 +1,4 @@
if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/${dependency}_case_dependency-built.txt")
set(RunCMake_TEST_FAILED
"test_prep did not build ${dependency}_case_dependency.")
endif()
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 4.3)
set(CMAKE_TEST_BUILD_DEPENDS ON)
project(TestDependencyCase NONE)
enable_testing()
# Test names that differ only in case each get a working test_prep target,
# even where the build tool's target names are case-insensitive.
foreach(case IN ITEMS "Mixed;upper" "mixed;lower" "All;all")
list(GET case 0 test)
list(GET case 1 dependency)
add_custom_target(${dependency}_case_dependency
COMMAND ${CMAKE_COMMAND} -E touch ${dependency}_case_dependency-built.txt)
set_property(TARGET ${dependency}_case_dependency
PROPERTY EXCLUDE_FROM_ALL TRUE)
add_test(NAME ${test}
COMMAND ${CMAKE_COMMAND} -E true
BUILD_DEPENDS ${dependency}_case_dependency)
endforeach()
@@ -201,6 +201,21 @@ if(RunCMake_GENERATOR MATCHES "Ninja|FASTBuild|Makefiles")
-R "^ImportedTest$")
endblock()
# Test names that differ only in case each get a working test_prep target.
block()
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/ON-case-build)
run_cmake(ON-case)
set(RunCMake_TEST_NO_CLEAN 1)
foreach(case IN ITEMS "Mixed;upper" "mixed;lower" "All;all")
list(GET case 0 test)
list(GET case 1 dependency)
set(RunCMake-check-file ON-case-build-check.cmake)
run_cmake_command(ON-case-${dependency}-build
${CMAKE_COMMAND} --build . ${TestDependency_BUILD_CONFIG_ARG}
--target test_prep/${test})
endforeach()
endblock()
# Exceptions for Makefile generator
if(RunCMake_GENERATOR MATCHES Makefiles)
# Diagnostics specific to the Makefile generators.