瀏覽代碼

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 年之前
父節點
當前提交
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
   List of files on which linking the target's consumers depends, for
   those that are executables, shared libraries, or module libraries.
   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`:
 
 
 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.
   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`
 :ref:`Compatible Interface Properties`
   These evaluate as a single value combined from the target itself,
   These evaluate as a single value combined from the target itself,
   from targets named by the target's :prop_tgt:`LINK_LIBRARIES`, and
   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/Swift_MODULE_NAME
    /prop_tgt/SYSTEM
    /prop_tgt/SYSTEM
    /prop_tgt/TEST_LAUNCHER
    /prop_tgt/TEST_LAUNCHER
+   /prop_tgt/TRANSITIVE_COMPILE_PROPERTIES
    /prop_tgt/TYPE
    /prop_tgt/TYPE
    /prop_tgt/UNITY_BUILD
    /prop_tgt/UNITY_BUILD
    /prop_tgt/UNITY_BUILD_BATCH_SIZE
    /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);
         gte, cmGeneratorExpression::BuildInterface, properties);
     }
     }
     this->PopulateCompatibleInterfaceProperties(gte, properties);
     this->PopulateCompatibleInterfaceProperties(gte, properties);
+    this->PopulateCustomTransitiveInterfaceProperties(
+      gte, cmGeneratorExpression::BuildInterface, properties);
 
 
     this->GenerateInterfaceProperties(gte, os, 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(
 void cmExportFileGenerator::GenerateInterfaceProperties(
   const cmGeneratorTarget* target, std::ostream& os,
   const cmGeneratorTarget* target, std::ostream& os,
   const ImportPropertyMap& properties)
   const ImportPropertyMap& properties)

+ 4 - 0
Source/cmExportFileGenerator.h

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

+ 2 - 0
Source/cmExportInstallFileGenerator.cxx

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

+ 3 - 2
Source/cmGeneratorExpressionDAGChecker.cxx

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

+ 7 - 5
Source/cmGeneratorExpressionNode.cxx

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

+ 2 - 0
Source/cmGeneratorTarget.cxx

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

+ 37 - 1
Source/cmGeneratorTarget.h

@@ -907,7 +907,8 @@ public:
     BuiltinTransitiveProperties;
     BuiltinTransitiveProperties;
 
 
   cm::optional<TransitiveProperty> IsTransitiveProperty(
   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;
   bool HaveInstallTreeRPATH(const std::string& config) const;
 
 
@@ -989,6 +990,30 @@ public:
   bool DiscoverSyntheticTargets(cmSyntheticTargetCache& cache,
   bool DiscoverSyntheticTargets(cmSyntheticTargetCache& cache,
                                 std::string const& config);
                                 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:
 private:
   void AddSourceCommon(const std::string& src, bool before = false);
   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& base, std::string const& suffix,
                             std::string const& name, cmValue version) const;
                             std::string const& name, cmValue version) const;
 
 
+  mutable std::map<std::string, CustomTransitiveProperties>
+    CustomTransitiveBuildPropertiesMap;
+  mutable std::map<std::string, CustomTransitiveProperties>
+    CustomTransitiveInterfacePropertiesMap;
+
   struct CompatibleInterfacesBase
   struct CompatibleInterfacesBase
   {
   {
     std::set<std::string> PropsBool;
     std::set<std::string> PropsBool;
@@ -1306,6 +1336,12 @@ private:
   void ComputeLinkInterfaceRuntimeLibraries(
   void ComputeLinkInterfaceRuntimeLibraries(
     const std::string& config, cmOptionalLinkInterface& iface) const;
     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:
 public:
   const std::vector<const cmGeneratorTarget*>& GetLinkImplementationClosure(
   const std::vector<const cmGeneratorTarget*>& GetLinkImplementationClosure(
     const std::string& config, UseTo usage) const;
     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*>&
 const std::vector<const cmGeneratorTarget*>&
 cmGeneratorTarget::GetLinkImplementationClosure(const std::string& config,
 cmGeneratorTarget::GetLinkImplementationClosure(const std::string& config,
                                                 UseTo usage) const
                                                 UseTo usage) const

+ 88 - 2
Source/cmGeneratorTarget_TransitiveProperty.cxx

@@ -10,6 +10,7 @@
 #include <utility>
 #include <utility>
 #include <vector>
 #include <vector>
 
 
+#include <cm/memory>
 #include <cm/optional>
 #include <cm/optional>
 #include <cm/string_view>
 #include <cm/string_view>
 #include <cmext/string_view>
 #include <cmext/string_view>
@@ -19,6 +20,7 @@
 #include "cmGeneratorExpressionDAGChecker.h"
 #include "cmGeneratorExpressionDAGChecker.h"
 #include "cmGeneratorExpressionNode.h"
 #include "cmGeneratorExpressionNode.h"
 #include "cmLinkItem.h"
 #include "cmLinkItem.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalGenerator.h"
 #include "cmPolicies.h"
 #include "cmPolicies.h"
 #include "cmStringAlgorithms.h"
 #include "cmStringAlgorithms.h"
@@ -176,11 +178,16 @@ std::string cmGeneratorTarget::EvaluateInterfaceProperty(
 
 
 cm::optional<cmGeneratorTarget::TransitiveProperty>
 cm::optional<cmGeneratorTarget::TransitiveProperty>
 cmGeneratorTarget::IsTransitiveProperty(cm::string_view prop,
 cmGeneratorTarget::IsTransitiveProperty(cm::string_view prop,
-                                        cmLocalGenerator const* lg) const
+                                        cmLocalGenerator const* lg,
+                                        std::string const& config,
+                                        bool evaluatingLinkLibraries) const
 {
 {
   cm::optional<TransitiveProperty> result;
   cm::optional<TransitiveProperty> result;
   static const cm::string_view kINTERFACE_ = "INTERFACE_"_s;
   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());
     prop = prop.substr(kINTERFACE_.length());
   }
   }
   auto i = BuiltinTransitiveProperties.find(prop);
   auto i = BuiltinTransitiveProperties.find(prop);
@@ -202,6 +209,85 @@ cmGeneratorTarget::IsTransitiveProperty(cm::string_view prop,
       result = TransitiveProperty{ "INTERFACE_COMPILE_DEFINITIONS"_s,
       result = TransitiveProperty{ "INTERFACE_COMPILE_DEFINITIONS"_s,
                                    UseTo::Compile };
                                    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;
   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")
   set_property(TEST CompileOptions APPEND PROPERTY LABELS "Fortran")
 
 
   ADD_TEST_MACRO(CompatibleInterface CompatibleInterface)
   ADD_TEST_MACRO(CompatibleInterface CompatibleInterface)
+  ADD_TEST_MACRO(CustomTransitiveProperties CustomTransitiveProperties)
   ADD_TEST_MACRO(AliasTarget AliasTarget)
   ADD_TEST_MACRO(AliasTarget AliasTarget)
   ADD_TEST_MACRO(StagingPrefix StagingPrefix)
   ADD_TEST_MACRO(StagingPrefix StagingPrefix)
   ADD_TEST_MACRO(ImportedSameName ImportedSameName)
   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)
 target_link_libraries(testLib9 PUBLIC Foo::Foo)
 cmake_policy(POP)
 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
 # Test using the target_link_libraries command to set the
 # LINK_INTERFACE_LIBRARIES* properties.  We construct two libraries
 # LINK_INTERFACE_LIBRARIES* properties.  We construct two libraries
 # providing the same two symbols.  In each library one of the symbols
 # providing the same two symbols.  In each library one of the symbols
@@ -574,6 +597,7 @@ install(
   testExe2lib testLib4lib testLib4libdbg testLib4libopt
   testExe2lib testLib4lib testLib4libdbg testLib4libopt
   testLib6 testLib7 testLib8
   testLib6 testLib7 testLib8
   testLib9
   testLib9
+  testLib10 testLib11
   testLibDeprecation
   testLibDeprecation
   testLibCycleA testLibCycleB
   testLibCycleA testLibCycleB
   testLibNoSONAME
   testLibNoSONAME
@@ -653,6 +677,7 @@ export(TARGETS testExe1 testLib1 testLib2 testLib3
 export(TARGETS testExe2 testLib4 testLib5 testLib6 testLib7 testExe3 testExe4 testExe2lib
 export(TARGETS testExe2 testLib4 testLib5 testLib6 testLib7 testExe3 testExe4 testExe2lib
   testLib8
   testLib8
   testLib9 testLib9ObjPub testLib9ObjPriv testLib9ObjIface
   testLib9 testLib9ObjPub testLib9ObjPriv testLib9ObjIface
+  testLib10 testLib11
   testLibDeprecation
   testLibDeprecation
   testLib4lib testLib4libdbg testLib4libopt
   testLib4lib testLib4libdbg testLib4libopt
   testLibCycleA testLibCycleB
   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()
   endif()
 endforeach()
 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,
 # Test that handling imported targets, including transitive dependencies,
 # works in CheckFunctionExists (...and hopefully all other try_compile() checks
 # 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();
+}