Browse Source

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
`$<COMPILE_ONLY>` and `$<LINK_ONLY>` when generating instances of
either. A TODO is left in the code for this case.

See: #15415
Ben Boeckel 2 years ago
parent
commit
0fb923c460

+ 18 - 1
Help/manual/cmake-generator-expressions.7.rst

@@ -959,6 +959,22 @@ Compile Features
   :manual:`cmake-compile-features(7)` manual for information on
   :manual:`cmake-compile-features(7)` manual for information on
   compile features and a list of supported compilers.
   compile features and a list of supported compilers.
 
 
+Compile Context
+^^^^^^^^^^^^^^^
+
+.. genex:: $<COMPILE_ONLY:...>
+
+  .. 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
 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
   in which case it is the empty string.  This is intended for use in an
   :prop_tgt:`INTERFACE_LINK_LIBRARIES` target property, typically populated
   :prop_tgt:`INTERFACE_LINK_LIBRARIES` target property, typically populated
   via the :command:`target_link_libraries` command, to specify private link
   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
   .. versionadded:: 3.24
     ``LINK_ONLY`` may also be used in a :prop_tgt:`LINK_LIBRARIES` target
     ``LINK_ONLY`` may also be used in a :prop_tgt:`LINK_LIBRARIES` target

+ 5 - 0
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.

+ 16 - 0
Source/cmExportFileGenerator.cxx

@@ -734,6 +734,22 @@ void cmExportFileGenerator::ResolveTargetsInGeneratorExpression(
     lastPos = nameStartPos + libName.size() + 1;
     lastPos = nameStartPos + libName.size() + 1;
   }
   }
 
 
+  while (errorString.empty() &&
+         (pos = input.find("$<COMPILE_ONLY:", lastPos)) != std::string::npos) {
+    std::string::size_type nameStartPos = pos + cmStrLen("$<COMPILE_ONLY:");
+    std::string::size_type endPos = input.find('>', nameStartPos);
+    if (endPos == std::string::npos) {
+      errorString = "$<COMPILE_ONLY:...> 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);
   this->ReplaceInstallPrefix(input);
 
 
   if (!errorString.empty()) {
   if (!errorString.empty()) {

+ 25 - 0
Source/cmGeneratorExpressionNode.cxx

@@ -1351,6 +1351,29 @@ static const VersionNode<cmSystemTools::OP_LESS> versionLessNode;
 static const VersionNode<cmSystemTools::OP_LESS_EQUAL> versionLessEqNode;
 static const VersionNode<cmSystemTools::OP_LESS_EQUAL> versionLessEqNode;
 static const VersionNode<cmSystemTools::OP_EQUAL> versionEqualNode;
 static const VersionNode<cmSystemTools::OP_EQUAL> versionEqualNode;
 
 
+static const struct CompileOnlyNode : public cmGeneratorExpressionNode
+{
+  CompileOnlyNode() {} // NOLINT(modernize-use-equals-default)
+
+  std::string Evaluate(
+    const std::vector<std::string>& parameters,
+    cmGeneratorExpressionContext* context,
+    const GeneratorExpressionContent* content,
+    cmGeneratorExpressionDAGChecker* dagChecker) const override
+  {
+    if (!dagChecker) {
+      reportError(context, content->GetOriginalExpression(),
+                  "$<COMPILE_ONLY:...> 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
 static const struct LinkOnlyNode : public cmGeneratorExpressionNode
 {
 {
   LinkOnlyNode() {} // NOLINT(modernize-use-equals-default)
   LinkOnlyNode() {} // NOLINT(modernize-use-equals-default)
@@ -1366,6 +1389,7 @@ static const struct LinkOnlyNode : public cmGeneratorExpressionNode
                   "$<LINK_ONLY:...> may only be used for linking");
                   "$<LINK_ONLY:...> may only be used for linking");
       return std::string();
       return std::string();
     }
     }
+    // Compile-only checks for the inverse, so linking is the opposite.
     if (!dagChecker->GetTransitivePropertiesOnly()) {
     if (!dagChecker->GetTransitivePropertiesOnly()) {
       return parameters.front();
       return parameters.front();
     }
     }
@@ -3805,6 +3829,7 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode(
     { "BUILD_LOCAL_INTERFACE", &buildLocalInterfaceNode },
     { "BUILD_LOCAL_INTERFACE", &buildLocalInterfaceNode },
     { "INSTALL_PREFIX", &installPrefixNode },
     { "INSTALL_PREFIX", &installPrefixNode },
     { "JOIN", &joinNode },
     { "JOIN", &joinNode },
+    { "COMPILE_ONLY", &compileOnlyNode },
     { "LINK_ONLY", &linkOnlyNode },
     { "LINK_ONLY", &linkOnlyNode },
     { "COMPILE_LANG_AND_ID", &languageAndIdNode },
     { "COMPILE_LANG_AND_ID", &languageAndIdNode },
     { "COMPILE_LANGUAGE", &languageNode },
     { "COMPILE_LANGUAGE", &languageNode },

+ 1 - 0
Source/cmTargetLinkLibrariesCommand.cxx

@@ -552,6 +552,7 @@ bool TLL::HandleLibrary(ProcessingState currentProcessingState,
       currentProcessingState == ProcessingPlainPrivateInterface) {
       currentProcessingState == ProcessingPlainPrivateInterface) {
     if (this->Target->GetType() == cmStateEnums::STATIC_LIBRARY ||
     if (this->Target->GetType() == cmStateEnums::STATIC_LIBRARY ||
         this->Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
         this->Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
+      // TODO: Detect and no-op `$<COMPILE_ONLY>` genexes here.
       std::string configLib =
       std::string configLib =
         this->Target->GetDebugGeneratorExpressions(lib, llt);
         this->Target->GetDebugGeneratorExpressions(lib, llt);
       if (cmGeneratorExpression::IsValidTargetName(lib) ||
       if (cmGeneratorExpression::IsValidTargetName(lib) ||

+ 1 - 0
Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-result.txt

@@ -0,0 +1 @@
+1

+ 8 - 0
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:
+
+    \$<COMPILE_ONLY:something>
+
+  \$<COMPILE_ONLY:...> may only be used via linking
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 1 - 0
Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling.cmake

@@ -0,0 +1 @@
+add_custom_target(Custom ALL COMMAND ${CMAKE_COMMAND} -E echo $<COMPILE_ONLY:something>)

+ 1 - 0
Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake

@@ -22,6 +22,7 @@ run_cmake(NonValidTarget-CXX_COMPILER_VERSION)
 run_cmake(NonValidTarget-Fortran_COMPILER_VERSION)
 run_cmake(NonValidTarget-Fortran_COMPILER_VERSION)
 run_cmake(NonValidTarget-TARGET_PROPERTY)
 run_cmake(NonValidTarget-TARGET_PROPERTY)
 run_cmake(NonValidTarget-TARGET_POLICY)
 run_cmake(NonValidTarget-TARGET_POLICY)
+run_cmake(COMPILE_ONLY-not-compiling)
 run_cmake(LINK_ONLY-not-linking)
 run_cmake(LINK_ONLY-not-linking)
 run_cmake(TARGET_EXISTS-no-arg)
 run_cmake(TARGET_EXISTS-no-arg)
 run_cmake(TARGET_EXISTS-empty-arg)
 run_cmake(TARGET_EXISTS-empty-arg)