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
This commit is contained in:
Brad King
2024-05-09 13:38:35 -04:00
parent 633afa0b2e
commit b9ee79b8a1
27 changed files with 643 additions and 10 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()) { } else if (evaluatingLinkLibraries) {
evaluatingLinkLibraries = true;
if (!interfacePropertyName.empty()) { if (!interfacePropertyName.empty()) {
reportError( reportError(
context, content->GetOriginalExpression(), context, content->GetOriginalExpression(),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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