From 0fb923c46041d67110c8e0907afdf66b3b25f25a Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 14 Jan 2023 23:58:28 -0500 Subject: cmGeneratorExpressionNode: implement `COMPILE_ONLY` genex This generator expression is the inverse of `LINK_ONLY` and only coveys usage requirements for the purposes of compilation. Its intended use is to avoid needing to export targets that do not have link usage requirements (e.g., header-only libraries) when used by another target. It will also be used to represent private usage requirements on exported C++ module-containing targets in the future. Eventually there should be logic to collapse nesting of `$` and `$` when generating instances of either. A TODO is left in the code for this case. See: #15415 --- Help/manual/cmake-generator-expressions.7.rst | 19 +++++++++++++++- Help/release/dev/genex-compile-only.rst | 5 +++++ Source/cmExportFileGenerator.cxx | 16 ++++++++++++++ Source/cmGeneratorExpressionNode.cxx | 25 ++++++++++++++++++++++ Source/cmTargetLinkLibrariesCommand.cxx | 1 + .../COMPILE_ONLY-not-compiling-result.txt | 1 + .../COMPILE_ONLY-not-compiling-stderr.txt | 8 +++++++ .../COMPILE_ONLY-not-compiling.cmake | 1 + .../GeneratorExpression/RunCMakeTest.cmake | 1 + 9 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 Help/release/dev/genex-compile-only.rst create mode 100644 Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-result.txt create mode 100644 Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-stderr.txt create mode 100644 Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling.cmake diff --git a/Help/manual/cmake-generator-expressions.7.rst b/Help/manual/cmake-generator-expressions.7.rst index 9da3799..186a9d2 100644 --- a/Help/manual/cmake-generator-expressions.7.rst +++ b/Help/manual/cmake-generator-expressions.7.rst @@ -959,6 +959,22 @@ Compile Features :manual:`cmake-compile-features(7)` manual for information on compile features and a list of supported compilers. +Compile Context +^^^^^^^^^^^^^^^ + +.. genex:: $ + + .. versionadded:: 3.27 + + Content of ``...``, except while collecting :ref:`Target Usage Requirements`, + in which case it is the empty string. This is intended for use in an + :prop_tgt:`INTERFACE_LINK_LIBRARIES` target property, typically populated + via the :command:`target_link_libraries` command, to specify private + compilation requirements without other usage requirements. + + Use cases include header-only usage where all usages are known to not have + linking requirements (e.g., all-``inline`` or C++ template libraries). + Linker Language And ID ^^^^^^^^^^^^^^^^^^^^^^ @@ -1339,7 +1355,8 @@ Link Context in which case it is the empty string. This is intended for use in an :prop_tgt:`INTERFACE_LINK_LIBRARIES` target property, typically populated via the :command:`target_link_libraries` command, to specify private link - dependencies without other usage requirements. + dependencies without other usage requirements such as include directories or + compile options. .. versionadded:: 3.24 ``LINK_ONLY`` may also be used in a :prop_tgt:`LINK_LIBRARIES` target diff --git a/Help/release/dev/genex-compile-only.rst b/Help/release/dev/genex-compile-only.rst new file mode 100644 index 0000000..1f898d8 --- /dev/null +++ b/Help/release/dev/genex-compile-only.rst @@ -0,0 +1,5 @@ +genex-compile-only +------------------ + +* The :genex:`COMPILE_ONLY` generator expression has been added which provides + compilation usage requirements without any linking requirements. diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx index 6e7ef4e..e997158 100644 --- a/Source/cmExportFileGenerator.cxx +++ b/Source/cmExportFileGenerator.cxx @@ -734,6 +734,22 @@ void cmExportFileGenerator::ResolveTargetsInGeneratorExpression( lastPos = nameStartPos + libName.size() + 1; } + while (errorString.empty() && + (pos = input.find("$', nameStartPos); + if (endPos == std::string::npos) { + errorString = "$ expression incomplete"; + break; + } + std::string libName = input.substr(nameStartPos, endPos - nameStartPos); + if (cmGeneratorExpression::IsValidTargetName(libName) && + this->AddTargetNamespace(libName, target, lg)) { + input.replace(nameStartPos, endPos - nameStartPos, libName); + } + lastPos = nameStartPos + libName.size() + 1; + } + this->ReplaceInstallPrefix(input); if (!errorString.empty()) { diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx index a47366b..a221498 100644 --- a/Source/cmGeneratorExpressionNode.cxx +++ b/Source/cmGeneratorExpressionNode.cxx @@ -1351,6 +1351,29 @@ static const VersionNode versionLessNode; static const VersionNode versionLessEqNode; static const VersionNode versionEqualNode; +static const struct CompileOnlyNode : public cmGeneratorExpressionNode +{ + CompileOnlyNode() {} // NOLINT(modernize-use-equals-default) + + std::string Evaluate( + const std::vector& parameters, + cmGeneratorExpressionContext* context, + const GeneratorExpressionContent* content, + cmGeneratorExpressionDAGChecker* dagChecker) const override + { + if (!dagChecker) { + reportError(context, content->GetOriginalExpression(), + "$ may only be used via linking"); + return std::string(); + } + // Linking checks for the inverse, so compiling is the opposite. + if (dagChecker->GetTransitivePropertiesOnly()) { + return parameters.front(); + } + return std::string(); + } +} compileOnlyNode; + static const struct LinkOnlyNode : public cmGeneratorExpressionNode { LinkOnlyNode() {} // NOLINT(modernize-use-equals-default) @@ -1366,6 +1389,7 @@ static const struct LinkOnlyNode : public cmGeneratorExpressionNode "$ may only be used for linking"); return std::string(); } + // Compile-only checks for the inverse, so linking is the opposite. if (!dagChecker->GetTransitivePropertiesOnly()) { return parameters.front(); } @@ -3805,6 +3829,7 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode( { "BUILD_LOCAL_INTERFACE", &buildLocalInterfaceNode }, { "INSTALL_PREFIX", &installPrefixNode }, { "JOIN", &joinNode }, + { "COMPILE_ONLY", &compileOnlyNode }, { "LINK_ONLY", &linkOnlyNode }, { "COMPILE_LANG_AND_ID", &languageAndIdNode }, { "COMPILE_LANGUAGE", &languageNode }, diff --git a/Source/cmTargetLinkLibrariesCommand.cxx b/Source/cmTargetLinkLibrariesCommand.cxx index 0b123b2..03d7c9f 100644 --- a/Source/cmTargetLinkLibrariesCommand.cxx +++ b/Source/cmTargetLinkLibrariesCommand.cxx @@ -552,6 +552,7 @@ bool TLL::HandleLibrary(ProcessingState currentProcessingState, currentProcessingState == ProcessingPlainPrivateInterface) { if (this->Target->GetType() == cmStateEnums::STATIC_LIBRARY || this->Target->GetType() == cmStateEnums::OBJECT_LIBRARY) { + // TODO: Detect and no-op `$` genexes here. std::string configLib = this->Target->GetDebugGeneratorExpressions(lib, llt); if (cmGeneratorExpression::IsValidTargetName(lib) || diff --git a/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-result.txt b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-stderr.txt b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-stderr.txt new file mode 100644 index 0000000..5e7fce2 --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-stderr.txt @@ -0,0 +1,8 @@ +CMake Error at COMPILE_ONLY-not-compiling.cmake:1 \(add_custom_target\): + Error evaluating generator expression: + + \$ + + \$ may only be used via linking +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling.cmake b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling.cmake new file mode 100644 index 0000000..1bc75f9 --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling.cmake @@ -0,0 +1 @@ +add_custom_target(Custom ALL COMMAND ${CMAKE_COMMAND} -E echo $) diff --git a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake index b139210..3fd9947 100644 --- a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake +++ b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake @@ -22,6 +22,7 @@ run_cmake(NonValidTarget-CXX_COMPILER_VERSION) run_cmake(NonValidTarget-Fortran_COMPILER_VERSION) run_cmake(NonValidTarget-TARGET_PROPERTY) run_cmake(NonValidTarget-TARGET_POLICY) +run_cmake(COMPILE_ONLY-not-compiling) run_cmake(LINK_ONLY-not-linking) run_cmake(TARGET_EXISTS-no-arg) run_cmake(TARGET_EXISTS-empty-arg) -- cgit v0.12 From c42630ee62df80e649211e99c510cab7ac28fc0b Mon Sep 17 00:00:00 2001 From: Robert Maynard Date: Tue, 11 Apr 2023 15:49:52 -0400 Subject: cmGeneratorExpressionNode: implement `COMPILE_ONLY` genex This generator expression is the inverse of `LINK_ONLY` and only coveys usage requirements for the purposes of compilation. Its intended use is to avoid needing to export targets that do not have link usage requirements (e.g., header-only libraries) when used by another target. See: #15415 --- Help/manual/cmake-generator-expressions.7.rst | 13 ++++++++----- Source/cmGeneratorExpressionDAGChecker.cxx | 8 ++++++++ Source/cmGeneratorExpressionDAGChecker.h | 4 ++++ Source/cmGeneratorExpressionNode.cxx | 8 +++----- Source/cmGeneratorTarget.cxx | 4 +++- Tests/CMakeCommands/target_link_libraries/CMakeLists.txt | 16 ++++++++++++++++ .../CMakeCommands/target_link_libraries/compile_only.cpp | 8 ++++++++ Tests/ExportImport/Export/CMakeLists.txt | 10 ++++++++++ Tests/ExportImport/Export/testLib2.c | 4 ++++ .../COMPILE_ONLY-not-compiling-stderr.txt | 2 +- 10 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 Tests/CMakeCommands/target_link_libraries/compile_only.cpp diff --git a/Help/manual/cmake-generator-expressions.7.rst b/Help/manual/cmake-generator-expressions.7.rst index 186a9d2..da7df70 100644 --- a/Help/manual/cmake-generator-expressions.7.rst +++ b/Help/manual/cmake-generator-expressions.7.rst @@ -966,15 +966,18 @@ Compile Context .. versionadded:: 3.27 - Content of ``...``, except while collecting :ref:`Target Usage Requirements`, - in which case it is the empty string. This is intended for use in an - :prop_tgt:`INTERFACE_LINK_LIBRARIES` target property, typically populated - via the :command:`target_link_libraries` command, to specify private - compilation requirements without other usage requirements. + Content of ``...``, when collecting :ref:`Target Usage Requirements`, + otherwise it is the empty string. This is intended for use in an + :prop_tgt:`INTERFACE_LINK_LIBRARIES` and :prop_tgt:`LINK_LIBRARIES` target + properties, typically populated via the :command:`target_link_libraries` command. + Provides compilation usage requirements without any linking requirements. Use cases include header-only usage where all usages are known to not have linking requirements (e.g., all-``inline`` or C++ template libraries). + Note that for proper evaluation of this expression requires policy :policy:`CMP0099` + to be set to `NEW`. + Linker Language And ID ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Source/cmGeneratorExpressionDAGChecker.cxx b/Source/cmGeneratorExpressionDAGChecker.cxx index 82a6c57..d51dbd0 100644 --- a/Source/cmGeneratorExpressionDAGChecker.cxx +++ b/Source/cmGeneratorExpressionDAGChecker.cxx @@ -27,6 +27,7 @@ cmGeneratorExpressionDAGChecker::cmGeneratorExpressionDAGChecker( , Content(content) , Backtrace(std::move(backtrace)) , TransitivePropertiesOnly(false) + , CMP0131(false) { this->Initialize(); } @@ -41,6 +42,7 @@ cmGeneratorExpressionDAGChecker::cmGeneratorExpressionDAGChecker( , Content(content) , Backtrace() , TransitivePropertiesOnly(false) + , CMP0131(false) { this->Initialize(); } @@ -143,6 +145,12 @@ bool cmGeneratorExpressionDAGChecker::GetTransitivePropertiesOnly() const return this->Top()->TransitivePropertiesOnly; } +bool cmGeneratorExpressionDAGChecker::GetTransitivePropertiesOnlyCMP0131() + const +{ + return this->Top()->CMP0131; +} + bool cmGeneratorExpressionDAGChecker::EvaluatingGenexExpression() const { return cmHasLiteralPrefix(this->Property, "TARGET_GENEX_EVAL:") || diff --git a/Source/cmGeneratorExpressionDAGChecker.h b/Source/cmGeneratorExpressionDAGChecker.h index df1e005..1919b01 100644 --- a/Source/cmGeneratorExpressionDAGChecker.h +++ b/Source/cmGeneratorExpressionDAGChecker.h @@ -90,6 +90,9 @@ struct cmGeneratorExpressionDAGChecker bool GetTransitivePropertiesOnly() const; void SetTransitivePropertiesOnly() { this->TransitivePropertiesOnly = true; } + bool GetTransitivePropertiesOnlyCMP0131() const; + void SetTransitivePropertiesOnlyCMP0131() { this->CMP0131 = true; } + cmGeneratorExpressionDAGChecker const* Top() const; cmGeneratorTarget const* TopTarget() const; @@ -105,4 +108,5 @@ private: const cmListFileBacktrace Backtrace; Result CheckResult; bool TransitivePropertiesOnly; + bool CMP0131; }; diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx index a221498..ca61f75 100644 --- a/Source/cmGeneratorExpressionNode.cxx +++ b/Source/cmGeneratorExpressionNode.cxx @@ -1363,14 +1363,13 @@ static const struct CompileOnlyNode : public cmGeneratorExpressionNode { if (!dagChecker) { reportError(context, content->GetOriginalExpression(), - "$ may only be used via linking"); + "$ may only be used for linking"); return std::string(); } - // Linking checks for the inverse, so compiling is the opposite. if (dagChecker->GetTransitivePropertiesOnly()) { return parameters.front(); } - return std::string(); + return std::string{}; } } compileOnlyNode; @@ -1389,8 +1388,7 @@ static const struct LinkOnlyNode : public cmGeneratorExpressionNode "$ may only be used for linking"); return std::string(); } - // Compile-only checks for the inverse, so linking is the opposite. - if (!dagChecker->GetTransitivePropertiesOnly()) { + if (!dagChecker->GetTransitivePropertiesOnlyCMP0131()) { return parameters.front(); } return std::string(); diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index 90cddb5..7e5ef0a 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -6791,6 +6791,7 @@ void cmGeneratorTarget::ExpandLinkItems( // requirements. if (interfaceFor == LinkInterfaceFor::Usage) { dagChecker.SetTransitivePropertiesOnly(); + dagChecker.SetTransitivePropertiesOnlyCMP0131(); } cmMakefile const* mf = this->LocalGenerator->GetMakefile(); LookupLinkItemScope scope{ this->LocalGenerator }; @@ -8229,6 +8230,7 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries( // The $ expression may be used to specify link dependencies // that are otherwise excluded from usage requirements. if (implFor == LinkInterfaceFor::Usage) { + dagChecker.SetTransitivePropertiesOnly(); switch (this->GetPolicyStatusCMP0131()) { case cmPolicies::WARN: case cmPolicies::OLD: @@ -8236,7 +8238,7 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries( case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: case cmPolicies::NEW: - dagChecker.SetTransitivePropertiesOnly(); + dagChecker.SetTransitivePropertiesOnlyCMP0131(); break; } } diff --git a/Tests/CMakeCommands/target_link_libraries/CMakeLists.txt b/Tests/CMakeCommands/target_link_libraries/CMakeLists.txt index b2365ca..8231a5c 100644 --- a/Tests/CMakeCommands/target_link_libraries/CMakeLists.txt +++ b/Tests/CMakeCommands/target_link_libraries/CMakeLists.txt @@ -155,3 +155,19 @@ target_link_libraries(TopDir SubDirC) add_library(TopDirImported IMPORTED INTERFACE) target_compile_definitions(TopDirImported INTERFACE DEF_TopDirImported) cmake_policy(POP) + +#---------------------------------------------------------------------------- +# Test $ genex. +cmake_policy(SET CMP0099 NEW) +add_library(dont_link_too SHARED compile_only.cpp) +target_compile_definitions(dont_link_too PUBLIC USE_EXAMPLE) +target_link_options(dont_link_too INTERFACE invalid_link_option) +target_link_libraries(dont_link_too INTERFACE invalid_link_library) + +add_library(uses_compile_only_genex SHARED compile_only.cpp) +target_link_libraries(uses_compile_only_genex PUBLIC $) + +add_library(uses_compile_only_genex_static STATIC compile_only.cpp) +target_link_libraries(uses_compile_only_genex_static PRIVATE $) +add_executable(uses_via_static_linking main.cxx) +target_link_libraries(uses_via_static_linking PRIVATE uses_compile_only_genex_static) diff --git a/Tests/CMakeCommands/target_link_libraries/compile_only.cpp b/Tests/CMakeCommands/target_link_libraries/compile_only.cpp new file mode 100644 index 0000000..7519bd0 --- /dev/null +++ b/Tests/CMakeCommands/target_link_libraries/compile_only.cpp @@ -0,0 +1,8 @@ + +#ifndef USE_EXAMPLE +# error "Missing propagated define" +#endif + +// Solaris needs non-empty content so ensure +// we have at least one symbol +int Solaris_requires_a_symbol_here = 0; diff --git a/Tests/ExportImport/Export/CMakeLists.txt b/Tests/ExportImport/Export/CMakeLists.txt index 6f19c13..67f2fcb 100644 --- a/Tests/ExportImport/Export/CMakeLists.txt +++ b/Tests/ExportImport/Export/CMakeLists.txt @@ -22,9 +22,18 @@ add_executable(testExe2 testExe2.c) set_property(TARGET testExe2 PROPERTY ENABLE_EXPORTS 1) set_property(TARGET testExe2 PROPERTY LINK_INTERFACE_LIBRARIES testExe2lib) +add_library(compileOnly INTERFACE) +target_compile_definitions(compileOnly INTERFACE FROM_compileOnly) +target_link_options(compileOnly INTERFACE -fthis-flag-does-not-exist) + add_library(testLib1 STATIC testLib1.c) add_library(testLib2 STATIC testLib2.c) target_link_libraries(testLib2 testLib1) +target_link_libraries(testLib2 + PRIVATE + testLib1 + "$") + # Test install(FILES) with generator expressions referencing testLib1. add_custom_command(TARGET testLib1 POST_BUILD @@ -556,6 +565,7 @@ install(FILES # Install and export from install tree. install( TARGETS + compileOnly testExe1 testLib1 testLib2 testExe2 testLib3 testLib4 testExe3 testExe4 testExe2lib testLib4lib testLib4libdbg testLib4libopt testLib6 testLib7 testLib8 diff --git a/Tests/ExportImport/Export/testLib2.c b/Tests/ExportImport/Export/testLib2.c index 7a5206f..f5faffa 100644 --- a/Tests/ExportImport/Export/testLib2.c +++ b/Tests/ExportImport/Export/testLib2.c @@ -1,4 +1,8 @@ +#ifndef FROM_compileOnly +# error "Usage requirements from `compileOnly` not found" +#endif + extern int testLib1(void); int testLib2(void) diff --git a/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-stderr.txt b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-stderr.txt index 5e7fce2..8c93d59 100644 --- a/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-stderr.txt +++ b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-stderr.txt @@ -3,6 +3,6 @@ CMake Error at COMPILE_ONLY-not-compiling.cmake:1 \(add_custom_target\): \$ - \$ may only be used via linking + \$ may only be used for linking Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) -- cgit v0.12