diff options
author | Brad King <brad.king@kitware.com> | 2024-05-09 17:38:35 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2024-05-21 13:22:51 (GMT) |
commit | b9ee79b8a13abb957a176ff0b5eab1e5d33efc50 (patch) | |
tree | b3cb6301b38f03f953dcea719fa1dec800022ada | |
parent | 633afa0b2e27a6eca3a4b1e123a80cf4338fe509 (diff) | |
download | CMake-b9ee79b8a13abb957a176ff0b5eab1e5d33efc50.zip CMake-b9ee79b8a13abb957a176ff0b5eab1e5d33efc50.tar.gz CMake-b9ee79b8a13abb957a176ff0b5eab1e5d33efc50.tar.bz2 |
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
27 files changed, 643 insertions, 10 deletions
diff --git a/Help/manual/cmake-buildsystem.7.rst b/Help/manual/cmake-buildsystem.7.rst index 93d55c7..4dc2306 100644 --- a/Help/manual/cmake-buildsystem.7.rst +++ b/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 diff --git a/Help/manual/cmake-generator-expressions.7.rst b/Help/manual/cmake-generator-expressions.7.rst index d9b10b8..d0d751b 100644 --- a/Help/manual/cmake-generator-expressions.7.rst +++ b/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 diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst index 7e640df..48e1d54 100644 --- a/Help/manual/cmake-properties.7.rst +++ b/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 diff --git a/Help/prop_tgt/TRANSITIVE_COMPILE_PROPERTIES.rst b/Help/prop_tgt/TRANSITIVE_COMPILE_PROPERTIES.rst new file mode 100644 index 0000000..a7777c3 --- /dev/null +++ b/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. diff --git a/Help/release/dev/custom-transitive-properties.rst b/Help/release/dev/custom-transitive-properties.rst new file mode 100644 index 0000000..aafa2f2 --- /dev/null +++ b/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. diff --git a/Source/cmExportBuildFileGenerator.cxx b/Source/cmExportBuildFileGenerator.cxx index 2345d64..d877d76 100644 --- a/Source/cmExportBuildFileGenerator.cxx +++ b/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); diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx index 9bd7f49..a7d0d7e 100644 --- a/Source/cmExportFileGenerator.cxx +++ b/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) diff --git a/Source/cmExportFileGenerator.h b/Source/cmExportFileGenerator.h index f619576..f275a12 100644 --- a/Source/cmExportFileGenerator.h +++ b/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); diff --git a/Source/cmExportInstallFileGenerator.cxx b/Source/cmExportInstallFileGenerator.cxx index 5c95ecd..f5f22ef 100644 --- a/Source/cmExportInstallFileGenerator.cxx +++ b/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); diff --git a/Source/cmGeneratorExpressionDAGChecker.cxx b/Source/cmGeneratorExpressionDAGChecker.cxx index 1ae422e..aad25f0 100644 --- a/Source/cmGeneratorExpressionDAGChecker.cxx +++ b/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(); } diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx index e07f26d..01cd18d 100644 --- a/Source/cmGeneratorExpressionNode.cxx +++ b/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(), diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index 55f34f8..aa3e36f 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/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(); diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h index 3090a5b..8f27a91 100644 --- a/Source/cmGeneratorTarget.h +++ b/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; diff --git a/Source/cmGeneratorTarget_Link.cxx b/Source/cmGeneratorTarget_Link.cxx index 7c6f7b5..0799429 100644 --- a/Source/cmGeneratorTarget_Link.cxx +++ b/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 diff --git a/Source/cmGeneratorTarget_TransitiveProperty.cxx b/Source/cmGeneratorTarget_TransitiveProperty.cxx index 2cd3665..d63d11c 100644 --- a/Source/cmGeneratorTarget_TransitiveProperty.cxx +++ b/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; +} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 9ff38bf..853380b 100644 --- a/Tests/CMakeLists.txt +++ b/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) diff --git a/Tests/CustomTransitiveProperties/CMakeLists.txt b/Tests/CustomTransitiveProperties/CMakeLists.txt new file mode 100644 index 0000000..219ec12 --- /dev/null +++ b/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" + ) diff --git a/Tests/CustomTransitiveProperties/check.cmake b/Tests/CustomTransitiveProperties/check.cmake new file mode 100644 index 0000000..2d024ce --- /dev/null +++ b/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() diff --git a/Tests/CustomTransitiveProperties/main.c b/Tests/CustomTransitiveProperties/main.c new file mode 100644 index 0000000..9ce600c --- /dev/null +++ b/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); +} diff --git a/Tests/CustomTransitiveProperties/object1.c b/Tests/CustomTransitiveProperties/object1.c new file mode 100644 index 0000000..dcc5224 --- /dev/null +++ b/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; +} diff --git a/Tests/CustomTransitiveProperties/static1.c b/Tests/CustomTransitiveProperties/static1.c new file mode 100644 index 0000000..857595b --- /dev/null +++ b/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; +} diff --git a/Tests/ExportImport/Export/CMakeLists.txt b/Tests/ExportImport/Export/CMakeLists.txt index 0b7f739..20e7c08 100644 --- a/Tests/ExportImport/Export/CMakeLists.txt +++ b/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 diff --git a/Tests/ExportImport/Export/testLib10.c b/Tests/ExportImport/Export/testLib10.c new file mode 100644 index 0000000..d5ecb7f --- /dev/null +++ b/Tests/ExportImport/Export/testLib10.c @@ -0,0 +1,4 @@ +int testLib10(void) +{ + return 0; +} diff --git a/Tests/ExportImport/Export/testLib11.c b/Tests/ExportImport/Export/testLib11.c new file mode 100644 index 0000000..b288b29 --- /dev/null +++ b/Tests/ExportImport/Export/testLib11.c @@ -0,0 +1,6 @@ +int testLib10(void); + +int testLib11(void) +{ + return testLib10(); +} diff --git a/Tests/ExportImport/Import/A/CMakeLists.txt b/Tests/ExportImport/Import/A/CMakeLists.txt index 2a57633..632825d 100644 --- a/Tests/ExportImport/Import/A/CMakeLists.txt +++ b/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 diff --git a/Tests/ExportImport/Import/A/imp_testLib10.c b/Tests/ExportImport/Import/A/imp_testLib10.c new file mode 100644 index 0000000..bac772c --- /dev/null +++ b/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(); +} diff --git a/Tests/ExportImport/Import/A/imp_testLib11.c b/Tests/ExportImport/Import/A/imp_testLib11.c new file mode 100644 index 0000000..a17d7e7 --- /dev/null +++ b/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(); +} |