From ba6b6fbc242f5ae9fa616d003d68332eb9a98e0f Mon Sep 17 00:00:00 2001 From: Martin Duffy Date: Tue, 7 Apr 2026 11:01:24 -0400 Subject: [PATCH] Ninja: Add Build Dependencies for Tests Add `test_prep/` convenience targets to the ninja generator to build dependencies of tests, and `test_prep/all` to build the dependencies of all tests. Build dependencies of a test include: - Executables directly invoked by test `COMMAND`. - Targets mentioned in generator expressionf os test `COMMAND`. - Anything explicitly listed with the new `BUILD_DEPENDS` argument of `add_test`. Issue: #27613 --- Help/command/add_test.rst | 31 +++++++- Help/generator/Ninja.rst | 20 +++++ Help/manual/cmake-variables.7.rst | 1 + Help/release/dev/test-dependency-targets.rst | 15 ++++ Help/variable/CMAKE_TEST_BUILD_DEPENDS.rst | 17 ++++ Source/cmAddTestCommand.cxx | 16 ++++ Source/cmGlobalNinjaGenerator.cxx | 77 +++++++++++++++++++ Source/cmGlobalNinjaGenerator.h | 1 + Source/cmTest.cxx | 7 ++ Source/cmTest.h | 7 ++ Source/cmTestGenerator.cxx | 64 +++++++++++++++ Source/cmTestGenerator.h | 8 ++ Tests/RunCMake/add_test/RunCMakeTest.cmake | 47 +++++++++++ .../add_test/TestDependency-DEFAULT.cmake | 2 + .../add_test/TestDependency-OFF.cmake | 3 + ...Dependency-ON-invalid-test-name-result.txt | 1 + ...Dependency-ON-invalid-test-name-stderr.txt | 5 ++ .../TestDependency-ON-invalid-test-name.cmake | 20 +++++ .../RunCMake/add_test/TestDependency-ON.cmake | 3 + .../add_test/TestDependency-build-check.cmake | 52 +++++++++++++ .../TestDependency-check-targets.cmake | 28 +++++++ Tests/RunCMake/add_test/TestDependency.cmake | 49 ++++++++++++ .../TestDependencySubdir/CMakeLists.txt | 25 ++++++ 23 files changed, 498 insertions(+), 1 deletion(-) create mode 100644 Help/release/dev/test-dependency-targets.rst create mode 100644 Help/variable/CMAKE_TEST_BUILD_DEPENDS.rst create mode 100644 Tests/RunCMake/add_test/TestDependency-DEFAULT.cmake create mode 100644 Tests/RunCMake/add_test/TestDependency-OFF.cmake create mode 100644 Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-result.txt create mode 100644 Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-stderr.txt create mode 100644 Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name.cmake create mode 100644 Tests/RunCMake/add_test/TestDependency-ON.cmake create mode 100644 Tests/RunCMake/add_test/TestDependency-build-check.cmake create mode 100644 Tests/RunCMake/add_test/TestDependency-check-targets.cmake create mode 100644 Tests/RunCMake/add_test/TestDependency.cmake create mode 100644 Tests/RunCMake/add_test/TestDependencySubdir/CMakeLists.txt diff --git a/Help/command/add_test.rst b/Help/command/add_test.rst index bbfcca2931..919b1076ae 100644 --- a/Help/command/add_test.rst +++ b/Help/command/add_test.rst @@ -8,7 +8,8 @@ Add a test to the project to be run by :manual:`ctest(1)`. add_test(NAME COMMAND [...] [CONFIGURATIONS ...] [WORKING_DIRECTORY ] - [COMMAND_EXPAND_LISTS]) + [COMMAND_EXPAND_LISTS] + [BUILD_DEPENDS ...]) Adds a test called ````. The test name may contain arbitrary characters, expressed as a :ref:`Quoted Argument` or :ref:`Bracket Argument` @@ -59,6 +60,25 @@ directory the test is created in. + * .. versionadded:: 4.4 + + When the :variable:`CMAKE_TEST_BUILD_DEPENDS` variable is enabled, + the :ref:`Ninja Generators` generate a convenience build target named + ``test_prep/`` that depends on the test executable target. Building + this target ensures the executable is up-to-date before the test runs. + + Additionally, targets referenced by the test command via generator + expressions are added as dependencies of the ``test_prep/`` target. + + If multiple tests in different directories share the same name, their + dependencies are merged into a single ``test_prep/`` target. + + Tests with names that are not valid target names are excluded from this + behavior. + + The ``BUILD_DEPENDS`` keyword can be used to add explicit build + dependencies. + The command may be specified using :manual:`generator expressions `. @@ -71,6 +91,15 @@ directory the test is created in. :variable:`CMAKE_CURRENT_BINARY_DIR`. The working directory may be specified using :manual:`generator expressions `. +``BUILD_DEPENDS`` + .. versionadded:: 4.4 + + Specify a list of targets or files that must be built before the test can + run. Each dependency is added to the ``test_prep/`` build target + described above when :variable:`CMAKE_TEST_BUILD_DEPENDS` is enabled + with the :ref:`Ninja Generators`. The test name must be a valid target name + in order to list build dependencies with this keyword. + ``COMMAND_EXPAND_LISTS`` .. versionadded:: 3.16 diff --git a/Help/generator/Ninja.rst b/Help/generator/Ninja.rst index 5f5ffb8fd9..d90e8c527c 100644 --- a/Help/generator/Ninja.rst +++ b/Help/generator/Ninja.rst @@ -31,6 +31,26 @@ Builtin Targets Created only if the :prop_gbl:`INSTALL_PARALLEL` global property is ``ON``. Runs the install step for each subdirectory independently and in parallel. +``test_prep/`` + + .. versionadded:: 4.4 + + Created only if the :variable:`CMAKE_TEST_BUILD_DEPENDS` variable is + enabled. Builds all known build dependencies for the named test, including + the executable target invoked by the test, targets referenced by generator + expressions in the test command, and explicit ``BUILD_DEPENDS`` entries. + + Tests with names that are not valid target names are excluded. If multiple + tests in different directories share the same name, their dependencies are + merged into one ``test_prep/`` target. + +``test_prep/all`` + + .. versionadded:: 4.4 + + Created only if the :variable:`CMAKE_TEST_BUILD_DEPENDS` variable is + enabled. Depends on every generated ``test_prep/`` target. + For each subdirectory ``sub/dir`` of the project, additional targets are generated: diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst index 71318ddc4b..b3240109ad 100644 --- a/Help/manual/cmake-variables.7.rst +++ b/Help/manual/cmake-variables.7.rst @@ -143,6 +143,7 @@ Variables that Provide Information /variable/CMAKE_Swift_MODULE_DIRECTORY /variable/CMAKE_Swift_NUM_THREADS /variable/CMAKE_Swift_SEPARATE_MODULE_EMISSION + /variable/CMAKE_TEST_BUILD_DEPENDS /variable/CMAKE_TEST_LAUNCHER /variable/CMAKE_TOOLCHAIN_FILE /variable/CMAKE_TWEAK_VERSION diff --git a/Help/release/dev/test-dependency-targets.rst b/Help/release/dev/test-dependency-targets.rst new file mode 100644 index 0000000000..54d6b5ae1f --- /dev/null +++ b/Help/release/dev/test-dependency-targets.rst @@ -0,0 +1,15 @@ +test-build-dependencies +----------------------- + +* The :ref:`Ninja Generators` now support generating a build target named + ``test_prep/`` for each test added by :command:`add_test`, which + builds all dependencies for that test. Build dependencies are registered for + an executable target invoked by the test, targets referenced in generator + expressions in the test command, and explicit dependencies added using the + new ``BUILD_DEPENDS`` option. This behavior is enabled by the new + :variable:`CMAKE_TEST_BUILD_DEPENDS` variable. A ``test_prep/all`` + target is also generated to depend on every ``test_prep/`` + target. Tests with names that are not valid target names are excluded from + this behavior. If multiple tests in different directories share the same + name, their dependencies are merged into one ``test_prep/`` + target. diff --git a/Help/variable/CMAKE_TEST_BUILD_DEPENDS.rst b/Help/variable/CMAKE_TEST_BUILD_DEPENDS.rst new file mode 100644 index 0000000000..cdc5b18dd7 --- /dev/null +++ b/Help/variable/CMAKE_TEST_BUILD_DEPENDS.rst @@ -0,0 +1,17 @@ +CMAKE_TEST_BUILD_DEPENDS +------------------------ + +.. versionadded:: 4.4 + +Enable ``test_prep/`` build targets for tests added by +:command:`add_test` when using the :ref:`Ninja Generators`. + +When this variable is set to a true value, CMake generates a +``test_prep/`` target for each eligible test and a ``test_prep/all`` +target for all such tests. Building these targets ensures the test executable, +targets referenced by test command generator expressions, and explicit +``BUILD_DEPENDS`` entries are up-to-date before the test runs. + +Tests whose names are not valid target names are excluded from this behavior. +If multiple tests in different directories share the same name, their +dependencies are merged into a single ``test_prep/`` target. diff --git a/Source/cmAddTestCommand.cxx b/Source/cmAddTestCommand.cxx index f2579e31b3..5663eaf456 100644 --- a/Source/cmAddTestCommand.cxx +++ b/Source/cmAddTestCommand.cxx @@ -7,6 +7,7 @@ #include #include "cmExecutionStatus.h" +#include "cmGeneratorExpression.h" #include "cmMakefile.h" #include "cmPolicies.h" #include "cmStringAlgorithms.h" @@ -90,6 +91,7 @@ bool cmAddTestCommandHandleNameMode(std::vector const& args, std::vector configurations; std::string working_directory; std::vector command; + std::vector buildDepends; bool command_expand_lists = false; cmPolicies::PolicyStatus cmp0178 = mf.GetPolicyStatus(cmPolicies::CMP0178); @@ -100,6 +102,7 @@ bool cmAddTestCommandHandleNameMode(std::vector const& args, DoingCommand, DoingConfigs, DoingWorkingDirectory, + DoingBuildDepends, DoingCmp0178, DoingNone }; @@ -117,6 +120,8 @@ bool cmAddTestCommandHandleNameMode(std::vector const& args, return false; } doing = DoingConfigs; + } else if (args[i] == "BUILD_DEPENDS") { + doing = DoingBuildDepends; } else if (args[i] == "WORKING_DIRECTORY") { if (!working_directory.empty()) { status.SetError(" may be given at most one WORKING_DIRECTORY."); @@ -139,6 +144,8 @@ bool cmAddTestCommandHandleNameMode(std::vector const& args, command.push_back(args[i]); } else if (doing == DoingConfigs) { configurations.push_back(args[i]); + } else if (doing == DoingBuildDepends) { + buildDepends.push_back(args[i]); } else if (doing == DoingWorkingDirectory) { working_directory = args[i]; doing = DoingNone; @@ -185,6 +192,15 @@ bool cmAddTestCommandHandleNameMode(std::vector const& args, test->SetProperty("WORKING_DIRECTORY", working_directory); } test->SetCommandExpandLists(command_expand_lists); + if (!buildDepends.empty()) { + if (!cmGeneratorExpression::IsValidTargetName(name)) { + status.SetError(cmStrCat("Cannot set build dependencies for a test with" + " NAME \"", + name, "\" which is not a valid target name.")); + return false; + } + test->SetBuildDependencies(buildDepends); + } mf.AddTestGenerator(cm::make_unique(test, configurations)); return true; diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx index 23c7ef4458..8d29ec42ec 100644 --- a/Source/cmGlobalNinjaGenerator.cxx +++ b/Source/cmGlobalNinjaGenerator.cxx @@ -54,6 +54,8 @@ #include "cmSystemTools.h" #include "cmTarget.h" #include "cmTargetDepend.h" +#include "cmTest.h" +#include "cmTestGenerator.h" #include "cmValue.h" #include "cmVersion.h" #include "cmake.h" @@ -641,6 +643,7 @@ void cmGlobalNinjaGenerator::Generate() this->cmGlobalGenerator::Generate(); this->WriteAssumedSourceDependencies(); + this->WriteTestPrepTargets(); this->WriteTargetAliases(*this->GetCommonFileStream()); this->WriteFolderTargets(*this->GetCommonFileStream()); this->WriteBuiltinTargets(*this->GetCommonFileStream()); @@ -1293,6 +1296,80 @@ void cmGlobalNinjaGenerator::WriteAssumedSourceDependencies() } } +void cmGlobalNinjaGenerator::WriteTestPrepTargets() +{ + auto writeConfig = [this](std::string const& config, std::ostream& os) { + struct TestPrepTarget + { + std::string Comment; + cmNinjaDeps ExplicitDeps; + }; + + std::map testPrepTargets; + for (auto const& localGen : this->LocalGenerators) { + auto* lg = static_cast(localGen.get()); + auto const& testGenerators = lg->GetMakefile()->GetTestGenerators(); + for (auto const& tester : testGenerators) { + cmTestGenerator::BuildDependencies testDeps; + if (!tester->GetBuildDependencies(lg, testDeps)) { + continue; + } + std::string const depName = this->ConvertToNinjaPath( + cmStrCat("test_prep/", tester->GetTest()->GetName())); + // Merge with existing target if multiple tests have the same name + auto& testPrepTarget = testPrepTargets[depName]; + testPrepTarget.Comment = cmStrCat("Build dependencies for test ", + tester->GetTest()->GetName()); + + for (cmGeneratorTarget* depTarget : testDeps.Targets) { + this->AppendTargetOutputs(depTarget, testPrepTarget.ExplicitDeps, + config, DependOnTargetArtifact); + } + std::transform(testDeps.Files.begin(), testDeps.Files.end(), + std::back_inserter(testPrepTarget.ExplicitDeps), + this->MapToNinjaPath()); + } + } + + std::vector allDeps; + for (auto& prepTarget : testPrepTargets) { + cmNinjaBuild build("phony"); + build.Comment = prepTarget.second.Comment; + build.Outputs.push_back(prepTarget.first); + + // Avoid duplicate dependencies when merging test_prep/ targets + auto& explicitDeps = prepTarget.second.ExplicitDeps; + std::sort(explicitDeps.begin(), explicitDeps.end()); + explicitDeps.erase(std::unique(explicitDeps.begin(), explicitDeps.end()), + explicitDeps.end()); + build.ExplicitDeps = std::move(explicitDeps); + + allDeps.push_back(prepTarget.first); + this->WriteBuild(os, build); + } + + cmNinjaBuild build("phony"); + build.Comment = "Build dependencies for all tests"; + build.Outputs.push_back(this->ConvertToNinjaPath("test_prep/all")); + build.ExplicitDeps = std::move(allDeps); + this->WriteBuild(os, build); + return true; + }; + + if (!this->Makefiles.front()->IsOn("CMAKE_TEST_BUILD_DEPENDS")) { + return; + } + if (this->IsMultiConfig()) { + for (std::string const& config : this->GetConfigNames()) { + if (!writeConfig(config, *this->GetConfigFileStream(config))) { + return; + } + } + } else { + writeConfig(std::string(), *this->GetCommonFileStream()); + } +} + std::string cmGlobalNinjaGenerator::OrderDependsTargetForTarget( cmGeneratorTarget const* target, std::string const& /*config*/) const { diff --git a/Source/cmGlobalNinjaGenerator.h b/Source/cmGlobalNinjaGenerator.h index 059dea87d3..7ade62a746 100644 --- a/Source/cmGlobalNinjaGenerator.h +++ b/Source/cmGlobalNinjaGenerator.h @@ -524,6 +524,7 @@ private: void WriteDisclaimer(std::ostream& os) const; void WriteAssumedSourceDependencies(); + void WriteTestPrepTargets(); void WriteTargetAliases(std::ostream& os); void WriteFolderTargets(std::ostream& os); diff --git a/Source/cmTest.cxx b/Source/cmTest.cxx index 1620b46002..75a81b840a 100644 --- a/Source/cmTest.cxx +++ b/Source/cmTest.cxx @@ -2,6 +2,8 @@ file LICENSE.rst or https://cmake.org/licensing for details. */ #include "cmTest.h" +#include + #include "cmMakefile.h" #include "cmProperty.h" #include "cmState.h" @@ -33,6 +35,11 @@ void cmTest::SetCommand(std::vector const& command) this->Command = command; } +void cmTest::SetBuildDependencies(std::vector deps) +{ + this->BuildDependencies = std::move(deps); +} + cmValue cmTest::GetProperty(std::string const& prop) const { cmValue retVal = this->Properties.GetPropertyValue(prop); diff --git a/Source/cmTest.h b/Source/cmTest.h index 7d347a82c7..ee7d935441 100644 --- a/Source/cmTest.h +++ b/Source/cmTest.h @@ -35,6 +35,12 @@ public: void SetCommand(std::vector const& command); std::vector const& GetCommand() const { return this->Command; } + void SetBuildDependencies(std::vector deps); + std::vector const& GetDependencies() const + { + return this->BuildDependencies; + } + //! Set/Get a property of this source file void SetProperty(std::string const& prop, cmValue value); void SetProperty(std::string const& prop, std::nullptr_t) @@ -85,6 +91,7 @@ private: cmPropertyMap Properties; std::string Name; std::vector Command; + std::vector BuildDependencies; bool CommandExpandLists = false; bool OldStyle; diff --git a/Source/cmTestGenerator.cxx b/Source/cmTestGenerator.cxx index c3564706c3..1ae1d8afcb 100644 --- a/Source/cmTestGenerator.cxx +++ b/Source/cmTestGenerator.cxx @@ -5,6 +5,7 @@ #include // IWYU pragma: keep #include #include +#include #include #include #include @@ -12,10 +13,12 @@ #include "cmDiagnostics.h" #include "cmGeneratorExpression.h" #include "cmGeneratorTarget.h" +#include "cmGlobalGenerator.h" #include "cmList.h" #include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" +#include "cmMessageType.h" #include "cmPolicies.h" #include "cmPropertyMap.h" #include "cmRange.h" @@ -92,6 +95,67 @@ cmTest* cmTestGenerator::GetTest() const return this->Test; } +bool cmTestGenerator::GetBuildDependencies(cmLocalGenerator* lg, + BuildDependencies& info) +{ + if (this->Test == nullptr || + !cmGeneratorExpression::IsValidTargetName(this->Test->GetName()) || + cmGlobalGenerator::IsReservedTarget(this->Test->GetName())) { + return false; + } + + std::set dependencies; + + // Get dependencies from generator expressions + cmGeneratorExpression ge(*this->Test->GetMakefile()->GetCMakeInstance(), + this->Test->GetBacktrace()); + std::string const config; + for (std::string const& arg : this->Test->GetCommand()) { + auto parsed = ge.Parse(arg); + parsed->Evaluate(lg, config); + for (cmGeneratorTarget* dep : parsed->GetTargets()) { + if (dep && !dep->IsImported()) { + dependencies.insert(dep); + } + } + } + + // Add target executed by test + if (!this->Test->GetCommand().empty()) { + std::string exe = this->Test->GetCommand().front(); + cmGeneratorTarget* target = lg->FindGeneratorTargetToUse(exe); + if (target && target->GetType() == cmStateEnums::EXECUTABLE && + !target->IsImported()) { + dependencies.insert(target); + } + } + + // Add dependencies from BUILD_DEPENDS keyword + for (auto const& depName : this->Test->GetDependencies()) { + if (depName.empty()) { + continue; + } + cmGeneratorTarget* depTarget = lg->FindGeneratorTargetToUse(depName); + if (!depTarget) { + info.Files.push_back(depName); + continue; + } + if (depTarget->IsImported()) { + lg->GetMakefile()->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Test \"", this->Test->GetName(), "\" DEPENDS target \"", + depName, "\" which is imported and cannot be built."), + this->Test->GetBacktrace()); + return false; + } + dependencies.insert(depTarget); + } + + info.Targets.insert(info.Targets.end(), dependencies.begin(), + dependencies.end()); + return true; +} + void cmTestGenerator::GenerateScriptActions(std::ostream& os, Indent indent) { if (this->ActionsPerConfig) { diff --git a/Source/cmTestGenerator.h b/Source/cmTestGenerator.h index cd59b11d94..fe65a54e1a 100644 --- a/Source/cmTestGenerator.h +++ b/Source/cmTestGenerator.h @@ -13,6 +13,7 @@ class cmListFileBacktrace; class cmGeneratorExpression; +class cmGeneratorTarget; class cmLocalGenerator; class cmTest; @@ -23,6 +24,12 @@ class cmTest; class cmTestGenerator : public cmScriptGenerator { public: + struct BuildDependencies + { + std::vector Targets; + std::vector Files; + }; + cmTestGenerator(cmTest* test, std::vector const& configurations = std::vector()); @@ -32,6 +39,7 @@ public: cmTestGenerator& operator=(cmTestGenerator const&) = delete; void Compute(cmLocalGenerator* lg); + bool GetBuildDependencies(cmLocalGenerator* lg, BuildDependencies& deps); /** Test if this generator installs the test for a given configuration. */ bool TestsForConfig(std::string const& config); diff --git a/Tests/RunCMake/add_test/RunCMakeTest.cmake b/Tests/RunCMake/add_test/RunCMakeTest.cmake index 6e7d53aad7..69593e641c 100644 --- a/Tests/RunCMake/add_test/RunCMakeTest.cmake +++ b/Tests/RunCMake/add_test/RunCMakeTest.cmake @@ -58,3 +58,50 @@ block() run_cmake_command(TestLauncher-test ${CMAKE_CTEST_COMMAND} -C Debug -V) endblock() unset(RunCMake_TEST_OPTIONS) + +function(run_testdependency_case CASE_NAME EXPECT_PRESENT) + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/TestDependency-${CASE_NAME}-build) + run_cmake(TestDependency-${CASE_NAME}) + set(RunCMake_TEST_NO_CLEAN 1) + run_cmake_command(TestDependency-${CASE_NAME}-check + ${CMAKE_COMMAND} + -DRunCMake_TEST_BINARY_DIR=${RunCMake_TEST_BINARY_DIR} + -Dexpect_present=${EXPECT_PRESENT} + -P ${RunCMake_SOURCE_DIR}/TestDependency-check-targets.cmake) + unset(RunCMake_TEST_NO_CLEAN) +endfunction() + +if(RunCMake_GENERATOR MATCHES Ninja) + block() + run_testdependency_case(DEFAULT FALSE) + run_testdependency_case(OFF FALSE) + run_cmake(TestDependency-ON-invalid-test-name) + + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/TestDependency-ON-build) + run_testdependency_case(ON TRUE) + + if(RunCMake_GENERATOR_IS_MULTI_CONFIG) + set(TestDependency_CONFIG Debug) + else() + set(TestDependency_CONFIG "") + endif() + set(TestDependency_BUILD_CONFIG_ARG) + if(TestDependency_CONFIG) + set(TestDependency_BUILD_CONFIG_ARG --config ${TestDependency_CONFIG}) + endif() + set(RunCMake_TEST_OUTPUT_MERGE 1) + set(RunCMake_TEST_NO_CLEAN 1) + run_cmake_command(TestDependency-ON-all + ${CMAKE_COMMAND} --build . ${TestDependency_BUILD_CONFIG_ARG} + --target test_prep/all) + run_cmake_command(TestDependency-ON-build + ${CMAKE_COMMAND} --build . ${TestDependency_BUILD_CONFIG_ARG} + --target test_prep/TargetBuildTest) + run_cmake_command(TestDependency-ON-build-check + ${CMAKE_COMMAND} + -DRunCMake_TEST_BINARY_DIR=${RunCMake_TEST_BINARY_DIR} + -P ${RunCMake_SOURCE_DIR}/TestDependency-build-check.cmake) + unset(RunCMake_TEST_OUTPUT_MERGE) + unset(RunCMake_TEST_NO_CLEAN) + endblock() +endif() diff --git a/Tests/RunCMake/add_test/TestDependency-DEFAULT.cmake b/Tests/RunCMake/add_test/TestDependency-DEFAULT.cmake new file mode 100644 index 0000000000..73970661b2 --- /dev/null +++ b/Tests/RunCMake/add_test/TestDependency-DEFAULT.cmake @@ -0,0 +1,2 @@ +cmake_minimum_required(VERSION 4.3) +include(${CMAKE_CURRENT_LIST_DIR}/TestDependency.cmake) diff --git a/Tests/RunCMake/add_test/TestDependency-OFF.cmake b/Tests/RunCMake/add_test/TestDependency-OFF.cmake new file mode 100644 index 0000000000..5ae6829a51 --- /dev/null +++ b/Tests/RunCMake/add_test/TestDependency-OFF.cmake @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 4.3) +set(CMAKE_TEST_BUILD_DEPENDS OFF) +include(${CMAKE_CURRENT_LIST_DIR}/TestDependency.cmake) diff --git a/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-result.txt b/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-stderr.txt b/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-stderr.txt new file mode 100644 index 0000000000..7df35de302 --- /dev/null +++ b/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-stderr.txt @@ -0,0 +1,5 @@ +CMake Error at TestDependency-ON-invalid-test-name.cmake:[0-9]+ \(add_test\): + add_test Cannot set build dependencies for a test with NAME "Target Build + Test Invalid Name" which is not a valid target name. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name.cmake b/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name.cmake new file mode 100644 index 0000000000..9264f72f91 --- /dev/null +++ b/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name.cmake @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 4.3) +set(CMAKE_TEST_BUILD_DEPENDS ON) + +project(TestDependencyInvalidTestName C) + +enable_testing() + +add_custom_target(TestDependencyPrereq + COMMAND + "${CMAKE_COMMAND}" -E touch + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyPrereq-built.txt" + BYPRODUCTS + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyPrereq-built.txt" + VERBATIM) + +add_test(NAME "Target Build Test Invalid Name" + COMMAND + "${CMAKE_COMMAND}" -E true + BUILD_DEPENDS + TestDependencyPrereq) diff --git a/Tests/RunCMake/add_test/TestDependency-ON.cmake b/Tests/RunCMake/add_test/TestDependency-ON.cmake new file mode 100644 index 0000000000..7a4d3ab615 --- /dev/null +++ b/Tests/RunCMake/add_test/TestDependency-ON.cmake @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 4.3) +set(CMAKE_TEST_BUILD_DEPENDS ON) +include(${CMAKE_CURRENT_LIST_DIR}/TestDependency.cmake) diff --git a/Tests/RunCMake/add_test/TestDependency-build-check.cmake b/Tests/RunCMake/add_test/TestDependency-build-check.cmake new file mode 100644 index 0000000000..7234dbc605 --- /dev/null +++ b/Tests/RunCMake/add_test/TestDependency-build-check.cmake @@ -0,0 +1,52 @@ +foreach(name IN ITEMS + TestDependencyExe + TestDependencyGenex + TestDependencyPrereq + TestDependencyFile + TestDependencySubdirPrereq + TestDependencySubdirFile) + set(candidates) + list(APPEND candidates + "${RunCMake_TEST_BINARY_DIR}/${name}-built.txt") + list(APPEND candidates + "${RunCMake_TEST_BINARY_DIR}/TestDependencySubdir/${name}-built.txt") + + set(found FALSE) + foreach(path IN LISTS candidates) + if(EXISTS "${path}") + set(found TRUE) + break() + endif() + endforeach() + + if(NOT found) + message(FATAL_ERROR + "Expected ${name}-built.txt not found.") + endif() +endforeach() + +set(prereq_file "${RunCMake_TEST_BINARY_DIR}/TestDependencyPrereq-built.txt") +if(NOT EXISTS "${prereq_file}") + message(FATAL_ERROR + "Expected TestDependencyPrereq-built.txt to be generated by DEPENDS target.") +endif() + +set(file_dep_file "${RunCMake_TEST_BINARY_DIR}/TestDependencyFile-built.txt") +if(NOT EXISTS "${file_dep_file}") + message(FATAL_ERROR + "Expected TestDependencyFile-built.txt to be generated by DEPENDS file.") +endif() + +set(subdir_prereq_file + "${RunCMake_TEST_BINARY_DIR}/TestDependencySubdir/TestDependencySubdirPrereq-built.txt") +if(NOT EXISTS "${subdir_prereq_file}") + message(FATAL_ERROR + "Expected TestDependencySubdirPrereq-built.txt to be generated by duplicate test DEPENDS target.") +endif() + +set(subdir_file_dep_file + "${RunCMake_TEST_BINARY_DIR}/TestDependencySubdir/TestDependencySubdirFile-built.txt") +if(NOT EXISTS "${subdir_file_dep_file}") + message(FATAL_ERROR + "Expected TestDependencySubdirFile-built.txt to be generated by duplicate test DEPENDS file.") +endif() diff --git a/Tests/RunCMake/add_test/TestDependency-check-targets.cmake b/Tests/RunCMake/add_test/TestDependency-check-targets.cmake new file mode 100644 index 0000000000..9185ed9ca8 --- /dev/null +++ b/Tests/RunCMake/add_test/TestDependency-check-targets.cmake @@ -0,0 +1,28 @@ +if(NOT DEFINED RunCMake_TEST_BINARY_DIR) + message(FATAL_ERROR "RunCMake_TEST_BINARY_DIR not set") +endif() + +if(NOT DEFINED expect_present) + message(FATAL_ERROR "expect_present not set") +endif() + +file(GLOB ninja_files LIST_DIRECTORIES false + "${RunCMake_TEST_BINARY_DIR}/*.ninja" + "${RunCMake_TEST_BINARY_DIR}/*.ninja.in") + +set(found FALSE) +foreach(ninja_file IN LISTS ninja_files) + file(READ "${ninja_file}" content) + if(content MATCHES "test_prep.all|test_prep.TargetBuildTest") + set(found TRUE) + break() + endif() +endforeach() + +if(expect_present AND NOT found) + message(FATAL_ERROR "Expected test_prep targets to be present in ninja files.") +endif() + +if(NOT expect_present AND found) + message(FATAL_ERROR "Expected test_prep targets to be absent from ninja files.") +endif() diff --git a/Tests/RunCMake/add_test/TestDependency.cmake b/Tests/RunCMake/add_test/TestDependency.cmake new file mode 100644 index 0000000000..312dd8a030 --- /dev/null +++ b/Tests/RunCMake/add_test/TestDependency.cmake @@ -0,0 +1,49 @@ +project(TestDependency C) + +enable_testing() + +add_executable(TestDependencyExe main.c) +add_custom_command(TARGET TestDependencyExe POST_BUILD + COMMAND + "${CMAKE_COMMAND}" -E touch + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyExe-built.txt" + BYPRODUCTS + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyExe-built.txt") + +add_executable(TestDependencyGenex main.c) +add_custom_command(TARGET TestDependencyGenex POST_BUILD + COMMAND + "${CMAKE_COMMAND}" -E touch + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyGenex-built.txt" + BYPRODUCTS + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyGenex-built.txt") + +add_custom_target(TestDependencyPrereq + COMMAND + "${CMAKE_COMMAND}" -E touch + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyPrereq-built.txt" + BYPRODUCTS + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyPrereq-built.txt" + VERBATIM) + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyFile-built.txt" + COMMAND + "${CMAKE_COMMAND}" -E touch + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyFile-built.txt" + VERBATIM) + +add_custom_target( + FileDependsTarget + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyFile-built.txt" +) + +add_subdirectory(TestDependencySubdir) + +add_test(NAME TargetBuildTest + COMMAND + TestDependencyExe + $ + BUILD_DEPENDS + TestDependencyPrereq + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyFile-built.txt") diff --git a/Tests/RunCMake/add_test/TestDependencySubdir/CMakeLists.txt b/Tests/RunCMake/add_test/TestDependencySubdir/CMakeLists.txt new file mode 100644 index 0000000000..262230d4d7 --- /dev/null +++ b/Tests/RunCMake/add_test/TestDependencySubdir/CMakeLists.txt @@ -0,0 +1,25 @@ +add_custom_target(TestDependencySubdirPrereq + COMMAND + "${CMAKE_COMMAND}" -E touch + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencySubdirPrereq-built.txt" + BYPRODUCTS + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencySubdirPrereq-built.txt" + VERBATIM) + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TestDependencySubdirFile-built.txt" + COMMAND + "${CMAKE_COMMAND}" -E touch + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencySubdirFile-built.txt" + VERBATIM) + +add_custom_target(TestDependencySubdirFile + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/TestDependencySubdirFile-built.txt") + +# Duplicate test name with parent directory +add_test(NAME TargetBuildTest + COMMAND + "${CMAKE_COMMAND}" -E true + BUILD_DEPENDS + TestDependencySubdirPrereq + "${CMAKE_CURRENT_BINARY_DIR}/TestDependencySubdirFile-built.txt")