Makefile: Generate test_prep targets for CMAKE_TEST_BUILD_DEPENDS

Extend the test_prep/<test> and test_prep/all build targets, generated
from add_test(BUILD_DEPENDS) when CMAKE_TEST_BUILD_DEPENDS is enabled, to
the Makefile generators.  Previously only the Ninja and FASTBuild
generators provided them.

Resolve each test's dependencies in a shared layer so all command-line
generators consume one result.  cmTestGenerator now filters target
dependencies to build-system targets and records, for each file
dependency, the single target that produces it as a primary
custom-command output (cmGlobalGenerator::FindOutputOwningTarget).  The
Makefile generators emit phony rules in CMakeFiles/Makefile2 depending on
<target>.dir/all, with top-level forwarding rules.  Sharing the
resolution also stops Ninja and FASTBuild from emitting a dead test_prep
edge for a non-buildable (e.g. INTERFACE) BUILD_DEPENDS target.

On the Makefile generators a file dependency that no single target
produces, and a test whose name contains ':', cannot be expressed as a
recursive-make rule and are reported with a warning and skipped.

Fixes: #27879
This commit is contained in:
Daksh Mamodiya
2026-06-24 16:38:16 +02:00
parent 23c5b40924
commit 503bdb9777
21 changed files with 496 additions and 32 deletions

View File

@@ -82,7 +82,12 @@ directory the test is created in.
.. versionchanged:: 4.5
:generator:`FASTBuild` gained support for ``test_prep/<name>`` targets.
:generator:`FASTBuild` and the :ref:`Makefile Generators` gained support
for ``test_prep/<name>`` targets. With the Makefile generators a
``BUILD_DEPENDS`` file is built by building the target whose build
produces it; a file that is not the unique output of a single target is
reported with a warning, and a test whose name contains a ``:`` character
is excluded.
The command may be specified using
:manual:`generator expressions <cmake-generator-expressions(7)>`.
@@ -101,9 +106,10 @@ directory the test is created in.
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` or :generator:`FASTBuild`. The test name must be
a valid target name in order to list build dependencies with this keyword.
described above when :variable:`CMAKE_TEST_BUILD_DEPENDS` is enabled with the
:ref:`Ninja Generators`, :generator:`FASTBuild`, or :ref:`Makefile
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

View File

@@ -29,3 +29,23 @@ be created, containing the following targets:
``package``
Runs the package step in the subdirectory, if any.
.. versionadded:: 4.5
When the :variable:`CMAKE_TEST_BUILD_DEPENDS` variable is enabled, the
top-level makefile additionally provides the following targets:
``test_prep/<test-name>``
Builds all known build dependencies for the test named ``<test-name>``
added by :command:`add_test`, including the executable target invoked by
the test, targets referenced by generator expressions in the test command,
and explicit ``BUILD_DEPENDS`` entries. A ``BUILD_DEPENDS`` file is built
by building the target whose build produces it.
Tests whose names are not valid target names, and tests whose names
contain a ``:`` character, 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``
Depends on every generated ``test_prep/<test-name>`` target.

View File

@@ -0,0 +1,9 @@
test-prep-makefile-generators
-----------------------------
* The :generator:`FASTBuild` generator and the :ref:`Makefile Generators`
now generate ``test_prep/<test-name>`` and ``test_prep/all`` convenience
build targets when the :variable:`CMAKE_TEST_BUILD_DEPENDS` variable is
enabled, matching the behavior of the :ref:`Ninja Generators`. These
targets build the dependencies of tests added by :command:`add_test`,
including those listed with the ``BUILD_DEPENDS`` keyword.

View File

@@ -5,11 +5,12 @@ CMAKE_TEST_BUILD_DEPENDS
.. versionchanged:: 4.5
:generator:`FASTBuild` gained support for ``test_prep/<name>`` targets.
:generator:`FASTBuild` and the :ref:`Makefile Generators` gained support
for ``test_prep/<name>`` targets.
Enable ``test_prep/<name>`` build targets for tests added by
:command:`add_test` when using the :ref:`Ninja Generators` or
:generator:`FASTBuild`.
:command:`add_test` when using the :ref:`Ninja Generators`,
:generator:`FASTBuild`, or :ref:`Makefile 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``
@@ -20,3 +21,9 @@ targets referenced by test command generator expressions, and explicit
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.
With the :ref:`Makefile Generators`, a ``BUILD_DEPENDS`` file is built by
building the target whose build produces it. A generated file that is not the
unique output of a single target is reported with a warning, and a test whose
name contains a ``:`` character is excluded because it cannot be expressed as
a Makefile target.

View File

@@ -1614,8 +1614,9 @@ void cmGlobalFastbuildGenerator::WriteTestPrepTargets()
for (cmGeneratorTarget* depTarget : testDeps.Targets) {
testPrepTarget.Dependencies.emplace(depTarget->GetName());
}
for (std::string const& depFile : testDeps.Files) {
testPrepTarget.Dependencies.emplace(depFile);
for (cmTestGenerator::BuildDependencies::FileDependency const& depFile :
testDeps.Files) {
testPrepTarget.Dependencies.emplace(depFile.Path);
}
}
}

View File

@@ -2232,10 +2232,51 @@ void cmGlobalGenerator::CreateGeneratorTargets(TargetTypes targetTypes)
}
}
void cmGlobalGenerator::ComputeOutputOwnerIndex()
{
this->OutputOwnerIndexComputed = true;
for (auto const& lg : this->LocalGenerators) {
for (auto const& gt : lg->GetGeneratorTargets()) {
if (!gt->IsInBuildSystem()) {
continue;
}
for (cmGeneratorTarget::AllConfigSource const& acs :
gt->GetAllConfigSources(
cmGeneratorTarget::SourceKindCustomCommand)) {
cmCustomCommand const* cc = acs.Source->GetCustomCommand();
if (!cc) {
continue;
}
for (std::string const& out : cc->GetOutputs()) {
this->OutputOwnerIndex[cmSystemTools::CollapseFullPath(out)]
.push_back(gt.get());
}
}
}
}
}
cmGeneratorTarget* cmGlobalGenerator::FindOutputOwningTarget(
std::string const& output)
{
if (!this->OutputOwnerIndexComputed) {
this->ComputeOutputOwnerIndex();
}
auto it =
this->OutputOwnerIndex.find(cmSystemTools::CollapseFullPath(output));
if (it != this->OutputOwnerIndex.end() && it->second.size() == 1) {
return it->second.front();
}
return nullptr;
}
void cmGlobalGenerator::ClearGeneratorMembers()
{
this->BuildExportSets.clear();
this->OutputOwnerIndex.clear();
this->OutputOwnerIndexComputed = false;
this->Makefiles.clear();
this->LocalGenerators.clear();

View File

@@ -313,6 +313,11 @@ public:
std::vector<cmGeneratorTarget*> GetLocalGeneratorTargetsInOrder(
cmLocalGenerator* lg) const;
// Find the single build-system target that produces the given path as a
// primary custom-command output, or nullptr if there is none or more than
// one. Used to resolve file-level test build dependencies.
cmGeneratorTarget* FindOutputOwningTarget(std::string const& output);
cmMakefile* GetCurrentMakefile() const
{
return this->CurrentConfigureMakefile;
@@ -978,6 +983,13 @@ private:
using TargetDependMap = std::map<cmGeneratorTarget const*, TargetDependSet>;
TargetDependMap TargetDependencies;
// Map from a custom-command primary output (collapsed full path) to the
// build-system target(s) that produce it. Built lazily on first use and
// cleared with the other generator members.
std::map<std::string, std::vector<cmGeneratorTarget*>> OutputOwnerIndex;
bool OutputOwnerIndexComputed = false;
void ComputeOutputOwnerIndex();
friend class cmake;
void CreateGeneratorTargets(
TargetTypes targetTypes, cmMakefile* mf, cmLocalGenerator* lg,

View File

@@ -1326,9 +1326,11 @@ void cmGlobalNinjaGenerator::WriteTestPrepTargets()
this->AppendTargetOutputs(depTarget, testPrepTarget.ExplicitDeps,
config, DependOnTargetArtifact);
}
std::transform(testDeps.Files.begin(), testDeps.Files.end(),
std::back_inserter(testPrepTarget.ExplicitDeps),
this->MapToNinjaPath());
for (cmTestGenerator::BuildDependencies::FileDependency const& file :
testDeps.Files) {
testPrepTarget.ExplicitDeps.push_back(
this->ConvertToNinjaPath(file.Path));
}
}
}

View File

@@ -18,6 +18,7 @@
#include "cmLocalUnixMakefileGenerator3.h"
#include "cmMakefile.h"
#include "cmMakefileTargetGenerator.h"
#include "cmMessageType.h"
#include "cmOutputConverter.h"
#include "cmState.h"
#include "cmStateTypes.h"
@@ -25,6 +26,8 @@
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmTargetDepend.h"
#include "cmTest.h"
#include "cmTestGenerator.h"
#include "cmValue.h"
#include "cmake.h"
@@ -109,6 +112,10 @@ void cmGlobalUnixMakefileGenerator3::Generate()
this->ClangTidyExportFixesDirs.clear();
this->ClangTidyExportFixesFiles.clear();
// Compute the "test_prep/" targets before generating the local makefiles
// so their convenience rules can be written into the top-level Makefile.
this->ComputeTestPrepTargets();
// first do superclass method
this->cmGlobalGenerator::Generate();
@@ -233,6 +240,9 @@ void cmGlobalUnixMakefileGenerator3::WriteMainMakefile2()
cm::static_reference_cast<cmLocalUnixMakefileGenerator3>(localGen));
}
// Write the internal test_prep/ rules.
this->WriteTestPrepRules(makefileStream, rootLG);
// Write special bottom targets
rootLG.WriteSpecialTargetsBottom(makefileStream);
}
@@ -648,6 +658,9 @@ void cmGlobalUnixMakefileGenerator3::WriteConvenienceRules(
}
}
}
// Forward the test_prep/ convenience targets to CMakeFiles/Makefile2.
this->WriteTestPrepConvenienceRules(ruleFileStream);
}
void cmGlobalUnixMakefileGenerator3::WriteConvenienceRules2(
@@ -932,6 +945,159 @@ void cmGlobalUnixMakefileGenerator3::AppendCodegenTargetDepends(
}
}
void cmGlobalUnixMakefileGenerator3::ComputeTestPrepTargets()
{
this->TestPrepTargets.clear();
this->TestPrepEnabled = false;
if (this->Makefiles.empty() ||
!this->Makefiles.front()->IsOn("CMAKE_TEST_BUILD_DEPENDS")) {
return;
}
this->TestPrepEnabled = true;
// Map a generator target to its "<target>.dir/all" recursive rule.
auto targetAllRule = [](cmGeneratorTarget* gt) -> std::string {
auto* lg3 =
static_cast<cmLocalUnixMakefileGenerator3*>(gt->GetLocalGenerator());
return cmStrCat(lg3->GetRelativeTargetDirectory(gt), "/all");
};
// Collect the dependencies of each test, merging tests that share a name
// across directories (as the Ninja generator does).
for (auto const& lg : this->LocalGenerators) {
for (auto const& tester : lg->GetMakefile()->GetTestGenerators()) {
cmTestGenerator::BuildDependencies deps;
if (!tester->GetBuildDependencies(lg.get(), deps)) {
continue;
}
cmTest* test = tester->GetTest();
std::string const& testName = test->GetName();
// A Makefile target name cannot contain ':'. Such a name is valid for
// add_test() (e.g. a namespaced name) and works with the Ninja
// generator, but cannot be expressed as a Makefile rule.
if (testName.find(':') != std::string::npos) {
test->GetMakefile()->IssueMessage(
MessageType::WARNING,
cmStrCat("Test \"", testName,
"\" has a name containing ':', which cannot be used as a "
"Makefile build target. No \"test_prep/\" target will be "
"generated for it. Use the Ninja generator or rename the "
"test to build its dependencies with a \"test_prep/\" "
"target."),
test->GetBacktrace());
continue;
}
std::vector<std::string>& rules =
this->TestPrepTargets[cmStrCat("test_prep/", testName)];
// Target dependencies are filtered to build-system targets by
// GetBuildDependencies.
for (cmGeneratorTarget* dep : deps.Targets) {
rules.push_back(targetAllRule(dep));
}
for (cmTestGenerator::BuildDependencies::FileDependency const& file :
deps.Files) {
if (file.Owner) {
// The file is the primary output of one build-system target; build
// that target to produce the file.
rules.push_back(targetAllRule(file.Owner));
} else if (file.Generated) {
// The file is generated but cannot be attributed to a single owning
// target (e.g. a byproduct or an ambiguous/shared output), so the
// recursive Makefile graph cannot build it from a top-level rule.
test->GetMakefile()->IssueMessage(
MessageType::WARNING,
cmStrCat("Test \"", testName, "\" BUILD_DEPENDS file\n ",
file.Path,
"\nis generated but is not the unique output of a build "
"target, so the \"test_prep/",
testName,
"\" target cannot build it with this generator. Depend "
"on the target that produces it (for example one created "
"with add_custom_target) instead."),
test->GetBacktrace());
}
// Otherwise the file is not generated by the build (e.g. a source
// file that already exists) and needs no build rule.
}
}
}
// Sort and de-duplicate each rule list (as the Ninja generator does).
for (auto& entry : this->TestPrepTargets) {
std::vector<std::string>& rules = entry.second;
std::sort(rules.begin(), rules.end());
rules.erase(std::unique(rules.begin(), rules.end()), rules.end());
}
}
void cmGlobalUnixMakefileGenerator3::WriteTestPrepRules(
std::ostream& makefileStream, cmLocalUnixMakefileGenerator3& rootLG)
{
if (!this->TestPrepEnabled) {
return;
}
rootLG.WriteDivider(makefileStream);
makefileStream << "# Targets to build the dependencies of tests.\n\n";
std::vector<std::string> no_commands;
std::vector<std::string> allDeps;
for (auto const& entry : this->TestPrepTargets) {
std::vector<std::string> depends = entry.second;
if (depends.empty() && !this->EmptyRuleHackDepends.empty()) {
depends.push_back(this->EmptyRuleHackDepends);
}
rootLG.WriteMakeRule(makefileStream, "Build the dependencies of a test.",
entry.first, depends, no_commands, true);
allDeps.push_back(entry.first);
}
if (allDeps.empty() && !this->EmptyRuleHackDepends.empty()) {
allDeps.push_back(this->EmptyRuleHackDepends);
}
rootLG.WriteMakeRule(makefileStream, "Build the dependencies of all tests.",
"test_prep/all", allDeps, no_commands, true);
}
void cmGlobalUnixMakefileGenerator3::WriteTestPrepConvenienceRules(
std::ostream& ruleFileStream)
{
if (!this->TestPrepEnabled) {
return;
}
auto& lg = cm::static_reference_cast<cmLocalUnixMakefileGenerator3>(
this->LocalGenerators[0]);
bool regenerate = !this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION");
std::string const makefile2 = "CMakeFiles/Makefile2";
std::vector<std::string> depends;
std::vector<std::string> commands;
auto writeForward = [&](std::string const& prepName) {
depends.clear();
if (regenerate) {
depends.emplace_back("cmake_check_build_system");
}
commands.clear();
commands.push_back(lg.GetRecursiveMakeCall(makefile2, prepName));
lg.WriteMakeRule(ruleFileStream, "Build the dependencies of a test.",
prepName, depends, commands, true);
};
lg.WriteDivider(ruleFileStream);
ruleFileStream << "# Convenience rules to build test dependencies.\n\n";
for (auto const& entry : this->TestPrepTargets) {
writeForward(entry.first);
}
writeForward("test_prep/all");
}
void cmGlobalUnixMakefileGenerator3::WriteHelpRule(
std::ostream& ruleFileStream, cmLocalUnixMakefileGenerator3* lg)
{

View File

@@ -226,6 +226,21 @@ protected:
void AppendCodegenTargetDepends(std::vector<std::string>& depends,
cmGeneratorTarget* target);
// Compute the "test_prep/<name>" build targets requested via the
// CMAKE_TEST_BUILD_DEPENDS variable. Each test's dependencies are
// resolved to the build-system targets that produce them so that the
// recursive Makefile graph can build them.
void ComputeTestPrepTargets();
// Write the internal "test_prep/<name>" and "test_prep/all" rules into
// CMakeFiles/Makefile2.
void WriteTestPrepRules(std::ostream& makefileStream,
cmLocalUnixMakefileGenerator3& rootLG);
// Write the top-level "test_prep/<name>" convenience rules that forward
// into CMakeFiles/Makefile2.
void WriteTestPrepConvenienceRules(std::ostream& ruleFileStream);
// Target name hooks for superclass.
char const* GetAllTargetName() const override { return "all"; }
char const* GetInstallTargetName() const override { return "install"; }
@@ -298,4 +313,10 @@ private:
cmStateSnapshot::StrictWeakOrder>
DirectoryTargetsMap;
void InitializeProgressMarks() override;
// Ordered map of "test_prep/<name>" -> the "<target>.dir/all" recursive
// rules that must be built to prepare the named test. Populated by
// ComputeTestPrepTargets when CMAKE_TEST_BUILD_DEPENDS is enabled.
std::map<std::string, std::vector<std::string>> TestPrepTargets;
bool TestPrepEnabled = false;
};

View File

@@ -64,6 +64,27 @@ std::string TestName(cmTest* test)
return name;
}
// Whether a path is produced by the build (a custom-command output or
// byproduct) rather than a pre-existing file. The output-to-source map
// records every generated path regardless of which target, if any, builds it.
bool fileIsGenerated(cmGlobalGenerator* gg, std::string const& file)
{
std::string const collapsed = cmSystemTools::CollapseFullPath(file);
for (auto const& lg : gg->GetLocalGenerators()) {
cmSourcesWithOutput so = lg->GetSourcesWithOutput(collapsed);
if (so.Source || so.Target) {
return true;
}
if (file != collapsed) {
so = lg->GetSourcesWithOutput(file);
if (so.Source || so.Target) {
return true;
}
}
}
return false;
}
} // End: anonymous namespace
cmTestGenerator::cmTestGenerator(
@@ -135,7 +156,12 @@ bool cmTestGenerator::GetBuildDependencies(cmLocalGenerator* lg,
}
cmGeneratorTarget* depTarget = lg->FindGeneratorTargetToUse(depName);
if (!depTarget) {
info.Files.push_back(depName);
cmGlobalGenerator* gg = lg->GetGlobalGenerator();
BuildDependencies::FileDependency file;
file.Path = depName;
file.Owner = gg->FindOutputOwningTarget(depName);
file.Generated = fileIsGenerated(gg, depName);
info.Files.push_back(std::move(file));
continue;
}
if (depTarget->IsImported()) {
@@ -149,8 +175,11 @@ bool cmTestGenerator::GetBuildDependencies(cmLocalGenerator* lg,
dependencies.insert(depTarget);
}
info.Targets.insert(info.Targets.end(), dependencies.begin(),
dependencies.end());
for (cmGeneratorTarget* gt : dependencies) {
if (gt->IsInBuildSystem()) {
info.Targets.push_back(gt);
}
}
return true;
}

View File

@@ -26,8 +26,21 @@ class cmTestGenerator : public cmScriptGenerator
public:
struct BuildDependencies
{
// A file dependency together with how the build produces it.
struct FileDependency
{
std::string Path;
// The single build-system target that produces Path as a primary
// custom-command output, or nullptr if there is no such unique target.
cmGeneratorTarget* Owner = nullptr;
// Whether Path is produced by the build at all (output or byproduct).
bool Generated = false;
};
// Build-system targets the test depends on (filtered to targets that are
// part of the build system).
std::vector<cmGeneratorTarget*> Targets;
std::vector<std::string> Files;
// BUILD_DEPENDS entries that did not name a target.
std::vector<FileDependency> Files;
};
cmTestGenerator(cmTest* test,

View File

@@ -71,24 +71,57 @@ function(run_testdependency_case CASE_NAME EXPECT_PRESENT)
unset(RunCMake_TEST_NO_CLEAN)
endfunction()
if(RunCMake_GENERATOR MATCHES "Ninja|FASTBuild")
if(RunCMake_GENERATOR MATCHES "Ninja|FASTBuild|Makefiles")
block()
if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(TestDependency_BUILD_CONFIG_ARG --config Debug)
else()
set(TestDependency_BUILD_CONFIG_ARG)
endif()
run_testdependency_case(DEFAULT FALSE)
run_testdependency_case(OFF FALSE)
run_cmake(TestDependency-ON-invalid-test-name)
block()
# A header-only INTERFACE library dependency is filtered out by the
# generator-independent dependency resolution, so building the
# test_prep target must not fail on a missing rule.
set(RunCMake_TEST_BINARY_DIR
${RunCMake_BINARY_DIR}/TestDependency-ON-interface-build)
run_cmake(TestDependency-ON-interface)
set(RunCMake_TEST_NO_CLEAN 1)
set(RunCMake_TEST_OUTPUT_MERGE 1)
run_cmake_command(TestDependency-ON-interface-build
${CMAKE_COMMAND} --build . ${TestDependency_BUILD_CONFIG_ARG}
--target test_prep/InterfaceTest)
endblock()
if(RunCMake_GENERATOR MATCHES Makefiles)
# Diagnostics specific to the Makefile generators.
block()
# A ':' in a test name cannot be expressed as a Makefile target.
set(RunCMake_TEST_BINARY_DIR
${RunCMake_BINARY_DIR}/TestDependency-ON-colon-name-build)
run_cmake(TestDependency-ON-colon-name)
endblock()
block()
# A byproduct file cannot be built through a single owning target.
set(RunCMake_TEST_BINARY_DIR
${RunCMake_BINARY_DIR}/TestDependency-ON-byproduct-build)
run_cmake(TestDependency-ON-byproduct)
endblock()
block()
# A custom-command output owned by no target cannot be built.
set(RunCMake_TEST_BINARY_DIR
${RunCMake_BINARY_DIR}/TestDependency-ON-orphan-build)
run_cmake(TestDependency-ON-orphan)
endblock()
endif()
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

View File

@@ -0,0 +1,9 @@
CMake Warning at TestDependency-ON-byproduct.cmake:[0-9]+ \(add_test\):
Test "ByproductTest" BUILD_DEPENDS file
.*/TestDependencyByproduct-built.txt
is generated but is not the unique output of a build target, so the
"test_prep/ByproductTest" target cannot build it with this generator.
Depend on the target that produces it \(for example one created with
add_custom_target\) instead.

View File

@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 4.3)
set(CMAKE_TEST_BUILD_DEPENDS ON)
project(TestDependencyByproduct C)
enable_testing()
add_executable(TestDependencyByproductExe main.c)
add_custom_command(TARGET TestDependencyByproductExe POST_BUILD
COMMAND
"${CMAKE_COMMAND}" -E touch
"${CMAKE_CURRENT_BINARY_DIR}/TestDependencyByproduct-built.txt"
BYPRODUCTS
"${CMAKE_CURRENT_BINARY_DIR}/TestDependencyByproduct-built.txt")
add_test(NAME ByproductTest
COMMAND
"${CMAKE_COMMAND}" -E true
BUILD_DEPENDS
"${CMAKE_CURRENT_BINARY_DIR}/TestDependencyByproduct-built.txt")

View File

@@ -0,0 +1,5 @@
CMake Warning at TestDependency-ON-colon-name.cmake:[0-9]+ \(add_test\):
Test "Foo::Bar" has a name containing ':', which cannot be used as a
Makefile build target. No "test_prep/" target will be generated for it.
Use the Ninja generator or rename the test to build its dependencies with a
"test_prep/" target.

View File

@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 4.3)
set(CMAKE_TEST_BUILD_DEPENDS ON)
project(TestDependencyColonName 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 "Foo::Bar"
COMMAND
"${CMAKE_COMMAND}" -E true
BUILD_DEPENDS
TestDependencyPrereq)

View File

@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 4.3)
set(CMAKE_TEST_BUILD_DEPENDS ON)
project(TestDependencyInterface C)
enable_testing()
add_executable(TestDependencyInterfaceExe main.c)
# Header-only INTERFACE library is not part of the build system and must be
# filtered out of the test_prep dependencies so that no dead
# "TestDependencyIface.dir/all" prerequisite is generated.
add_library(TestDependencyIface INTERFACE)
add_test(NAME InterfaceTest
COMMAND
TestDependencyInterfaceExe
BUILD_DEPENDS
TestDependencyIface)

View File

@@ -0,0 +1,9 @@
CMake Warning at TestDependency-ON-orphan.cmake:[0-9]+ \(add_test\):
Test "OrphanTest" BUILD_DEPENDS file
.*/TestDependencyOrphan-built.txt
is generated but is not the unique output of a build target, so the
"test_prep/OrphanTest" target cannot build it with this generator. Depend
on the target that produces it \(for example one created with
add_custom_target\) instead.

View File

@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 4.3)
set(CMAKE_TEST_BUILD_DEPENDS ON)
project(TestDependencyOrphan C)
enable_testing()
# A custom-command output that no target builds.
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyOrphan-built.txt"
COMMAND
"${CMAKE_COMMAND}" -E touch
"${CMAKE_CURRENT_BINARY_DIR}/TestDependencyOrphan-built.txt"
VERBATIM)
add_test(NAME OrphanTest
COMMAND
"${CMAKE_COMMAND}" -E true
BUILD_DEPENDS
"${CMAKE_CURRENT_BINARY_DIR}/TestDependencyOrphan-built.txt")

View File

@@ -6,14 +6,16 @@ if(NOT DEFINED expect_present)
message(FATAL_ERROR "expect_present not set")
endif()
file(GLOB ninja_files LIST_DIRECTORIES false
file(GLOB build_files LIST_DIRECTORIES false
"${RunCMake_TEST_BINARY_DIR}/*.ninja"
"${RunCMake_TEST_BINARY_DIR}/*.ninja.in"
"${RunCMake_TEST_BINARY_DIR}/fbuild.bff")
"${RunCMake_TEST_BINARY_DIR}/fbuild.bff"
"${RunCMake_TEST_BINARY_DIR}/Makefile"
"${RunCMake_TEST_BINARY_DIR}/CMakeFiles/Makefile2")
set(found FALSE)
foreach(ninja_file IN LISTS ninja_files)
file(READ "${ninja_file}" content)
foreach(build_file IN LISTS build_files)
file(READ "${build_file}" content)
if(content MATCHES "test_prep.all|test_prep.TargetBuildTest")
set(found TRUE)
break()
@@ -21,9 +23,9 @@ foreach(ninja_file IN LISTS ninja_files)
endforeach()
if(expect_present AND NOT found)
message(FATAL_ERROR "Expected test_prep targets to be present in ninja files.")
message(FATAL_ERROR "Expected test_prep targets to be present in build files.")
endif()
if(NOT expect_present AND found)
message(FATAL_ERROR "Expected test_prep targets to be absent from ninja files.")
message(FATAL_ERROR "Expected test_prep targets to be absent from build files.")
endif()