Browse Source

GenEx: Add support for custom transitive compile properties

Teach the `$<TARGET_PROPERTY:...>` generator expression to check for a
new `TRANSITIVE_COMPILE_PROPERTIES` property in the target's link
closure to enable transitive evaluation of named properties through
the link closure, excluding entries guarded by `$<LINK_ONLY:...>`.

Issue: #20416
Brad King 1 year ago
parent
commit
b9ee79b8a1

+ 47 - 0
Help/manual/cmake-buildsystem.7.rst

@@ -602,6 +602,53 @@ linking consumers.
   List of files on which linking the target's consumers depends, for
   those that are executables, shared libraries, or module libraries.
 
+.. _`Custom Transitive Properties`:
+
+Custom Transitive Properties
+----------------------------
+
+.. versionadded:: 3.30
+
+The :genex:`TARGET_PROPERTY` generator expression evaluates the above
+`build specification <Target Build Specification_>`_ and
+`usage requirement <Target Usage Requirements_>`_ properties
+as builtin transitive properties.  It also supports custom transitive
+properties defined by the :prop_tgt:`TRANSITIVE_COMPILE_PROPERTIES`
+property on the target and its link dependencies.
+
+For example:
+
+.. code-block:: cmake
+
+  add_library(example INTERFACE)
+  set_target_properties(example PROPERTIES
+    TRANSITIVE_COMPILE_PROPERTIES "CUSTOM_C"
+
+    INTERFACE_CUSTOM_C "EXAMPLE_CUSTOM_C"
+    )
+
+  add_library(mylib STATIC mylib.c)
+  target_link_libraries(mylib PRIVATE example)
+  set_target_properties(mylib PROPERTIES
+    CUSTOM_C           "MYLIB_PRIVATE_CUSTOM_C"
+    INTERFACE_CUSTOM_C "MYLIB_IFACE_CUSTOM_C"
+    )
+
+  add_executable(myexe myexe.c)
+  target_link_libraries(myexe PRIVATE mylib)
+  set_target_properties(myexe PROPERTIES
+    CUSTOM_C "MYEXE_CUSTOM_C"
+    )
+
+  add_custom_target(print ALL VERBATIM
+    COMMAND ${CMAKE_COMMAND} -E echo
+      # Prints "MYLIB_PRIVATE_CUSTOM_C;EXAMPLE_CUSTOM_C"
+      "$<TARGET_PROPERTY:mylib,CUSTOM_C>"
+
+      # Prints "MYEXE_CUSTOM_C"
+      "$<TARGET_PROPERTY:myexe,CUSTOM_C>"
+    )
+
 .. _`Compatible Interface Properties`:
 
 Compatible Interface Properties

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

@@ -1810,6 +1810,49 @@ The expressions have special evaluation rules for some properties:
 
   Evaluation of :prop_tgt:`INTERFACE_LINK_LIBRARIES` itself is not transitive.
 
+:ref:`Custom Transitive Properties`
+  .. versionadded:: 3.30
+
+  These are processed during evaluation as follows:
+
+  * Evaluation of :genex:`$<TARGET_PROPERTY:tgt,PROP>` for some property
+    ``PROP``, named without an ``INTERFACE_`` prefix,
+    checks the :prop_tgt:`TRANSITIVE_COMPILE_PROPERTIES`
+    property on target ``tgt``,
+    on targets named by its :prop_tgt:`LINK_LIBRARIES`, and on the
+    transitive closure of targets named by the linked targets'
+    :prop_tgt:`INTERFACE_LINK_LIBRARIES`.
+
+    If ``PROP`` is listed by one of those properties, then it evaluates as
+    a :ref:`semicolon-separated list <CMake Language Lists>` representing
+    the union of the value on the target itself with the values of the
+    corresponding ``INTERFACE_PROP`` on targets named by the target's
+    :prop_tgt:`LINK_LIBRARIES`:
+
+    * If ``PROP`` is named by :prop_tgt:`TRANSITIVE_COMPILE_PROPERTIES`,
+      evaluation of the corresponding ``INTERFACE_PROP`` is transitive over
+      the closure of the linked targets' :prop_tgt:`INTERFACE_LINK_LIBRARIES`,
+      excluding entries guarded by the :genex:`LINK_ONLY` generator expression.
+
+  * Evaluation of :genex:`$<TARGET_PROPERTY:tgt,INTERFACE_PROP>` for some
+    property ``INTERFACE_PROP``, named with an ``INTERFACE_`` prefix,
+    checks the :prop_tgt:`TRANSITIVE_COMPILE_PROPERTIES`
+    property on target ``tgt``,
+    and on the transitive closure of targets named by its
+    :prop_tgt:`INTERFACE_LINK_LIBRARIES`.
+
+    If the corresponding ``PROP`` is listed by one of those properties,
+    then ``INTERFACE_PROP`` evaluates as a
+    :ref:`semicolon-separated list <CMake Language Lists>` representing the
+    union of the value on the target itself with the value of the same
+    property on targets named by the target's
+    :prop_tgt:`INTERFACE_LINK_LIBRARIES`:
+
+    * If ``PROP`` is named by :prop_tgt:`TRANSITIVE_COMPILE_PROPERTIES`,
+      evaluation of the corresponding ``INTERFACE_PROP`` is transitive over
+      the closure of the target's :prop_tgt:`INTERFACE_LINK_LIBRARIES`,
+      excluding entries guarded by the :genex:`LINK_ONLY` generator expression.
+
 :ref:`Compatible Interface Properties`
   These evaluate as a single value combined from the target itself,
   from targets named by the target's :prop_tgt:`LINK_LIBRARIES`, and

+ 1 - 0
Help/manual/cmake-properties.7.rst

@@ -401,6 +401,7 @@ Properties on Targets
    /prop_tgt/Swift_MODULE_NAME
    /prop_tgt/SYSTEM
    /prop_tgt/TEST_LAUNCHER
+   /prop_tgt/TRANSITIVE_COMPILE_PROPERTIES
    /prop_tgt/TYPE
    /prop_tgt/UNITY_BUILD
    /prop_tgt/UNITY_BUILD_BATCH_SIZE

+ 17 - 0
Help/prop_tgt/TRANSITIVE_COMPILE_PROPERTIES.rst

@@ -0,0 +1,17 @@
+TRANSITIVE_COMPILE_PROPERTIES
+-----------------------------
+
+.. versionadded:: 3.30
+
+Properties that the :genex:`TARGET_PROPERTY` generator expression, on the
+target and its dependents, evaluates as the union of values collected from
+the transitive closure of link dependencies, excluding entries guarded by
+:genex:`LINK_ONLY`.
+
+The value is a :ref:`semicolon-separated list <CMake Language Lists>`
+of :ref:`custom transitive property <Custom Transitive Properties>` names.
+Any leading ``INTERFACE_`` prefix is ignored, e.g., ``INTERFACE_PROP`` is
+treated as just ``PROP``.
+
+See documentation of the :genex:`TARGET_PROPERTY` generator expression
+for details of custom transitive property evaluation.

+ 7 - 0
Help/release/dev/custom-transitive-properties.rst

@@ -0,0 +1,7 @@
+custom-transitive-properties
+----------------------------
+
+* The :genex:`TARGET_PROPERTY` generator expression learned to evaluate
+  :ref:`custom transitive properties <Custom Transitive Properties>`
+  defined by a new :prop_tgt:`TRANSITIVE_COMPILE_PROPERTIES`
+  target property.

+ 2 - 0
Source/cmExportBuildFileGenerator.cxx

@@ -152,6 +152,8 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
         gte, cmGeneratorExpression::BuildInterface, properties);
     }
     this->PopulateCompatibleInterfaceProperties(gte, properties);
+    this->PopulateCustomTransitiveInterfaceProperties(
+      gte, cmGeneratorExpression::BuildInterface, properties);
 
     this->GenerateInterfaceProperties(gte, os, properties);
 

+ 19 - 0
Source/cmExportFileGenerator.cxx

@@ -604,6 +604,25 @@ void cmExportFileGenerator::PopulateCompatibleInterfaceProperties(
   }
 }
 
+void cmExportFileGenerator::PopulateCustomTransitiveInterfaceProperties(
+  cmGeneratorTarget const* target,
+  cmGeneratorExpression::PreprocessContext preprocessRule,
+  ImportPropertyMap& properties)
+{
+  this->PopulateInterfaceProperty("TRANSITIVE_COMPILE_PROPERTIES", target,
+                                  properties);
+  std::set<std::string> ifaceProperties;
+  for (std::string const& config : this->Configurations) {
+    for (auto const& i : target->GetCustomTransitiveProperties(
+           config, cmGeneratorTarget::PropertyFor::Interface)) {
+      ifaceProperties.emplace(i.second.InterfaceName);
+    }
+  }
+  for (std::string const& ip : ifaceProperties) {
+    this->PopulateInterfaceProperty(ip, target, preprocessRule, properties);
+  }
+}
+
 void cmExportFileGenerator::GenerateInterfaceProperties(
   const cmGeneratorTarget* target, std::ostream& os,
   const ImportPropertyMap& properties)

+ 4 - 0
Source/cmExportFileGenerator.h

@@ -145,6 +145,10 @@ protected:
                                  ImportPropertyMap& properties);
   void PopulateCompatibleInterfaceProperties(cmGeneratorTarget const* target,
                                              ImportPropertyMap& properties);
+  void PopulateCustomTransitiveInterfaceProperties(
+    cmGeneratorTarget const* target,
+    cmGeneratorExpression::PreprocessContext preprocessRule,
+    ImportPropertyMap& properties);
   virtual void GenerateInterfaceProperties(
     cmGeneratorTarget const* target, std::ostream& os,
     const ImportPropertyMap& properties);

+ 2 - 0
Source/cmExportInstallFileGenerator.cxx

@@ -160,6 +160,8 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
                                     properties);
 
     this->PopulateCompatibleInterfaceProperties(gt, properties);
+    this->PopulateCustomTransitiveInterfaceProperties(
+      gt, cmGeneratorExpression::InstallInterface, properties);
 
     this->GenerateInterfaceProperties(gt, os, properties);
 

+ 3 - 2
Source/cmGeneratorExpressionDAGChecker.cxx

@@ -40,12 +40,13 @@ cmGeneratorExpressionDAGChecker::cmGeneratorExpressionDAGChecker(
   , Content(content)
   , Backtrace(std::move(backtrace))
 {
-  static_cast<void>(contextConfig);
   if (parent) {
     this->TopIsTransitiveProperty = parent->TopIsTransitiveProperty;
   } else {
     this->TopIsTransitiveProperty =
-      this->Target->IsTransitiveProperty(this->Property, contextLG)
+      this->Target
+        ->IsTransitiveProperty(this->Property, contextLG, contextConfig,
+                               this->EvaluatingLinkLibraries())
         .has_value();
   }
 

+ 7 - 5
Source/cmGeneratorExpressionNode.cxx

@@ -2873,19 +2873,22 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode
       return target->GetLinkerLanguage(context->Config);
     }
 
+    bool const evaluatingLinkLibraries =
+      dagCheckerParent && dagCheckerParent->EvaluatingLinkLibraries();
+
     std::string interfacePropertyName;
     bool isInterfaceProperty = false;
     cmGeneratorTarget::UseTo usage = cmGeneratorTarget::UseTo::Compile;
 
     if (cm::optional<cmGeneratorTarget::TransitiveProperty> transitiveProp =
-          target->IsTransitiveProperty(propertyName, context->LG)) {
+          target->IsTransitiveProperty(propertyName, context->LG,
+                                       context->Config,
+                                       evaluatingLinkLibraries)) {
       interfacePropertyName = std::string(transitiveProp->InterfaceName);
       isInterfaceProperty = transitiveProp->InterfaceName == propertyName;
       usage = transitiveProp->Usage;
     }
 
-    bool evaluatingLinkLibraries = false;
-
     if (dagCheckerParent) {
       // This $<TARGET_PROPERTY:...> node has been reached while evaluating
       // another target property value.  Check that the outermost evaluation
@@ -2894,8 +2897,7 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode
           dagCheckerParent->EvaluatingPICExpression() ||
           dagCheckerParent->EvaluatingLinkerLauncher()) {
         // No check required.
-      } else if (dagCheckerParent->EvaluatingLinkLibraries()) {
-        evaluatingLinkLibraries = true;
+      } else if (evaluatingLinkLibraries) {
         if (!interfacePropertyName.empty()) {
           reportError(
             context, content->GetOriginalExpression(),

+ 2 - 0
Source/cmGeneratorTarget.cxx

@@ -518,6 +518,8 @@ void cmGeneratorTarget::ClearSourcesCache()
   this->IncludeDirectoriesCache.clear();
   this->CompileOptionsCache.clear();
   this->CompileDefinitionsCache.clear();
+  this->CustomTransitiveBuildPropertiesMap.clear();
+  this->CustomTransitiveInterfacePropertiesMap.clear();
   this->PrecompileHeadersCache.clear();
   this->LinkOptionsCache.clear();
   this->LinkDirectoriesCache.clear();

+ 37 - 1
Source/cmGeneratorTarget.h

@@ -907,7 +907,8 @@ public:
     BuiltinTransitiveProperties;
 
   cm::optional<TransitiveProperty> IsTransitiveProperty(
-    cm::string_view prop, cmLocalGenerator const* lg) const;
+    cm::string_view prop, cmLocalGenerator const* lg,
+    std::string const& config, bool evaluatingLinkLibraries) const;
 
   bool HaveInstallTreeRPATH(const std::string& config) const;
 
@@ -989,6 +990,30 @@ public:
   bool DiscoverSyntheticTargets(cmSyntheticTargetCache& cache,
                                 std::string const& config);
 
+  class CustomTransitiveProperty : public TransitiveProperty
+  {
+    std::unique_ptr<std::string> InterfaceNameBuf;
+    CustomTransitiveProperty(std::unique_ptr<std::string> interfaceNameBuf,
+                             UseTo usage);
+
+  public:
+    CustomTransitiveProperty(std::string interfaceName, UseTo usage);
+  };
+  struct CustomTransitiveProperties
+    : public std::map<std::string, CustomTransitiveProperty>
+  {
+    void Add(cmValue props, UseTo usage);
+  };
+
+  enum class PropertyFor
+  {
+    Build,
+    Interface,
+  };
+
+  CustomTransitiveProperties const& GetCustomTransitiveProperties(
+    std::string const& config, PropertyFor propertyFor) const;
+
 private:
   void AddSourceCommon(const std::string& src, bool before = false);
 
@@ -1056,6 +1081,11 @@ private:
                             std::string const& base, std::string const& suffix,
                             std::string const& name, cmValue version) const;
 
+  mutable std::map<std::string, CustomTransitiveProperties>
+    CustomTransitiveBuildPropertiesMap;
+  mutable std::map<std::string, CustomTransitiveProperties>
+    CustomTransitiveInterfacePropertiesMap;
+
   struct CompatibleInterfacesBase
   {
     std::set<std::string> PropsBool;
@@ -1306,6 +1336,12 @@ private:
   void ComputeLinkInterfaceRuntimeLibraries(
     const std::string& config, cmOptionalLinkInterface& iface) const;
 
+  // If this method is made public, or call sites are added outside of
+  // methods computing cached members, add dedicated caching members.
+  std::vector<cmGeneratorTarget const*> GetLinkInterfaceClosure(
+    std::string const& config, cmGeneratorTarget const* headTarget,
+    UseTo usage) const;
+
 public:
   const std::vector<const cmGeneratorTarget*>& GetLinkImplementationClosure(
     const std::string& config, UseTo usage) const;

+ 17 - 0
Source/cmGeneratorTarget_Link.cxx

@@ -282,6 +282,23 @@ static void processILibs(const std::string& config,
   }
 }
 
+std::vector<cmGeneratorTarget const*>
+cmGeneratorTarget::GetLinkInterfaceClosure(std::string const& config,
+                                           cmGeneratorTarget const* headTarget,
+                                           UseTo usage) const
+{
+  cmGlobalGenerator* gg = this->GetLocalGenerator()->GetGlobalGenerator();
+  std::vector<cmGeneratorTarget const*> tgts;
+  std::set<cmGeneratorTarget const*> emitted;
+  if (cmLinkInterfaceLibraries const* iface =
+        this->GetLinkInterfaceLibraries(config, headTarget, usage)) {
+    for (cmLinkItem const& lib : iface->Libraries) {
+      processILibs(config, headTarget, lib, gg, tgts, emitted, usage);
+    }
+  }
+  return tgts;
+}
+
 const std::vector<const cmGeneratorTarget*>&
 cmGeneratorTarget::GetLinkImplementationClosure(const std::string& config,
                                                 UseTo usage) const

+ 88 - 2
Source/cmGeneratorTarget_TransitiveProperty.cxx

@@ -10,6 +10,7 @@
 #include <utility>
 #include <vector>
 
+#include <cm/memory>
 #include <cm/optional>
 #include <cm/string_view>
 #include <cmext/string_view>
@@ -19,6 +20,7 @@
 #include "cmGeneratorExpressionDAGChecker.h"
 #include "cmGeneratorExpressionNode.h"
 #include "cmLinkItem.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmPolicies.h"
 #include "cmStringAlgorithms.h"
@@ -176,11 +178,16 @@ std::string cmGeneratorTarget::EvaluateInterfaceProperty(
 
 cm::optional<cmGeneratorTarget::TransitiveProperty>
 cmGeneratorTarget::IsTransitiveProperty(cm::string_view prop,
-                                        cmLocalGenerator const* lg) const
+                                        cmLocalGenerator const* lg,
+                                        std::string const& config,
+                                        bool evaluatingLinkLibraries) const
 {
   cm::optional<TransitiveProperty> result;
   static const cm::string_view kINTERFACE_ = "INTERFACE_"_s;
-  if (cmHasPrefix(prop, kINTERFACE_)) {
+  PropertyFor const propertyFor = cmHasPrefix(prop, kINTERFACE_)
+    ? PropertyFor::Interface
+    : PropertyFor::Build;
+  if (propertyFor == PropertyFor::Interface) {
     prop = prop.substr(kINTERFACE_.length());
   }
   auto i = BuiltinTransitiveProperties.find(prop);
@@ -202,6 +209,85 @@ cmGeneratorTarget::IsTransitiveProperty(cm::string_view prop,
       result = TransitiveProperty{ "INTERFACE_COMPILE_DEFINITIONS"_s,
                                    UseTo::Compile };
     }
+  } else if (!evaluatingLinkLibraries) {
+    // Honor TRANSITIVE_COMPILE_PROPERTIES
+    // from the link closure when we are not evaluating the closure itself.
+    CustomTransitiveProperties const& ctp =
+      this->GetCustomTransitiveProperties(config, propertyFor);
+    auto ci = ctp.find(std::string(prop));
+    if (ci != ctp.end()) {
+      result = ci->second;
+    }
   }
   return result;
 }
+
+cmGeneratorTarget::CustomTransitiveProperty::CustomTransitiveProperty(
+  std::string interfaceName, UseTo usage)
+  : CustomTransitiveProperty(
+      cm::make_unique<std::string>(std::move(interfaceName)), usage)
+{
+}
+cmGeneratorTarget::CustomTransitiveProperty::CustomTransitiveProperty(
+  std::unique_ptr<std::string> interfaceNameBuf, UseTo usage)
+  : TransitiveProperty{ *interfaceNameBuf, usage }
+  , InterfaceNameBuf(std::move(interfaceNameBuf))
+{
+}
+
+void cmGeneratorTarget::CustomTransitiveProperties::Add(cmValue props,
+                                                        UseTo usage)
+{
+  if (props) {
+    cmList propsList(*props);
+    for (std::string p : propsList) {
+      std::string ip;
+      static const cm::string_view kINTERFACE_ = "INTERFACE_"_s;
+      if (cmHasPrefix(p, kINTERFACE_)) {
+        ip = std::move(p);
+        p = ip.substr(kINTERFACE_.length());
+      } else {
+        ip = cmStrCat(kINTERFACE_, p);
+      }
+      this->emplace(std::move(p),
+                    CustomTransitiveProperty(std::move(ip), usage));
+    }
+  }
+}
+
+cmGeneratorTarget::CustomTransitiveProperties const&
+cmGeneratorTarget::GetCustomTransitiveProperties(std::string const& config,
+                                                 PropertyFor propertyFor) const
+{
+  std::map<std::string, CustomTransitiveProperties>& ctpm =
+    propertyFor == PropertyFor::Build
+    ? this->CustomTransitiveBuildPropertiesMap
+    : this->CustomTransitiveInterfacePropertiesMap;
+  auto i = ctpm.find(config);
+  if (i == ctpm.end()) {
+    CustomTransitiveProperties ctp;
+    auto addTransitiveProperties = [this, &config, propertyFor,
+                                    &ctp](std::string const& tp, UseTo usage) {
+      // Add transitive properties named by the target itself.
+      ctp.Add(this->GetProperty(tp), usage);
+      // Add transitive properties named by the target's link dependencies.
+      if (propertyFor == PropertyFor::Build) {
+        for (cmGeneratorTarget const* gt :
+             this->GetLinkImplementationClosure(config, usage)) {
+          ctp.Add(gt->GetProperty(tp), usage);
+        }
+      } else {
+        // The set of custom transitive INTERFACE_ properties does not
+        // depend on the consumer.  Use the target as its own head.
+        cmGeneratorTarget const* headTarget = this;
+        for (cmGeneratorTarget const* gt :
+             this->GetLinkInterfaceClosure(config, headTarget, usage)) {
+          ctp.Add(gt->GetProperty(tp), usage);
+        }
+      }
+    };
+    addTransitiveProperties("TRANSITIVE_COMPILE_PROPERTIES", UseTo::Compile);
+    i = ctpm.emplace(config, std::move(ctp)).first;
+  }
+  return i->second;
+}

+ 1 - 0
Tests/CMakeLists.txt

@@ -527,6 +527,7 @@ if(BUILD_TESTING)
   set_property(TEST CompileOptions APPEND PROPERTY LABELS "Fortran")
 
   ADD_TEST_MACRO(CompatibleInterface CompatibleInterface)
+  ADD_TEST_MACRO(CustomTransitiveProperties CustomTransitiveProperties)
   ADD_TEST_MACRO(AliasTarget AliasTarget)
   ADD_TEST_MACRO(StagingPrefix StagingPrefix)
   ADD_TEST_MACRO(ImportedSameName ImportedSameName)

+ 100 - 0
Tests/CustomTransitiveProperties/CMakeLists.txt

@@ -0,0 +1,100 @@
+cmake_minimum_required(VERSION 3.29)
+cmake_policy(SET CMP0166 NEW)
+project(CustomTransitiveProperties C)
+
+add_library(iface1 INTERFACE)
+set_target_properties(iface1 PROPERTIES
+  TRANSITIVE_COMPILE_PROPERTIES "CUSTOM_A" # LINK_ONLY not pierced
+
+  INTERFACE_CUSTOM_A "CUSTOM_A_IFACE1;CUSTOM_A_TARGET_NAME_$<UPPER_CASE:$<TARGET_PROPERTY:NAME>>"
+  INTERFACE_CUSTOM_B "CUSTOM_B_IFACE1"
+  INTERFACE_CUSTOM_C "CUSTOM_C_IFACE1"
+  )
+
+add_library(iface2 INTERFACE)
+set_target_properties(iface2 PROPERTIES
+  INTERFACE_CUSTOM_A "CUSTOM_A_IFACE2;CUSTOM_A_TARGET_TYPE_$<TARGET_PROPERTY:TYPE>"
+  )
+target_link_libraries(iface2 INTERFACE iface1)
+
+# Test that the INTERFACE prefix is removed.
+set(unnecessary_INTERFACE_ "INTERFACE_")
+
+add_library(static1 STATIC static1.c)
+target_link_libraries(static1 PRIVATE iface2)
+set_target_properties(static1 PROPERTIES
+  TRANSITIVE_COMPILE_PROPERTIES "${unnecessary_INTERFACE_}CUSTOM_B" # LINK_ONLY not pierced
+
+  CUSTOM_A "CUSTOM_A_STATIC1"
+  CUSTOM_B "CUSTOM_B_STATIC1"
+  INTERFACE_CUSTOM_A "CUSTOM_A_STATIC1_IFACE"
+  INTERFACE_CUSTOM_B "CUSTOM_B_STATIC1_IFACE"
+  )
+target_compile_definitions(static1 PRIVATE
+  $<TARGET_PROPERTY:CUSTOM_A>
+  $<TARGET_PROPERTY:CUSTOM_B>
+  )
+
+add_library(object1 OBJECT object1.c)
+target_link_libraries(object1 PRIVATE iface2)
+set_target_properties(object1 PROPERTIES
+  TRANSITIVE_COMPILE_PROPERTIES "${unnecessary_INTERFACE_}CUSTOM_C" # LINK_ONLY not pierced
+
+  CUSTOM_A "CUSTOM_A_OBJECT1"
+  CUSTOM_C "CUSTOM_C_OBJECT1"
+  INTERFACE_CUSTOM_A "CUSTOM_A_OBJECT1_IFACE"
+  INTERFACE_CUSTOM_C "CUSTOM_C_OBJECT1_IFACE"
+  )
+target_compile_definitions(object1 PRIVATE
+  $<TARGET_PROPERTY:CUSTOM_A>
+  $<TARGET_PROPERTY:CUSTOM_C>
+  )
+
+add_executable(CustomTransitiveProperties main.c)
+target_link_libraries(CustomTransitiveProperties PRIVATE static1 object1)
+set_target_properties(CustomTransitiveProperties PROPERTIES
+  CUSTOM_A "CUSTOM_A_MAIN"
+  CUSTOM_B "CUSTOM_B_MAIN"
+  CUSTOM_C "CUSTOM_C_MAIN"
+  )
+
+# Test TRANSITIVE_*_PROPERTY evaluation within usage requirements.
+target_compile_definitions(CustomTransitiveProperties PRIVATE
+  $<TARGET_PROPERTY:CUSTOM_A>
+  $<TARGET_PROPERTY:CUSTOM_B>
+  $<TARGET_PROPERTY:CUSTOM_C>
+  )
+
+# Test TRANSITIVE_*_PROPERTY evaluation outside of usage requirements.
+set(out "${CMAKE_CURRENT_BINARY_DIR}/out-$<CONFIG>.txt")
+file(GENERATE OUTPUT "${out}" CONTENT "# file(GENERATE) produced:
+iface1 CUSTOM_A: '$<TARGET_PROPERTY:iface1,CUSTOM_A>'
+iface1 INTERFACE_CUSTOM_A: '$<TARGET_PROPERTY:iface1,INTERFACE_CUSTOM_A>'
+iface2 CUSTOM_A: '$<TARGET_PROPERTY:iface2,CUSTOM_A>'
+iface2 INTERFACE_CUSTOM_A: '$<TARGET_PROPERTY:iface2,INTERFACE_CUSTOM_A>'
+static1 CUSTOM_A: '$<TARGET_PROPERTY:static1,CUSTOM_A>'
+static1 INTERFACE_CUSTOM_A: '$<TARGET_PROPERTY:static1,INTERFACE_CUSTOM_A>'
+static1 CUSTOM_B: '$<TARGET_PROPERTY:static1,CUSTOM_B>'
+static1 INTERFACE_CUSTOM_B: '$<TARGET_PROPERTY:static1,INTERFACE_CUSTOM_B>'
+object1 CUSTOM_A: '$<TARGET_PROPERTY:object1,CUSTOM_A>'
+object1 INTERFACE_CUSTOM_A: '$<TARGET_PROPERTY:object1,INTERFACE_CUSTOM_A>'
+object1 CUSTOM_C: '$<TARGET_PROPERTY:object1,CUSTOM_C>'
+object1 INTERFACE_CUSTOM_C: '$<TARGET_PROPERTY:object1,INTERFACE_CUSTOM_C>'
+main CUSTOM_A: '$<TARGET_PROPERTY:CustomTransitiveProperties,CUSTOM_A>'
+main INTERFACE_CUSTOM_A: '$<TARGET_PROPERTY:CustomTransitiveProperties,INTERFACE_CUSTOM_A>'
+main CUSTOM_B: '$<TARGET_PROPERTY:CustomTransitiveProperties,CUSTOM_B>'
+main INTERFACE_CUSTOM_B: '$<TARGET_PROPERTY:CustomTransitiveProperties,INTERFACE_CUSTOM_B>'
+main CUSTOM_C: '$<TARGET_PROPERTY:CustomTransitiveProperties,CUSTOM_C>'
+main INTERFACE_CUSTOM_C: '$<TARGET_PROPERTY:CustomTransitiveProperties,INTERFACE_CUSTOM_C>'
+")
+add_custom_target(check ALL VERBATIM
+  COMMAND ${CMAKE_COMMAND} -Dconfig=$<CONFIG> -Dout=${out} -P${CMAKE_CURRENT_SOURCE_DIR}/check.cmake
+  COMMAND CustomTransitiveProperties
+  "$<TARGET_PROPERTY:static1,CUSTOM_A>" "CUSTOM_A_STATIC1;CUSTOM_A_IFACE2;CUSTOM_A_TARGET_TYPE_STATIC_LIBRARY;CUSTOM_A_IFACE1;CUSTOM_A_TARGET_NAME_STATIC1"
+  "$<TARGET_PROPERTY:static1,CUSTOM_B>" "CUSTOM_B_STATIC1;CUSTOM_B_IFACE1"
+  "$<TARGET_PROPERTY:object1,CUSTOM_A>" "CUSTOM_A_OBJECT1;CUSTOM_A_IFACE2;CUSTOM_A_TARGET_TYPE_OBJECT_LIBRARY;CUSTOM_A_IFACE1;CUSTOM_A_TARGET_NAME_OBJECT1"
+  "$<TARGET_PROPERTY:object1,CUSTOM_C>" "CUSTOM_C_OBJECT1;CUSTOM_C_IFACE1"
+  "$<TARGET_PROPERTY:CustomTransitiveProperties,CUSTOM_A>" "CUSTOM_A_MAIN"
+  "$<TARGET_PROPERTY:CustomTransitiveProperties,CUSTOM_B>" "CUSTOM_B_MAIN;CUSTOM_B_STATIC1_IFACE"
+  "$<TARGET_PROPERTY:CustomTransitiveProperties,CUSTOM_C>" "CUSTOM_C_MAIN;CUSTOM_C_OBJECT1_IFACE"
+  )

+ 33 - 0
Tests/CustomTransitiveProperties/check.cmake

@@ -0,0 +1,33 @@
+set(expect [[
+# file\(GENERATE\) produced:
+iface1 CUSTOM_A: ''
+iface1 INTERFACE_CUSTOM_A: 'CUSTOM_A_IFACE1;CUSTOM_A_TARGET_NAME_IFACE1'
+iface2 CUSTOM_A: ''
+iface2 INTERFACE_CUSTOM_A: 'CUSTOM_A_IFACE2;CUSTOM_A_TARGET_TYPE_INTERFACE_LIBRARY;CUSTOM_A_IFACE1;CUSTOM_A_TARGET_NAME_IFACE2'
+static1 CUSTOM_A: 'CUSTOM_A_STATIC1;CUSTOM_A_IFACE2;CUSTOM_A_TARGET_TYPE_STATIC_LIBRARY;CUSTOM_A_IFACE1;CUSTOM_A_TARGET_NAME_STATIC1'
+static1 INTERFACE_CUSTOM_A: 'CUSTOM_A_STATIC1_IFACE'
+static1 CUSTOM_B: 'CUSTOM_B_STATIC1;CUSTOM_B_IFACE1'
+static1 INTERFACE_CUSTOM_B: 'CUSTOM_B_STATIC1_IFACE'
+object1 CUSTOM_A: 'CUSTOM_A_OBJECT1;CUSTOM_A_IFACE2;CUSTOM_A_TARGET_TYPE_OBJECT_LIBRARY;CUSTOM_A_IFACE1;CUSTOM_A_TARGET_NAME_OBJECT1'
+object1 INTERFACE_CUSTOM_A: 'CUSTOM_A_OBJECT1_IFACE'
+object1 CUSTOM_C: 'CUSTOM_C_OBJECT1;CUSTOM_C_IFACE1'
+object1 INTERFACE_CUSTOM_C: 'CUSTOM_C_OBJECT1_IFACE'
+main CUSTOM_A: 'CUSTOM_A_MAIN'
+main INTERFACE_CUSTOM_A: ''
+main CUSTOM_B: 'CUSTOM_B_MAIN;CUSTOM_B_STATIC1_IFACE'
+main INTERFACE_CUSTOM_B: ''
+main CUSTOM_C: 'CUSTOM_C_MAIN;CUSTOM_C_OBJECT1_IFACE'
+main INTERFACE_CUSTOM_C: ''
+]])
+string(REGEX REPLACE "\r\n" "\n" expect "${expect}")
+string(REGEX REPLACE "\n+$" "" expect "${expect}")
+
+file(READ "${out}" actual)
+string(REGEX REPLACE "\r\n" "\n" actual "${actual}")
+string(REGEX REPLACE "\n+$" "" actual "${actual}")
+
+if(NOT actual MATCHES "^${expect}$")
+  string(REPLACE "\n" "\n expect> " expect " expect> ${expect}")
+  string(REPLACE "\n" "\n actual> " actual " actual> ${actual}")
+  message(FATAL_ERROR "Expected file(GENERATE) output:\n${expect}\ndoes not match actual output:\n${actual}")
+endif()

+ 68 - 0
Tests/CustomTransitiveProperties/main.c

@@ -0,0 +1,68 @@
+#include <stdio.h>
+#include <string.h>
+
+#ifdef CUSTOM_A_IFACE1
+#  error "CUSTOM_A_IFACE1 incorrectly defined"
+#endif
+
+#ifdef CUSTOM_A_IFACE2
+#  error "CUSTOM_A_IFACE2 incorrectly defined"
+#endif
+
+#ifdef CUSTOM_A_STATIC1_IFACE
+#  error "CUSTOM_A_STATIC1_IFACE incorrectly defined"
+#endif
+
+#ifdef CUSTOM_A_OBJECT1_IFACE
+#  error "CUSTOM_A_OBJECT1_IFACE incorrectly defined"
+#endif
+
+#ifndef CUSTOM_A_MAIN
+#  error "CUSTOM_A_MAIN incorrectly not defined"
+#endif
+
+#ifdef CUSTOM_B_IFACE1
+#  error "CUSTOM_B_IFACE1 incorrectly defined"
+#endif
+
+#ifndef CUSTOM_B_STATIC1_IFACE
+#  error "CUSTOM_B_STATIC1_IFACE incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_B_MAIN
+#  error "CUSTOM_B_MAIN incorrectly not defined"
+#endif
+
+#ifdef CUSTOM_C_IFACE1
+#  error "CUSTOM_C_IFACE1 incorrectly defined"
+#endif
+
+#ifndef CUSTOM_C_OBJECT1_IFACE
+#  error "CUSTOM_C_OBJECT1_IFACE incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_C_MAIN
+#  error "CUSTOM_C_MAIN incorrectly not defined"
+#endif
+
+extern int static1(void);
+extern int object1(void);
+
+int check_args(int argc, char** argv)
+{
+  int result = 0;
+  int i;
+  for (i = 2; i < argc; i += 2) {
+    if (strcmp(argv[i - 1], argv[i]) != 0) {
+      fprintf(stderr, "Argument %d expected '%s' but got '%s'.\n", i, argv[i],
+              argv[i - 1]);
+      result = 1;
+    }
+  }
+  return result;
+}
+
+int main(int argc, char** argv)
+{
+  return static1() + object1() + check_args(argc, argv);
+}

+ 36 - 0
Tests/CustomTransitiveProperties/object1.c

@@ -0,0 +1,36 @@
+#ifndef CUSTOM_A_IFACE1
+#  error "CUSTOM_A_IFACE1 incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_A_IFACE2
+#  error "CUSTOM_A_IFACE2 incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_A_OBJECT1
+#  error "CUSTOM_A_OBJECT1 incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_A_TARGET_NAME_OBJECT1
+#  error "CUSTOM_A_TARGET_NAME_OBJECT1 incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_A_TARGET_TYPE_OBJECT_LIBRARY
+#  error "CUSTOM_A_TARGET_TYPE_OBJECT_LIBRARY incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_C_IFACE1
+#  error "CUSTOM_C_IFACE1 incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_C_OBJECT1
+#  error "CUSTOM_C_OBJECT1 incorrectly not defined"
+#endif
+
+#ifdef CUSTOM_C_OBJECT1_IFACE
+#  error "CUSTOM_C_OBJECT1_IFACE incorrectly defined"
+#endif
+
+int object1(void)
+{
+  return 0;
+}

+ 36 - 0
Tests/CustomTransitiveProperties/static1.c

@@ -0,0 +1,36 @@
+#ifndef CUSTOM_A_IFACE1
+#  error "CUSTOM_A_IFACE1 incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_A_IFACE2
+#  error "CUSTOM_A_IFACE2 incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_A_STATIC1
+#  error "CUSTOM_A_STATIC1 incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_A_TARGET_NAME_STATIC1
+#  error "CUSTOM_A_TARGET_NAME_STATIC1 incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_A_TARGET_TYPE_STATIC_LIBRARY
+#  error "CUSTOM_A_TARGET_TYPE_STATIC_LIBRARY incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_B_IFACE1
+#  error "CUSTOM_B_IFACE1 incorrectly not defined"
+#endif
+
+#ifndef CUSTOM_B_STATIC1
+#  error "CUSTOM_B_STATIC1 incorrectly not defined"
+#endif
+
+#ifdef CUSTOM_B_STATIC1_IFACE
+#  error "CUSTOM_B_STATIC1_IFACE incorrectly defined"
+#endif
+
+int static1(void)
+{
+  return 0;
+}

+ 25 - 0
Tests/ExportImport/Export/CMakeLists.txt

@@ -116,6 +116,29 @@ target_link_libraries(testLib9 INTERFACE testLib9ObjIface PUBLIC testLib9ObjPub
 target_link_libraries(testLib9 PUBLIC Foo::Foo)
 cmake_policy(POP)
 
+block()
+  cmake_policy(SET CMP0022 NEW)
+  add_library(testLib10 STATIC testLib10.c)
+  set_target_properties(testLib10 PROPERTIES
+    TRANSITIVE_COMPILE_PROPERTIES "CUSTOM_C"
+    INTERFACE_CUSTOM_C "TESTLIB10_INTERFACE_CUSTOM_C"
+    )
+  target_compile_definitions(testLib10 INTERFACE
+    "$<TARGET_PROPERTY:CUSTOM_C>"
+    )
+  add_library(testLib11 STATIC testLib11.c)
+  target_link_libraries(testLib11 PRIVATE testLib10)
+  set_target_properties(testLib11 PROPERTIES
+    INTERFACE_CUSTOM_C "TESTLIB11_INTERFACE_CUSTOM_C"
+    TRANSITIVE_COMPILE_PROPERTIES "CUSTOM_D"
+    INTERFACE_CUSTOM_D "TESTLIB11_INTERFACE_CUSTOM_D"
+    )
+  target_compile_definitions(testLib11 INTERFACE
+    "$<TARGET_PROPERTY:CUSTOM_C>"
+    "$<TARGET_PROPERTY:CUSTOM_D>"
+    )
+endblock()
+
 # Test using the target_link_libraries command to set the
 # LINK_INTERFACE_LIBRARIES* properties.  We construct two libraries
 # providing the same two symbols.  In each library one of the symbols
@@ -574,6 +597,7 @@ install(
   testExe2lib testLib4lib testLib4libdbg testLib4libopt
   testLib6 testLib7 testLib8
   testLib9
+  testLib10 testLib11
   testLibDeprecation
   testLibCycleA testLibCycleB
   testLibNoSONAME
@@ -653,6 +677,7 @@ export(TARGETS testExe1 testLib1 testLib2 testLib3
 export(TARGETS testExe2 testLib4 testLib5 testLib6 testLib7 testExe3 testExe4 testExe2lib
   testLib8
   testLib9 testLib9ObjPub testLib9ObjPriv testLib9ObjIface
+  testLib10 testLib11
   testLibDeprecation
   testLib4lib testLib4libdbg testLib4libopt
   testLibCycleA testLibCycleB

+ 4 - 0
Tests/ExportImport/Export/testLib10.c

@@ -0,0 +1,4 @@
+int testLib10(void)
+{
+  return 0;
+}

+ 6 - 0
Tests/ExportImport/Export/testLib11.c

@@ -0,0 +1,6 @@
+int testLib10(void);
+
+int testLib11(void)
+{
+  return testLib10();
+}

+ 10 - 0
Tests/ExportImport/Import/A/CMakeLists.txt

@@ -328,6 +328,16 @@ foreach(vis Pub Priv Iface)
   endif()
 endforeach()
 
+# Create executables to verify custom transitive properties.
+add_executable(imp_testLib10 imp_testLib10.c)
+target_link_libraries(imp_testLib10 PRIVATE exp_testLib10)
+add_executable(imp_testLib10b imp_testLib10.c)
+target_link_libraries(imp_testLib10b PRIVATE bld_testLib10)
+add_executable(imp_testLib11 imp_testLib11.c)
+target_link_libraries(imp_testLib11 PRIVATE exp_testLib11)
+add_executable(imp_testLib11b imp_testLib11.c)
+target_link_libraries(imp_testLib11b PRIVATE bld_testLib11)
+
 #-----------------------------------------------------------------------------
 # Test that handling imported targets, including transitive dependencies,
 # works in CheckFunctionExists (...and hopefully all other try_compile() checks

+ 10 - 0
Tests/ExportImport/Import/A/imp_testLib10.c

@@ -0,0 +1,10 @@
+#ifndef TESTLIB10_INTERFACE_CUSTOM_C
+#  error "TESTLIB10_INTERFACE_CUSTOM_C incorrectly not defined!"
+#endif
+
+int testLib10(void);
+
+int main(void)
+{
+  return testLib10();
+}

+ 18 - 0
Tests/ExportImport/Import/A/imp_testLib11.c

@@ -0,0 +1,18 @@
+#ifdef TESTLIB10_INTERFACE_CUSTOM_C
+#  error "TESTLIB10_INTERFACE_CUSTOM_C incorrectly defined!"
+#endif
+
+#ifdef TESTLIB11_INTERFACE_CUSTOM_C
+#  error "TESTLIB11_INTERFACE_CUSTOM_C incorrectly defined!"
+#endif
+
+#ifndef TESTLIB11_INTERFACE_CUSTOM_D
+#  error "TESTLIB11_INTERFACE_CUSTOM_D incorrectly not defined!"
+#endif
+
+int testLib11(void);
+
+int main(void)
+{
+  return testLib11();
+}