Ninja: Add Build Dependencies for Tests

Add `test_prep/<test_name>` 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
This commit is contained in:
Martin Duffy
2026-04-07 11:01:24 -04:00
parent d78407d434
commit ba6b6fbc24
23 changed files with 498 additions and 1 deletions

View File

@@ -8,7 +8,8 @@ Add a test to the project to be run by :manual:`ctest(1)`.
add_test(NAME <name> COMMAND <command> [<arg>...] add_test(NAME <name> COMMAND <command> [<arg>...]
[CONFIGURATIONS <config>...] [CONFIGURATIONS <config>...]
[WORKING_DIRECTORY <dir>] [WORKING_DIRECTORY <dir>]
[COMMAND_EXPAND_LISTS]) [COMMAND_EXPAND_LISTS]
[BUILD_DEPENDS <dependencies>...])
Adds a test called ``<name>``. The test name may contain arbitrary Adds a test called ``<name>``. The test name may contain arbitrary
characters, expressed as a :ref:`Quoted Argument` or :ref:`Bracket Argument` characters, expressed as a :ref:`Quoted Argument` or :ref:`Bracket Argument`
@@ -59,6 +60,25 @@ directory the test is created in.
<launcher> <emulator> <command> <launcher> <emulator> <command>
* .. 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/<name>`` 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/<name>`` target.
If multiple tests in different directories share the same name, their
dependencies are merged into a single ``test_prep/<name>`` 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 The command may be specified using
:manual:`generator expressions <cmake-generator-expressions(7)>`. :manual:`generator expressions <cmake-generator-expressions(7)>`.
@@ -71,6 +91,15 @@ directory the test is created in.
:variable:`CMAKE_CURRENT_BINARY_DIR`. The working directory may be specified :variable:`CMAKE_CURRENT_BINARY_DIR`. The working directory may be specified
using :manual:`generator expressions <cmake-generator-expressions(7)>`. using :manual:`generator expressions <cmake-generator-expressions(7)>`.
``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/<name>`` 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`` ``COMMAND_EXPAND_LISTS``
.. versionadded:: 3.16 .. versionadded:: 3.16

View File

@@ -31,6 +31,26 @@ Builtin Targets
Created only if the :prop_gbl:`INSTALL_PARALLEL` global property is ``ON``. Created only if the :prop_gbl:`INSTALL_PARALLEL` global property is ``ON``.
Runs the install step for each subdirectory independently and in parallel. Runs the install step for each subdirectory independently and in parallel.
``test_prep/<test-name>``
.. 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/<test-name>`` 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/<test-name>`` target.
For each subdirectory ``sub/dir`` of the project, additional targets For each subdirectory ``sub/dir`` of the project, additional targets
are generated: are generated:

View File

@@ -143,6 +143,7 @@ Variables that Provide Information
/variable/CMAKE_Swift_MODULE_DIRECTORY /variable/CMAKE_Swift_MODULE_DIRECTORY
/variable/CMAKE_Swift_NUM_THREADS /variable/CMAKE_Swift_NUM_THREADS
/variable/CMAKE_Swift_SEPARATE_MODULE_EMISSION /variable/CMAKE_Swift_SEPARATE_MODULE_EMISSION
/variable/CMAKE_TEST_BUILD_DEPENDS
/variable/CMAKE_TEST_LAUNCHER /variable/CMAKE_TEST_LAUNCHER
/variable/CMAKE_TOOLCHAIN_FILE /variable/CMAKE_TOOLCHAIN_FILE
/variable/CMAKE_TWEAK_VERSION /variable/CMAKE_TWEAK_VERSION

View File

@@ -0,0 +1,15 @@
test-build-dependencies
-----------------------
* The :ref:`Ninja Generators` now support generating a build target named
``test_prep/<test-name>`` 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/<test-name>``
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/<test-name>``
target.

View File

@@ -0,0 +1,17 @@
CMAKE_TEST_BUILD_DEPENDS
------------------------
.. versionadded:: 4.4
Enable ``test_prep/<name>`` 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/<name>`` 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/<name>`` target.

View File

@@ -7,6 +7,7 @@
#include <cm/memory> #include <cm/memory>
#include "cmExecutionStatus.h" #include "cmExecutionStatus.h"
#include "cmGeneratorExpression.h"
#include "cmMakefile.h" #include "cmMakefile.h"
#include "cmPolicies.h" #include "cmPolicies.h"
#include "cmStringAlgorithms.h" #include "cmStringAlgorithms.h"
@@ -90,6 +91,7 @@ bool cmAddTestCommandHandleNameMode(std::vector<std::string> const& args,
std::vector<std::string> configurations; std::vector<std::string> configurations;
std::string working_directory; std::string working_directory;
std::vector<std::string> command; std::vector<std::string> command;
std::vector<std::string> buildDepends;
bool command_expand_lists = false; bool command_expand_lists = false;
cmPolicies::PolicyStatus cmp0178 = mf.GetPolicyStatus(cmPolicies::CMP0178); cmPolicies::PolicyStatus cmp0178 = mf.GetPolicyStatus(cmPolicies::CMP0178);
@@ -100,6 +102,7 @@ bool cmAddTestCommandHandleNameMode(std::vector<std::string> const& args,
DoingCommand, DoingCommand,
DoingConfigs, DoingConfigs,
DoingWorkingDirectory, DoingWorkingDirectory,
DoingBuildDepends,
DoingCmp0178, DoingCmp0178,
DoingNone DoingNone
}; };
@@ -117,6 +120,8 @@ bool cmAddTestCommandHandleNameMode(std::vector<std::string> const& args,
return false; return false;
} }
doing = DoingConfigs; doing = DoingConfigs;
} else if (args[i] == "BUILD_DEPENDS") {
doing = DoingBuildDepends;
} else if (args[i] == "WORKING_DIRECTORY") { } else if (args[i] == "WORKING_DIRECTORY") {
if (!working_directory.empty()) { if (!working_directory.empty()) {
status.SetError(" may be given at most one WORKING_DIRECTORY."); status.SetError(" may be given at most one WORKING_DIRECTORY.");
@@ -139,6 +144,8 @@ bool cmAddTestCommandHandleNameMode(std::vector<std::string> const& args,
command.push_back(args[i]); command.push_back(args[i]);
} else if (doing == DoingConfigs) { } else if (doing == DoingConfigs) {
configurations.push_back(args[i]); configurations.push_back(args[i]);
} else if (doing == DoingBuildDepends) {
buildDepends.push_back(args[i]);
} else if (doing == DoingWorkingDirectory) { } else if (doing == DoingWorkingDirectory) {
working_directory = args[i]; working_directory = args[i];
doing = DoingNone; doing = DoingNone;
@@ -185,6 +192,15 @@ bool cmAddTestCommandHandleNameMode(std::vector<std::string> const& args,
test->SetProperty("WORKING_DIRECTORY", working_directory); test->SetProperty("WORKING_DIRECTORY", working_directory);
} }
test->SetCommandExpandLists(command_expand_lists); 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<cmTestGenerator>(test, configurations)); mf.AddTestGenerator(cm::make_unique<cmTestGenerator>(test, configurations));
return true; return true;

View File

@@ -54,6 +54,8 @@
#include "cmSystemTools.h" #include "cmSystemTools.h"
#include "cmTarget.h" #include "cmTarget.h"
#include "cmTargetDepend.h" #include "cmTargetDepend.h"
#include "cmTest.h"
#include "cmTestGenerator.h"
#include "cmValue.h" #include "cmValue.h"
#include "cmVersion.h" #include "cmVersion.h"
#include "cmake.h" #include "cmake.h"
@@ -641,6 +643,7 @@ void cmGlobalNinjaGenerator::Generate()
this->cmGlobalGenerator::Generate(); this->cmGlobalGenerator::Generate();
this->WriteAssumedSourceDependencies(); this->WriteAssumedSourceDependencies();
this->WriteTestPrepTargets();
this->WriteTargetAliases(*this->GetCommonFileStream()); this->WriteTargetAliases(*this->GetCommonFileStream());
this->WriteFolderTargets(*this->GetCommonFileStream()); this->WriteFolderTargets(*this->GetCommonFileStream());
this->WriteBuiltinTargets(*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<std::string, TestPrepTarget> testPrepTargets;
for (auto const& localGen : this->LocalGenerators) {
auto* lg = static_cast<cmLocalNinjaGenerator*>(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<std::string> 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( std::string cmGlobalNinjaGenerator::OrderDependsTargetForTarget(
cmGeneratorTarget const* target, std::string const& /*config*/) const cmGeneratorTarget const* target, std::string const& /*config*/) const
{ {

View File

@@ -524,6 +524,7 @@ private:
void WriteDisclaimer(std::ostream& os) const; void WriteDisclaimer(std::ostream& os) const;
void WriteAssumedSourceDependencies(); void WriteAssumedSourceDependencies();
void WriteTestPrepTargets();
void WriteTargetAliases(std::ostream& os); void WriteTargetAliases(std::ostream& os);
void WriteFolderTargets(std::ostream& os); void WriteFolderTargets(std::ostream& os);

View File

@@ -2,6 +2,8 @@
file LICENSE.rst or https://cmake.org/licensing for details. */ file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmTest.h" #include "cmTest.h"
#include <utility>
#include "cmMakefile.h" #include "cmMakefile.h"
#include "cmProperty.h" #include "cmProperty.h"
#include "cmState.h" #include "cmState.h"
@@ -33,6 +35,11 @@ void cmTest::SetCommand(std::vector<std::string> const& command)
this->Command = command; this->Command = command;
} }
void cmTest::SetBuildDependencies(std::vector<std::string> deps)
{
this->BuildDependencies = std::move(deps);
}
cmValue cmTest::GetProperty(std::string const& prop) const cmValue cmTest::GetProperty(std::string const& prop) const
{ {
cmValue retVal = this->Properties.GetPropertyValue(prop); cmValue retVal = this->Properties.GetPropertyValue(prop);

View File

@@ -35,6 +35,12 @@ public:
void SetCommand(std::vector<std::string> const& command); void SetCommand(std::vector<std::string> const& command);
std::vector<std::string> const& GetCommand() const { return this->Command; } std::vector<std::string> const& GetCommand() const { return this->Command; }
void SetBuildDependencies(std::vector<std::string> deps);
std::vector<std::string> const& GetDependencies() const
{
return this->BuildDependencies;
}
//! Set/Get a property of this source file //! Set/Get a property of this source file
void SetProperty(std::string const& prop, cmValue value); void SetProperty(std::string const& prop, cmValue value);
void SetProperty(std::string const& prop, std::nullptr_t) void SetProperty(std::string const& prop, std::nullptr_t)
@@ -85,6 +91,7 @@ private:
cmPropertyMap Properties; cmPropertyMap Properties;
std::string Name; std::string Name;
std::vector<std::string> Command; std::vector<std::string> Command;
std::vector<std::string> BuildDependencies;
bool CommandExpandLists = false; bool CommandExpandLists = false;
bool OldStyle; bool OldStyle;

View File

@@ -5,6 +5,7 @@
#include <cstddef> // IWYU pragma: keep #include <cstddef> // IWYU pragma: keep
#include <memory> #include <memory>
#include <ostream> #include <ostream>
#include <set>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -12,10 +13,12 @@
#include "cmDiagnostics.h" #include "cmDiagnostics.h"
#include "cmGeneratorExpression.h" #include "cmGeneratorExpression.h"
#include "cmGeneratorTarget.h" #include "cmGeneratorTarget.h"
#include "cmGlobalGenerator.h"
#include "cmList.h" #include "cmList.h"
#include "cmListFileCache.h" #include "cmListFileCache.h"
#include "cmLocalGenerator.h" #include "cmLocalGenerator.h"
#include "cmMakefile.h" #include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmPolicies.h" #include "cmPolicies.h"
#include "cmPropertyMap.h" #include "cmPropertyMap.h"
#include "cmRange.h" #include "cmRange.h"
@@ -92,6 +95,67 @@ cmTest* cmTestGenerator::GetTest() const
return this->Test; 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<cmGeneratorTarget*> 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) void cmTestGenerator::GenerateScriptActions(std::ostream& os, Indent indent)
{ {
if (this->ActionsPerConfig) { if (this->ActionsPerConfig) {

View File

@@ -13,6 +13,7 @@
class cmListFileBacktrace; class cmListFileBacktrace;
class cmGeneratorExpression; class cmGeneratorExpression;
class cmGeneratorTarget;
class cmLocalGenerator; class cmLocalGenerator;
class cmTest; class cmTest;
@@ -23,6 +24,12 @@ class cmTest;
class cmTestGenerator : public cmScriptGenerator class cmTestGenerator : public cmScriptGenerator
{ {
public: public:
struct BuildDependencies
{
std::vector<cmGeneratorTarget*> Targets;
std::vector<std::string> Files;
};
cmTestGenerator(cmTest* test, cmTestGenerator(cmTest* test,
std::vector<std::string> const& configurations = std::vector<std::string> const& configurations =
std::vector<std::string>()); std::vector<std::string>());
@@ -32,6 +39,7 @@ public:
cmTestGenerator& operator=(cmTestGenerator const&) = delete; cmTestGenerator& operator=(cmTestGenerator const&) = delete;
void Compute(cmLocalGenerator* lg); void Compute(cmLocalGenerator* lg);
bool GetBuildDependencies(cmLocalGenerator* lg, BuildDependencies& deps);
/** Test if this generator installs the test for a given configuration. */ /** Test if this generator installs the test for a given configuration. */
bool TestsForConfig(std::string const& config); bool TestsForConfig(std::string const& config);

View File

@@ -58,3 +58,50 @@ block()
run_cmake_command(TestLauncher-test ${CMAKE_CTEST_COMMAND} -C Debug -V) run_cmake_command(TestLauncher-test ${CMAKE_CTEST_COMMAND} -C Debug -V)
endblock() endblock()
unset(RunCMake_TEST_OPTIONS) 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()

View File

@@ -0,0 +1,2 @@
cmake_minimum_required(VERSION 4.3)
include(${CMAKE_CURRENT_LIST_DIR}/TestDependency.cmake)

View File

@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 4.3)
set(CMAKE_TEST_BUILD_DEPENDS OFF)
include(${CMAKE_CURRENT_LIST_DIR}/TestDependency.cmake)

View File

@@ -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\)

View File

@@ -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)

View File

@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 4.3)
set(CMAKE_TEST_BUILD_DEPENDS ON)
include(${CMAKE_CURRENT_LIST_DIR}/TestDependency.cmake)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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
$<TARGET_FILE:TestDependencyGenex>
BUILD_DEPENDS
TestDependencyPrereq
"${CMAKE_CURRENT_BINARY_DIR}/TestDependencyFile-built.txt")

View File

@@ -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")