Pārlūkot izejas kodu

GenEx: Limit TARGET_PROPERTY transitive closure optimization to subgraphs

Fixes: #25728
Michael Herwig 1 gadu atpakaļ
vecāks
revīzija
4a11772618

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

@@ -1877,6 +1877,14 @@ These expressions look up the values of
     rather than the directory of the consuming target for which the
     rather than the directory of the consuming target for which the
     expression is being evaluated.
     expression is being evaluated.
 
 
+  .. versionchanged:: 3.31
+    Generator expressions for transitive interface properties, such as
+    ``$<TARGET_PROPERTY:target,INTERFACE_*>``, now correctly handle
+    repeated evaluations within nested generator expressions.
+    Previously, these repeated evaluations returned empty values due
+    to an optimization for transitive closures.
+    This change ensures consistent evaluation for non-union operations.
+
 .. genex:: $<TARGET_PROPERTY:prop>
 .. genex:: $<TARGET_PROPERTY:prop>
   :target: TARGET_PROPERTY:prop
   :target: TARGET_PROPERTY:prop
 
 

+ 17 - 5
Source/cmGeneratorExpressionDAGChecker.cxx

@@ -24,7 +24,7 @@ cmGeneratorExpressionDAGChecker::cmGeneratorExpressionDAGChecker(
   std::string const& contextConfig)
   std::string const& contextConfig)
   : cmGeneratorExpressionDAGChecker(cmListFileBacktrace(), target,
   : cmGeneratorExpressionDAGChecker(cmListFileBacktrace(), target,
                                     std::move(property), content, parent,
                                     std::move(property), content, parent,
-                                    contextLG, contextConfig)
+                                    contextLG, contextConfig, INHERIT)
 {
 {
 }
 }
 
 
@@ -33,8 +33,20 @@ cmGeneratorExpressionDAGChecker::cmGeneratorExpressionDAGChecker(
   std::string property, const GeneratorExpressionContent* content,
   std::string property, const GeneratorExpressionContent* content,
   cmGeneratorExpressionDAGChecker* parent, cmLocalGenerator const* contextLG,
   cmGeneratorExpressionDAGChecker* parent, cmLocalGenerator const* contextLG,
   std::string const& contextConfig)
   std::string const& contextConfig)
+  : cmGeneratorExpressionDAGChecker(std::move(backtrace), target,
+                                    std::move(property), content, parent,
+                                    contextLG, contextConfig, INHERIT)
+{
+}
+
+cmGeneratorExpressionDAGChecker::cmGeneratorExpressionDAGChecker(
+  cmListFileBacktrace backtrace, cmGeneratorTarget const* target,
+  std::string property, const GeneratorExpressionContent* content,
+  cmGeneratorExpressionDAGChecker* parent, cmLocalGenerator const* contextLG,
+  std::string const& contextConfig, TransitiveClosure closure)
   : Parent(parent)
   : Parent(parent)
   , Top(parent ? parent->Top : this)
   , Top(parent ? parent->Top : this)
+  , Closure((closure == SUBGRAPH || !parent) ? this : parent->Closure)
   , Target(target)
   , Target(target)
   , Property(std::move(property))
   , Property(std::move(property))
   , Content(content)
   , Content(content)
@@ -53,16 +65,16 @@ cmGeneratorExpressionDAGChecker::cmGeneratorExpressionDAGChecker(
   this->CheckResult = this->CheckGraph();
   this->CheckResult = this->CheckGraph();
 
 
   if (this->CheckResult == DAG && this->EvaluatingTransitiveProperty()) {
   if (this->CheckResult == DAG && this->EvaluatingTransitiveProperty()) {
-    const auto* top = this->Top;
-    auto it = top->Seen.find(this->Target);
-    if (it != top->Seen.end()) {
+    const auto* transitiveClosure = this->Closure;
+    auto it = transitiveClosure->Seen.find(this->Target);
+    if (it != transitiveClosure->Seen.end()) {
       const std::set<std::string>& propSet = it->second;
       const std::set<std::string>& propSet = it->second;
       if (propSet.find(this->Property) != propSet.end()) {
       if (propSet.find(this->Property) != propSet.end()) {
         this->CheckResult = ALREADY_SEEN;
         this->CheckResult = ALREADY_SEEN;
         return;
         return;
       }
       }
     }
     }
-    top->Seen[this->Target].insert(this->Property);
+    transitiveClosure->Seen[this->Target].insert(this->Property);
   }
   }
 }
 }
 
 

+ 12 - 0
Source/cmGeneratorExpressionDAGChecker.h

@@ -17,6 +17,17 @@ class cmLocalGenerator;
 
 
 struct cmGeneratorExpressionDAGChecker
 struct cmGeneratorExpressionDAGChecker
 {
 {
+  enum TransitiveClosure
+  {
+    INHERIT,
+    SUBGRAPH,
+  };
+
+  cmGeneratorExpressionDAGChecker(
+    cmListFileBacktrace backtrace, cmGeneratorTarget const* target,
+    std::string property, const GeneratorExpressionContent* content,
+    cmGeneratorExpressionDAGChecker* parent, cmLocalGenerator const* contextLG,
+    std::string const& contextConfig, TransitiveClosure closure);
   cmGeneratorExpressionDAGChecker(cmListFileBacktrace backtrace,
   cmGeneratorExpressionDAGChecker(cmListFileBacktrace backtrace,
                                   cmGeneratorTarget const* target,
                                   cmGeneratorTarget const* target,
                                   std::string property,
                                   std::string property,
@@ -76,6 +87,7 @@ private:
 
 
   const cmGeneratorExpressionDAGChecker* const Parent;
   const cmGeneratorExpressionDAGChecker* const Parent;
   const cmGeneratorExpressionDAGChecker* const Top;
   const cmGeneratorExpressionDAGChecker* const Top;
+  const cmGeneratorExpressionDAGChecker* const Closure;
   cmGeneratorTarget const* Target;
   cmGeneratorTarget const* Target;
   const std::string Property;
   const std::string Property;
   mutable std::map<cmGeneratorTarget const*, std::set<std::string>> Seen;
   mutable std::map<cmGeneratorTarget const*, std::set<std::string>> Seen;

+ 3 - 2
Source/cmGeneratorExpressionNode.cxx

@@ -2983,8 +2983,9 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode
 
 
     if (isInterfaceProperty) {
     if (isInterfaceProperty) {
       return cmGeneratorExpression::StripEmptyListElements(
       return cmGeneratorExpression::StripEmptyListElements(
-        target->EvaluateInterfaceProperty(propertyName, context,
-                                          dagCheckerParent, usage));
+        target->EvaluateInterfaceProperty(
+          propertyName, context, dagCheckerParent, usage,
+          cmGeneratorTarget::TransitiveClosure::Subgraph));
     }
     }
 
 
     cmGeneratorExpressionDAGChecker dagChecker(
     cmGeneratorExpressionDAGChecker dagChecker(

+ 12 - 0
Source/cmGeneratorTarget.h

@@ -918,11 +918,23 @@ public:
                             const std::string& report,
                             const std::string& report,
                             const std::string& compatibilityType) const;
                             const std::string& compatibilityType) const;
 
 
+  /** Configures TransitiveClosureOptimization. Re-evaluation of a
+      transitive property will only be optimized within a subgraph. */
+  enum class TransitiveClosure
+  {
+    Inherit,  // node is in the same subgraph as its' parent
+    Subgraph, // the current node spans a new subgraph
+  };
+
   class TargetPropertyEntry;
   class TargetPropertyEntry;
 
 
   std::string EvaluateInterfaceProperty(
   std::string EvaluateInterfaceProperty(
     std::string const& prop, cmGeneratorExpressionContext* context,
     std::string const& prop, cmGeneratorExpressionContext* context,
     cmGeneratorExpressionDAGChecker* dagCheckerParent, UseTo usage) const;
     cmGeneratorExpressionDAGChecker* dagCheckerParent, UseTo usage) const;
+  std::string EvaluateInterfaceProperty(
+    std::string const& prop, cmGeneratorExpressionContext* context,
+    cmGeneratorExpressionDAGChecker* dagCheckerParent, UseTo usage,
+    TransitiveClosure closure) const;
 
 
   struct TransitiveProperty
   struct TransitiveProperty
   {
   {

+ 13 - 1
Source/cmGeneratorTarget_TransitiveProperty.cxx

@@ -98,6 +98,15 @@ bool cmGeneratorTarget::MaybeHaveInterfaceProperty(
 std::string cmGeneratorTarget::EvaluateInterfaceProperty(
 std::string cmGeneratorTarget::EvaluateInterfaceProperty(
   std::string const& prop, cmGeneratorExpressionContext* context,
   std::string const& prop, cmGeneratorExpressionContext* context,
   cmGeneratorExpressionDAGChecker* dagCheckerParent, UseTo usage) const
   cmGeneratorExpressionDAGChecker* dagCheckerParent, UseTo usage) const
+{
+  return EvaluateInterfaceProperty(prop, context, dagCheckerParent, usage,
+                                   TransitiveClosure::Inherit);
+}
+
+std::string cmGeneratorTarget::EvaluateInterfaceProperty(
+  std::string const& prop, cmGeneratorExpressionContext* context,
+  cmGeneratorExpressionDAGChecker* dagCheckerParent, UseTo usage,
+  TransitiveClosure closure) const
 {
 {
   std::string result;
   std::string result;
 
 
@@ -106,12 +115,15 @@ std::string cmGeneratorTarget::EvaluateInterfaceProperty(
     return result;
     return result;
   }
   }
 
 
+  auto const dagClosure = closure == TransitiveClosure::Inherit
+    ? cmGeneratorExpressionDAGChecker::INHERIT
+    : cmGeneratorExpressionDAGChecker::SUBGRAPH;
   // Evaluate $<TARGET_PROPERTY:this,prop> as if it were compiled.  This is
   // Evaluate $<TARGET_PROPERTY:this,prop> as if it were compiled.  This is
   // a subset of TargetPropertyNode::Evaluate without stringify/parse steps
   // a subset of TargetPropertyNode::Evaluate without stringify/parse steps
   // but sufficient for transitive interface properties.
   // but sufficient for transitive interface properties.
   cmGeneratorExpressionDAGChecker dagChecker(
   cmGeneratorExpressionDAGChecker dagChecker(
     context->Backtrace, this, prop, nullptr, dagCheckerParent,
     context->Backtrace, this, prop, nullptr, dagCheckerParent,
-    this->LocalGenerator, context->Config);
+    this->LocalGenerator, context->Config, dagClosure);
   switch (dagChecker.Check()) {
   switch (dagChecker.Check()) {
     case cmGeneratorExpressionDAGChecker::SELF_REFERENCE:
     case cmGeneratorExpressionDAGChecker::SELF_REFERENCE:
       dagChecker.ReportError(
       dagChecker.ReportError(

+ 11 - 0
Tests/GeneratorExpression/CMakeLists.txt

@@ -112,6 +112,15 @@ target_link_libraries(empty3 LINK_PUBLIC empty2 empty4)
 add_library(empty5 empty.cpp)
 add_library(empty5 empty.cpp)
 target_include_directories(empty5 PRIVATE /empty5/private1 /empty5/private2)
 target_include_directories(empty5 PRIVATE /empty5/private1 /empty5/private2)
 
 
+add_library(interface1 INTERFACE)
+target_sources(interface1 INTERFACE foo.c bar.cpp)
+
+set(interface1Sources $<TARGET_PROPERTY:interface1,INTERFACE_SOURCES>)
+set(interface1MergeSources $<LIST:APPEND,$<LIST:FILTER,${interface1Sources},INCLUDE,.*\\.c$>,$<LIST:FILTER,${interface1Sources},EXCLUDE,.*\\.c$>>)
+
+add_library(interface2 INTERFACE)
+target_sources(interface2 INTERFACE ${interface1MergeSources})
+
 add_custom_target(check-part2 ALL
 add_custom_target(check-part2 ALL
   COMMAND ${msys2_no_conv} ${CMAKE_COMMAND}
   COMMAND ${msys2_no_conv} ${CMAKE_COMMAND}
     -Dtest_incomplete_1=$<
     -Dtest_incomplete_1=$<
@@ -147,6 +156,8 @@ add_custom_target(check-part2 ALL
     -Dtest_target_includes6=$<TARGET_PROPERTY:empty3,INCLUDE_DIRECTORIES>
     -Dtest_target_includes6=$<TARGET_PROPERTY:empty3,INCLUDE_DIRECTORIES>
     -Dtest_target_includes7=$<TARGET_PROPERTY:empty1,INTERFACE_INCLUDE_DIRECTORIES>
     -Dtest_target_includes7=$<TARGET_PROPERTY:empty1,INTERFACE_INCLUDE_DIRECTORIES>
     -Dtest_target_includes8=$<TARGET_PROPERTY:empty5,INCLUDE_DIRECTORIES>
     -Dtest_target_includes8=$<TARGET_PROPERTY:empty5,INCLUDE_DIRECTORIES>
+    -Dtest_target_closure1=$<JOIN:$<LIST:SORT,${interface1MergeSources}>,$<COMMA>>
+    -Dtest_target_closure2=$<JOIN:$<LIST:SORT,$<TARGET_PROPERTY:interface2,INTERFACE_SOURCES>>,$<COMMA>>
     -Dtest_arbitrary_content_comma_1=$<1:a,>
     -Dtest_arbitrary_content_comma_1=$<1:a,>
     -Dtest_arbitrary_content_comma_2=$<1:,a>
     -Dtest_arbitrary_content_comma_2=$<1:,a>
     -Dtest_arbitrary_content_comma_3=$<1:a,,>
     -Dtest_arbitrary_content_comma_3=$<1:a,,>

+ 2 - 0
Tests/GeneratorExpression/check-part2.cmake

@@ -34,6 +34,8 @@ check(test_target_includes5 "/empty2/public;/empty3/public;/empty2/public;/empty
 check(test_target_includes6 "/empty3/public;/empty3/private;/empty2/public;/empty3/public;/empty4/public")
 check(test_target_includes6 "/empty3/public;/empty3/private;/empty2/public;/empty3/public;/empty4/public")
 check(test_target_includes7 "/empty1/public;/empty2/public;/empty3/public;/empty4/public")
 check(test_target_includes7 "/empty1/public;/empty2/public;/empty3/public;/empty4/public")
 check(test_target_includes8 "/empty5/private1;/empty5/private2")
 check(test_target_includes8 "/empty5/private1;/empty5/private2")
+check(test_target_closure1 "bar.cpp,foo.c")
+check(test_target_closure2 "bar.cpp,foo.c")
 check(test_arbitrary_content_comma_1 "a,")
 check(test_arbitrary_content_comma_1 "a,")
 check(test_arbitrary_content_comma_2 ",a")
 check(test_arbitrary_content_comma_2 ",a")
 check(test_arbitrary_content_comma_3 "a,,")
 check(test_arbitrary_content_comma_3 "a,,")