Genex: Add $<TARGET_GENEX_EVAL:...> and $<GENEX_EVAL:...>

Fixes: #17884
This commit is contained in:
Marc Chevrier 2018-04-13 16:16:32 +02:00
parent 1f372ac4e5
commit 4d15046edd
31 changed files with 390 additions and 1 deletions

View File

@ -305,3 +305,42 @@ Available output expressions are:
Content of ``...`` converted to shell path style. For example, slashes are
converted to backslashes in Windows shells and drive letters are converted
to posix paths in MSYS shells. The ``...`` must be an absolute path.
``$<GENEX_EVAL:...>``
Content of ``...`` evaluated as a generator expression in the current
context. This enables consumption of generator expressions
whose evaluation results itself in generator expressions.
``$<TARGET_GENEX_EVAL:tgt,...>``
Content of ``...`` evaluated as a generator expression in the context of
``tgt`` target. This enables consumption of custom target properties that
themselves contain generator expressions.
Having the capability to evaluate generator expressions is very useful when
you want to manage custom properties supporting generator expressions.
For example:
.. code-block:: cmake
add_library(foo ...)
set_property(TARGET foo PROPERTY
CUSTOM_KEYS $<$<CONFIG:DEBUG>:FOO_EXTRA_THINGS>
)
add_custom_target(printFooKeys
COMMAND ${CMAKE_COMMAND} -E echo $<TARGET_PROPERTY:foo,CUSTOM_KEYS>
)
This naive implementation of the ``printFooKeys`` custom command is wrong
because ``CUSTOM_KEYS`` target property is not evaluated and the content
is passed as is (i.e. ``$<$<CONFIG:DEBUG>:FOO_EXTRA_THINGS>``).
To have the expected result (i.e. ``FOO_EXTRA_THINGS`` if config is
``Debug``), it is required to evaluate the output of
``$<TARGET_PROPERTY:foo,CUSTOM_KEYS>``:
.. code-block:: cmake
add_custom_target(printFooKeys
COMMAND ${CMAKE_COMMAND} -E
echo $<TARGET_GENEX_EVAL:foo,$<TARGET_PROPERTY:foo,CUSTOM_KEYS>>
)

View File

@ -0,0 +1,7 @@
genex-GENEX_EVAL
----------------
* New ``$<GENEX_EVAL:...>`` and ``$<TARGET_GENEX_EVAL:target,...>``
:manual:`generator expression <cmake-generator-expressions(7)>`
had been added to enable consumption of generator expressions whose
evaluation results itself in generator expressions.

View File

@ -154,6 +154,18 @@ bool cmGeneratorExpressionDAGChecker::GetTransitivePropertiesOnly()
return top->TransitivePropertiesOnly;
}
bool cmGeneratorExpressionDAGChecker::EvaluatingGenexExpression()
{
const cmGeneratorExpressionDAGChecker* top = this;
const cmGeneratorExpressionDAGChecker* parent = this->Parent;
while (parent) {
top = parent;
parent = parent->Parent;
}
return top->Property == "TARGET_GENEX_EVAL" || top->Property == "GENEX_EVAL";
}
bool cmGeneratorExpressionDAGChecker::EvaluatingLinkLibraries(const char* tgt)
{
const cmGeneratorExpressionDAGChecker* top = this;

View File

@ -61,6 +61,7 @@ struct cmGeneratorExpressionDAGChecker
void ReportError(cmGeneratorExpressionContext* context,
const std::string& expr);
bool EvaluatingGenexExpression();
bool EvaluatingLinkLibraries(const char* tgt = nullptr);
#define DECLARE_TRANSITIVE_PROPERTY_METHOD(METHOD) bool METHOD() const;

View File

@ -365,6 +365,113 @@ static const struct TargetNameIfExistsNode : public cmGeneratorExpressionNode
}
} targetNameIfExistsNode;
struct GenexEvaluator : public cmGeneratorExpressionNode
{
GenexEvaluator() {}
protected:
std::string EvaluateExpression(
const std::string& genexOperator, const std::string& expression,
cmGeneratorExpressionContext* context,
const GeneratorExpressionContent* content,
cmGeneratorExpressionDAGChecker* dagCheckerParent) const
{
if (context->HeadTarget) {
cmGeneratorExpressionDAGChecker dagChecker(
context->Backtrace, context->HeadTarget->GetName(), genexOperator,
content, dagCheckerParent);
switch (dagChecker.Check()) {
case cmGeneratorExpressionDAGChecker::SELF_REFERENCE:
case cmGeneratorExpressionDAGChecker::CYCLIC_REFERENCE: {
dagChecker.ReportError(context, content->GetOriginalExpression());
return std::string();
}
case cmGeneratorExpressionDAGChecker::ALREADY_SEEN:
case cmGeneratorExpressionDAGChecker::DAG:
break;
}
return this->EvaluateDependentExpression(
expression, context->LG, context, context->HeadTarget,
context->CurrentTarget, &dagChecker);
}
return this->EvaluateDependentExpression(
expression, context->LG, context, context->HeadTarget,
context->CurrentTarget, dagCheckerParent);
}
};
static const struct TargetGenexEvalNode : public GenexEvaluator
{
TargetGenexEvalNode() {}
int NumExpectedParameters() const override { return 2; }
bool AcceptsArbitraryContentParameter() const override { return true; }
std::string Evaluate(
const std::vector<std::string>& parameters,
cmGeneratorExpressionContext* context,
const GeneratorExpressionContent* content,
cmGeneratorExpressionDAGChecker* dagCheckerParent) const override
{
const std::string& targetName = parameters.front();
if (targetName.empty() ||
!cmGeneratorExpression::IsValidTargetName(targetName)) {
reportError(context, content->GetOriginalExpression(),
"$<TARGET_GENEX_EVAL:tgt, ...> expression requires a "
"non-empty valid target name.");
return std::string();
}
const auto* target = context->LG->FindGeneratorTargetToUse(targetName);
if (!target) {
std::ostringstream e;
e << "$<TARGET_GENEX_EVAL:tgt, ...> target \"" << targetName
<< "\" not found.";
reportError(context, content->GetOriginalExpression(), e.str());
return std::string();
}
const std::string& expression = parameters[1];
if (expression.empty()) {
return expression;
}
cmGeneratorExpressionContext targetContext(
context->LG, context->Config, context->Quiet, target, target,
context->EvaluateForBuildsystem, context->Backtrace, context->Language);
return this->EvaluateExpression("TARGET_GENEX_EVAL", expression,
&targetContext, content, dagCheckerParent);
}
} targetGenexEvalNode;
static const struct GenexEvalNode : public GenexEvaluator
{
GenexEvalNode() {}
int NumExpectedParameters() const override { return 1; }
bool AcceptsArbitraryContentParameter() const override { return true; }
std::string Evaluate(
const std::vector<std::string>& parameters,
cmGeneratorExpressionContext* context,
const GeneratorExpressionContent* content,
cmGeneratorExpressionDAGChecker* dagCheckerParent) const override
{
const std::string& expression = parameters[0];
if (expression.empty()) {
return expression;
}
return this->EvaluateExpression("GENEX_EVAL", expression, context, content,
dagCheckerParent);
}
} genexEvalNode;
static const struct LowerCaseNode : public cmGeneratorExpressionNode
{
LowerCaseNode() {}
@ -1124,7 +1231,9 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode
const char* prop = target->GetProperty(propertyName);
if (dagCheckerParent) {
if (dagCheckerParent->EvaluatingLinkLibraries()) {
if (dagCheckerParent->EvaluatingGenexExpression()) {
// No check required.
} else if (dagCheckerParent->EvaluatingLinkLibraries()) {
#define TRANSITIVE_PROPERTY_COMPARE(PROPERTY) \
(#PROPERTY == propertyName || "INTERFACE_" #PROPERTY == propertyName) ||
if (CM_FOR_EACH_TRANSITIVE_PROPERTY_NAME(
@ -1933,6 +2042,8 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode(
nodeMap["TARGET_POLICY"] = &targetPolicyNode;
nodeMap["TARGET_EXISTS"] = &targetExistsNode;
nodeMap["TARGET_NAME_IF_EXISTS"] = &targetNameIfExistsNode;
nodeMap["TARGET_GENEX_EVAL"] = &targetGenexEvalNode;
nodeMap["GENEX_EVAL"] = &genexEvalNode;
nodeMap["BUILD_INTERFACE"] = &buildInterfaceNode;
nodeMap["INSTALL_INTERFACE"] = &installInterfaceNode;
nodeMap["INSTALL_PREFIX"] = &installPrefixNode;

View File

@ -0,0 +1,6 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/GENEX_EVAL-generated.txt" content)
set(expected "BEFORE_PROPERTY1_AFTER")
if(NOT content STREQUAL expected)
set(RunCMake_TEST_FAILED "actual content:\n [[${content}]]\nbut expected:\n [[${expected}]]")
endif()

View File

@ -0,0 +1,26 @@
^CMake Error at GENEX_EVAL-recursion1.cmake:7 \(add_custom_target\):
Error evaluating generator expression:
\$<GENEX_EVAL:\$<TARGET_PROPERTY:CUSTOM_PROPERTY>>
Dependency loop found.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
CMake Error at GENEX_EVAL-recursion1.cmake:7 \(add_custom_target\):
Loop step 1
\$<GENEX_EVAL:\$<TARGET_PROPERTY:CUSTOM_PROPERTY>>
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
CMake Error at GENEX_EVAL-recursion1.cmake:7 \(add_custom_target\):
Loop step 2
\$<TARGET_GENEX_EVAL:recursion,\$<TARGET_PROPERTY:recursion,CUSTOM_PROPERTY>>
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,9 @@
enable_language(C)
add_library (recursion SHARED empty.c)
set_property (TARGET recursion PROPERTY CUSTOM_PROPERTY "$<GENEX_EVAL:$<TARGET_PROPERTY:CUSTOM_PROPERTY>>")
add_custom_target (drive
COMMAND echo "$<TARGET_GENEX_EVAL:recursion,$<TARGET_PROPERTY:recursion,CUSTOM_PROPERTY>>"
DEPENDS recursion)

View File

@ -0,0 +1,26 @@
^CMake Error at GENEX_EVAL-recursion2.cmake:8 \(add_custom_target\):
Error evaluating generator expression:
\$<GENEX_EVAL:\$<TARGET_PROPERTY:CUSTOM_PROPERTY1>>
Dependency loop found.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
CMake Error at GENEX_EVAL-recursion2.cmake:8 \(add_custom_target\):
Loop step 1
\$<GENEX_EVAL:\$<TARGET_PROPERTY:CUSTOM_PROPERTY2>>
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
CMake Error at GENEX_EVAL-recursion2.cmake:8 \(add_custom_target\):
Loop step 2
\$<TARGET_GENEX_EVAL:recursion,\$<TARGET_PROPERTY:recursion,CUSTOM_PROPERTY1>>
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,10 @@
enable_language(C)
add_library(recursion SHARED empty.c)
set_property (TARGET recursion PROPERTY CUSTOM_PROPERTY1 "$<GENEX_EVAL:$<TARGET_PROPERTY:CUSTOM_PROPERTY2>>")
set_property (TARGET recursion PROPERTY CUSTOM_PROPERTY2 "$<GENEX_EVAL:$<TARGET_PROPERTY:CUSTOM_PROPERTY1>>")
add_custom_target (drive
COMMAND echo "$<TARGET_GENEX_EVAL:recursion,$<TARGET_PROPERTY:recursion,CUSTOM_PROPERTY1>>"
DEPENDS recursion)

View File

@ -0,0 +1,11 @@
cmake_policy(VERSION 3.11)
enable_language(C)
add_library (example SHARED empty.c)
set_property (TARGET example PROPERTY CUSTOM_PROPERTY1 "PROPERTY1")
set_property (TARGET example PROPERTY CUSTOM_PROPERTY2 "$<TARGET_PROPERTY:CUSTOM_PROPERTY1>")
set_property (TARGET example PROPERTY CUSTOM_PROPERTY3 "$<GENEX_EVAL:BEFORE_$<TARGET_PROPERTY:CUSTOM_PROPERTY2>_AFTER>")
file(GENERATE OUTPUT "GENEX_EVAL-generated.txt" CONTENT "$<TARGET_GENEX_EVAL:example,$<TARGET_PROPERTY:example,CUSTOM_PROPERTY3>>")

View File

@ -42,6 +42,15 @@ run_cmake(TARGET_NAME_IF_EXISTS-no-arg)
run_cmake(TARGET_NAME_IF_EXISTS-empty-arg)
run_cmake(TARGET_NAME_IF_EXISTS)
run_cmake(TARGET_NAME_IF_EXISTS-not-a-target)
run_cmake(TARGET_GENEX_EVAL-no-arg)
run_cmake(TARGET_GENEX_EVAL-no-target)
run_cmake(TARGET_GENEX_EVAL-non-valid-target)
run_cmake(TARGET_GENEX_EVAL-recursion1)
run_cmake(TARGET_GENEX_EVAL-recursion2)
run_cmake(TARGET_GENEX_EVAL)
run_cmake(GENEX_EVAL-recursion1)
run_cmake(GENEX_EVAL-recursion2)
run_cmake(GENEX_EVAL)
run_cmake(ImportedTarget-TARGET_BUNDLE_DIR)
run_cmake(ImportedTarget-TARGET_BUNDLE_CONTENT_DIR)

View File

@ -0,0 +1,6 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/TARGET_GENEX_EVAL-generated.txt" content)
set(expected "BEFORE_PROPERTY1_AFTER")
if(NOT content STREQUAL expected)
set(RunCMake_TEST_FAILED "actual content:\n [[${content}]]\nbut expected:\n [[${expected}]]")
endif()

View File

@ -0,0 +1,9 @@
^CMake Error at TARGET_GENEX_EVAL-no-arg.cmake:4 \(add_custom_command\):
Error evaluating generator expression:
\$<TARGET_GENEX_EVAL:>
\$<TARGET_GENEX_EVAL> expression requires 2 comma separated parameters, but
got 1 instead.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,7 @@
cmake_policy(VERSION 3.11)
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/copied_file.c"
COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/empty.c" "${CMAKE_CURRENT_BINARY_DIR}/copied_file$<TARGET_GENEX_EVAL:>.c"
)
add_custom_target(drive DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/copied_file.c")

View File

@ -0,0 +1,9 @@
^CMake Error at TARGET_GENEX_EVAL-no-target.cmake:2 \(add_custom_command\):
Error evaluating generator expression:
\$<TARGET_GENEX_EVAL:,>
\$<TARGET_GENEX_EVAL:tgt, ...> expression requires a non-empty valid target
name.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,5 @@
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/copied_file.c"
COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/empty.c" "${CMAKE_CURRENT_BINARY_DIR}/copied_file$<TARGET_GENEX_EVAL:,>.c"
)
add_custom_target(drive DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/copied_file.c")

View File

@ -0,0 +1,8 @@
^CMake Error at TARGET_GENEX_EVAL-non-valid-target.cmake:2 \(add_custom_command\):
Error evaluating generator expression:
\$<TARGET_GENEX_EVAL:bad-target,>
\$<TARGET_GENEX_EVAL:tgt, ...> target "bad-target" not found.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,5 @@
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/copied_file.c"
COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/empty.c" "${CMAKE_CURRENT_BINARY_DIR}/copied_file$<TARGET_GENEX_EVAL:bad-target,>.c"
)
add_custom_target(drive DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/copied_file.c")

View File

@ -0,0 +1,9 @@
^CMake Error at TARGET_GENEX_EVAL-recursion1.cmake:7 \(add_custom_target\):
Error evaluating generator expression:
\$<TARGET_GENEX_EVAL:recursion,\$<TARGET_PROPERTY:CUSTOM_PROPERTY>>
Self reference on target "recursion".
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,9 @@
enable_language(C)
add_library (recursion SHARED empty.c)
set_property (TARGET recursion PROPERTY CUSTOM_PROPERTY "$<TARGET_GENEX_EVAL:recursion,$<TARGET_PROPERTY:CUSTOM_PROPERTY>>")
add_custom_target (drive
COMMAND echo "$<TARGET_GENEX_EVAL:recursion,$<TARGET_PROPERTY:recursion,CUSTOM_PROPERTY>>"
DEPENDS recursion)

View File

@ -0,0 +1,26 @@
^CMake Error at TARGET_GENEX_EVAL-recursion2.cmake:10 \(add_custom_target\):
Error evaluating generator expression:
\$<TARGET_GENEX_EVAL:recursion1,\$<TARGET_PROPERTY:recursion1,CUSTOM_PROPERTY1>>
Dependency loop found.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
CMake Error at TARGET_GENEX_EVAL-recursion2.cmake:10 \(add_custom_target\):
Loop step 1
\$<TARGET_GENEX_EVAL:recursion2,\$<TARGET_PROPERTY:recursion2,CUSTOM_PROPERTY2>>
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
CMake Error at TARGET_GENEX_EVAL-recursion2.cmake:10 \(add_custom_target\):
Loop step 2
\$<TARGET_GENEX_EVAL:recursion1,\$<TARGET_PROPERTY:recursion1,CUSTOM_PROPERTY1>>
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,12 @@
enable_language(C)
add_library (recursion1 SHARED empty.c)
set_property (TARGET recursion1 PROPERTY CUSTOM_PROPERTY1 "$<TARGET_GENEX_EVAL:recursion2,$<TARGET_PROPERTY:recursion2,CUSTOM_PROPERTY2>>")
add_library (recursion2 SHARED empty.c)
set_property (TARGET recursion2 PROPERTY CUSTOM_PROPERTY2 "$<TARGET_GENEX_EVAL:recursion1,$<TARGET_PROPERTY:recursion1,CUSTOM_PROPERTY1>>")
add_custom_target (drive
COMMAND echo "$<TARGET_GENEX_EVAL:recursion1,$<TARGET_PROPERTY:recursion1,CUSTOM_PROPERTY1>>"
DEPENDS recursion)

View File

@ -0,0 +1,10 @@
cmake_policy(VERSION 3.11)
enable_language(C)
add_library (example SHARED empty.c)
set_property (TARGET example PROPERTY CUSTOM_PROPERTY1 "PROPERTY1")
set_property (TARGET example PROPERTY CUSTOM_PROPERTY2 "BEFORE_$<TARGET_PROPERTY:CUSTOM_PROPERTY1>_AFTER")
file(GENERATE OUTPUT "TARGET_GENEX_EVAL-generated.txt" CONTENT "$<TARGET_GENEX_EVAL:example,$<TARGET_PROPERTY:example,CUSTOM_PROPERTY2>>")