mirror of
https://github.com/Kitware/CMake.git
synced 2026-11-06 05:16:58 +00:00
Evaluate generator expressions in test and install BUILD_DEPENDS
The `BUILD_DEPENDS` options of `add_test`, `discover_tests` and `install`, and the `CMAKE_TEST_BUILD_DEPENDS` directory property, took their entries literally, so a dependency could not be computed per configuration or made conditional, unlike the test `COMMAND`, from whose generator expressions the test's target dependencies are already taken. Evaluate an entry that contains a generator expression, as the test `COMMAND` is evaluated: targets the expression references become target dependencies, an evaluated value naming an artifact of such a target is built by that dependency, any other evaluated value is a target or file as before, and an entry evaluating to an empty string is ignored. The directory property is evaluated as a whole, so that an expression may produce a list. `install()` rules are configuration-independent, so an `install(... BUILD_DEPENDS)` entry is evaluated for each configuration and the rule depends on the union of the results. Entries without generator expressions are resolved exactly as before. Closes: #28223
This commit is contained in:
1 parent
7047fa76f6
commit
192199fae0
18 files changed
+286
-17
No files matched your search
@@ -101,6 +101,18 @@ directory the test is created in.
|
||||
directory if the file exists there, and otherwise with respect to the
|
||||
current binary directory.
|
||||
|
||||
* .. versionchanged:: 4.6
|
||||
|
||||
Entries may be specified using
|
||||
:manual:`generator expressions <cmake-generator-expressions(7)>`.
|
||||
An entry containing a generator expression is evaluated for the
|
||||
configuration. Targets it references, as with ``$<TARGET_FILE:tgt>``,
|
||||
are target dependencies, as they are when referenced in the test
|
||||
``COMMAND``, and an evaluated value naming an artifact of such a target
|
||||
is built by that target dependency. Any other evaluated value is a
|
||||
target or file, as above. An entry that evaluates to an empty string is
|
||||
ignored.
|
||||
|
||||
``COMMAND_EXPAND_LISTS``
|
||||
.. versionadded:: 3.16
|
||||
|
||||
|
||||
@@ -117,6 +117,11 @@ the provided regular expression and replacement strings.
|
||||
|
||||
Tests with missing build dependencies will not be run.
|
||||
|
||||
.. versionchanged:: 4.6
|
||||
Entries may be specified using
|
||||
:manual:`generator expressions <cmake-generator-expressions(7)>`,
|
||||
evaluated as for the ``BUILD_DEPENDS`` option of :command:`add_test`.
|
||||
|
||||
CTest executes the discovery step to obtain the list of tests and then runs
|
||||
each discovered test using the command-line produced by ``COMMAND`` together
|
||||
with ``TEST_ARGS``. The pass/fail behavior of each discovered test follows
|
||||
|
||||
@@ -156,6 +156,17 @@ signatures that specify them. The common options are:
|
||||
binary directory. If this option is used multiple times in a single call,
|
||||
its list of dependencies accumulates.
|
||||
|
||||
.. versionchanged:: 4.6
|
||||
Entries may be specified using
|
||||
:manual:`generator expressions <cmake-generator-expressions(7)>`.
|
||||
An entry containing a generator expression is evaluated for each build
|
||||
configuration, and the rule depends on the union of the results.
|
||||
Targets it references, as with ``$<TARGET_FILE:tgt>``, are target
|
||||
dependencies, and an evaluated value naming an artifact of such a target
|
||||
is built by that target dependency. Any other evaluated value is a
|
||||
target or file, as above. An entry that evaluates to an empty string is
|
||||
ignored.
|
||||
|
||||
.. versionadded:: 3.1
|
||||
Command signatures that install files may print messages during
|
||||
installation. Use the :variable:`CMAKE_INSTALL_MESSAGE` variable
|
||||
|
||||
@@ -15,3 +15,12 @@ there, and otherwise with respect to the current binary directory.
|
||||
Projects may append to this property directly with :command:`set_property` or
|
||||
:command:`set_directory_properties` to extend ``test_prep/all`` with
|
||||
directory-scoped build dependencies.
|
||||
|
||||
.. versionchanged:: 4.6
|
||||
The property value may contain
|
||||
:manual:`generator expressions <cmake-generator-expressions(7)>`.
|
||||
A value containing a generator expression is evaluated as a whole for each
|
||||
configuration, and the result is the list of dependencies. Targets
|
||||
referenced by the expression, as with ``$<TARGET_FILE:tgt>``, are target
|
||||
dependencies, and an evaluated entry naming an artifact of such a target is
|
||||
built by that target dependency. Empty entries are ignored.
|
||||
@@ -0,0 +1,9 @@
|
||||
build-depends-genex
|
||||
-------------------
|
||||
|
||||
* The ``BUILD_DEPENDS`` options of the :command:`add_test`,
|
||||
:command:`discover_tests` and :command:`install` commands, and the
|
||||
:prop_dir:`CMAKE_TEST_BUILD_DEPENDS` directory property, now support
|
||||
:manual:`generator expressions <cmake-generator-expressions(7)>`.
|
||||
Targets referenced by an expression, such as ``$<TARGET_FILE:tgt>``,
|
||||
become dependencies, and entries evaluating to an empty string are ignored.
|
||||
@@ -3,14 +3,20 @@
|
||||
#include "cmInstallGenerator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
#include <cm/string_view>
|
||||
#include <cmext/algorithm>
|
||||
|
||||
#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"
|
||||
@@ -288,11 +294,16 @@ bool cmInstallGenerator::ResolveBuildDepends(
|
||||
return gg->FindArtifactOwningTarget(file, config);
|
||||
});
|
||||
};
|
||||
// Resolve a name to a target or a file.
|
||||
auto const resolve = [this, lg, gg, &signature, &entries,
|
||||
&isArtifact](std::string const& name) -> bool {
|
||||
// Resolve a name to a target or a file. Names evaluated from generator
|
||||
// expressions are added once, since they are evaluated for each
|
||||
// configuration.
|
||||
auto const resolve = [this, lg, gg, &signature, &entries, &isArtifact](
|
||||
std::string const& name, std::string const& entry,
|
||||
bool evaluated) -> bool {
|
||||
if (cmGeneratorTarget const* target = lg->FindGeneratorTargetToUse(name)) {
|
||||
entries.Targets.push_back(target);
|
||||
if (!evaluated || !cm::contains(entries.Targets, target)) {
|
||||
entries.Targets.push_back(target);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// A relative path is interpreted with respect to the current source
|
||||
@@ -301,21 +312,61 @@ bool cmInstallGenerator::ResolveBuildDepends(
|
||||
std::string file = lg->GetRealFileDependency(name);
|
||||
if (gg->IsGeneratedByBuild(file) || isArtifact(file) ||
|
||||
cmSystemTools::FileExists(file)) {
|
||||
entries.Files.push_back(std::move(file));
|
||||
if (!evaluated || !cm::contains(entries.Files, file)) {
|
||||
entries.Files.push_back(std::move(file));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
lg->IssueMessage(
|
||||
MessageType::FATAL_ERROR,
|
||||
cmStrCat("install(", signature, ") BUILD_DEPENDS entry \"", name,
|
||||
"\" is neither a target nor a file that exists or is "
|
||||
"generated by the build."),
|
||||
evaluated
|
||||
? cmStrCat("install(", signature, ") BUILD_DEPENDS entry \"", entry,
|
||||
"\" evaluates to \"", name,
|
||||
"\", which is neither a target nor a file that exists or "
|
||||
"is generated by the build.")
|
||||
: cmStrCat("install(", signature, ") BUILD_DEPENDS entry \"", name,
|
||||
"\" is neither a target nor a file that exists or is "
|
||||
"generated by the build."),
|
||||
this->GetBacktrace());
|
||||
return false;
|
||||
};
|
||||
|
||||
cmGeneratorExpression ge(*lg->GetCMakeInstance(), this->GetBacktrace());
|
||||
for (std::string const& name : names) {
|
||||
if (!resolve(name)) {
|
||||
return false;
|
||||
if (cmGeneratorExpression::Find(name) == std::string::npos) {
|
||||
if (!resolve(name, name, false)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// An entry containing generator expressions is evaluated for each
|
||||
// configuration, and the rule depends on the union of the results.
|
||||
// Targets an entry references are dependencies. An evaluated value
|
||||
// naming an artifact of such a target is built by that target
|
||||
// dependency, and any other value is a target or file, like an entry
|
||||
// without generator expressions.
|
||||
std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(name);
|
||||
for (std::string const& config : configs) {
|
||||
cmList const evaluated{ cge->Evaluate(lg, config) };
|
||||
std::set<cmGeneratorTarget const*> referenced;
|
||||
for (cmGeneratorTarget const* target : cge->GetTargets()) {
|
||||
if (target && !target->IsImported()) {
|
||||
referenced.insert(target);
|
||||
if (!cm::contains(entries.Targets, target)) {
|
||||
entries.Targets.push_back(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (std::string const& value : evaluated) {
|
||||
if (value.empty() ||
|
||||
(cmSystemTools::FileIsFullPath(value) &&
|
||||
referenced.count(gg->FindArtifactOwningTarget(value, config)))) {
|
||||
continue;
|
||||
}
|
||||
if (!resolve(value, name, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -269,16 +269,23 @@ bool cmLocalGenerator::GetDirectoryTestPrepTarget(DirectoryTestPrepTarget& out,
|
||||
out.Dependencies = discovery->second;
|
||||
}
|
||||
|
||||
cmList buildDepends;
|
||||
std::vector<std::string> buildDepends;
|
||||
if (directoryTestPrepDependencies) {
|
||||
buildDepends.assign(
|
||||
cmList{ *directoryTestPrepDependencies, cmList::EmptyElements::Yes });
|
||||
if (cmGeneratorExpression::Find(*directoryTestPrepDependencies) !=
|
||||
std::string::npos) {
|
||||
// Evaluate the value as a whole, so that a generator expression may
|
||||
// produce a list. The evaluated list is the list of dependencies.
|
||||
buildDepends.push_back(*directoryTestPrepDependencies);
|
||||
} else {
|
||||
cmList const list{ *directoryTestPrepDependencies,
|
||||
cmList::EmptyElements::Yes };
|
||||
buildDepends.assign(list.begin(), list.end());
|
||||
}
|
||||
}
|
||||
cmTestGenerator::BuildDependencies deps;
|
||||
cmTestGenerator::EvaluateBuildDependencies(
|
||||
this, config, this->Makefile->GetBacktrace(), "directory_test_prep", {},
|
||||
std::vector<std::string>(buildDepends.begin(), buildDepends.end()), deps,
|
||||
this->Makefile);
|
||||
buildDepends, deps, this->Makefile);
|
||||
for (cmGeneratorTarget* target : deps.Targets) {
|
||||
out.Dependencies.push_back({ target->GetName(), target });
|
||||
}
|
||||
|
||||
@@ -150,7 +150,32 @@ bool cmTestGenerator::EvaluateBuildDependencies(
|
||||
if (depName.empty()) {
|
||||
continue;
|
||||
}
|
||||
addBuildDependency(depName);
|
||||
if (cmGeneratorExpression::Find(depName) == std::string::npos) {
|
||||
addBuildDependency(depName);
|
||||
continue;
|
||||
}
|
||||
// An entry containing generator expressions is evaluated for the
|
||||
// configuration. Targets it references are dependencies, as are those
|
||||
// referenced by the test command. An evaluated value naming an artifact
|
||||
// of such a target is built by that target dependency, and any other
|
||||
// value is a target or file, like an entry without generator expressions.
|
||||
auto parsed = ge.Parse(depName);
|
||||
cmList const evaluated{ parsed->Evaluate(lg, config) };
|
||||
std::set<cmGeneratorTarget*> referenced;
|
||||
for (cmGeneratorTarget* dep : parsed->GetTargets()) {
|
||||
if (dep && !dep->IsImported()) {
|
||||
dependencies.insert(dep);
|
||||
referenced.insert(dep);
|
||||
}
|
||||
}
|
||||
for (std::string const& value : evaluated) {
|
||||
if (value.empty() ||
|
||||
(cmSystemTools::FileIsFullPath(value) &&
|
||||
referenced.count(gg->FindArtifactOwningTarget(value, config)))) {
|
||||
continue;
|
||||
}
|
||||
addBuildDependency(value);
|
||||
}
|
||||
}
|
||||
|
||||
for (cmGeneratorTarget* gt : dependencies) {
|
||||
|
||||
@@ -74,3 +74,31 @@ install(CODE "
|
||||
)
|
||||
|
||||
add_library(unit_tests STATIC neverbuild.c)
|
||||
|
||||
# BUILD_DEPENDS entries may contain generator expressions, evaluated for each
|
||||
# configuration. A target referenced by an expression is a dependency, so the
|
||||
# artifact the expression evaluates to is prepared by building it. An entry
|
||||
# evaluating to a target name names that target, and one evaluating to
|
||||
# nothing adds no dependency.
|
||||
add_library(depends_genex STATIC EXCLUDE_FROM_ALL lib.c)
|
||||
set_property(TARGET depends_genex
|
||||
PROPERTY ARCHIVE_OUTPUT_DIRECTORY "$<1:${artifact_dir}>")
|
||||
set(genex_payload "${CMAKE_CURRENT_BINARY_DIR}/genex-payload.txt")
|
||||
add_custom_target(generate_genex_payload
|
||||
COMMAND ${CMAKE_COMMAND} -E touch "${genex_payload}"
|
||||
BYPRODUCTS "${genex_payload}"
|
||||
)
|
||||
install(CODE "
|
||||
if(NOT EXISTS \"$<TARGET_FILE:depends_genex>\")
|
||||
message(FATAL_ERROR \"Preparation did not build depends_genex\")
|
||||
endif()
|
||||
if(NOT EXISTS \"${genex_payload}\")
|
||||
message(FATAL_ERROR \"Preparation did not generate genex-payload.txt\")
|
||||
endif()
|
||||
"
|
||||
COMPONENT Payload
|
||||
BUILD_DEPENDS
|
||||
"$<TARGET_FILE:depends_genex>"
|
||||
"$<$<BOOL:ON>:generate_genex_payload>"
|
||||
"$<$<BOOL:OFF>:${CMAKE_CURRENT_BINARY_DIR}/never-generated.txt>"
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
# Under a multi-configuration generator, preparing the Debug configuration
|
||||
# also builds what the entry evaluated for Release names. A single
|
||||
# configuration generator evaluates the entries for its one configuration.
|
||||
set(debug_built "${RunCMake_TEST_BINARY_DIR}/union-debug-built.txt")
|
||||
set(release_built "${RunCMake_TEST_BINARY_DIR}/union-release-built.txt")
|
||||
if(NOT EXISTS "${debug_built}")
|
||||
set(RunCMake_TEST_FAILED
|
||||
"Preparation did not build the Debug dependency union_debug.")
|
||||
elseif(RunCMake_GENERATOR_IS_MULTI_CONFIG AND NOT EXISTS "${release_built}")
|
||||
set(RunCMake_TEST_FAILED "Preparation for Debug did not build the \
|
||||
Release dependency, so the rule does not depend on the union of the \
|
||||
entries evaluated for each configuration.")
|
||||
elseif(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG AND EXISTS "${release_built}")
|
||||
set(RunCMake_TEST_FAILED
|
||||
"Preparation built the Release dependency, but only Debug is configured.")
|
||||
endif()
|
||||
@@ -0,0 +1,20 @@
|
||||
# An install(... BUILD_DEPENDS) entry is evaluated for each configuration, and
|
||||
# the rule depends on the union of the results, so preparing for any one
|
||||
# configuration also prepares what the other configurations' entries name.
|
||||
# Each dependency records that it was built.
|
||||
set(union_debug_built "${CMAKE_CURRENT_BINARY_DIR}/union-debug-built.txt")
|
||||
add_custom_target(union_debug
|
||||
COMMAND ${CMAKE_COMMAND} -E touch "${union_debug_built}"
|
||||
BYPRODUCTS "${union_debug_built}"
|
||||
)
|
||||
set(union_release_built "${CMAKE_CURRENT_BINARY_DIR}/union-release-built.txt")
|
||||
add_custom_target(union_release
|
||||
COMMAND ${CMAKE_COMMAND} -E touch "${union_release_built}"
|
||||
BYPRODUCTS "${union_release_built}"
|
||||
)
|
||||
install(CODE "message(STATUS \"Installing Union\")"
|
||||
COMPONENT Union
|
||||
BUILD_DEPENDS
|
||||
"$<$<CONFIG:Debug>:union_debug>"
|
||||
"$<$<CONFIG:Release>:${union_release_built}>"
|
||||
)
|
||||
@@ -179,6 +179,16 @@ endfunction()
|
||||
run_configured_install_prep(ConfigurationsDebug Debug)
|
||||
run_configured_install_prep(ConfigurationsRelease Release)
|
||||
|
||||
# The rule depends on the union of the entries evaluated for each
|
||||
# configuration, which BuildDependsUnion-prep-check.cmake verifies.
|
||||
block()
|
||||
configure_install_prep(BuildDependsUnion Debug)
|
||||
run_cmake_command(BuildDependsUnion-prep ${CMAKE_COMMAND}
|
||||
--build . --config Debug --target install_prep/Union)
|
||||
run_cmake_command(BuildDependsUnion-install ${CMAKE_COMMAND}
|
||||
--install . --config Debug --component Union)
|
||||
endblock()
|
||||
|
||||
if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
|
||||
block()
|
||||
configure_install_prep(ConfigurationsMulti Debug)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
project(TestDependencyBuildDependsGenex C)
|
||||
enable_testing()
|
||||
|
||||
# Targets reached only through generator expressions in build dependencies.
|
||||
# Each records that it was built, so the tests below fail unless building
|
||||
# test_prep/all built it.
|
||||
foreach(name IN ITEMS genex_test genex_discover genex_directory)
|
||||
add_executable(${name} EXCLUDE_FROM_ALL ../add_test/main.c)
|
||||
add_custom_command(TARGET ${name} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E touch
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${name}-built.txt"
|
||||
VERBATIM)
|
||||
endforeach()
|
||||
add_custom_target(genex_utility
|
||||
COMMAND "${CMAKE_COMMAND}" -E touch
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/genex_utility-built.txt"
|
||||
VERBATIM)
|
||||
|
||||
# An entry that evaluates to nothing adds no dependency. Were it taken
|
||||
# literally, the tests would not run for want of this file.
|
||||
set(never "${CMAKE_CURRENT_BINARY_DIR}/never-built.txt")
|
||||
|
||||
# A target referenced by an expression is a dependency, and the artifact the
|
||||
# expression evaluates to is built by it. An entry evaluating to a target
|
||||
# name is a target dependency.
|
||||
add_test(NAME GenexBuildDepends
|
||||
COMMAND "${CMAKE_COMMAND}" -E cat
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/genex_test-built.txt"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/genex_utility-built.txt"
|
||||
BUILD_DEPENDS
|
||||
"$<TARGET_FILE:genex_test>"
|
||||
"$<$<BOOL:OFF>:${never}>"
|
||||
"$<$<BOOL:ON>:genex_utility>")
|
||||
|
||||
add_executable(fake_discovery fake_discovery.c)
|
||||
discover_tests(COMMAND fake_discovery
|
||||
DISCOVERY_ARGS --list_tests
|
||||
DISCOVERY_MATCH "^([^,]+),([^,]+)$"
|
||||
TEST_NAME "GENEX.\\1"
|
||||
TEST_ARGS "\\1"
|
||||
BUILD_DEPENDS
|
||||
"$<TARGET_FILE:genex_discover>"
|
||||
"$<$<BOOL:OFF>:${never}>")
|
||||
add_test(NAME GenexDiscoverBuilt
|
||||
COMMAND "${CMAKE_COMMAND}" -E cat
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/genex_discover-built.txt")
|
||||
|
||||
# The directory property is evaluated as a whole.
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_TEST_BUILD_DEPENDS
|
||||
"$<TARGET_FILE:genex_directory>" "$<$<BOOL:OFF>:${never}>")
|
||||
add_test(NAME GenexDirectoryBuilt
|
||||
COMMAND "${CMAKE_COMMAND}" -E cat
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/genex_directory-built.txt")
|
||||
@@ -241,7 +241,7 @@ if(RunCMake_GENERATOR MATCHES "Ninja|FASTBuild|Makefiles")
|
||||
# Verify successful behavior
|
||||
block()
|
||||
foreach(case IN ITEMS DiscoverTestsUtility DiscoverTestsCommands
|
||||
DiscoverTestsGeneratedFile DirectoryGeneratedFile)
|
||||
DiscoverTestsGeneratedFile DirectoryGeneratedFile BuildDependsGenex)
|
||||
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build)
|
||||
run_cmake(${case})
|
||||
set(RunCMake_TEST_NO_CLEAN 1)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,9 @@
|
||||
^CMake Error at FILES-BUILD_DEPENDS-genex-missing\.cmake:[0-9]+ \(install\):
|
||||
install\(FILES\) BUILD_DEPENDS entry "\$<1:missing_target>" evaluates to
|
||||
"missing_target", which is neither a target nor a file that exists or is
|
||||
generated by the build\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:[0-9]+ \(include\)
|
||||
|
||||
|
||||
CMake Generate step failed\. Build files cannot be regenerated correctly\.$
|
||||
@@ -0,0 +1,2 @@
|
||||
install(FILES empty.c DESTINATION .
|
||||
BUILD_DEPENDS "$<$<BOOL:OFF>:missing_off>" "$<1:missing_target>")
|
||||
@@ -136,6 +136,7 @@ if (CMake_TEST_install_VARIANT STREQUAL "Files")
|
||||
run_cmake(DIRECTORY-PATTERN-BUILD_DEPENDS)
|
||||
run_cmake(SCRIPT-BUILD_DEPENDS-missing-target)
|
||||
run_cmake(FILES-BUILD_DEPENDS-missing-target)
|
||||
run_cmake(FILES-BUILD_DEPENDS-genex-missing)
|
||||
run_cmake(PROGRAMS-BUILD_DEPENDS-missing-target)
|
||||
run_cmake(DIRECTORY-BUILD_DEPENDS-missing-target)
|
||||
if(RunCMake_GENERATOR MATCHES "Ninja|Make|FASTBuild")
|
||||
|
||||
Reference in new issue
Block a user