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.

See: #15415
Robert Maynard 2 years ago
parent
commit
c42630ee62

+ 8 - 5
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
 ^^^^^^^^^^^^^^^^^^^^^^
 

+ 8 - 0
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:") ||

+ 4 - 0
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;
 };

+ 3 - 5
Source/cmGeneratorExpressionNode.cxx

@@ -1363,14 +1363,13 @@ static const struct CompileOnlyNode : public cmGeneratorExpressionNode
   {
     if (!dagChecker) {
       reportError(context, content->GetOriginalExpression(),
-                  "$<COMPILE_ONLY:...> may only be used via linking");
+                  "$<COMPILE_ONLY:...> 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
                   "$<LINK_ONLY:...> 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();

+ 3 - 1
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 $<LINK_ONLY> 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;
       }
     }

+ 16 - 0
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 $<COMPILE_ONLY:> 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 $<COMPILE_ONLY:dont_link_too>)
+
+add_library(uses_compile_only_genex_static STATIC compile_only.cpp)
+target_link_libraries(uses_compile_only_genex_static PRIVATE $<COMPILE_ONLY:dont_link_too>)
+add_executable(uses_via_static_linking main.cxx)
+target_link_libraries(uses_via_static_linking PRIVATE uses_compile_only_genex_static)

+ 8 - 0
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;

+ 10 - 0
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
+    "$<COMPILE_ONLY:compileOnly>")
+
 
 # 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

+ 4 - 0
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)

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