Genex: Optimize usage requirement TARGET_PROPERTY recursion

In large projects the generation process spends a lot of time evaluating
usage requirements through transitive interface properties on targets.
This can be seen in a contrived example with deep dependencies:

    set(prev "")
    foreach(i RANGE 1 500)
      add_library(a${i} a.c)
      target_compile_definitions(a${i} PUBLIC A${i})
      target_link_libraries(a${i} PUBLIC ${prev})
      set(prev a${i})
    endforeach()

For each usage requirement (such as `INTERFACE_COMPILE_DEFINITIONS` or
`INTERFACE_INCLUDE_DIRECTORIES`), the value of the generator expression
`$<TARGET_PROPERTY:target,prop>` includes the values of the same
property from the transitive closure of link libraries of the target.

Previously we computed this by constructing a generator expression
string like `$<TARGET_PROPERTY:lib,INTERFACE_COMPILE_DEFINITIONS>` and
recursively evaluating it with the generator expression engine.  Avoid
the string construction and parsing by creating and using a dedicated
evaluation method `cmGeneratorTarget::EvaluateInterfaceProperty` that
looks up the properties directly.

Issue: #18964, #18965
This commit is contained in:
Brad King 2019-07-21 07:57:08 -04:00
parent 0239bf8ac8
commit 11fa818ecd
3 changed files with 95 additions and 22 deletions

View File

@ -1243,6 +1243,11 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode
}
}
if (isInterfaceProperty) {
return target->EvaluateInterfaceProperty(propertyName, context,
dagCheckerParent);
}
cmGeneratorExpressionDAGChecker dagChecker(
context->Backtrace, target, propertyName, content, dagCheckerParent);
@ -1254,10 +1259,8 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode
// No error. We just skip cyclic references.
return std::string();
case cmGeneratorExpressionDAGChecker::ALREADY_SEEN:
if (isInterfaceProperty) {
// No error. We're not going to find anything new here.
return std::string();
}
// We handle transitive properties above. For non-transitive
// properties we accept repeats anyway.
case cmGeneratorExpressionDAGChecker::DAG:
break;
}
@ -1328,27 +1331,15 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode
}
if (!interfacePropertyName.empty()) {
cmGeneratorTarget const* headTarget =
context->HeadTarget && isInterfaceProperty ? context->HeadTarget
: target;
cmGeneratorTarget const* headTarget = target;
result = this->EvaluateDependentExpression(
result, context->LG, context, headTarget, target, &dagChecker);
std::string linkedTargetsContent;
if (isInterfaceProperty) {
if (cmLinkInterfaceLibraries const* iface =
target->GetLinkInterfaceLibraries(context->Config, headTarget,
true)) {
linkedTargetsContent = getLinkedTargetsContent(
iface->Libraries, target, headTarget, context, &dagChecker,
interfacePropertyName);
}
} else {
if (cmLinkImplementationLibraries const* impl =
target->GetLinkImplementationLibraries(context->Config)) {
linkedTargetsContent =
getLinkedTargetsContent(impl->Libraries, target, target, context,
&dagChecker, interfacePropertyName);
}
if (cmLinkImplementationLibraries const* impl =
target->GetLinkImplementationLibraries(context->Config)) {
linkedTargetsContent =
getLinkedTargetsContent(impl->Libraries, target, target, context,
&dagChecker, interfacePropertyName);
}
if (!linkedTargetsContent.empty()) {
result += (result.empty() ? "" : ";") + linkedTargetsContent;

View File

@ -22,7 +22,9 @@
#include "cmCustomCommandGenerator.h"
#include "cmCustomCommandLines.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorExpressionContext.h"
#include "cmGeneratorExpressionDAGChecker.h"
#include "cmGeneratorExpressionNode.h"
#include "cmGlobalGenerator.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
@ -1141,6 +1143,79 @@ bool cmGeneratorTarget::GetPropertyAsBool(const std::string& prop) const
return this->Target->GetPropertyAsBool(prop);
}
std::string cmGeneratorTarget::EvaluateInterfaceProperty(
std::string const& prop, cmGeneratorExpressionContext* context,
cmGeneratorExpressionDAGChecker* dagCheckerParent) const
{
std::string result;
// Evaluate $<TARGET_PROPERTY:this,prop> as if it were compiled. This is
// a subset of TargetPropertyNode::Evaluate without stringify/parse steps
// but sufficient for transitive interface properties.
cmGeneratorExpressionDAGChecker dagChecker(context->Backtrace, this, prop,
nullptr, dagCheckerParent);
switch (dagChecker.Check()) {
case cmGeneratorExpressionDAGChecker::SELF_REFERENCE:
dagChecker.ReportError(
context, "$<TARGET_PROPERTY:" + this->GetName() + "," + prop + ">");
return result;
case cmGeneratorExpressionDAGChecker::CYCLIC_REFERENCE:
// No error. We just skip cyclic references.
return result;
case cmGeneratorExpressionDAGChecker::ALREADY_SEEN:
// No error. We have already seen this transitive property.
return result;
case cmGeneratorExpressionDAGChecker::DAG:
break;
}
cmGeneratorTarget const* headTarget =
context->HeadTarget ? context->HeadTarget : this;
if (const char* p = this->GetProperty(prop)) {
result = cmGeneratorExpressionNode::EvaluateDependentExpression(
p, context->LG, context, headTarget, this, &dagChecker);
}
if (cmLinkInterfaceLibraries const* iface =
this->GetLinkInterfaceLibraries(context->Config, headTarget, true)) {
for (cmLinkItem const& lib : iface->Libraries) {
// Broken code can have a target in its own link interface.
// Don't follow such link interface entries so as not to create a
// self-referencing loop.
if (lib.Target && lib.Target != this) {
// Pretend $<TARGET_PROPERTY:lib.Target,prop> appeared in the
// above property and hand-evaluate it as if it were compiled.
// Create a context as cmCompiledGeneratorExpression::Evaluate does.
cmGeneratorExpressionContext libContext(
context->LG, context->Config, context->Quiet, headTarget, this,
context->EvaluateForBuildsystem, context->Backtrace,
context->Language);
std::string libResult = cmGeneratorExpression::StripEmptyListElements(
lib.Target->EvaluateInterfaceProperty(prop, &libContext,
&dagChecker));
if (!libResult.empty()) {
if (result.empty()) {
result = std::move(libResult);
} else {
result.reserve(result.size() + 1 + libResult.size());
result += ";";
result += libResult;
}
}
context->HadContextSensitiveCondition =
context->HadContextSensitiveCondition ||
libContext.HadContextSensitiveCondition;
context->HadHeadSensitiveCondition =
context->HadHeadSensitiveCondition ||
libContext.HadHeadSensitiveCondition;
}
}
}
return result;
}
namespace {
void AddInterfaceEntries(cmGeneratorTarget const* headTarget,
std::string const& config, std::string const& prop,

View File

@ -25,6 +25,9 @@ class cmMakefile;
class cmSourceFile;
class cmTarget;
struct cmGeneratorExpressionContext;
struct cmGeneratorExpressionDAGChecker;
class cmGeneratorTarget
{
public:
@ -674,6 +677,10 @@ public:
class TargetPropertyEntry;
std::string EvaluateInterfaceProperty(
std::string const& prop, cmGeneratorExpressionContext* context,
cmGeneratorExpressionDAGChecker* dagCheckerParent) const;
bool HaveInstallTreeRPATH(const std::string& config) const;
bool GetBuildRPATH(const std::string& config, std::string& rpath) const;