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

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

View File

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

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);
}
this->PopulateCompatibleInterfaceProperties(gte, properties);
this->PopulateCustomTransitiveInterfaceProperties(
gte, cmGeneratorExpression::BuildInterface, 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(
const cmGeneratorTarget* target, std::ostream& os,
const ImportPropertyMap& properties)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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*>&
cmGeneratorTarget::GetLinkImplementationClosure(const std::string& config,
UseTo usage) const

View File

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

View File

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

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

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

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