Merge topic 'interface-includes-defines'

894f52f Handle INTERFACE properties transitively for includes and defines.
f5b1980 Populate the ExportedTargets member early in GenerateMainFile
c67b812 Make cycles in target properties ignored, not an error.
d0f950f Use mapped config properties to evaluate $<CONFIG>
26def17 Make all relevant targets available in the genex context.
0c657dc Add API to populate INTERFACE properties in exported targets.
e04f737 Add API to extract target names from a genex string.
b0c8f73 Add the TARGET_NAME generator expression.
77475fe Allow generator expressions to require literals.
b2f1700 GenEx: Add expressions to specify build- or install-only values
This commit is contained in:
Brad King 2013-01-07 14:20:13 -05:00 committed by CMake Topic Stage
commit db925e3532
26 changed files with 892 additions and 193 deletions

View File

@ -26,6 +26,16 @@
"strings which contain a '>' for example.\n" \
" $<COMMA> = A literal ','. Used to compare " \
"strings which contain a ',' for example.\n" \
" $<TARGET_NAME:...> = Marks ... as being the name of a " \
"target. This is required if exporting targets to multiple " \
"dependent export sets. The '...' must be a literal name of a " \
"target- it may not contain generator expressions.\n" \
" $<INSTALL_INTERFACE:...> = content of \"...\" when the property " \
"is exported using install(EXPORT), and empty otherwise.\n" \
" $<BUILD_INTERFACE:...> = content of \"...\" when the property " \
"is exported using export(), or when the target is used by another " \
"target in the same buildsystem. Expands to the empty string " \
"otherwise.\n" \
" $<TARGET_FILE:tgt> = main file (.exe, .so.1.2, .a)\n" \
" $<TARGET_LINKER_FILE:tgt> = file used to link (.a, .lib, .so)\n" \
" $<TARGET_SONAME_FILE:tgt> = file with soname (.so.3)\n" \

View File

@ -22,6 +22,7 @@ cmExportBuildFileGenerator::cmExportBuildFileGenerator()
//----------------------------------------------------------------------------
bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
{
std::vector<cmTarget*> allTargets;
{
std::string expectedTargets;
std::string sep;
@ -31,20 +32,10 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
{
expectedTargets += sep + this->Namespace + (*tei)->GetName();
sep = " ";
}
this->GenerateExpectedTargetsCode(os, expectedTargets);
}
// Create all the imported targets.
for(std::vector<cmTarget*>::const_iterator
tei = this->Exports->begin();
tei != this->Exports->end(); ++tei)
{
cmTarget* te = *tei;
if(this->ExportedTargets.insert(te).second)
{
this->GenerateImportTargetCode(os, te);
allTargets.push_back(te);
}
else
{
@ -58,6 +49,33 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
}
}
this->GenerateExpectedTargetsCode(os, expectedTargets);
}
std::vector<std::string> missingTargets;
// Create all the imported targets.
for(std::vector<cmTarget*>::const_iterator
tei = allTargets.begin();
tei != allTargets.end(); ++tei)
{
cmTarget* te = *tei;
this->GenerateImportTargetCode(os, te);
ImportPropertyMap properties;
this->PopulateInterfaceProperty("INTERFACE_INCLUDE_DIRECTORIES", te,
cmGeneratorExpression::BuildInterface,
properties, missingTargets);
this->PopulateInterfaceProperty("INTERFACE_COMPILE_DEFINITIONS", te,
cmGeneratorExpression::BuildInterface,
properties, missingTargets);
this->GenerateInterfaceProperties(te, os, properties);
}
this->GenerateMissingTargetsCheckCode(os, missingTargets);
// Generate import file content for each configuration.
for(std::vector<std::string>::const_iterator
ci = this->Configurations.begin();

View File

@ -124,6 +124,201 @@ void cmExportFileGenerator::GenerateImportConfig(std::ostream& os,
this->GenerateImportTargetsConfig(os, config, suffix);
}
//----------------------------------------------------------------------------
void cmExportFileGenerator::PopulateInterfaceProperty(const char *propName,
const char *outputName,
cmTarget *target,
cmGeneratorExpression::PreprocessContext preprocessRule,
ImportPropertyMap &properties,
std::vector<std::string> &missingTargets)
{
const char *input = target->GetProperty(propName);
if (input)
{
if (!*input)
{
// Set to empty
properties[outputName] = "";
return;
}
std::string prepro = cmGeneratorExpression::Preprocess(input,
preprocessRule);
if (!prepro.empty())
{
this->ResolveTargetsInGeneratorExpressions(prepro, target,
missingTargets);
properties[outputName] = prepro;
}
}
}
//----------------------------------------------------------------------------
void cmExportFileGenerator::PopulateInterfaceProperty(const char *propName,
cmTarget *target,
cmGeneratorExpression::PreprocessContext preprocessRule,
ImportPropertyMap &properties,
std::vector<std::string> &missingTargets)
{
this->PopulateInterfaceProperty(propName, propName, target, preprocessRule,
properties, missingTargets);
}
//----------------------------------------------------------------------------
void cmExportFileGenerator::GenerateInterfaceProperties(cmTarget *target,
std::ostream& os,
const ImportPropertyMap &properties)
{
if (!properties.empty())
{
std::string targetName = this->Namespace;
targetName += target->GetName();
os << "SET_TARGET_PROPERTIES(" << targetName << " PROPERTIES\n";
for(ImportPropertyMap::const_iterator pi = properties.begin();
pi != properties.end(); ++pi)
{
os << " " << pi->first << " \"" << pi->second << "\"\n";
}
os << ")\n\n";
}
}
//----------------------------------------------------------------------------
void
cmExportFileGenerator::ResolveTargetsInGeneratorExpressions(
std::string &input,
cmTarget* target,
std::vector<std::string> &missingTargets)
{
std::string::size_type pos = 0;
std::string::size_type lastPos = pos;
cmMakefile *mf = target->GetMakefile();
std::string errorString;
while((pos = input.find("$<TARGET_PROPERTY:", lastPos)) != input.npos)
{
std::string::size_type nameStartPos = pos +
sizeof("$<TARGET_PROPERTY:") - 1;
std::string::size_type closePos = input.find(">", nameStartPos);
std::string::size_type commaPos = input.find(",", nameStartPos);
std::string::size_type nextOpenPos = input.find("$<", nameStartPos);
if (commaPos == input.npos // Implied 'this' target
|| closePos == input.npos // Imcomplete expression.
|| closePos < commaPos // Implied 'this' target
|| nextOpenPos < commaPos) // Non-literal
{
lastPos = nameStartPos;
continue;
}
const std::string targetName = input.substr(nameStartPos,
commaPos - nameStartPos);
pos = nameStartPos; // We're not going to replace the entire expression,
// but only the target parameter.
if (cmTarget *tgt = mf->FindTargetToUse(targetName.c_str()))
{
if(tgt->IsImported())
{
pos += targetName.size();
}
else if(this->ExportedTargets.find(tgt) != this->ExportedTargets.end())
{
input.replace(pos, targetName.size(),
this->Namespace + targetName);
pos += this->Namespace.size() + targetName.size();
}
else
{
std::string namespacedTarget;
this->HandleMissingTarget(namespacedTarget, missingTargets,
mf, target, tgt);
if (!namespacedTarget.empty())
{
input.replace(pos, targetName.size(), namespacedTarget);
pos += namespacedTarget.size();
}
}
}
else
{
errorString = "$<TARGET_PROPERTY:" + targetName + ",prop> requires "
"its first parameter to be a reachable target.";
}
lastPos = pos;
if (!errorString.empty())
{
break;
}
}
if (!errorString.empty())
{
mf->IssueMessage(cmake::FATAL_ERROR, errorString);
return;
}
pos = 0;
lastPos = pos;
while((pos = input.find("$<TARGET_NAME:", lastPos)) != input.npos)
{
std::string::size_type nameStartPos = pos + sizeof("$<TARGET_NAME:") - 1;
std::string::size_type endPos = input.find(">", nameStartPos);
if (endPos == input.npos)
{
errorString = "$<TARGET_NAME:...> expression incomplete";
}
const std::string targetName = input.substr(nameStartPos,
endPos - nameStartPos);
if(targetName.find("$<", lastPos) != input.npos)
{
errorString = "$<TARGET_NAME:...> requires its parameter to be a "
"literal.";
}
if (cmTarget *tgt = mf->FindTargetToUse(targetName.c_str()))
{
if(tgt->IsImported())
{
input.replace(pos, sizeof("$<TARGET_NAME:") + targetName.size(),
targetName);
pos += sizeof("$<TARGET_NAME:") + targetName.size();
}
else if(this->ExportedTargets.find(tgt) != this->ExportedTargets.end())
{
input.replace(pos, sizeof("$<TARGET_NAME:") + targetName.size(),
this->Namespace + targetName);
pos += sizeof("$<TARGET_NAME:") + targetName.size();
}
else
{
std::string namespacedTarget;
this->HandleMissingTarget(namespacedTarget, missingTargets,
mf, target, tgt);
if (!namespacedTarget.empty())
{
input.replace(pos, sizeof("$<TARGET_NAME:") + targetName.size(),
namespacedTarget);
pos += sizeof("$<TARGET_NAME:") + targetName.size();
}
}
}
else
{
errorString = "$<TARGET_NAME:...> requires its parameter to be a "
"reachable target.";
}
lastPos = pos;
if (!errorString.empty())
{
break;
}
}
if (!errorString.empty())
{
mf->IssueMessage(cmake::FATAL_ERROR, errorString);
}
}
//----------------------------------------------------------------------------
void
cmExportFileGenerator

View File

@ -13,6 +13,7 @@
#define cmExportFileGenerator_h
#include "cmCommand.h"
#include "cmGeneratorExpression.h"
/** \class cmExportFileGenerator
* \brief Generate a file exporting targets from a build or install tree.
@ -93,6 +94,17 @@ protected:
cmMakefile* mf,
cmTarget* depender,
cmTarget* dependee) = 0;
void PopulateInterfaceProperty(const char *,
cmTarget *target,
cmGeneratorExpression::PreprocessContext,
ImportPropertyMap &properties,
std::vector<std::string> &missingTargets);
void GenerateInterfaceProperties(cmTarget *target, std::ostream& os,
const ImportPropertyMap &properties);
void ResolveTargetsInGeneratorExpressions(std::string &input,
cmTarget* target,
std::vector<std::string> &missingTargets);
// The namespace in which the exports are placed in the generated file.
std::string Namespace;
@ -109,6 +121,13 @@ protected:
// The set of targets included in the export.
std::set<cmTarget*> ExportedTargets;
private:
void PopulateInterfaceProperty(const char *, const char *,
cmTarget *target,
cmGeneratorExpression::PreprocessContext,
ImportPropertyMap &properties,
std::vector<std::string> &missingTargets);
};
#endif

View File

@ -39,6 +39,7 @@ std::string cmExportInstallFileGenerator::GetConfigImportFileGlob()
//----------------------------------------------------------------------------
bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
{
std::vector<cmTarget*> allTargets;
{
std::string expectedTargets;
std::string sep;
@ -48,20 +49,10 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
{
expectedTargets += sep + this->Namespace + (*tei)->Target->GetName();
sep = " ";
}
this->GenerateExpectedTargetsCode(os, expectedTargets);
}
// Create all the imported targets.
for(std::vector<cmTargetExport*>::const_iterator
tei = this->IEGen->GetExportSet()->GetTargetExports()->begin();
tei != this->IEGen->GetExportSet()->GetTargetExports()->end(); ++tei)
{
cmTargetExport const* te = *tei;
if(this->ExportedTargets.insert(te->Target).second)
{
this->GenerateImportTargetCode(os, te->Target);
allTargets.push_back(te->Target);
}
else
{
@ -75,6 +66,35 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
}
}
this->GenerateExpectedTargetsCode(os, expectedTargets);
}
std::vector<std::string> missingTargets;
// Create all the imported targets.
for(std::vector<cmTarget*>::const_iterator
tei = allTargets.begin();
tei != allTargets.end(); ++tei)
{
cmTarget* te = *tei;
this->GenerateImportTargetCode(os, te);
ImportPropertyMap properties;
this->PopulateInterfaceProperty("INTERFACE_INCLUDE_DIRECTORIES",
te,
cmGeneratorExpression::InstallInterface,
properties, missingTargets);
this->PopulateInterfaceProperty("INTERFACE_COMPILE_DEFINITIONS",
te,
cmGeneratorExpression::InstallInterface,
properties, missingTargets);
this->GenerateInterfaceProperties(te, os, properties);
}
this->GenerateMissingTargetsCheckCode(os, missingTargets);
// Now load per-configuration properties for them.
os << "# Load information for each installed configuration.\n"
<< "GET_FILENAME_COMPONENT(_DIR \"${CMAKE_CURRENT_LIST_FILE}\" PATH)\n"

View File

@ -53,7 +53,22 @@ cmGeneratorExpression::~cmGeneratorExpression()
//----------------------------------------------------------------------------
const char *cmCompiledGeneratorExpression::Evaluate(
cmMakefile* mf, const char* config, bool quiet,
cmTarget *target,
cmTarget *headTarget,
cmGeneratorExpressionDAGChecker *dagChecker) const
{
return this->Evaluate(mf,
config,
quiet,
headTarget,
headTarget,
dagChecker);
}
//----------------------------------------------------------------------------
const char *cmCompiledGeneratorExpression::Evaluate(
cmMakefile* mf, const char* config, bool quiet,
cmTarget *headTarget,
cmTarget *currentTarget,
cmGeneratorExpressionDAGChecker *dagChecker) const
{
if (!this->NeedsParsing)
@ -73,7 +88,8 @@ const char *cmCompiledGeneratorExpression::Evaluate(
context.Config = config;
context.Quiet = quiet;
context.HadError = false;
context.Target = target;
context.HeadTarget = headTarget;
context.CurrentTarget = currentTarget ? currentTarget : headTarget;
context.Backtrace = this->Backtrace;
for ( ; it != end; ++it)
@ -123,15 +139,9 @@ cmCompiledGeneratorExpression::~cmCompiledGeneratorExpression()
}
}
std::string cmGeneratorExpression::Preprocess(const std::string &input,
PreprocessContext context)
//----------------------------------------------------------------------------
static std::string stripAllGeneratorExpressions(const std::string &input)
{
if (context != StripAllGeneratorExpressions)
{
assert(!"cmGeneratorExpression::Preprocess called with invalid args");
return std::string();
}
std::string result;
std::string::size_type pos = 0;
std::string::size_type lastPos = pos;
@ -170,3 +180,81 @@ std::string cmGeneratorExpression::Preprocess(const std::string &input,
result += input.substr(lastPos);
return result;
}
//----------------------------------------------------------------------------
static std::string stripExportInterface(const std::string &input,
cmGeneratorExpression::PreprocessContext context)
{
std::string result;
std::string::size_type pos = 0;
std::string::size_type lastPos = pos;
while((pos = input.find("$<BUILD_INTERFACE:", lastPos)) != input.npos
|| (pos = input.find("$<INSTALL_INTERFACE:", lastPos)) != input.npos)
{
result += input.substr(lastPos, pos - lastPos);
const bool gotInstallInterface = input[pos + 2] == 'I';
pos += gotInstallInterface ? sizeof("$<INSTALL_INTERFACE:") - 1
: sizeof("$<BUILD_INTERFACE:") - 1;
int nestingLevel = 1;
const char *c = input.c_str() + pos;
const char * const cStart = c;
for ( ; *c; ++c)
{
if(c[0] == '$' && c[1] == '<')
{
++nestingLevel;
++c;
continue;
}
if(c[0] == '>')
{
--nestingLevel;
if (nestingLevel != 0)
{
continue;
}
if(context == cmGeneratorExpression::BuildInterface
&& !gotInstallInterface)
{
result += input.substr(pos, c - cStart);
}
else if(context == cmGeneratorExpression::InstallInterface
&& gotInstallInterface)
{
result += input.substr(pos, c - cStart);
}
break;
}
}
const std::string::size_type traversed = (c - cStart) + 1;
if (!*c)
{
result += std::string(gotInstallInterface ? "$<INSTALL_INTERFACE:"
: "$<BUILD_INTERFACE:")
+ input.substr(pos, traversed);
}
pos += traversed;
lastPos = pos;
}
result += input.substr(lastPos);
return result;
}
//----------------------------------------------------------------------------
std::string cmGeneratorExpression::Preprocess(const std::string &input,
PreprocessContext context)
{
if (context == StripAllGeneratorExpressions)
{
return stripAllGeneratorExpressions(input);
}
else if (context == BuildInterface || context == InstallInterface)
{
return stripExportInterface(input, context);
}
assert(!"cmGeneratorExpression::Preprocess called with invalid args");
return std::string();
}

View File

@ -51,7 +51,9 @@ public:
cmsys::auto_ptr<cmCompiledGeneratorExpression> Parse(const char* input);
enum PreprocessContext {
StripAllGeneratorExpressions
StripAllGeneratorExpressions,
BuildInterface,
InstallInterface
};
static std::string Preprocess(const std::string &input,
@ -69,8 +71,13 @@ class cmCompiledGeneratorExpression
public:
const char* Evaluate(cmMakefile* mf, const char* config,
bool quiet = false,
cmTarget *target = 0,
cmTarget *headTarget = 0,
cmTarget *currentTarget = 0,
cmGeneratorExpressionDAGChecker *dagChecker = 0) const;
const char* Evaluate(cmMakefile* mf, const char* config,
bool quiet,
cmTarget *headTarget,
cmGeneratorExpressionDAGChecker *dagChecker) const;
/** Get set of targets found during evaluations. */
std::set<cmTarget*> const& GetTargets() const

View File

@ -24,13 +24,14 @@ cmGeneratorExpressionDAGChecker::cmGeneratorExpressionDAGChecker(
: Parent(parent), Target(target), Property(property),
Content(content), Backtrace(backtrace)
{
this->IsDAG = this->isDAG();
this->CheckResult = this->checkGraph();
}
//----------------------------------------------------------------------------
bool cmGeneratorExpressionDAGChecker::check() const
cmGeneratorExpressionDAGChecker::Result
cmGeneratorExpressionDAGChecker::check() const
{
return this->IsDAG;
return this->CheckResult;
}
//----------------------------------------------------------------------------
@ -38,7 +39,7 @@ void cmGeneratorExpressionDAGChecker::reportError(
cmGeneratorExpressionContext *context,
const std::string &expr)
{
if (this->IsDAG)
if (this->CheckResult == DAG)
{
return;
}
@ -57,7 +58,7 @@ void cmGeneratorExpressionDAGChecker::reportError(
e << "Error evaluating generator expression:\n"
<< " " << expr << "\n"
<< "Self reference on target \""
<< context->Target->GetName() << "\".\n";
<< context->HeadTarget->GetName() << "\".\n";
context->Makefile->GetCMakeInstance()
->IssueMessage(cmake::FATAL_ERROR, e.str().c_str(),
parent->Backtrace);
@ -91,16 +92,17 @@ void cmGeneratorExpressionDAGChecker::reportError(
}
//----------------------------------------------------------------------------
bool cmGeneratorExpressionDAGChecker::isDAG() const
cmGeneratorExpressionDAGChecker::Result
cmGeneratorExpressionDAGChecker::checkGraph() const
{
const cmGeneratorExpressionDAGChecker *parent = this->Parent;
while (parent)
{
if (this->Target == parent->Target && this->Property == parent->Property)
{
return false;
return parent->Parent ? CYCLIC_REFERENCE : SELF_REFERENCE;
}
parent = parent->Parent;
}
return true;
return DAG;
}

View File

@ -25,12 +25,18 @@ struct cmGeneratorExpressionDAGChecker
const GeneratorExpressionContent *content,
cmGeneratorExpressionDAGChecker *parent);
bool check() const;
enum Result {
DAG,
SELF_REFERENCE,
CYCLIC_REFERENCE
};
Result check() const;
void reportError(cmGeneratorExpressionContext *context,
const std::string &expr);
private:
bool isDAG() const;
Result checkGraph() const;
private:
const cmGeneratorExpressionDAGChecker * const Parent;
@ -38,7 +44,7 @@ private:
const std::string Property;
const GeneratorExpressionContent * const Content;
const cmListFileBacktrace Backtrace;
bool IsDAG;
Result CheckResult;
};
#endif

View File

@ -49,6 +49,8 @@ struct cmGeneratorExpressionNode
virtual bool GeneratesContent() const { return true; }
virtual bool RequiresLiteralInput() const { return false; }
virtual bool AcceptsSingleArbitraryContentParameter() const
{ return false; }
@ -97,6 +99,12 @@ static const struct OneNode : public cmGeneratorExpressionNode
}
} oneNode;
//----------------------------------------------------------------------------
static const struct OneNode buildInterfaceNode;
//----------------------------------------------------------------------------
static const struct ZeroNode installInterfaceNode;
//----------------------------------------------------------------------------
#define BOOLEAN_OP_NODE(OPNAME, OP, SUCCESS_VALUE, FAILURE_VALUE) \
static const struct OP ## Node : public cmGeneratorExpressionNode \
@ -259,11 +267,34 @@ static const struct ConfigurationTestNode : public cmGeneratorExpressionNode
return parameters.front().empty() ? "1" : "0";
}
return cmsysString_strcasecmp(parameters.begin()->c_str(),
context->Config) == 0 ? "1" : "0";
if (cmsysString_strcasecmp(parameters.begin()->c_str(),
context->Config) == 0)
{
return "1";
}
if (context->CurrentTarget
&& context->CurrentTarget->IsImported())
{
const char* loc = 0;
const char* imp = 0;
std::string suffix;
return context->CurrentTarget->GetMappedConfig(context->Config,
&loc,
&imp,
suffix) ? "1" : "0";
}
return "0";
}
} configurationTestNode;
//----------------------------------------------------------------------------
static const char* targetPropertyTransitiveWhitelist[] = {
"INTERFACE_INCLUDE_DIRECTORIES"
, "INTERFACE_COMPILE_DEFINITIONS"
};
//----------------------------------------------------------------------------
static const struct TargetPropertyNode : public cmGeneratorExpressionNode
{
@ -291,7 +322,7 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode
cmsys::RegularExpression propertyNameValidator;
propertyNameValidator.compile("^[A-Za-z0-9_]+$");
cmTarget* target = context->Target;
cmTarget* target = context->HeadTarget;
std::string propertyName = *parameters.begin();
if (!target && parameters.size() == 1)
@ -372,17 +403,66 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode
content,
dagCheckerParent);
if (!dagChecker.check())
switch (dagChecker.check())
{
case cmGeneratorExpressionDAGChecker::SELF_REFERENCE:
dagChecker.reportError(context, content->GetOriginalExpression());
return std::string();
case cmGeneratorExpressionDAGChecker::CYCLIC_REFERENCE:
// No error. We just skip cyclic references.
return std::string();
case cmGeneratorExpressionDAGChecker::DAG:
break;
}
const char *prop = target->GetProperty(propertyName.c_str());
return prop ? prop : "";
if (!prop)
{
return std::string();
}
for (size_t i = 0;
i < (sizeof(targetPropertyTransitiveWhitelist) /
sizeof(*targetPropertyTransitiveWhitelist));
++i)
{
if (targetPropertyTransitiveWhitelist[i] == propertyName)
{
cmGeneratorExpression ge(context->Backtrace);
return ge.Parse(prop)->Evaluate(context->Makefile,
context->Config,
context->Quiet,
context->HeadTarget,
target,
&dagChecker);
}
}
return prop;
}
} targetPropertyNode;
//----------------------------------------------------------------------------
static const struct TargetNameNode : public cmGeneratorExpressionNode
{
TargetNameNode() {}
virtual bool GeneratesContent() const { return true; }
virtual bool AcceptsSingleArbitraryContentParameter() const { return true; }
virtual bool RequiresLiteralInput() const { return true; }
std::string Evaluate(const std::vector<std::string> &parameters,
cmGeneratorExpressionContext *,
const GeneratorExpressionContent *,
cmGeneratorExpressionDAGChecker *) const
{
return parameters.front();
}
virtual int NumExpectedParameters() const { return 1; }
} targetNameNode;
//----------------------------------------------------------------------------
template<bool linker, bool soname>
struct TargetFilesystemArtifactResultCreator
@ -608,6 +688,12 @@ cmGeneratorExpressionNode* GetNode(const std::string &identifier)
return &commaNode;
else if (identifier == "TARGET_PROPERTY")
return &targetPropertyNode;
else if (identifier == "TARGET_NAME")
return &targetNameNode;
else if (identifier == "BUILD_INTERFACE")
return &buildInterfaceNode;
else if (identifier == "INSTALL_INTERFACE")
return &installInterfaceNode;
return 0;
}
@ -697,6 +783,15 @@ std::string GeneratorExpressionContent::Evaluate(
= pit->end();
for ( ; it != end; ++it)
{
if (node->RequiresLiteralInput())
{
if ((*it)->GetType() != cmGeneratorExpressionEvaluator::Text)
{
reportError(context, this->GetOriginalExpression(),
"$<" + identifier + "> expression requires literal input.");
return std::string();
}
}
result += (*it)->Evaluate(context, dagChecker);
if (context->HadError)
{
@ -704,6 +799,12 @@ std::string GeneratorExpressionContent::Evaluate(
}
}
}
if (node->RequiresLiteralInput())
{
std::vector<std::string> parameters;
parameters.push_back(result);
return node->Evaluate(parameters, context, this, dagChecker);
}
return result;
}

View File

@ -26,7 +26,9 @@ struct cmGeneratorExpressionContext
std::set<cmTarget*> Targets;
cmMakefile *Makefile;
const char *Config;
cmTarget *Target;
cmTarget *HeadTarget; // The target whose property is being evaluated.
cmTarget *CurrentTarget; // The dependent of HeadTarget which appears
// directly or indirectly in the property.
bool Quiet;
bool HadError;
};

View File

@ -719,6 +719,28 @@ void cmTarget::DefineProperties(cmake *cm)
"If set, this property completely overrides the generic property "
"for the named configuration.");
cm->DefineProperty
("INTERFACE_INCLUDE_DIRECTORIES", cmProperty::TARGET,
"List of public include directories for a library.",
"Targets may populate this property to publish the include directories "
"required to compile against the headers for the target. Consuming "
"targets can add entries to their own INCLUDE_DIRECTORIES property such "
"as $<TARGET_PROPERTY:foo,INTERFACE_INCLUDE_DIRECTORIES> to use the "
"include directories specified in the interface of 'foo'."
"\n"
CM_DOCUMENT_COMMAND_GENERATOR_EXPRESSIONS);
cm->DefineProperty
("INTERFACE_COMPILE_DEFINITIONS", cmProperty::TARGET,
"List of public compile definitions for a library.",
"Targets may populate this property to publish the compile definitions "
"required to compile against the headers for the target. Consuming "
"targets can add entries to their own COMPILE_DEFINITIONS property such "
"as $<TARGET_PROPERTY:foo,INTERFACE_COMPILE_DEFINITIONS> to use the "
"compile definitions specified in the interface of 'foo'."
"\n"
CM_DOCUMENT_COMMAND_GENERATOR_EXPRESSIONS);
cm->DefineProperty
("LINK_INTERFACE_MULTIPLICITY", cmProperty::TARGET,
"Repetition count for STATIC libraries with cyclic dependencies.",
@ -4365,6 +4387,128 @@ cmTarget::GetImportInfo(const char* config)
return &i->second;
}
bool cmTarget::GetMappedConfig(std::string const& desired_config,
const char** loc,
const char** imp,
std::string& suffix)
{
// Track the configuration-specific property suffix.
suffix = "_";
suffix += desired_config;
std::vector<std::string> mappedConfigs;
{
std::string mapProp = "MAP_IMPORTED_CONFIG_";
mapProp += desired_config;
if(const char* mapValue = this->GetProperty(mapProp.c_str()))
{
cmSystemTools::ExpandListArgument(mapValue, mappedConfigs);
}
}
// If we needed to find one of the mapped configurations but did not
// On a DLL platform there may be only IMPORTED_IMPLIB for a shared
// library or an executable with exports.
bool allowImp = this->HasImportLibrary();
// If a mapping was found, check its configurations.
for(std::vector<std::string>::const_iterator mci = mappedConfigs.begin();
!*loc && !*imp && mci != mappedConfigs.end(); ++mci)
{
// Look for this configuration.
std::string mcUpper = cmSystemTools::UpperCase(mci->c_str());
std::string locProp = "IMPORTED_LOCATION_";
locProp += mcUpper;
*loc = this->GetProperty(locProp.c_str());
if(allowImp)
{
std::string impProp = "IMPORTED_IMPLIB_";
impProp += mcUpper;
*imp = this->GetProperty(impProp.c_str());
}
// If it was found, use it for all properties below.
if(*loc || *imp)
{
suffix = "_";
suffix += mcUpper;
}
}
// If we needed to find one of the mapped configurations but did not
// then the target is not found. The project does not want any
// other configuration.
if(!mappedConfigs.empty() && !*loc && !*imp)
{
return false;
}
// If we have not yet found it then there are no mapped
// configurations. Look for an exact-match.
if(!*loc && !*imp)
{
std::string locProp = "IMPORTED_LOCATION";
locProp += suffix;
*loc = this->GetProperty(locProp.c_str());
if(allowImp)
{
std::string impProp = "IMPORTED_IMPLIB";
impProp += suffix;
*imp = this->GetProperty(impProp.c_str());
}
}
// If we have not yet found it then there are no mapped
// configurations and no exact match.
if(!*loc && !*imp)
{
// The suffix computed above is not useful.
suffix = "";
// Look for a configuration-less location. This may be set by
// manually-written code.
*loc = this->GetProperty("IMPORTED_LOCATION");
if(allowImp)
{
*imp = this->GetProperty("IMPORTED_IMPLIB");
}
}
// If we have not yet found it then the project is willing to try
// any available configuration.
if(!*loc && !*imp)
{
std::vector<std::string> availableConfigs;
if(const char* iconfigs = this->GetProperty("IMPORTED_CONFIGURATIONS"))
{
cmSystemTools::ExpandListArgument(iconfigs, availableConfigs);
}
for(std::vector<std::string>::const_iterator
aci = availableConfigs.begin();
!*loc && !*imp && aci != availableConfigs.end(); ++aci)
{
suffix = "_";
suffix += cmSystemTools::UpperCase(*aci);
std::string locProp = "IMPORTED_LOCATION";
locProp += suffix;
*loc = this->GetProperty(locProp.c_str());
if(allowImp)
{
std::string impProp = "IMPORTED_IMPLIB";
impProp += suffix;
*imp = this->GetProperty(impProp.c_str());
}
}
}
// If we have not yet found it then the target is not available.
if(!*loc && !*imp)
{
return false;
}
return true;
}
//----------------------------------------------------------------------------
void cmTarget::ComputeImportInfo(std::string const& desired_config,
ImportInfo& info)
@ -4376,120 +4520,10 @@ void cmTarget::ComputeImportInfo(std::string const& desired_config,
// Initialize members.
info.NoSOName = false;
// Track the configuration-specific property suffix.
std::string suffix = "_";
suffix += desired_config;
// On a DLL platform there may be only IMPORTED_IMPLIB for a shared
// library or an executable with exports.
bool allowImp = this->HasImportLibrary();
// Look for a mapping from the current project's configuration to
// the imported project's configuration.
std::vector<std::string> mappedConfigs;
{
std::string mapProp = "MAP_IMPORTED_CONFIG_";
mapProp += desired_config;
if(const char* mapValue = this->GetProperty(mapProp.c_str()))
{
cmSystemTools::ExpandListArgument(mapValue, mappedConfigs);
}
}
// If a mapping was found, check its configurations.
const char* loc = 0;
const char* imp = 0;
for(std::vector<std::string>::const_iterator mci = mappedConfigs.begin();
!loc && !imp && mci != mappedConfigs.end(); ++mci)
{
// Look for this configuration.
std::string mcUpper = cmSystemTools::UpperCase(mci->c_str());
std::string locProp = "IMPORTED_LOCATION_";
locProp += mcUpper;
loc = this->GetProperty(locProp.c_str());
if(allowImp)
{
std::string impProp = "IMPORTED_IMPLIB_";
impProp += mcUpper;
imp = this->GetProperty(impProp.c_str());
}
// If it was found, use it for all properties below.
if(loc || imp)
{
suffix = "_";
suffix += mcUpper;
}
}
// If we needed to find one of the mapped configurations but did not
// then the target is not found. The project does not want any
// other configuration.
if(!mappedConfigs.empty() && !loc && !imp)
{
return;
}
// If we have not yet found it then there are no mapped
// configurations. Look for an exact-match.
if(!loc && !imp)
{
std::string locProp = "IMPORTED_LOCATION";
locProp += suffix;
loc = this->GetProperty(locProp.c_str());
if(allowImp)
{
std::string impProp = "IMPORTED_IMPLIB";
impProp += suffix;
imp = this->GetProperty(impProp.c_str());
}
}
// If we have not yet found it then there are no mapped
// configurations and no exact match.
if(!loc && !imp)
{
// The suffix computed above is not useful.
suffix = "";
// Look for a configuration-less location. This may be set by
// manually-written code.
loc = this->GetProperty("IMPORTED_LOCATION");
if(allowImp)
{
imp = this->GetProperty("IMPORTED_IMPLIB");
}
}
// If we have not yet found it then the project is willing to try
// any available configuration.
if(!loc && !imp)
{
std::vector<std::string> availableConfigs;
if(const char* iconfigs = this->GetProperty("IMPORTED_CONFIGURATIONS"))
{
cmSystemTools::ExpandListArgument(iconfigs, availableConfigs);
}
for(std::vector<std::string>::const_iterator
aci = availableConfigs.begin();
!loc && !imp && aci != availableConfigs.end(); ++aci)
{
suffix = "_";
suffix += cmSystemTools::UpperCase(*aci);
std::string locProp = "IMPORTED_LOCATION";
locProp += suffix;
loc = this->GetProperty(locProp.c_str());
if(allowImp)
{
std::string impProp = "IMPORTED_IMPLIB";
impProp += suffix;
imp = this->GetProperty(impProp.c_str());
}
}
}
// If we have not yet found it then the target is not available.
if(!loc && !imp)
std::string suffix;
if (!this->GetMappedConfig(desired_config, &loc, &imp, suffix))
{
return;
}

View File

@ -404,6 +404,11 @@ public:
// Get the properties
cmPropertyMap &GetProperties() { return this->Properties; };
bool GetMappedConfig(std::string const& desired_config,
const char** loc,
const char** imp,
std::string& suffix);
// Define the properties
static void DefineProperties(cmake *cm);

View File

@ -90,9 +90,82 @@ set_property(TARGET testLibCycleA PROPERTY LINK_INTERFACE_MULTIPLICITY 3)
# Test exporting dependent libraries into different exports
add_library(testLibRequired testLibRequired.c)
add_library(testLibDepends testLibDepends.c)
set_property(TARGET testLibDepends APPEND PROPERTY
INCLUDE_DIRECTORIES
$<TARGET_PROPERTY:testLibRequired,INTERFACE_INCLUDE_DIRECTORIES>
)
set_property(TARGET testLibDepends APPEND PROPERTY
COMPILE_DEFINITIONS
$<TARGET_PROPERTY:testLibRequired,INTERFACE_COMPILE_DEFINITIONS>
)
set_property(TARGET testLibDepends APPEND PROPERTY
INTERFACE_INCLUDE_DIRECTORIES
$<TARGET_PROPERTY:testLibRequired,INTERFACE_INCLUDE_DIRECTORIES>
)
set_property(TARGET testLibDepends APPEND PROPERTY
INTERFACE_COMPILE_DEFINITIONS
$<TARGET_PROPERTY:testLibRequired,INTERFACE_COMPILE_DEFINITIONS>
)
target_link_libraries(testLibDepends testLibRequired)
install(TARGETS testLibRequired EXPORT RequiredExp DESTINATION lib )
macro(add_include_lib _libName)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${_libName}.c" "// no content\n")
add_library(${_libName} "${CMAKE_CURRENT_BINARY_DIR}/${_libName}.c")
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${_libName}")
set_property(TARGET ${_libName} APPEND PROPERTY
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/${_libName}")
if (NOT "${ARGV1}" STREQUAL "NO_HEADER")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${_libName}/${_libName}.h" "// no content\n")
endif()
endmacro()
add_include_lib(testLibIncludeRequired1)
add_include_lib(testLibIncludeRequired2)
add_include_lib(testLibIncludeRequired3 NO_HEADER)
# Generate testLibIncludeRequired4 in the testLibIncludeRequired3 directory
# with an error. If the includes from testLibIncludeRequired3 appear first,
# the error will be hit.
# Below, the '3' library appears before the '4' library
# but we are testing that the INSTALL_INTERFACE causes it not to be used
# at build time.
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/testLibIncludeRequired3/testLibIncludeRequired4.h" "#error Should not be included\n")
add_include_lib(testLibIncludeRequired4)
add_include_lib(testLibIncludeRequired5 NO_HEADER)
# Generate testLibIncludeRequired6 in the testLibIncludeRequired5 directory
# with an error. If the includes from testLibIncludeRequired5 appear first,
# the error will be hit.
# Below, the '5' library appears before the '6' library
# but we are testing that when the installed IMPORTED target is used, from
# the Import side of this unit test, the '6' include from the '5' directory
# will not be used because it is in the BUILD_INTERFACE only.
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/testLibIncludeRequired5/testLibIncludeRequired6.h" "#error Should not be included\n")
add_include_lib(testLibIncludeRequired6)
set_property(TARGET testLibRequired APPEND PROPERTY
INTERFACE_INCLUDE_DIRECTORIES
$<TARGET_PROPERTY:testLibIncludeRequired1,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:$<1:$<TARGET_NAME:testLibIncludeRequired2>>,INTERFACE_INCLUDE_DIRECTORIES>
$<INSTALL_INTERFACE:$<TARGET_PROPERTY:testLibIncludeRequired3,INTERFACE_INCLUDE_DIRECTORIES>>
$<BUILD_INTERFACE:$<TARGET_PROPERTY:testLibIncludeRequired4,INTERFACE_INCLUDE_DIRECTORIES>>
$<BUILD_INTERFACE:$<TARGET_PROPERTY:testLibIncludeRequired5,INTERFACE_INCLUDE_DIRECTORIES>>
$<INSTALL_INTERFACE:$<TARGET_PROPERTY:testLibIncludeRequired6,INTERFACE_INCLUDE_DIRECTORIES>>
)
set_property(TARGET testLibRequired APPEND PROPERTY
INTERFACE_COMPILE_DEFINITIONS
testLibRequired_IFACE_DEFINE
$<BUILD_INTERFACE:BuildOnly_DEFINE>
$<INSTALL_INTERFACE:InstallOnly_DEFINE>
)
install(TARGETS testLibRequired
testLibIncludeRequired1
testLibIncludeRequired2
testLibIncludeRequired3
testLibIncludeRequired4
testLibIncludeRequired5
testLibIncludeRequired6
EXPORT RequiredExp DESTINATION lib )
install(EXPORT RequiredExp NAMESPACE Req:: FILE testLibRequiredConfig.cmake DESTINATION lib/cmake/testLibRequired)
install(TARGETS testLibDepends EXPORT DependsExp DESTINATION lib )

View File

@ -1,4 +1,20 @@
#include "testLibIncludeRequired1.h"
#include "testLibIncludeRequired2.h"
#include "testLibIncludeRequired4.h"
#ifndef testLibRequired_IFACE_DEFINE
#error Expected testLibRequired_IFACE_DEFINE
#endif
#ifndef BuildOnly_DEFINE
#error Expected BuildOnly_DEFINE
#endif
#ifdef InstallOnly_DEFINE
#error Unexpected InstallOnly_DEFINE
#endif
extern int testLibRequired(void);
int testLibDepends(void) { return testLibRequired(); }

View File

@ -152,3 +152,18 @@ check_function_exists(testLib1 HAVE_TESTLIB1_FUNCTION)
if (NOT HAVE_TESTLIB1_FUNCTION)
message(SEND_ERROR "Using imported target testLib2 in check_function_exists() failed !")
endif()
#-----------------------------------------------------------------------------
# Test that dependent imported targets have usable
# INTERFACE_COMPILE_DEFINITIONS and INTERFACE_INCLUDE_DIRECTORIES
add_library(deps_iface deps_iface.cpp)
target_link_libraries(deps_iface testLibsDepends)
set_property(TARGET deps_iface APPEND PROPERTY
COMPILE_DEFINITIONS
$<TARGET_PROPERTY:testLibDepends,INTERFACE_COMPILE_DEFINITIONS>
)
set_property(TARGET deps_iface APPEND PROPERTY
INCLUDE_DIRECTORIES
$<TARGET_PROPERTY:testLibDepends,INTERFACE_INCLUDE_DIRECTORIES>
)

View File

@ -0,0 +1,24 @@
#include "testLibIncludeRequired1.h"
#include "testLibIncludeRequired2.h"
#include "testLibIncludeRequired6.h"
#ifndef testLibRequired_IFACE_DEFINE
#error Expected testLibRequired_IFACE_DEFINE
#endif
#ifdef BuildOnly_DEFINE
#error Unexpected BuildOnly_DEFINE
#endif
#ifndef InstallOnly_DEFINE
#error Expected InstallOnly_DEFINE
#endif
extern int testLibDepends(void);
int main(int,char **)
{
return testLibDepends();
}

View File

@ -1,7 +1,7 @@
cmake_minimum_required (VERSION 2.8.8)
project(GeneratorExpression NONE)
add_custom_target(check ALL
add_custom_target(check-part1 ALL
COMMAND ${CMAKE_COMMAND}
-Dtest_0=$<0:nothing>
-Dtest_0_with_comma=$<0:-Wl,--no-undefined>
@ -57,6 +57,13 @@ add_custom_target(check ALL
-Dtest_colons_3=$<1:Qt5::Core>
-Dtest_colons_4=$<1:C:\\CMake>
-Dtest_colons_5=$<1:C:/CMake>
-P ${CMAKE_CURRENT_SOURCE_DIR}/check-part1.cmake
COMMAND ${CMAKE_COMMAND} -E echo "check done (part 1 of 2)"
VERBATIM
)
add_custom_target(check-part2 ALL
COMMAND ${CMAKE_COMMAND}
-Dtest_incomplete_1=$<
-Dtest_incomplete_2=$<something
-Dtest_incomplete_3=$<something:
@ -78,7 +85,11 @@ add_custom_target(check ALL
-Dtest_incomplete_19=$<1:some,thing$<ANGLE-R>
-Dtest_incomplete_20=$<CONFIGURATION$<ANGLE-R>
-Dtest_incomplete_21=$<BOOL:something$<ANGLE-R>
-P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake
COMMAND ${CMAKE_COMMAND} -E echo "check done"
-Dtest_build_interface=$<BUILD_INTERFACE:build>
-Dtest_install_interface=$<INSTALL_INTERFACE:install>
-Dtest_target_name_1=$<TARGET_NAME:tgt,ok>
-Dtest_target_name_2=$<TARGET_NAME:tgt:ok>
-P ${CMAKE_CURRENT_SOURCE_DIR}/check-part2.cmake
COMMAND ${CMAKE_COMMAND} -E echo "check done (part 2 of 2)"
VERBATIM
)

View File

@ -0,0 +1,5 @@
macro(check var val)
if(NOT "${${var}}" STREQUAL "${val}")
message(SEND_ERROR "${var} is \"${${var}}\", not \"${val}\"")
endif()
endmacro()

View File

@ -1,8 +1,5 @@
macro(check var val)
if(NOT "${${var}}" STREQUAL "${val}")
message(SEND_ERROR "${var} is \"${${var}}\", not \"${val}\"")
endif()
endmacro()
include(${CMAKE_CURRENT_LIST_DIR}/check-common.cmake)
message(STATUS "config=[${config}]")
check(test_0 "")
@ -57,24 +54,3 @@ check(test_colons_2 "::")
check(test_colons_3 "Qt5::Core")
check(test_colons_4 "C:\\\\CMake")
check(test_colons_5 "C:/CMake")
check(test_incomplete_1 "$<")
check(test_incomplete_2 "$<something")
check(test_incomplete_3 "$<something:")
check(test_incomplete_4 "$<something:,")
check(test_incomplete_5 "$something:,>")
check(test_incomplete_6 "<something:,>")
check(test_incomplete_7 "$<something::")
check(test_incomplete_8 "$<something:,")
check(test_incomplete_9 "$<something:,,")
check(test_incomplete_10 "$<something:,:")
check(test_incomplete_11 "$<something,,")
check(test_incomplete_12 "$<,,")
check(test_incomplete_13 "$<somespecialthing")
check(test_incomplete_14 "$<>")
check(test_incomplete_15 "$<some$<thing")
check(test_incomplete_16 "$<BOOL:something")
check(test_incomplete_17 "some$thing")
check(test_incomplete_18 "$<1:some,thing")
check(test_incomplete_19 "$<1:some,thing>")
check(test_incomplete_20 "$<CONFIGURATION>")
check(test_incomplete_21 "$<BOOL:something>")

View File

@ -0,0 +1,28 @@
include(${CMAKE_CURRENT_LIST_DIR}/check-common.cmake)
check(test_incomplete_1 "$<")
check(test_incomplete_2 "$<something")
check(test_incomplete_3 "$<something:")
check(test_incomplete_4 "$<something:,")
check(test_incomplete_5 "$something:,>")
check(test_incomplete_6 "<something:,>")
check(test_incomplete_7 "$<something::")
check(test_incomplete_8 "$<something:,")
check(test_incomplete_9 "$<something:,,")
check(test_incomplete_10 "$<something:,:")
check(test_incomplete_11 "$<something,,")
check(test_incomplete_12 "$<,,")
check(test_incomplete_13 "$<somespecialthing")
check(test_incomplete_14 "$<>")
check(test_incomplete_15 "$<some$<thing")
check(test_incomplete_16 "$<BOOL:something")
check(test_incomplete_17 "some$thing")
check(test_incomplete_18 "$<1:some,thing")
check(test_incomplete_19 "$<1:some,thing>")
check(test_incomplete_20 "$<CONFIGURATION>")
check(test_incomplete_21 "$<BOOL:something>")
check(test_build_interface "build")
check(test_install_interface "")
check(test_target_name_1 "tgt,ok")
check(test_target_name_2 "tgt:ok")

View File

@ -37,6 +37,37 @@ include_directories("sing$<1:/ting>")
include_directories("$<1:${CMAKE_CURRENT_BINARY_DIR}/arguments;${CMAKE_CURRENT_BINARY_DIR}/list>")
create_header(fee)
create_header(fiy)
create_header(foh)
create_header(fum)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/lib1.cpp" "#include \"fee.h\"\n")
add_library(lib1 "${CMAKE_CURRENT_BINARY_DIR}/lib1.cpp")
set_property(TARGET lib1 APPEND PROPERTY INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/fee")
set_property(TARGET lib1 APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/fiy")
set_property(TARGET lib1 APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:${CMAKE_CURRENT_BINARY_DIR}/foh>")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/lib2.cpp" "#include \"fiy.h\"\n")
add_library(lib2 "${CMAKE_CURRENT_BINARY_DIR}/lib2.cpp")
set_property(TARGET lib2 APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/fum;$<TARGET_PROPERTY:lib1,INTERFACE_INCLUDE_DIRECTORIES>")
set_property(TARGET lib2 APPEND PROPERTY INCLUDE_DIRECTORIES "$<TARGET_PROPERTY:lib1,INTERFACE_INCLUDE_DIRECTORIES>")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/main3.cpp" "#include \"fiy.h\"\n#include \"foh.h\"\n#include \"fum.h\"\nint main(int,char**) { return 0; }\n")
add_executable(exe3 "${CMAKE_CURRENT_BINARY_DIR}/main3.cpp")
set_property(TARGET exe3 APPEND PROPERTY INCLUDE_DIRECTORIES "$<TARGET_PROPERTY:lib2,INTERFACE_INCLUDE_DIRECTORIES>")
# Test cycles
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/lib3.cpp" "#include \"fiy.h\"\n#include \"foh.h\"\n")
add_library(lib3 "${CMAKE_CURRENT_BINARY_DIR}/lib3.cpp")
set_property(TARGET lib3 APPEND PROPERTY INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/fiy;$<TARGET_PROPERTY:lib4,INTERFACE_INCLUDE_DIRECTORIES>")
set_property(TARGET lib3 APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/fiy;$<TARGET_PROPERTY:lib4,INTERFACE_INCLUDE_DIRECTORIES>")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/lib4.cpp" "#include \"fiy.h\"\n#include \"foh.h\"\n")
add_library(lib4 "${CMAKE_CURRENT_BINARY_DIR}/lib4.cpp")
set_property(TARGET lib4 APPEND PROPERTY INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/foh;$<TARGET_PROPERTY:lib3,INTERFACE_INCLUDE_DIRECTORIES>")
set_property(TARGET lib4 APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/foh;$<TARGET_PROPERTY:lib3,INTERFACE_INCLUDE_DIRECTORIES>")
add_library(somelib::withcolons UNKNOWN IMPORTED)
set_property(TARGET somelib::withcolons PROPERTY IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/target")
set_property(TARGET somelib::withcolons PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/target")

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,8 @@
CMake Error at BadTargetName.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<TARGET_NAME:\$<1:tgt>>
\$<TARGET_NAME> expression requires literal input.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)$

View File

@ -0,0 +1,3 @@
add_custom_target(check ALL COMMAND check
$<TARGET_NAME:$<1:tgt>>
VERBATIM)

View File

@ -6,3 +6,4 @@ run_cmake(BadAND)
run_cmake(BadNOT)
run_cmake(BadStrEqual)
run_cmake(BadZero)
run_cmake(BadTargetName)