From b2f1700bc7caf12c3f28890ebe183ae09c90d7dc Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Sun, 23 Sep 2012 13:16:44 +0200 Subject: GenEx: Add expressions to specify build- or install-only values This is for specifying INCLUDE_DIRECTORIES relevant to the build-location or the install location for example: set_property(TARGET foo PROPERTY INTERFACE_INCLUDE_DIRECTORIES "$" "$" ) A 'bar' target can then use: set_property(TARGET bar PROPERTY INCLUDE_DIRECTORIES "$" ) and it will work whether foo is in the same project, or an imported target from an installation location, or an imported target from a build location generated by the export() command. Because the generator expressions are only evaluated at build-time, these new expressions are equivalent to the ZeroNode and OneNode. The GeneratorExpression test is split into parts. Some shells can't run the custom command as it is getting too long. --- Source/cmDocumentGeneratorExpressions.h | 6 ++ Source/cmGeneratorExpression.cxx | 88 +++++++++++++++++++++++++--- Source/cmGeneratorExpression.h | 4 +- Source/cmGeneratorExpressionEvaluator.cxx | 10 ++++ Tests/GeneratorExpression/CMakeLists.txt | 15 ++++- Tests/GeneratorExpression/check-common.cmake | 5 ++ Tests/GeneratorExpression/check-part1.cmake | 56 ++++++++++++++++++ Tests/GeneratorExpression/check-part2.cmake | 26 ++++++++ Tests/GeneratorExpression/check.cmake | 80 ------------------------- 9 files changed, 198 insertions(+), 92 deletions(-) create mode 100644 Tests/GeneratorExpression/check-common.cmake create mode 100644 Tests/GeneratorExpression/check-part1.cmake create mode 100644 Tests/GeneratorExpression/check-part2.cmake delete mode 100644 Tests/GeneratorExpression/check.cmake diff --git a/Source/cmDocumentGeneratorExpressions.h b/Source/cmDocumentGeneratorExpressions.h index 445fd0e..1927012 100644 --- a/Source/cmDocumentGeneratorExpressions.h +++ b/Source/cmDocumentGeneratorExpressions.h @@ -26,6 +26,12 @@ "strings which contain a '>' for example.\n" \ " $ = A literal ','. Used to compare " \ "strings which contain a ',' for example.\n" \ + " $ = content of \"...\" when the property " \ + "is exported using install(EXPORT), and empty otherwise.\n" \ + " $ = 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" \ " $ = main file (.exe, .so.1.2, .a)\n" \ " $ = file used to link (.a, .lib, .so)\n" \ " $ = file with soname (.so.3)\n" \ diff --git a/Source/cmGeneratorExpression.cxx b/Source/cmGeneratorExpression.cxx index 3fd4a5f..841fbb7 100644 --- a/Source/cmGeneratorExpression.cxx +++ b/Source/cmGeneratorExpression.cxx @@ -123,15 +123,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 +164,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("$') + { + --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 ? "$ Parse(const char* input); enum PreprocessContext { - StripAllGeneratorExpressions + StripAllGeneratorExpressions, + BuildInterface, + InstallInterface }; static std::string Preprocess(const std::string &input, diff --git a/Source/cmGeneratorExpressionEvaluator.cxx b/Source/cmGeneratorExpressionEvaluator.cxx index 3b7cfc0..e20e203 100644 --- a/Source/cmGeneratorExpressionEvaluator.cxx +++ b/Source/cmGeneratorExpressionEvaluator.cxx @@ -96,6 +96,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 \ { \ @@ -593,6 +599,10 @@ cmGeneratorExpressionNode* GetNode(const std::string &identifier) return &commaNode; else if (identifier == "TARGET_PROPERTY") return &targetPropertyNode; + else if (identifier == "BUILD_INTERFACE") + return &buildInterfaceNode; + else if (identifier == "INSTALL_INTERFACE") + return &installInterfaceNode; return 0; } diff --git a/Tests/GeneratorExpression/CMakeLists.txt b/Tests/GeneratorExpression/CMakeLists.txt index 3a92d81..db6fb74 100644 --- a/Tests/GeneratorExpression/CMakeLists.txt +++ b/Tests/GeneratorExpression/CMakeLists.txt @@ -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=$ -Dtest_incomplete_20=$ -Dtest_incomplete_21=$ - -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake - COMMAND ${CMAKE_COMMAND} -E echo "check done" + -Dtest_build_interface=$ + -Dtest_install_interface=$ + -P ${CMAKE_CURRENT_SOURCE_DIR}/check-part2.cmake + COMMAND ${CMAKE_COMMAND} -E echo "check done (part 2 of 2)" VERBATIM ) diff --git a/Tests/GeneratorExpression/check-common.cmake b/Tests/GeneratorExpression/check-common.cmake new file mode 100644 index 0000000..8ffebd7 --- /dev/null +++ b/Tests/GeneratorExpression/check-common.cmake @@ -0,0 +1,5 @@ +macro(check var val) + if(NOT "${${var}}" STREQUAL "${val}") + message(SEND_ERROR "${var} is \"${${var}}\", not \"${val}\"") + endif() +endmacro() diff --git a/Tests/GeneratorExpression/check-part1.cmake b/Tests/GeneratorExpression/check-part1.cmake new file mode 100644 index 0000000..7abfa82 --- /dev/null +++ b/Tests/GeneratorExpression/check-part1.cmake @@ -0,0 +1,56 @@ + +include(${CMAKE_CURRENT_LIST_DIR}/check-common.cmake) + +message(STATUS "config=[${config}]") +check(test_0 "") +check(test_0_with_comma "") +check(test_1 "content") +check(test_1_with_comma "-Wl,--no-undefined") +check(test_and_0 "0") +check(test_and_0_0 "0") +check(test_and_0_1 "0") +check(test_and_1 "1") +check(test_and_1_0 "0") +check(test_and_1_1 "1") +check(test_and_0_invalidcontent "0") +check(test_config_0 "0") +check(test_config_1 "1") +foreach(c debug release relwithdebinfo minsizerel) + if(NOT "${test_config_${c}}" MATCHES "^(0+|1+)$") + message(SEND_ERROR "test_config_${c} is \"${test_config_${c}}\", not all 0 or all 1") + endif() +endforeach() +check(test_not_0 "1") +check(test_not_1 "0") +check(test_or_0 "0") +check(test_or_0_0 "0") +check(test_or_0_1 "1") +check(test_or_1 "1") +check(test_or_1_0 "1") +check(test_or_1_1 "1") +check(test_or_1_invalidcontent "1") +check(test_bool_notfound "0") +check(test_bool_foo_notfound "0") +check(test_bool_true "1") +check(test_bool_false "0") +check(test_bool_on "1") +check(test_bool_off "0") +check(test_bool_no "0") +check(test_bool_n "0") +check(test_bool_empty "0") +check(test_strequal_yes_yes "1") +check(test_strequal_yes_yes_cs "0") +check(test_strequal_yes_no "0") +check(test_strequal_no_yes "0") +check(test_strequal_angle_r "1") +check(test_strequal_comma "1") +check(test_strequal_angle_r_comma "0") +check(test_strequal_both_empty "1") +check(test_strequal_one_empty "0") +check(test_angle_r ">") +check(test_comma ",") +check(test_colons_1 ":") +check(test_colons_2 "::") +check(test_colons_3 "Qt5::Core") +check(test_colons_4 "C:\\\\CMake") +check(test_colons_5 "C:/CMake") diff --git a/Tests/GeneratorExpression/check-part2.cmake b/Tests/GeneratorExpression/check-part2.cmake new file mode 100644 index 0000000..149a658 --- /dev/null +++ b/Tests/GeneratorExpression/check-part2.cmake @@ -0,0 +1,26 @@ + +include(${CMAKE_CURRENT_LIST_DIR}/check-common.cmake) + +check(test_incomplete_1 "$<") +check(test_incomplete_2 "$") +check(test_incomplete_6 "") +check(test_incomplete_7 "$") +check(test_incomplete_15 "$") +check(test_incomplete_20 "$") +check(test_incomplete_21 "$") +check(test_build_interface "build") +check(test_install_interface "") diff --git a/Tests/GeneratorExpression/check.cmake b/Tests/GeneratorExpression/check.cmake deleted file mode 100644 index af436de..0000000 --- a/Tests/GeneratorExpression/check.cmake +++ /dev/null @@ -1,80 +0,0 @@ -macro(check var val) - if(NOT "${${var}}" STREQUAL "${val}") - message(SEND_ERROR "${var} is \"${${var}}\", not \"${val}\"") - endif() -endmacro() - -message(STATUS "config=[${config}]") -check(test_0 "") -check(test_0_with_comma "") -check(test_1 "content") -check(test_1_with_comma "-Wl,--no-undefined") -check(test_and_0 "0") -check(test_and_0_0 "0") -check(test_and_0_1 "0") -check(test_and_1 "1") -check(test_and_1_0 "0") -check(test_and_1_1 "1") -check(test_and_0_invalidcontent "0") -check(test_config_0 "0") -check(test_config_1 "1") -foreach(c debug release relwithdebinfo minsizerel) - if(NOT "${test_config_${c}}" MATCHES "^(0+|1+)$") - message(SEND_ERROR "test_config_${c} is \"${test_config_${c}}\", not all 0 or all 1") - endif() -endforeach() -check(test_not_0 "1") -check(test_not_1 "0") -check(test_or_0 "0") -check(test_or_0_0 "0") -check(test_or_0_1 "1") -check(test_or_1 "1") -check(test_or_1_0 "1") -check(test_or_1_1 "1") -check(test_or_1_invalidcontent "1") -check(test_bool_notfound "0") -check(test_bool_foo_notfound "0") -check(test_bool_true "1") -check(test_bool_false "0") -check(test_bool_on "1") -check(test_bool_off "0") -check(test_bool_no "0") -check(test_bool_n "0") -check(test_bool_empty "0") -check(test_strequal_yes_yes "1") -check(test_strequal_yes_yes_cs "0") -check(test_strequal_yes_no "0") -check(test_strequal_no_yes "0") -check(test_strequal_angle_r "1") -check(test_strequal_comma "1") -check(test_strequal_angle_r_comma "0") -check(test_strequal_both_empty "1") -check(test_strequal_one_empty "0") -check(test_angle_r ">") -check(test_comma ",") -check(test_colons_1 ":") -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 "$") -check(test_incomplete_6 "") -check(test_incomplete_7 "$") -check(test_incomplete_15 "$") -check(test_incomplete_20 "$") -check(test_incomplete_21 "$") -- cgit v0.12 From 77475fe61d0a1bbd562228bf66d5043bd001cb48 Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Mon, 10 Dec 2012 11:31:59 +0100 Subject: Allow generator expressions to require literals. --- Source/cmGeneratorExpressionEvaluator.cxx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Source/cmGeneratorExpressionEvaluator.cxx b/Source/cmGeneratorExpressionEvaluator.cxx index e20e203..a67cad5 100644 --- a/Source/cmGeneratorExpressionEvaluator.cxx +++ b/Source/cmGeneratorExpressionEvaluator.cxx @@ -47,6 +47,8 @@ struct cmGeneratorExpressionNode virtual bool GeneratesContent() const { return true; } + virtual bool RequiresLiteralInput() const { return false; } + virtual bool AcceptsSingleArbitraryContentParameter() const { return false; } @@ -692,6 +694,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) { @@ -699,6 +710,12 @@ std::string GeneratorExpressionContent::Evaluate( } } } + if (node->RequiresLiteralInput()) + { + std::vector parameters; + parameters.push_back(result); + return node->Evaluate(parameters, context, this, dagChecker); + } return result; } -- cgit v0.12 From b0c8f73eb62c5651b4a039f95676cae310bcada9 Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Mon, 10 Dec 2012 11:33:23 +0100 Subject: Add the TARGET_NAME generator expression. It will be used as a preprocessing marker. --- Source/cmDocumentGeneratorExpressions.h | 4 ++++ Source/cmGeneratorExpressionEvaluator.cxx | 24 ++++++++++++++++++++++ Tests/GeneratorExpression/CMakeLists.txt | 2 ++ Tests/GeneratorExpression/check-part2.cmake | 2 ++ .../GeneratorExpression/BadTargetName-result.txt | 1 + .../GeneratorExpression/BadTargetName-stderr.txt | 8 ++++++++ .../GeneratorExpression/BadTargetName.cmake | 3 +++ .../GeneratorExpression/RunCMakeTest.cmake | 1 + 8 files changed, 45 insertions(+) create mode 100644 Tests/RunCMake/GeneratorExpression/BadTargetName-result.txt create mode 100644 Tests/RunCMake/GeneratorExpression/BadTargetName-stderr.txt create mode 100644 Tests/RunCMake/GeneratorExpression/BadTargetName.cmake diff --git a/Source/cmDocumentGeneratorExpressions.h b/Source/cmDocumentGeneratorExpressions.h index 1927012..b8889ac 100644 --- a/Source/cmDocumentGeneratorExpressions.h +++ b/Source/cmDocumentGeneratorExpressions.h @@ -26,6 +26,10 @@ "strings which contain a '>' for example.\n" \ " $ = A literal ','. Used to compare " \ "strings which contain a ',' for example.\n" \ + " $ = 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" \ " $ = content of \"...\" when the property " \ "is exported using install(EXPORT), and empty otherwise.\n" \ " $ = content of \"...\" when the property " \ diff --git a/Source/cmGeneratorExpressionEvaluator.cxx b/Source/cmGeneratorExpressionEvaluator.cxx index a67cad5..c8f5b82 100644 --- a/Source/cmGeneratorExpressionEvaluator.cxx +++ b/Source/cmGeneratorExpressionEvaluator.cxx @@ -377,6 +377,28 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode } 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 ¶meters, + cmGeneratorExpressionContext *, + const GeneratorExpressionContent *, + cmGeneratorExpressionDAGChecker *) const + { + return parameters.front(); + } + + virtual int NumExpectedParameters() const { return 1; } + +} targetNameNode; + +//---------------------------------------------------------------------------- template struct TargetFilesystemArtifactResultCreator { @@ -601,6 +623,8 @@ 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") diff --git a/Tests/GeneratorExpression/CMakeLists.txt b/Tests/GeneratorExpression/CMakeLists.txt index db6fb74..ecbbedf 100644 --- a/Tests/GeneratorExpression/CMakeLists.txt +++ b/Tests/GeneratorExpression/CMakeLists.txt @@ -87,6 +87,8 @@ add_custom_target(check-part2 ALL -Dtest_incomplete_21=$ -Dtest_build_interface=$ -Dtest_install_interface=$ + -Dtest_target_name_1=$ + -Dtest_target_name_2=$ -P ${CMAKE_CURRENT_SOURCE_DIR}/check-part2.cmake COMMAND ${CMAKE_COMMAND} -E echo "check done (part 2 of 2)" VERBATIM diff --git a/Tests/GeneratorExpression/check-part2.cmake b/Tests/GeneratorExpression/check-part2.cmake index 149a658..8855a97 100644 --- a/Tests/GeneratorExpression/check-part2.cmake +++ b/Tests/GeneratorExpression/check-part2.cmake @@ -24,3 +24,5 @@ check(test_incomplete_20 "$") check(test_incomplete_21 "$") check(test_build_interface "build") check(test_install_interface "") +check(test_target_name_1 "tgt,ok") +check(test_target_name_2 "tgt:ok") diff --git a/Tests/RunCMake/GeneratorExpression/BadTargetName-result.txt b/Tests/RunCMake/GeneratorExpression/BadTargetName-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/BadTargetName-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/GeneratorExpression/BadTargetName-stderr.txt b/Tests/RunCMake/GeneratorExpression/BadTargetName-stderr.txt new file mode 100644 index 0000000..969393a --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/BadTargetName-stderr.txt @@ -0,0 +1,8 @@ +CMake Error at BadTargetName.cmake:1 \(add_custom_target\): + Error evaluating generator expression: + + \$> + + \$ expression requires literal input. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\)$ diff --git a/Tests/RunCMake/GeneratorExpression/BadTargetName.cmake b/Tests/RunCMake/GeneratorExpression/BadTargetName.cmake new file mode 100644 index 0000000..e125cab --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/BadTargetName.cmake @@ -0,0 +1,3 @@ +add_custom_target(check ALL COMMAND check + $> + VERBATIM) diff --git a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake index 992ba79..8a69675 100644 --- a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake +++ b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake @@ -6,3 +6,4 @@ run_cmake(BadAND) run_cmake(BadNOT) run_cmake(BadStrEqual) run_cmake(BadZero) +run_cmake(BadTargetName) -- cgit v0.12 From e04f737c7a3e66c49e2d50813af1fa40a415eff8 Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Mon, 10 Dec 2012 23:07:09 +0100 Subject: Add API to extract target names from a genex string. The TARGET_NAME expression, which requires a literal, provides target names. $ also provides target names in the cases where tgt is a literal, so that TARGET_NAME is not needed then in addition. --- Source/cmExportFileGenerator.cxx | 136 +++++++++++++++++++++++++++++++++++++++ Source/cmExportFileGenerator.h | 4 ++ 2 files changed, 140 insertions(+) diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx index 3f738cc..2507b72 100644 --- a/Source/cmExportFileGenerator.cxx +++ b/Source/cmExportFileGenerator.cxx @@ -126,6 +126,142 @@ void cmExportFileGenerator::GenerateImportConfig(std::ostream& os, //---------------------------------------------------------------------------- void +cmExportFileGenerator::ResolveTargetsInGeneratorExpressions( + std::string &input, + cmTarget* target, + std::vector &missingTargets) +{ + std::string::size_type pos = 0; + std::string::size_type lastPos = pos; + + cmMakefile *mf = target->GetMakefile(); + std::string errorString; + + while((pos = 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 = "$ 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("$", nameStartPos); + if (endPos == input.npos) + { + errorString = "$ expression incomplete"; + } + const std::string targetName = input.substr(nameStartPos, + endPos - nameStartPos); + if(targetName.find("$<", lastPos) != input.npos) + { + errorString = "$ requires its parameter to be a " + "literal."; + } + if (cmTarget *tgt = mf->FindTargetToUse(targetName.c_str())) + { + if(tgt->IsImported()) + { + input.replace(pos, sizeof("$ExportedTargets.find(tgt) != this->ExportedTargets.end()) + { + input.replace(pos, sizeof("$Namespace + targetName); + pos += sizeof("$HandleMissingTarget(namespacedTarget, missingTargets, + mf, target, tgt); + if (!namespacedTarget.empty()) + { + input.replace(pos, sizeof("$IssueMessage(cmake::FATAL_ERROR, errorString); + } +} + +//---------------------------------------------------------------------------- +void cmExportFileGenerator ::SetImportDetailProperties(const char* config, std::string const& suffix, cmTarget* target, ImportPropertyMap& properties, diff --git a/Source/cmExportFileGenerator.h b/Source/cmExportFileGenerator.h index 4a75c52..0b89a64 100644 --- a/Source/cmExportFileGenerator.h +++ b/Source/cmExportFileGenerator.h @@ -94,6 +94,10 @@ protected: cmTarget* depender, cmTarget* dependee) = 0; + void ResolveTargetsInGeneratorExpressions(std::string &input, + cmTarget* target, + std::vector &missingTargets); + // The namespace in which the exports are placed in the generated file. std::string Namespace; -- cgit v0.12 From 0c657dc637ca4da6861102c7306069ad157ec08d Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Mon, 10 Dec 2012 23:05:11 +0100 Subject: Add API to populate INTERFACE properties in exported targets. The INTERFACE properties need to be preprocessed for context (build location or install location) and to extract target names etc. --- Source/cmExportFileGenerator.cxx | 59 ++++++++++++++++++++++++++++++++++++++++ Source/cmExportFileGenerator.h | 15 ++++++++++ 2 files changed, 74 insertions(+) diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx index 2507b72..2b133be 100644 --- a/Source/cmExportFileGenerator.cxx +++ b/Source/cmExportFileGenerator.cxx @@ -125,6 +125,65 @@ void cmExportFileGenerator::GenerateImportConfig(std::ostream& os, } //---------------------------------------------------------------------------- +void cmExportFileGenerator::PopulateInterfaceProperty(const char *propName, + const char *outputName, + cmTarget *target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap &properties, + std::vector &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 &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, diff --git a/Source/cmExportFileGenerator.h b/Source/cmExportFileGenerator.h index 0b89a64..7c58ad8 100644 --- a/Source/cmExportFileGenerator.h +++ b/Source/cmExportFileGenerator.h @@ -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,13 @@ protected: cmMakefile* mf, cmTarget* depender, cmTarget* dependee) = 0; + void PopulateInterfaceProperty(const char *, + cmTarget *target, + cmGeneratorExpression::PreprocessContext, + ImportPropertyMap &properties, + std::vector &missingTargets); + void GenerateInterfaceProperties(cmTarget *target, std::ostream& os, + const ImportPropertyMap &properties); void ResolveTargetsInGeneratorExpressions(std::string &input, cmTarget* target, @@ -113,6 +121,13 @@ protected: // The set of targets included in the export. std::set ExportedTargets; + +private: + void PopulateInterfaceProperty(const char *, const char *, + cmTarget *target, + cmGeneratorExpression::PreprocessContext, + ImportPropertyMap &properties, + std::vector &missingTargets); }; #endif -- cgit v0.12 From 26def1771dfbf502129212c9e728e34860477fac Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Tue, 6 Nov 2012 16:06:31 +0100 Subject: Make all relevant targets available in the genex context. The current node being evaluated transitively in the generator expression must be available to resolve mapped configs. --- Source/cmGeneratorExpression.cxx | 20 ++++++++++++++++++-- Source/cmGeneratorExpression.h | 7 ++++++- Source/cmGeneratorExpressionDAGChecker.cxx | 2 +- Source/cmGeneratorExpressionEvaluator.cxx | 2 +- Source/cmGeneratorExpressionEvaluator.h | 4 +++- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Source/cmGeneratorExpression.cxx b/Source/cmGeneratorExpression.cxx index 841fbb7..6d003e1 100644 --- a/Source/cmGeneratorExpression.cxx +++ b/Source/cmGeneratorExpression.cxx @@ -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) diff --git a/Source/cmGeneratorExpression.h b/Source/cmGeneratorExpression.h index 99056a5..dcdfefb 100644 --- a/Source/cmGeneratorExpression.h +++ b/Source/cmGeneratorExpression.h @@ -71,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 const& GetTargets() const diff --git a/Source/cmGeneratorExpressionDAGChecker.cxx b/Source/cmGeneratorExpressionDAGChecker.cxx index bfb0ddf..4a9fc28 100644 --- a/Source/cmGeneratorExpressionDAGChecker.cxx +++ b/Source/cmGeneratorExpressionDAGChecker.cxx @@ -57,7 +57,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); diff --git a/Source/cmGeneratorExpressionEvaluator.cxx b/Source/cmGeneratorExpressionEvaluator.cxx index c8f5b82..4a7a034 100644 --- a/Source/cmGeneratorExpressionEvaluator.cxx +++ b/Source/cmGeneratorExpressionEvaluator.cxx @@ -297,7 +297,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 (parameters.size() == 2) { diff --git a/Source/cmGeneratorExpressionEvaluator.h b/Source/cmGeneratorExpressionEvaluator.h index d904b02..59804ff 100644 --- a/Source/cmGeneratorExpressionEvaluator.h +++ b/Source/cmGeneratorExpressionEvaluator.h @@ -26,7 +26,9 @@ struct cmGeneratorExpressionContext std::set 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; }; -- cgit v0.12 From d0f950fdba88ac08d0e25e340fe558eba008810e Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Sun, 23 Dec 2012 11:20:37 +0100 Subject: Use mapped config properties to evaluate $ --- Source/cmGeneratorExpressionEvaluator.cxx | 20 ++++++- Source/cmTarget.cxx | 86 ++++++++++++++++++------------- Source/cmTarget.h | 5 ++ 3 files changed, 72 insertions(+), 39 deletions(-) diff --git a/Source/cmGeneratorExpressionEvaluator.cxx b/Source/cmGeneratorExpressionEvaluator.cxx index 4a7a034..8b8b860 100644 --- a/Source/cmGeneratorExpressionEvaluator.cxx +++ b/Source/cmGeneratorExpressionEvaluator.cxx @@ -265,8 +265,24 @@ 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; diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index 58fab80..d60979c 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -4365,27 +4365,15 @@ cmTarget::GetImportInfo(const char* config) return &i->second; } -//---------------------------------------------------------------------------- -void cmTarget::ComputeImportInfo(std::string const& desired_config, - ImportInfo& info) +bool cmTarget::GetMappedConfig(std::string const& desired_config, + const char** loc, + const char** imp, + std::string& suffix) { - // This method finds information about an imported target from its - // properties. The "IMPORTED_" namespace is reserved for properties - // defined by the project exporting the target. - - // Initialize members. - info.NoSOName = false; - // Track the configuration-specific property suffix. - std::string suffix = "_"; + 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 mappedConfigs; { std::string mapProp = "MAP_IMPORTED_CONFIG_"; @@ -4396,26 +4384,29 @@ void cmTarget::ComputeImportInfo(std::string const& desired_config, } } + // 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. - const char* loc = 0; - const char* imp = 0; for(std::vector::const_iterator mci = mappedConfigs.begin(); - !loc && !imp && mci != mappedConfigs.end(); ++mci) + !*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()); + *loc = this->GetProperty(locProp.c_str()); if(allowImp) { std::string impProp = "IMPORTED_IMPLIB_"; impProp += mcUpper; - imp = this->GetProperty(impProp.c_str()); + *imp = this->GetProperty(impProp.c_str()); } // If it was found, use it for all properties below. - if(loc || imp) + if(*loc || *imp) { suffix = "_"; suffix += mcUpper; @@ -4425,45 +4416,45 @@ void cmTarget::ComputeImportInfo(std::string const& desired_config, // 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) + if(!mappedConfigs.empty() && !*loc && !*imp) { - return; + return false; } // If we have not yet found it then there are no mapped // configurations. Look for an exact-match. - if(!loc && !imp) + if(!*loc && !*imp) { std::string locProp = "IMPORTED_LOCATION"; locProp += suffix; - loc = this->GetProperty(locProp.c_str()); + *loc = this->GetProperty(locProp.c_str()); if(allowImp) { std::string impProp = "IMPORTED_IMPLIB"; impProp += suffix; - imp = this->GetProperty(impProp.c_str()); + *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) + 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"); + *loc = this->GetProperty("IMPORTED_LOCATION"); if(allowImp) { - imp = this->GetProperty("IMPORTED_IMPLIB"); + *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) + if(!*loc && !*imp) { std::vector availableConfigs; if(const char* iconfigs = this->GetProperty("IMPORTED_CONFIGURATIONS")) @@ -4472,24 +4463,45 @@ void cmTarget::ComputeImportInfo(std::string const& desired_config, } for(std::vector::const_iterator aci = availableConfigs.begin(); - !loc && !imp && aci != availableConfigs.end(); ++aci) + !*loc && !*imp && aci != availableConfigs.end(); ++aci) { suffix = "_"; suffix += cmSystemTools::UpperCase(*aci); std::string locProp = "IMPORTED_LOCATION"; locProp += suffix; - loc = this->GetProperty(locProp.c_str()); + *loc = this->GetProperty(locProp.c_str()); if(allowImp) { std::string impProp = "IMPORTED_IMPLIB"; impProp += suffix; - imp = this->GetProperty(impProp.c_str()); + *imp = this->GetProperty(impProp.c_str()); } } } - // If we have not yet found it then the target is not available. - if(!loc && !imp) + if(!*loc && !*imp) + { + return false; + } + + return true; +} + +//---------------------------------------------------------------------------- +void cmTarget::ComputeImportInfo(std::string const& desired_config, + ImportInfo& info) +{ + // This method finds information about an imported target from its + // properties. The "IMPORTED_" namespace is reserved for properties + // defined by the project exporting the target. + + // Initialize members. + info.NoSOName = false; + + const char* loc = 0; + const char* imp = 0; + std::string suffix; + if (!this->GetMappedConfig(desired_config, &loc, &imp, suffix)) { return; } diff --git a/Source/cmTarget.h b/Source/cmTarget.h index 3f36050..52d5ca6 100644 --- a/Source/cmTarget.h +++ b/Source/cmTarget.h @@ -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); -- cgit v0.12 From c67b8124f735e7f96567a276e16969607b300e43 Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Fri, 21 Dec 2012 15:49:19 +0100 Subject: Make cycles in target properties ignored, not an error. Constructs such as these are an error as they are direct self-references: set_property(TARGET foo APPEND PROPERTY INCLUDE_DIRECTORIES $) set_property(TARGET foo APPEND PROPERTY INCLUDE_DIRECTORIES $) However, this is an indirect self-reference in a cycle, and not an error: set_property(TARGET foo APPEND PROPERTY INCLUDE_DIRECTORIES $) set_property(TARGET bar APPEND PROPERTY INCLUDE_DIRECTORIES $) --- Source/cmGeneratorExpressionDAGChecker.cxx | 16 +++++++++------- Source/cmGeneratorExpressionDAGChecker.h | 12 +++++++++--- Source/cmGeneratorExpressionEvaluator.cxx | 8 +++++++- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Source/cmGeneratorExpressionDAGChecker.cxx b/Source/cmGeneratorExpressionDAGChecker.cxx index 4a9fc28..2e5b5ae 100644 --- a/Source/cmGeneratorExpressionDAGChecker.cxx +++ b/Source/cmGeneratorExpressionDAGChecker.cxx @@ -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; } @@ -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; } diff --git a/Source/cmGeneratorExpressionDAGChecker.h b/Source/cmGeneratorExpressionDAGChecker.h index ffc84f8..48f26ed 100644 --- a/Source/cmGeneratorExpressionDAGChecker.h +++ b/Source/cmGeneratorExpressionDAGChecker.h @@ -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 diff --git a/Source/cmGeneratorExpressionEvaluator.cxx b/Source/cmGeneratorExpressionEvaluator.cxx index 8b8b860..4b44ebe 100644 --- a/Source/cmGeneratorExpressionEvaluator.cxx +++ b/Source/cmGeneratorExpressionEvaluator.cxx @@ -381,10 +381,16 @@ 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()); -- cgit v0.12 From f5b1980fb27fa902d37856ec3e22daddfeb9d52f Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Sun, 30 Dec 2012 22:06:12 +0100 Subject: Populate the ExportedTargets member early in GenerateMainFile The member variable is checked to determine whether to call HandleMissingTarget(). As that could be called during the loop in the comming commits, ensure that it contains all targets being exported. --- Source/cmExportBuildFileGenerator.cxx | 25 ++++++++++++++----------- Source/cmExportInstallFileGenerator.cxx | 25 ++++++++++++++----------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Source/cmExportBuildFileGenerator.cxx b/Source/cmExportBuildFileGenerator.cxx index cd6a7ab..3e9a9fd 100644 --- a/Source/cmExportBuildFileGenerator.cxx +++ b/Source/cmExportBuildFileGenerator.cxx @@ -22,6 +22,7 @@ cmExportBuildFileGenerator::cmExportBuildFileGenerator() //---------------------------------------------------------------------------- bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os) { + std::vector 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::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,18 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os) } } + this->GenerateExpectedTargetsCode(os, expectedTargets); + } + + // Create all the imported targets. + for(std::vector::const_iterator + tei = allTargets.begin(); + tei != allTargets.end(); ++tei) + { + cmTarget* te = *tei; + this->GenerateImportTargetCode(os, te); + } + // Generate import file content for each configuration. for(std::vector::const_iterator ci = this->Configurations.begin(); diff --git a/Source/cmExportInstallFileGenerator.cxx b/Source/cmExportInstallFileGenerator.cxx index 6ba7d9f..2b7937e 100644 --- a/Source/cmExportInstallFileGenerator.cxx +++ b/Source/cmExportInstallFileGenerator.cxx @@ -39,6 +39,7 @@ std::string cmExportInstallFileGenerator::GetConfigImportFileGlob() //---------------------------------------------------------------------------- bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os) { + std::vector 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::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,18 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os) } } + this->GenerateExpectedTargetsCode(os, expectedTargets); + } + + // Create all the imported targets. + for(std::vector::const_iterator + tei = allTargets.begin(); + tei != allTargets.end(); ++tei) + { + cmTarget* te = *tei; + this->GenerateImportTargetCode(os, te); + } + // 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" -- cgit v0.12 From 894f52f32d96ae92df0ba3dac2a70fe9c87e818d Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Sun, 23 Sep 2012 13:45:17 +0200 Subject: Handle INTERFACE properties transitively for includes and defines. Contextually, the behavior is as if the properties content from another target is included in the string and then the result is evaluated. --- Source/cmExportBuildFileGenerator.cxx | 15 +++++ Source/cmExportInstallFileGenerator.cxx | 17 +++++ Source/cmGeneratorExpressionEvaluator.cxx | 30 ++++++++- Source/cmTarget.cxx | 22 +++++++ Tests/ExportImport/Export/CMakeLists.txt | 75 +++++++++++++++++++++- Tests/ExportImport/Export/testLibDepends.c | 16 +++++ Tests/ExportImport/Import/A/CMakeLists.txt | 15 +++++ Tests/ExportImport/Import/A/deps_iface.cpp | 24 +++++++ .../TargetIncludeDirectories/CMakeLists.txt | 31 +++++++++ 9 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 Tests/ExportImport/Import/A/deps_iface.cpp diff --git a/Source/cmExportBuildFileGenerator.cxx b/Source/cmExportBuildFileGenerator.cxx index 3e9a9fd..9533319 100644 --- a/Source/cmExportBuildFileGenerator.cxx +++ b/Source/cmExportBuildFileGenerator.cxx @@ -52,6 +52,8 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os) this->GenerateExpectedTargetsCode(os, expectedTargets); } + std::vector missingTargets; + // Create all the imported targets. for(std::vector::const_iterator tei = allTargets.begin(); @@ -59,8 +61,21 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os) { 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::const_iterator ci = this->Configurations.begin(); diff --git a/Source/cmExportInstallFileGenerator.cxx b/Source/cmExportInstallFileGenerator.cxx index 2b7937e..bc953c9 100644 --- a/Source/cmExportInstallFileGenerator.cxx +++ b/Source/cmExportInstallFileGenerator.cxx @@ -69,6 +69,8 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os) this->GenerateExpectedTargetsCode(os, expectedTargets); } + std::vector missingTargets; + // Create all the imported targets. for(std::vector::const_iterator tei = allTargets.begin(); @@ -76,8 +78,23 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os) { 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" diff --git a/Source/cmGeneratorExpressionEvaluator.cxx b/Source/cmGeneratorExpressionEvaluator.cxx index 4b44ebe..b7ce155 100644 --- a/Source/cmGeneratorExpressionEvaluator.cxx +++ b/Source/cmGeneratorExpressionEvaluator.cxx @@ -286,6 +286,13 @@ static const struct ConfigurationTestNode : public cmGeneratorExpressionNode } } configurationTestNode; + +//---------------------------------------------------------------------------- +static const char* targetPropertyTransitiveWhitelist[] = { + "INTERFACE_INCLUDE_DIRECTORIES" + , "INTERFACE_COMPILE_DEFINITIONS" +}; + //---------------------------------------------------------------------------- static const struct TargetPropertyNode : public cmGeneratorExpressionNode { @@ -394,7 +401,28 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode } 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; diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index d60979c..ca53a39 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -720,6 +720,28 @@ void cmTarget::DefineProperties(cmake *cm) "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 $ 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 $ 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.", "When linking to a STATIC library target with cyclic dependencies the " diff --git a/Tests/ExportImport/Export/CMakeLists.txt b/Tests/ExportImport/Export/CMakeLists.txt index e19ab88..569845a 100644 --- a/Tests/ExportImport/Export/CMakeLists.txt +++ b/Tests/ExportImport/Export/CMakeLists.txt @@ -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 + $ +) +set_property(TARGET testLibDepends APPEND PROPERTY + COMPILE_DEFINITIONS + $ +) +set_property(TARGET testLibDepends APPEND PROPERTY + INTERFACE_INCLUDE_DIRECTORIES + $ +) +set_property(TARGET testLibDepends APPEND PROPERTY + 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 + $ + $>,INTERFACE_INCLUDE_DIRECTORIES> + $> + $> + $> + $> +) + +set_property(TARGET testLibRequired APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS + testLibRequired_IFACE_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 ) diff --git a/Tests/ExportImport/Export/testLibDepends.c b/Tests/ExportImport/Export/testLibDepends.c index 2849b33..fb5a002 100644 --- a/Tests/ExportImport/Export/testLibDepends.c +++ b/Tests/ExportImport/Export/testLibDepends.c @@ -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(); } diff --git a/Tests/ExportImport/Import/A/CMakeLists.txt b/Tests/ExportImport/Import/A/CMakeLists.txt index 8841792..b77562e 100644 --- a/Tests/ExportImport/Import/A/CMakeLists.txt +++ b/Tests/ExportImport/Import/A/CMakeLists.txt @@ -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 + $ +) +set_property(TARGET deps_iface APPEND PROPERTY + INCLUDE_DIRECTORIES + $ +) diff --git a/Tests/ExportImport/Import/A/deps_iface.cpp b/Tests/ExportImport/Import/A/deps_iface.cpp new file mode 100644 index 0000000..7190b92 --- /dev/null +++ b/Tests/ExportImport/Import/A/deps_iface.cpp @@ -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(); +} diff --git a/Tests/IncludeDirectories/TargetIncludeDirectories/CMakeLists.txt b/Tests/IncludeDirectories/TargetIncludeDirectories/CMakeLists.txt index 7cb1b42..63a5492 100644 --- a/Tests/IncludeDirectories/TargetIncludeDirectories/CMakeLists.txt +++ b/Tests/IncludeDirectories/TargetIncludeDirectories/CMakeLists.txt @@ -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 "$<$,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;$") +set_property(TARGET lib2 APPEND PROPERTY 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 "$") + +# 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;$") +set_property(TARGET lib3 APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/fiy;$") + +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;$") +set_property(TARGET lib4 APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/foh;$") + 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") -- cgit v0.12