Browse Source

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

Fixes: #17884
Marc Chevrier 7 years ago
parent
commit
4d15046edd
31 changed files with 390 additions and 1 deletions
  1. 39 0
      Help/manual/cmake-generator-expressions.7.rst
  2. 7 0
      Help/release/dev/genex-GENEX_EVAL.rst
  3. 12 0
      Source/cmGeneratorExpressionDAGChecker.cxx
  4. 1 0
      Source/cmGeneratorExpressionDAGChecker.h
  5. 112 1
      Source/cmGeneratorExpressionNode.cxx
  6. 6 0
      Tests/RunCMake/GeneratorExpression/GENEX_EVAL-check.cmake
  7. 1 0
      Tests/RunCMake/GeneratorExpression/GENEX_EVAL-recursion1-result.txt
  8. 26 0
      Tests/RunCMake/GeneratorExpression/GENEX_EVAL-recursion1-stderr.txt
  9. 9 0
      Tests/RunCMake/GeneratorExpression/GENEX_EVAL-recursion1.cmake
  10. 1 0
      Tests/RunCMake/GeneratorExpression/GENEX_EVAL-recursion2-result.txt
  11. 26 0
      Tests/RunCMake/GeneratorExpression/GENEX_EVAL-recursion2-stderr.txt
  12. 10 0
      Tests/RunCMake/GeneratorExpression/GENEX_EVAL-recursion2.cmake
  13. 11 0
      Tests/RunCMake/GeneratorExpression/GENEX_EVAL.cmake
  14. 9 0
      Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
  15. 6 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-check.cmake
  16. 1 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-no-arg-result.txt
  17. 9 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-no-arg-stderr.txt
  18. 7 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-no-arg.cmake
  19. 1 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-no-target-result.txt
  20. 9 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-no-target-stderr.txt
  21. 5 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-no-target.cmake
  22. 1 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-non-valid-target-result.txt
  23. 8 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-non-valid-target-stderr.txt
  24. 5 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-non-valid-target.cmake
  25. 1 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-recursion1-result.txt
  26. 9 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-recursion1-stderr.txt
  27. 9 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-recursion1.cmake
  28. 1 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-recursion2-result.txt
  29. 26 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-recursion2-stderr.txt
  30. 12 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-recursion2.cmake
  31. 10 0
      Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL.cmake

+ 39 - 0
Help/manual/cmake-generator-expressions.7.rst

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

+ 7 - 0
Help/release/dev/genex-GENEX_EVAL.rst

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

+ 12 - 0
Source/cmGeneratorExpressionDAGChecker.cxx

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

+ 1 - 0
Source/cmGeneratorExpressionDAGChecker.h

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

+ 112 - 1
Source/cmGeneratorExpressionNode.cxx

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

+ 6 - 0
Tests/RunCMake/GeneratorExpression/GENEX_EVAL-check.cmake

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

+ 1 - 0
Tests/RunCMake/GeneratorExpression/GENEX_EVAL-recursion1-result.txt

@@ -0,0 +1 @@
+1

+ 26 - 0
Tests/RunCMake/GeneratorExpression/GENEX_EVAL-recursion1-stderr.txt

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

+ 9 - 0
Tests/RunCMake/GeneratorExpression/GENEX_EVAL-recursion1.cmake

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

+ 1 - 0
Tests/RunCMake/GeneratorExpression/GENEX_EVAL-recursion2-result.txt

@@ -0,0 +1 @@
+1

+ 26 - 0
Tests/RunCMake/GeneratorExpression/GENEX_EVAL-recursion2-stderr.txt

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

+ 10 - 0
Tests/RunCMake/GeneratorExpression/GENEX_EVAL-recursion2.cmake

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

+ 11 - 0
Tests/RunCMake/GeneratorExpression/GENEX_EVAL.cmake

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

+ 9 - 0
Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake

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

+ 6 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-check.cmake

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

+ 1 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-no-arg-result.txt

@@ -0,0 +1 @@
+1

+ 9 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-no-arg-stderr.txt

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

+ 7 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-no-arg.cmake

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

+ 1 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-no-target-result.txt

@@ -0,0 +1 @@
+1

+ 9 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-no-target-stderr.txt

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

+ 5 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-no-target.cmake

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

+ 1 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-non-valid-target-result.txt

@@ -0,0 +1 @@
+1

+ 8 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-non-valid-target-stderr.txt

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

+ 5 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-non-valid-target.cmake

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

+ 1 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-recursion1-result.txt

@@ -0,0 +1 @@
+1

+ 9 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-recursion1-stderr.txt

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

+ 9 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-recursion1.cmake

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

+ 1 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-recursion2-result.txt

@@ -0,0 +1 @@
+1

+ 26 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-recursion2-stderr.txt

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

+ 12 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL-recursion2.cmake

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

+ 10 - 0
Tests/RunCMake/GeneratorExpression/TARGET_GENEX_EVAL.cmake

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