Browse Source

Merge topic 'link-interface-direct'

f3ad061858 Add usage requirements to update direct link dependencies
193a999cd5 cmTarget: Add INTERFACE_LINK_LIBRARIES_DIRECT{,_EXCLUDE} backtrace storage
22d5427aa6 cmGeneratorTarget: Add LookupLinkItem option to consider own target name
f3d2eab36a cmGeneratorTarget: Fix link interface caching of partial results
d75ab9d066 cmGeneratorTarget: Clarify CMP0022 logic in ComputeLinkInterfaceLibraries
f3e9e03fe0 cmGeneratorTarget: Simplify CMP0022 warning check
216aa14997 cmGeneratorTarget: Return early from ExpandLinkItems with no items
1bc98371d1 Tests: Remove unnecessary policy setting from ObjectLibrary test
...

Acked-by: Kitware Robot <[email protected]>
Merge-request: !6886
Brad King 3 years ago
parent
commit
5305d5aa1a
80 changed files with 1442 additions and 120 deletions
  1. 2 0
      Help/manual/cmake-properties.7.rst
  2. 13 0
      Help/prop_tgt/INTERFACE_LINK_LIBRARIES.rst
  3. 221 0
      Help/prop_tgt/INTERFACE_LINK_LIBRARIES_DIRECT.rst
  4. 9 0
      Help/prop_tgt/INTERFACE_LINK_LIBRARIES_DIRECT.txt
  5. 32 0
      Help/prop_tgt/INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE.rst
  6. 10 2
      Help/prop_tgt/LINK_LIBRARIES.rst
  7. 7 0
      Help/release/dev/link-interface-direct.rst
  8. 17 10
      Source/cmExportFileGenerator.cxx
  9. 2 0
      Source/cmExportTryCompileFileGenerator.cxx
  10. 5 3
      Source/cmGeneratorExpressionDAGChecker.cxx
  11. 255 95
      Source/cmGeneratorTarget.cxx
  12. 20 3
      Source/cmGeneratorTarget.h
  13. 2 2
      Source/cmLinkItem.cxx
  14. 8 2
      Source/cmLinkItem.h
  15. 5 0
      Source/cmLocalGenerator.cxx
  16. 59 0
      Source/cmTarget.cxx
  17. 2 0
      Source/cmTarget.h
  18. 2 0
      Tests/CMakeLists.txt
  19. 16 0
      Tests/ExportImport/Export/CMakeLists.txt
  20. 14 0
      Tests/ExportImport/Import/A/CMakeLists.txt
  21. 4 2
      Tests/ExportImport/Import/A/imp_testExe1.c
  22. 156 0
      Tests/InterfaceLinkLibrariesDirect/CMakeLists.txt
  23. 21 0
      Tests/InterfaceLinkLibrariesDirect/ExePlugin.c
  24. 18 0
      Tests/InterfaceLinkLibrariesDirect/UseSharedLibWithHelper.c
  25. 3 0
      Tests/InterfaceLinkLibrariesDirect/a_always.c
  26. 3 0
      Tests/InterfaceLinkLibrariesDirect/a_not_direct_from_A.c
  27. 3 0
      Tests/InterfaceLinkLibrariesDirect/a_not_direct_from_A_for_exe.c
  28. 3 0
      Tests/InterfaceLinkLibrariesDirect/a_not_direct_from_A_optional.c
  29. 5 0
      Tests/InterfaceLinkLibrariesDirect/a_poison_direct_from_A.c
  30. 5 0
      Tests/InterfaceLinkLibrariesDirect/a_poison_direct_from_A_for_exe.c
  31. 5 0
      Tests/InterfaceLinkLibrariesDirect/a_poison_direct_from_A_optional.c
  32. 3 0
      Tests/InterfaceLinkLibrariesDirect/direct_from_A.c
  33. 3 0
      Tests/InterfaceLinkLibrariesDirect/direct_from_A_for_exe.c
  34. 5 0
      Tests/InterfaceLinkLibrariesDirect/direct_from_A_for_exe_poison.c
  35. 3 0
      Tests/InterfaceLinkLibrariesDirect/direct_from_A_optional.c
  36. 5 0
      Tests/InterfaceLinkLibrariesDirect/direct_from_A_optional_poison.c
  37. 5 0
      Tests/InterfaceLinkLibrariesDirect/direct_from_A_poison.c
  38. 23 0
      Tests/InterfaceLinkLibrariesDirect/exe_use_static_A_private.c
  39. 23 0
      Tests/InterfaceLinkLibrariesDirect/exe_use_static_A_public.c
  40. 5 0
      Tests/InterfaceLinkLibrariesDirect/main.c
  41. 5 0
      Tests/InterfaceLinkLibrariesDirect/order_A.c
  42. 5 0
      Tests/InterfaceLinkLibrariesDirect/order_B.c
  43. 6 0
      Tests/InterfaceLinkLibrariesDirect/order_B_poison.c
  44. 5 0
      Tests/InterfaceLinkLibrariesDirect/order_C.c
  45. 11 0
      Tests/InterfaceLinkLibrariesDirect/order_C_poison.c
  46. 5 0
      Tests/InterfaceLinkLibrariesDirect/order_D.c
  47. 16 0
      Tests/InterfaceLinkLibrariesDirect/order_D_poison.c
  48. 5 0
      Tests/InterfaceLinkLibrariesDirect/order_E.c
  49. 21 0
      Tests/InterfaceLinkLibrariesDirect/order_E_poison.c
  50. 5 0
      Tests/InterfaceLinkLibrariesDirect/order_F.c
  51. 26 0
      Tests/InterfaceLinkLibrariesDirect/order_F_poison.c
  52. 5 0
      Tests/InterfaceLinkLibrariesDirect/order_G.c
  53. 31 0
      Tests/InterfaceLinkLibrariesDirect/order_G_poison.c
  54. 5 0
      Tests/InterfaceLinkLibrariesDirect/order_H.c
  55. 36 0
      Tests/InterfaceLinkLibrariesDirect/order_H_poison.c
  56. 5 0
      Tests/InterfaceLinkLibrariesDirect/order_I.c
  57. 41 0
      Tests/InterfaceLinkLibrariesDirect/order_I_poison.c
  58. 3 0
      Tests/InterfaceLinkLibrariesDirect/order_J.c
  59. 46 0
      Tests/InterfaceLinkLibrariesDirect/order_J_poison.c
  60. 6 0
      Tests/InterfaceLinkLibrariesDirect/order_main.c
  61. 16 0
      Tests/InterfaceLinkLibrariesDirect/static_A_private.c
  62. 16 0
      Tests/InterfaceLinkLibrariesDirect/static_A_public.c
  63. 4 0
      Tests/InterfaceLinkLibrariesDirect/testExePluginHelperObj.c
  64. 12 0
      Tests/InterfaceLinkLibrariesDirect/testExeWithPluginHelper.c
  65. 7 0
      Tests/InterfaceLinkLibrariesDirect/testExeWithPluginHelper.cmake
  66. 4 0
      Tests/InterfaceLinkLibrariesDirect/testSharedLibHelperObj.c
  67. 7 0
      Tests/InterfaceLinkLibrariesDirect/testSharedLibWithHelper.c
  68. 6 0
      Tests/InterfaceLinkLibrariesDirect/testSharedLibWithHelper.cmake
  69. 6 0
      Tests/InterfaceLinkLibrariesDirect/testStaticLibPlugin.c
  70. 14 0
      Tests/InterfaceLinkLibrariesDirect/testStaticLibPlugin.cmake
  71. 5 0
      Tests/InterfaceLinkLibrariesDirect/testStaticLibPluginExtra.c
  72. 4 0
      Tests/InterfaceLinkLibrariesDirect/testStaticLibWithPlugin1.c
  73. 4 0
      Tests/InterfaceLinkLibrariesDirect/testStaticLibWithPlugin2.c
  74. 6 0
      Tests/InterfaceLinkLibrariesDirect/testStaticLibWithPluginBad1.c
  75. 7 0
      Tests/InterfaceLinkLibrariesDirect/testStaticLibWithPluginBad2.c
  76. 6 0
      Tests/ObjectLibrary/Transitive/BarMain.c
  77. 5 0
      Tests/ObjectLibrary/Transitive/BarObject1.c
  78. 5 0
      Tests/ObjectLibrary/Transitive/BarObject2.c
  79. 4 0
      Tests/ObjectLibrary/Transitive/BarObject3.c
  80. 14 1
      Tests/ObjectLibrary/Transitive/CMakeLists.txt

+ 2 - 0
Help/manual/cmake-properties.7.rst

@@ -266,6 +266,8 @@ Properties on Targets
    /prop_tgt/INTERFACE_LINK_DEPENDS
    /prop_tgt/INTERFACE_LINK_DIRECTORIES
    /prop_tgt/INTERFACE_LINK_LIBRARIES
+   /prop_tgt/INTERFACE_LINK_LIBRARIES_DIRECT
+   /prop_tgt/INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE
    /prop_tgt/INTERFACE_LINK_OPTIONS
    /prop_tgt/INTERFACE_POSITION_INDEPENDENT_CODE
    /prop_tgt/INTERFACE_PRECOMPILE_HEADERS

+ 13 - 0
Help/prop_tgt/INTERFACE_LINK_LIBRARIES.rst

@@ -12,6 +12,13 @@ other target also.  This property is overridden by the
 :prop_tgt:`LINK_INTERFACE_LIBRARIES_<CONFIG>` property if policy
 :policy:`CMP0022` is ``OLD`` or unset.
 
+The value of this property is used by the generators when constructing
+the link rule for a dependent target.  A dependent target's direct
+link dependencies, specified by its :prop_tgt:`LINK_LIBRARIES` target
+property, are linked first, followed by indirect dependencies from the
+transitive closure of the direct dependencies'
+``INTERFACE_LINK_LIBRARIES`` properties.  See policy :policy:`CMP0022`.
+
 Contents of ``INTERFACE_LINK_LIBRARIES`` may use "generator expressions"
 with the syntax ``$<...>``.  See the :manual:`cmake-generator-expressions(7)`
 manual for available expressions.  See the :manual:`cmake-buildsystem(7)`
@@ -19,6 +26,12 @@ manual for more on defining buildsystem properties.
 
 .. include:: LINK_LIBRARIES_INDIRECTION.txt
 
+``INTERFACE_LINK_LIBRARIES`` adds transitive link dependencies for a
+target's dependents.  In advanced use cases, one may update the
+direct link dependencies of a target's dependents by using the
+:prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT` and
+:prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE` target properties.
+
 Creating Relocatable Packages
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

+ 221 - 0
Help/prop_tgt/INTERFACE_LINK_LIBRARIES_DIRECT.rst

@@ -0,0 +1,221 @@
+INTERFACE_LINK_LIBRARIES_DIRECT
+-------------------------------
+
+List of libraries that consumers of this library should treat
+as direct link dependencies.
+
+This target property may be set to *include* items in a dependent
+target's final set of direct link dependencies.  See the
+:prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE` target property
+to exclude items.
+
+The initial set of a dependent target's direct link dependencies is
+specified by its :prop_tgt:`LINK_LIBRARIES` target property.  Indirect
+link dependencies are specified by the transitive closure of the direct
+link dependencies' :prop_tgt:`INTERFACE_LINK_LIBRARIES` properties.
+Any link dependency may specify additional direct link dependencies
+using the ``INTERFACE_LINK_LIBRARIES_DIRECT`` target property.
+The set of direct link dependencies is then filtered to exclude items named
+by any dependency's :prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE`
+target property.
+
+.. |INTERFACE_PROPERTY_LINK_DIRECT| replace:: ``INTERFACE_LINK_LIBRARIES_DIRECT``
+.. include:: INTERFACE_LINK_LIBRARIES_DIRECT.txt
+
+Direct Link Dependencies as Usage Requirements
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``INTERFACE_PROPERTY_LINK_DIRECT`` and
+``INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE`` target properties
+are :ref:`usage requirements <Target Usage Requirements>`.
+Their effects propagate to dependent targets transitively, and can
+therefore affect the direct link dependencies of every target in a
+chain of dependent libraries.  Whenever some library target ``X`` links
+to another library target ``Y`` whose direct or transitive usage
+requirements contain ``INTERFACE_PROPERTY_LINK_DIRECT`` or
+``INTERFACE_PROPERTY_LINK_DIRECT_EXCLUDE``, the properties may affect
+``X``'s list of direct link dependencies:
+
+* If ``X`` is a shared library or executable, its dependencies are linked.
+  They also affect the usage requirements with which ``X``'s sources are
+  compiled.
+
+* If ``X`` is a static library or object library, it does not actually
+  link, so its dependencies at most affect the usage requirements with
+  which ``X``'s sources are compiled.
+
+The properties may also affect the list of direct link dependencies
+on ``X``'s dependents:
+
+* If ``X`` links ``Y`` publicly:
+
+  .. code-block:: cmake
+
+    target_link_libraries(X PUBLIC Y)
+
+  then ``Y`` is placed in ``X``'s :prop_tgt:`INTERFACE_LINK_LIBRARIES`,
+  so ``Y``'s usage requirements, including ``INTERFACE_PROPERTY_LINK_DIRECT``
+  and ``INTERFACE_PROPERTY_LINK_DIRECT_EXCLUDE``, are propagated
+  to ``X``'s dependents.
+
+* If ``X`` links ``Y`` privately:
+
+  .. code-block:: cmake
+
+    target_link_libraries(X PRIVATE Y)
+
+  then ``Y`` is not placed in ``X``'s :prop_tgt:`INTERFACE_LINK_LIBRARIES`,
+  so ``Y``'s usage requirements, even ``INTERFACE_PROPERTY_LINK_DIRECT``
+  and ``INTERFACE_PROPERTY_LINK_DIRECT_EXCLUDE``, are not propagated
+  to ``X``'s dependents.
+  (If ``X`` is a static library or object library, then ``$<LINK_ONLY:Y>``
+  is placed in ``X``'s :prop_tgt:`INTERFACE_LINK_LIBRARIES`, but the
+  :genex:`LINK_ONLY` generator expression block ``Y``'s usage requirements.)
+
+* In either case, the content of ``X``'s :prop_tgt:`INTERFACE_LINK_LIBRARIES`
+  is not affected by ``Y``'s ``INTERFACE_PROPERTY_LINK_DIRECT`` or
+  ``INTERFACE_PROPERTY_LINK_DIRECT_EXCLUDE``.
+
+One may limit the effects of ``INTERFACE_PROPERTY_LINK_DIRECT`` and
+``INTERFACE_PROPERTY_LINK_DIRECT_EXCLUDE`` to a subset of dependent
+targets by using the :genex:`TARGET_PROPERTY` generator expression.
+For example, to limit the effects to executable targets, use an
+entry of the form::
+
+  "$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:...>"
+
+Similarly, to limit the effects to specific targets, use an entry
+of the form::
+
+  "$<$<BOOL:$<TARGET_PROPERTY:USE_IT>>:...>"
+
+This entry will only affect targets that set their ``USE_IT``
+target property to a true value.
+
+Direct Link Dependency Ordering
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The list of direct link dependencies for a target is computed from an
+initial ordered list in its :prop_tgt:`LINK_LIBRARIES` target property.
+For each item, additional direct link dependencies are discovered from
+its direct and transitive ``INTERFACE_LINK_LIBRARIES_DIRECT`` usage
+requirements.  Each discovered item is injected before the item that
+specified it.  However, a discovered item is added at most once,
+and only if it did not appear anywhere in the initial list.
+This gives :prop_tgt:`LINK_LIBRARIES` control over ordering of
+those direct link dependencies that it explicitly specifies.
+
+Once all direct link dependencies have been collected, items named by
+all of their :prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE`
+usage requirements are removed from the final list.  This does not
+affect the order of the items that remain.
+
+Example: Static Plugins
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Consider a static library ``Foo`` that provides a static plugin
+``FooPlugin`` to consuming application executables, where the
+implementation of the plugin depends on ``Foo`` and other things.
+In this case, the application should link to ``FooPlugin`` directly,
+before ``Foo``.  However, the application author only knows about ``Foo``.
+We can express this as follows:
+
+.. code-block:: cmake
+
+  # Core library used by other components.
+  add_library(Core STATIC core.cpp)
+
+  # Foo is a static library for use by applications.
+  # Implementation of Foo depends on Core.
+  add_library(Foo STATIC foo.cpp foo_plugin_helper.cpp)
+  target_link_libraries(Foo PRIVATE Core)
+
+  # Extra parts of Foo for use by its static plugins.
+  # Implementation of Foo's extra parts depends on both Core and Foo.
+  add_library(FooExtras STATIC foo_extras.cpp)
+  target_link_libraries(FooExtras PRIVATE Core Foo)
+
+  # The Foo library has an associated static plugin
+  # that should be linked into the final executable.
+  # Implementation of the plugin depends on Core, Foo, and FooExtras.
+  add_library(FooPlugin STATIC foo_plugin.cpp)
+  target_link_libraries(FooPlugin PRIVATE Core Foo FooExtras)
+
+  # An app that links Foo should link Foo's plugin directly.
+  set_property(TARGET Foo PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT FooPlugin)
+
+  # An app does not need to link Foo directly because the plugin links it.
+  set_property(TARGET Foo PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE Foo)
+
+An application ``app`` only needs to specify that it links to ``Foo``:
+
+.. code-block:: cmake
+
+  add_executable(app main.cpp)
+  target_link_libraries(app PRIVATE Foo)
+
+The ``INTERFACE_LINK_LIBRARIES_DIRECT`` target property on ``Foo`` tells
+CMake to pretend that ``app`` also links directly to ``FooPlugin``.
+The ``INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE`` target property on ``Foo``
+tells CMake to pretend that ``app`` did *not* link directly to ``Foo``.
+Instead, ``Foo`` will be linked as a dependency of ``FooPlugin``.  The
+final link line for ``app`` will link the libraries in the following
+order:
+
+* ``FooPlugin`` as a direct link dependency of ``app``
+  (via ``Foo``'s usage requiremens).
+* ``FooExtras`` as a dependency of ``FooPlugin``.
+* ``Foo`` as a dependency of ``FooPlugin`` and ``FooExtras``.
+* ``Core`` as a dependency of ``FooPlugin``, ``FooExtras``, and ``Foo``.
+
+Note that without the ``INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE`` target
+property, ``Foo`` would be linked twice: once as a direct dependency
+of ``app``, and once as a dependency of ``FooPlugin``.
+
+Example: Opt-In Static Plugins
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In the above `Example: Static Plugins`_, the ``app`` executable specifies
+that it links directly to ``Foo``.  In a real application, there might
+be an intermediate library:
+
+.. code-block:: cmake
+
+  add_library(app_impl STATIC app_impl.cpp)
+  target_link_libraries(app_impl PUBLIC Foo)
+
+  add_executable(app main.cpp)
+  target_link_libraries(app PRIVATE app_impl)
+
+In this case we do not want ``Foo``'s ``INTERFACE_LINK_LIBRARIES_DIRECT``
+and ``INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE`` target properties to affect
+the direct dependencies of ``app_impl``.  To avoid this, we can revise
+the property values to make their effects opt-in:
+
+.. code-block:: cmake
+
+  # An app that links Foo should link Foo's plugin directly.
+  set_property(TARGET Foo PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT
+    "$<$<BOOL:$<TARGET_PROPERTY:FOO_STATIC_PLUGINS>>:FooPlugin>"
+  )
+
+  # An app does not need to link Foo directly because the plugin links it.
+  set_property(TARGET Foo PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE
+    "$<$<BOOL:$<TARGET_PROPERTY:FOO_STATIC_PLUGINS>>:Foo>"
+  )
+
+Now, the ``app`` executable can opt-in to get ``Foo``'s plugin(s):
+
+.. code-block:: cmake
+
+  set_property(TARGET app PROPERTY FOO_STATIC_PLUGINS 1)
+
+The final link line for ``app`` will now link the libraries in the following
+order:
+
+* ``FooPlugin`` as a direct link dependency of ``app``
+  (via ``Foo``'s usage requiremens).
+* ``app_impl`` as a direct link dependency of ``app``.
+* ``FooExtras`` as a dependency of ``FooPlugin``.
+* ``Foo`` as a dependency of ``app_impl``, ``FooPlugin``, and ``FooExtras``.
+* ``Core`` as a dependency of ``FooPlugin``, ``FooExtras``, and ``Foo``.

+ 9 - 0
Help/prop_tgt/INTERFACE_LINK_LIBRARIES_DIRECT.txt

@@ -0,0 +1,9 @@
+The value of |INTERFACE_PROPERTY_LINK_DIRECT| may use
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+.. note::
+
+  The |INTERFACE_PROPERTY_LINK_DIRECT| target property is intended for
+  advanced use cases such as injection of static plugins into a consuming
+  executable.  It should not be used as a substitute for organizing
+  normal calls to :command:`target_link_libraries`.

+ 32 - 0
Help/prop_tgt/INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE.rst

@@ -0,0 +1,32 @@
+INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE
+---------------------------------------
+
+List of libraries that consumers of this library should *not* treat
+as direct link dependencies.
+
+This target property may be set to *exclude* items from a dependent
+target's final set of direct link dependencies.  This property is
+processed after the :prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT`
+target property of all other dependencies of the dependent target, so
+exclusion from direct link dependence takes priority over inclusion.
+
+The initial set of a dependent target's direct link dependencies is
+specified by its :prop_tgt:`LINK_LIBRARIES` target property.  Indirect
+link dependencies are specified by the transitive closure of the direct
+link dependencies' :prop_tgt:`INTERFACE_LINK_LIBRARIES` properties.
+Any link dependency may specify additional direct link dependencies
+using the :prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT` target property.
+The set of direct link dependencies is then filtered to exclude items named
+by any dependency's ``INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE`` target
+property.
+
+Excluding an item from a dependent target's direct link dependencies
+does not mean the dependent target won't link the item.  The item
+may still be linked as an indirect link dependency via the
+:prop_tgt:`INTERFACE_LINK_LIBRARIES` property on other dependencies.
+
+.. |INTERFACE_PROPERTY_LINK_DIRECT| replace:: ``INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE``
+.. include:: INTERFACE_LINK_LIBRARIES_DIRECT.txt
+
+See the :prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT` target property
+documentation for more details and examples.

+ 10 - 2
Help/prop_tgt/LINK_LIBRARIES.rst

@@ -8,8 +8,11 @@ used for linking.  In addition to accepting values from the
 :command:`target_link_libraries` command, values may be set directly on
 any target using the :command:`set_property` command.
 
-The value of this property is used by the generators to set the link
-libraries for the compiler.
+The value of this property is used by the generators to construct the
+link rule for the target.  The direct link dependencies are linked first,
+followed by indirect dependencies from the transitive closure of the
+direct dependencies' :prop_tgt:`INTERFACE_LINK_LIBRARIES` properties.
+See policy :policy:`CMP0022`.
 
 Contents of ``LINK_LIBRARIES`` may use "generator expressions" with the
 syntax ``$<...>``.  See the :manual:`cmake-generator-expressions(7)` manual
@@ -17,3 +20,8 @@ for available expressions.  See the :manual:`cmake-buildsystem(7)` manual
 for more on defining buildsystem properties.
 
 .. include:: LINK_LIBRARIES_INDIRECTION.txt
+
+In advanced use cases, the list of direct link dependencies specified
+by this property may be updated by usage requirements from dependencies.
+See the :prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT` and
+:prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE` target properties.

+ 7 - 0
Help/release/dev/link-interface-direct.rst

@@ -0,0 +1,7 @@
+link-interface-direct
+---------------------
+
+* The :prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT` and
+  :prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE` target properties
+  were added to express usage requirements affecting a consumer's
+  direct link dependencies.

+ 17 - 10
Source/cmExportFileGenerator.cxx

@@ -2,6 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmExportFileGenerator.h"
 
+#include <array>
 #include <cassert>
 #include <cstring>
 #include <sstream>
@@ -175,18 +176,24 @@ bool cmExportFileGenerator::PopulateInterfaceLinkLibrariesProperty(
   if (!target->IsLinkable()) {
     return false;
   }
-  cmValue input = target->GetProperty("INTERFACE_LINK_LIBRARIES");
-  if (input) {
-    std::string prepro =
-      cmGeneratorExpression::Preprocess(*input, preprocessRule);
-    if (!prepro.empty()) {
-      this->ResolveTargetsInGeneratorExpressions(
-        prepro, target, missingTargets, ReplaceFreeTargets);
-      properties["INTERFACE_LINK_LIBRARIES"] = prepro;
-      return true;
+  static const std::array<std::string, 3> linkIfaceProps = {
+    { "INTERFACE_LINK_LIBRARIES", "INTERFACE_LINK_LIBRARIES_DIRECT",
+      "INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE" }
+  };
+  bool hadINTERFACE_LINK_LIBRARIES = false;
+  for (std::string const& linkIfaceProp : linkIfaceProps) {
+    if (cmValue input = target->GetProperty(linkIfaceProp)) {
+      std::string prepro =
+        cmGeneratorExpression::Preprocess(*input, preprocessRule);
+      if (!prepro.empty()) {
+        this->ResolveTargetsInGeneratorExpressions(
+          prepro, target, missingTargets, ReplaceFreeTargets);
+        properties[linkIfaceProp] = prepro;
+        hadINTERFACE_LINK_LIBRARIES = true;
+      }
     }
   }
-  return false;
+  return hadINTERFACE_LINK_LIBRARIES;
 }
 
 static bool isSubDirectory(std::string const& a, std::string const& b)

+ 2 - 0
Source/cmExportTryCompileFileGenerator.cxx

@@ -111,6 +111,8 @@ void cmExportTryCompileFileGenerator::PopulateProperties(
   std::vector<std::string> props = target->GetPropertyKeys();
   // Include special properties that might be relevant here.
   props.emplace_back("INTERFACE_LINK_LIBRARIES");
+  props.emplace_back("INTERFACE_LINK_LIBRARIES_DIRECT");
+  props.emplace_back("INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE");
   for (std::string const& p : props) {
     cmValue v = target->GetProperty(p);
     if (!v) {

+ 5 - 3
Source/cmGeneratorExpressionDAGChecker.cxx

@@ -188,11 +188,13 @@ bool cmGeneratorExpressionDAGChecker::EvaluatingLinkLibraries(
     return top->Target == tgt && prop == "LINK_LIBRARIES"_s;
   }
 
-  return prop == "LINK_LIBRARIES"_s || prop == "LINK_INTERFACE_LIBRARIES"_s ||
+  return prop == "LINK_LIBRARIES"_s || prop == "INTERFACE_LINK_LIBRARIES"_s ||
+    prop == "INTERFACE_LINK_LIBRARIES_DIRECT"_s ||
+    prop == "INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE"_s ||
+    prop == "LINK_INTERFACE_LIBRARIES"_s ||
     prop == "IMPORTED_LINK_INTERFACE_LIBRARIES"_s ||
     cmHasLiteralPrefix(prop, "LINK_INTERFACE_LIBRARIES_") ||
-    cmHasLiteralPrefix(prop, "IMPORTED_LINK_INTERFACE_LIBRARIES_") ||
-    prop == "INTERFACE_LINK_LIBRARIES"_s;
+    cmHasLiteralPrefix(prop, "IMPORTED_LINK_INTERFACE_LIBRARIES_");
 }
 
 cmGeneratorExpressionDAGChecker const* cmGeneratorExpressionDAGChecker::Top()

+ 255 - 95
Source/cmGeneratorTarget.cxx

@@ -57,6 +57,11 @@ using LinkInterfaceFor = cmGeneratorTarget::LinkInterfaceFor;
 
 const cmsys::RegularExpression FrameworkRegularExpression(
   "^(.*/)?([^/]*)\\.framework/(.*)$");
+const std::string kINTERFACE_LINK_LIBRARIES = "INTERFACE_LINK_LIBRARIES";
+const std::string kINTERFACE_LINK_LIBRARIES_DIRECT =
+  "INTERFACE_LINK_LIBRARIES_DIRECT";
+const std::string kINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE =
+  "INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE";
 }
 
 template <>
@@ -749,6 +754,12 @@ void cmGeneratorTarget::ClearSourcesCache()
   this->LinkImplMap.clear();
 }
 
+void cmGeneratorTarget::ClearLinkInterfaceCache()
+{
+  this->LinkInterfaceMap.clear();
+  this->LinkInterfaceUsageRequirementsOnlyMap.clear();
+}
+
 void cmGeneratorTarget::AddSourceCommon(const std::string& src, bool before)
 {
   this->SourceEntries.insert(
@@ -3645,7 +3656,7 @@ void processIncludeDirectories(cmGeneratorTarget const* tgt,
     cmLinkImplItem const& item = entry.LinkImplItem;
     std::string const& targetName = item.AsStr();
     bool const fromImported = item.Target && item.Target->IsImported();
-    bool const checkCMP0027 = item.FromGenex;
+    bool const checkCMP0027 = item.CheckCMP0027;
 
     std::string usedIncludes;
     for (std::string& entryInclude : entry.Values) {
@@ -6638,7 +6649,7 @@ bool cmGeneratorTarget::IsLinkLookupScope(std::string const& n,
 
 cm::optional<cmLinkItem> cmGeneratorTarget::LookupLinkItem(
   std::string const& n, cmListFileBacktrace const& bt,
-  LookupLinkItemScope* scope) const
+  LookupLinkItemScope* scope, LookupSelf lookupSelf) const
 {
   cm::optional<cmLinkItem> maybeItem;
   if (this->IsLinkLookupScope(n, scope->LG)) {
@@ -6646,20 +6657,22 @@ cm::optional<cmLinkItem> cmGeneratorTarget::LookupLinkItem(
   }
 
   std::string name = this->CheckCMP0004(n);
-  if (name == this->GetName() || name.empty()) {
+  if (name.empty() ||
+      (lookupSelf == LookupSelf::No && name == this->GetName())) {
     return maybeItem;
   }
   maybeItem = this->ResolveLinkItem(BT<std::string>(name, bt), scope->LG);
   return maybeItem;
 }
 
-void cmGeneratorTarget::ExpandLinkItems(std::string const& prop,
-                                        cmBTStringRange entries,
-                                        std::string const& config,
-                                        cmGeneratorTarget const* headTarget,
-                                        LinkInterfaceFor interfaceFor,
-                                        cmLinkInterface& iface) const
+void cmGeneratorTarget::ExpandLinkItems(
+  std::string const& prop, cmBTStringRange entries, std::string const& config,
+  cmGeneratorTarget const* headTarget, LinkInterfaceFor interfaceFor,
+  LinkInterfaceField field, cmLinkInterface& iface) const
 {
+  if (entries.empty()) {
+    return;
+  }
   // Keep this logic in sync with ComputeLinkImplementationLibraries.
   cmGeneratorExpressionDAGChecker dagChecker(this, prop, nullptr, nullptr);
   // The $<LINK_ONLY> expression may be in a link interface to specify
@@ -6678,10 +6691,20 @@ void cmGeneratorTarget::ExpandLinkItems(std::string const& prop,
       cge->Evaluate(this->LocalGenerator, config, headTarget, &dagChecker,
                     this, headTarget->LinkerLanguage));
     for (std::string const& lib : libs) {
-      if (cm::optional<cmLinkItem> maybeItem =
-            this->LookupLinkItem(lib, cge->GetBacktrace(), &scope)) {
+      if (cm::optional<cmLinkItem> maybeItem = this->LookupLinkItem(
+            lib, cge->GetBacktrace(), &scope,
+            field == LinkInterfaceField::Libraries ? LookupSelf::No
+                                                   : LookupSelf::Yes)) {
         cmLinkItem item = std::move(*maybeItem);
 
+        if (field == LinkInterfaceField::HeadInclude) {
+          iface.HeadInclude.emplace_back(std::move(item));
+          continue;
+        }
+        if (field == LinkInterfaceField::HeadExclude) {
+          iface.HeadExclude.emplace_back(std::move(item));
+          continue;
+        }
         if (!item.Target) {
           // Report explicitly linked object files separately.
           std::string const& maybeObj = item.AsStr();
@@ -6736,17 +6759,16 @@ cmLinkInterface const* cmGeneratorTarget::GetLinkInterface(
   // Lookup any existing link interface for this configuration.
   cmHeadToLinkInterfaceMap& hm = this->GetHeadToLinkInterfaceMap(config);
 
-  if (secondPass) {
-    hm.erase(head);
-  }
-
   // If the link interface does not depend on the head target
-  // then return the one we computed first.
+  // then re-use the one from the head we computed first.
   if (!hm.empty() && !hm.begin()->second.HadHeadSensitiveCondition) {
-    return &hm.begin()->second;
+    head = hm.begin()->first;
   }
 
   cmOptionalLinkInterface& iface = hm[head];
+  if (secondPass) {
+    iface = cmOptionalLinkInterface();
+  }
   if (!iface.LibrariesDone) {
     iface.LibrariesDone = true;
     this->ComputeLinkInterfaceLibraries(config, iface, head,
@@ -6865,9 +6887,9 @@ const cmLinkInterfaceLibraries* cmGeneratorTarget::GetLinkInterfaceLibraries(
        : this->GetHeadToLinkInterfaceMap(config));
 
   // If the link interface does not depend on the head target
-  // then return the one we computed first.
+  // then re-use the one from the head we computed first.
   if (!hm.empty() && !hm.begin()->second.HadHeadSensitiveCondition) {
-    return &hm.begin()->second;
+    head = hm.begin()->first;
   }
 
   cmOptionalLinkInterface& iface = hm[head];
@@ -7146,59 +7168,66 @@ void cmGeneratorTarget::ComputeLinkInterfaceLibraries(
 
   // An explicit list of interface libraries may be set for shared
   // libraries and executables that export symbols.
-  cmValue explicitLibraries = nullptr;
-  std::string linkIfaceProp;
+  bool haveExplicitLibraries = false;
+  cmValue explicitLibrariesCMP0022OLD;
+  std::string linkIfacePropCMP0022OLD;
   bool const cmp0022NEW = (this->GetPolicyStatusCMP0022() != cmPolicies::OLD &&
                            this->GetPolicyStatusCMP0022() != cmPolicies::WARN);
   if (cmp0022NEW) {
     // CMP0022 NEW behavior is to use INTERFACE_LINK_LIBRARIES.
-    linkIfaceProp = "INTERFACE_LINK_LIBRARIES";
-    explicitLibraries = this->GetProperty(linkIfaceProp);
-  } else if (this->GetType() == cmStateEnums::SHARED_LIBRARY ||
-             this->IsExecutableWithExports()) {
+    haveExplicitLibraries = !this->Target->GetLinkInterfaceEntries().empty() ||
+      !this->Target->GetLinkInterfaceDirectEntries().empty() ||
+      !this->Target->GetLinkInterfaceDirectExcludeEntries().empty();
+  } else {
     // CMP0022 OLD behavior is to use LINK_INTERFACE_LIBRARIES if set on a
     // shared lib or executable.
-
-    // Lookup the per-configuration property.
-    linkIfaceProp = cmStrCat("LINK_INTERFACE_LIBRARIES", suffix);
-    explicitLibraries = this->GetProperty(linkIfaceProp);
-
-    // If not set, try the generic property.
-    if (!explicitLibraries) {
-      linkIfaceProp = "LINK_INTERFACE_LIBRARIES";
-      explicitLibraries = this->GetProperty(linkIfaceProp);
+    if (this->GetType() == cmStateEnums::SHARED_LIBRARY ||
+        this->IsExecutableWithExports()) {
+      // Lookup the per-configuration property.
+      linkIfacePropCMP0022OLD = cmStrCat("LINK_INTERFACE_LIBRARIES", suffix);
+      explicitLibrariesCMP0022OLD = this->GetProperty(linkIfacePropCMP0022OLD);
+
+      // If not set, try the generic property.
+      if (!explicitLibrariesCMP0022OLD) {
+        linkIfacePropCMP0022OLD = "LINK_INTERFACE_LIBRARIES";
+        explicitLibrariesCMP0022OLD =
+          this->GetProperty(linkIfacePropCMP0022OLD);
+      }
     }
-  }
 
-  if (explicitLibraries &&
-      this->GetPolicyStatusCMP0022() == cmPolicies::WARN &&
-      !this->PolicyWarnedCMP0022) {
-    // Compare the explicitly set old link interface properties to the
-    // preferred new link interface property one and warn if different.
-    cmValue newExplicitLibraries =
-      this->GetProperty("INTERFACE_LINK_LIBRARIES");
-    if (newExplicitLibraries &&
-        (*newExplicitLibraries != *explicitLibraries)) {
-      std::ostringstream w;
-      /* clang-format off */
-      w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0022) << "\n"
-        "Target \"" << this->GetName() << "\" has an "
-        "INTERFACE_LINK_LIBRARIES property which differs from its " <<
-        linkIfaceProp << " properties."
-        "\n"
-        "INTERFACE_LINK_LIBRARIES:\n"
-        "  " << *newExplicitLibraries << "\n" <<
-        linkIfaceProp << ":\n"
-        "  " << *explicitLibraries << "\n";
-      /* clang-format on */
-      this->LocalGenerator->IssueMessage(MessageType::AUTHOR_WARNING, w.str());
-      this->PolicyWarnedCMP0022 = true;
+    if (explicitLibrariesCMP0022OLD &&
+        this->GetPolicyStatusCMP0022() == cmPolicies::WARN &&
+        !this->PolicyWarnedCMP0022) {
+      // Compare the explicitly set old link interface properties to the
+      // preferred new link interface property one and warn if different.
+      cmValue newExplicitLibraries =
+        this->GetProperty("INTERFACE_LINK_LIBRARIES");
+      if (newExplicitLibraries &&
+          (*newExplicitLibraries != *explicitLibrariesCMP0022OLD)) {
+        std::ostringstream w;
+        /* clang-format off */
+        w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0022) << "\n"
+          "Target \"" << this->GetName() << "\" has an "
+          "INTERFACE_LINK_LIBRARIES property which differs from its " <<
+          linkIfacePropCMP0022OLD << " properties."
+          "\n"
+          "INTERFACE_LINK_LIBRARIES:\n"
+          "  " << *newExplicitLibraries << "\n" <<
+          linkIfacePropCMP0022OLD << ":\n"
+          "  " << *explicitLibrariesCMP0022OLD << "\n";
+        /* clang-format on */
+        this->LocalGenerator->IssueMessage(MessageType::AUTHOR_WARNING,
+                                           w.str());
+        this->PolicyWarnedCMP0022 = true;
+      }
     }
+
+    haveExplicitLibraries = static_cast<bool>(explicitLibrariesCMP0022OLD);
   }
 
   // There is no implicit link interface for executables or modules
   // so if none was explicitly set then there is no link interface.
-  if (!explicitLibraries &&
+  if (!haveExplicitLibraries &&
       (this->GetType() == cmStateEnums::EXECUTABLE ||
        (this->GetType() == cmStateEnums::MODULE_LIBRARY))) {
     return;
@@ -7208,22 +7237,29 @@ void cmGeneratorTarget::ComputeLinkInterfaceLibraries(
   // If CMP0022 is NEW then the plain tll signature sets the
   // INTERFACE_LINK_LIBRARIES property.  Even if the project
   // clears it, the link interface is still explicit.
-  iface.Explicit = cmp0022NEW || explicitLibraries;
-
-  if (explicitLibraries) {
-    // The interface libraries have been explicitly set.
-    if (cmp0022NEW) {
-      // The explicitLibraries came from INTERFACE_LINK_LIBRARIES.
-      // Use its special representation directly to get backtraces.
-      this->ExpandLinkItems(linkIfaceProp,
-                            this->Target->GetLinkInterfaceEntries(), config,
-                            headTarget, interfaceFor, iface);
-    } else {
-      std::vector<BT<std::string>> entries;
-      entries.emplace_back(*explicitLibraries);
-      this->ExpandLinkItems(linkIfaceProp, cmMakeRange(entries), config,
-                            headTarget, interfaceFor, iface);
-    }
+  iface.Explicit = cmp0022NEW || explicitLibrariesCMP0022OLD;
+
+  if (cmp0022NEW) {
+    // The interface libraries are specified by INTERFACE_LINK_LIBRARIES.
+    // Use its special representation directly to get backtraces.
+    this->ExpandLinkItems(
+      kINTERFACE_LINK_LIBRARIES, this->Target->GetLinkInterfaceEntries(),
+      config, headTarget, interfaceFor, LinkInterfaceField::Libraries, iface);
+    this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES_DIRECT,
+                          this->Target->GetLinkInterfaceDirectEntries(),
+                          config, headTarget, interfaceFor,
+                          LinkInterfaceField::HeadInclude, iface);
+    this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE,
+                          this->Target->GetLinkInterfaceDirectExcludeEntries(),
+                          config, headTarget, interfaceFor,
+                          LinkInterfaceField::HeadExclude, iface);
+  } else if (explicitLibrariesCMP0022OLD) {
+    // The interface libraries have been explicitly set in pre-CMP0022 style.
+    std::vector<BT<std::string>> entries;
+    entries.emplace_back(*explicitLibrariesCMP0022OLD);
+    this->ExpandLinkItems(linkIfacePropCMP0022OLD, cmMakeRange(entries),
+                          config, headTarget, interfaceFor,
+                          LinkInterfaceField::Libraries, iface);
   }
 
   // If the link interface is explicit, do not fall back to the link impl.
@@ -7241,13 +7277,10 @@ void cmGeneratorTarget::ComputeLinkInterfaceLibraries(
       // Compare the link implementation fallback link interface to the
       // preferred new link interface property and warn if different.
       cmLinkInterface ifaceNew;
-      static const std::string newProp = "INTERFACE_LINK_LIBRARIES";
-      if (cmValue newExplicitLibraries = this->GetProperty(newProp)) {
-        std::vector<BT<std::string>> entries;
-        entries.emplace_back(*newExplicitLibraries);
-        this->ExpandLinkItems(linkIfaceProp, cmMakeRange(entries), config,
-                              headTarget, interfaceFor, ifaceNew);
-      }
+      this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES,
+                            this->Target->GetLinkInterfaceEntries(), config,
+                            headTarget, interfaceFor,
+                            LinkInterfaceField::Libraries, ifaceNew);
       if (ifaceNew.Libraries != iface.Libraries) {
         std::string oldLibraries = cmJoin(impl->Libraries, ";");
         std::string newLibraries = cmJoin(ifaceNew.Libraries, ";");
@@ -7372,29 +7405,37 @@ const cmLinkInterface* cmGeneratorTarget::GetImportLinkInterface(
        ? this->GetHeadToLinkInterfaceUsageRequirementsMap(config)
        : this->GetHeadToLinkInterfaceMap(config));
 
-  if (secondPass) {
-    hm.erase(headTarget);
-  }
-
   // If the link interface does not depend on the head target
-  // then return the one we computed first.
+  // then re-use the one from the head we computed first.
   if (!hm.empty() && !hm.begin()->second.HadHeadSensitiveCondition) {
-    return &hm.begin()->second;
+    headTarget = hm.begin()->first;
   }
 
   cmOptionalLinkInterface& iface = hm[headTarget];
+  if (secondPass) {
+    iface = cmOptionalLinkInterface();
+  }
   if (!iface.AllDone) {
     iface.AllDone = true;
     iface.LibrariesDone = true;
     iface.Multiplicity = info->Multiplicity;
     cmExpandList(info->Languages, iface.Languages);
+    this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES_DIRECT,
+                          cmMakeRange(info->LibrariesHeadInclude), config,
+                          headTarget, interfaceFor,
+                          LinkInterfaceField::HeadInclude, iface);
+    this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE,
+                          cmMakeRange(info->LibrariesHeadExclude), config,
+                          headTarget, interfaceFor,
+                          LinkInterfaceField::HeadExclude, iface);
     this->ExpandLinkItems(info->LibrariesProp, cmMakeRange(info->Libraries),
-                          config, headTarget, interfaceFor, iface);
+                          config, headTarget, interfaceFor,
+                          LinkInterfaceField::Libraries, iface);
     std::vector<std::string> deps = cmExpandedList(info->SharedDeps);
     LookupLinkItemScope scope{ this->LocalGenerator };
     for (std::string const& dep : deps) {
-      if (cm::optional<cmLinkItem> maybeItem =
-            this->LookupLinkItem(dep, cmListFileBacktrace(), &scope)) {
+      if (cm::optional<cmLinkItem> maybeItem = this->LookupLinkItem(
+            dep, cmListFileBacktrace(), &scope, LookupSelf::No)) {
         iface.SharedDeps.emplace_back(std::move(*maybeItem));
       }
     }
@@ -7482,6 +7523,14 @@ void cmGeneratorTarget::ComputeImportInfo(std::string const& desired_config,
       }
     }
   }
+  for (BT<std::string> const& entry :
+       this->Target->GetLinkInterfaceDirectEntries()) {
+    info.LibrariesHeadInclude.emplace_back(entry);
+  }
+  for (BT<std::string> const& entry :
+       this->Target->GetLinkInterfaceDirectExcludeEntries()) {
+    info.LibrariesHeadExclude.emplace_back(entry);
+  }
   if (this->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
     if (loc) {
       info.LibName = *loc;
@@ -7896,9 +7945,9 @@ cmGeneratorTarget::GetLinkImplementationLibrariesInternal(
     this->LinkImplMap[cmSystemTools::UpperCase(config)];
 
   // If the link implementation does not depend on the head target
-  // then return the one we computed first.
+  // then re-use the one from the head we computed first.
   if (!hm.empty() && !hm.begin()->second.HadHeadSensitiveCondition) {
-    return &hm.begin()->second;
+    head = hm.begin()->first;
   }
 
   cmOptionalLinkImplementation& impl = hm[head];
@@ -7915,6 +7964,112 @@ bool cmGeneratorTarget::IsNullImpliedByLinkLibraries(
   return cm::contains(this->LinkImplicitNullProperties, p);
 }
 
+namespace {
+class TransitiveLinkImpl
+{
+  cmGeneratorTarget const* Self;
+  std::string const& Config;
+  cmLinkImplementation& Impl;
+
+  std::set<cmLinkItem> Emitted;
+  std::set<cmLinkItem> Excluded;
+  std::unordered_set<cmGeneratorTarget const*> Followed;
+
+  void Follow(cmGeneratorTarget const* target);
+
+public:
+  TransitiveLinkImpl(cmGeneratorTarget const* self, std::string const& config,
+                     cmLinkImplementation& impl)
+    : Self(self)
+    , Config(config)
+    , Impl(impl)
+  {
+  }
+
+  void Compute();
+};
+
+void TransitiveLinkImpl::Follow(cmGeneratorTarget const* target)
+{
+  if (!target || !this->Followed.insert(target).second ||
+      target->GetPolicyStatusCMP0022() == cmPolicies::OLD ||
+      target->GetPolicyStatusCMP0022() == cmPolicies::WARN) {
+    return;
+  }
+
+  // Get this target's usage requirements.
+  cmLinkInterfaceLibraries const* iface = target->GetLinkInterfaceLibraries(
+    this->Config, this->Self, LinkInterfaceFor::Usage);
+  if (!iface) {
+    return;
+  }
+  if (iface->HadContextSensitiveCondition) {
+    this->Impl.HadContextSensitiveCondition = true;
+  }
+
+  // Process 'INTERFACE_LINK_LIBRARIES_DIRECT' usage requirements.
+  for (cmLinkItem const& item : iface->HeadInclude) {
+    // Inject direct dependencies from the item's usage requirements
+    // before the item itself.
+    this->Follow(item.Target);
+
+    // Add the item itself, but at most once.
+    if (this->Emitted.insert(item).second) {
+      this->Impl.Libraries.emplace_back(item, /* checkCMP0027= */ false);
+    }
+  }
+
+  // Follow transitive dependencies.
+  for (cmLinkItem const& item : iface->Libraries) {
+    this->Follow(item.Target);
+  }
+
+  // Record exclusions from 'INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE'
+  // usage requirements.
+  for (cmLinkItem const& item : iface->HeadExclude) {
+    this->Excluded.insert(item);
+  }
+}
+
+void TransitiveLinkImpl::Compute()
+{
+  // Save the original items and start with an empty list.
+  std::vector<cmLinkImplItem> original = std::move(this->Impl.Libraries);
+
+  // Avoid injecting any original items as usage requirements.
+  // This gives LINK_LIBRARIES final control over the order
+  // if it explicitly lists everything.
+  this->Emitted.insert(original.cbegin(), original.cend());
+
+  // Process each original item.
+  for (cmLinkImplItem& item : original) {
+    // Inject direct dependencies listed in 'INTERFACE_LINK_LIBRARIES_DIRECT'
+    // usage requirements before the item itself.
+    this->Follow(item.Target);
+
+    // Add the item itself.
+    this->Impl.Libraries.emplace_back(std::move(item));
+  }
+
+  // Remove items listed in 'INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE'
+  // usage requirements found through any dependency above.
+  this->Impl.Libraries.erase(
+    std::remove_if(this->Impl.Libraries.begin(), this->Impl.Libraries.end(),
+                   [this](cmLinkImplItem const& item) {
+                     return this->Excluded.find(item) != this->Excluded.end();
+                   }),
+    this->Impl.Libraries.end());
+}
+
+void ComputeLinkImplTransitive(cmGeneratorTarget const* self,
+                               std::string const& config,
+                               cmLinkImplementation& impl)
+{
+  TransitiveLinkImpl transitiveLinkImpl(self, config, impl);
+  transitiveLinkImpl.Compute();
+}
+}
+
 void cmGeneratorTarget::ComputeLinkImplementationLibraries(
   const std::string& config, cmOptionalLinkImplementation& impl,
   cmGeneratorTarget const* head) const
@@ -7935,7 +8090,7 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries(
     std::string const& evaluated =
       cge->Evaluate(this->LocalGenerator, config, head, &dagChecker, nullptr,
                     this->LinkerLanguage);
-    bool const fromGenex = evaluated != entry.Value;
+    bool const checkCMP0027 = evaluated != entry.Value;
     cmExpandList(evaluated, llibs);
     if (cge->GetHadHeadSensitiveCondition()) {
       impl.HadHeadSensitiveCondition = true;
@@ -8009,7 +8164,7 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries(
         }
       }
 
-      impl.Libraries.emplace_back(std::move(item), fromGenex);
+      impl.Libraries.emplace_back(std::move(item), checkCMP0027);
     }
 
     std::set<std::string> const& seenProps = cge->GetSeenTargetProperties();
@@ -8021,6 +8176,11 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries(
     cge->GetMaxLanguageStandard(this, this->MaxLanguageStandards);
   }
 
+  // Update the list of direct link dependencies from usage requirements.
+  if (head == this) {
+    ComputeLinkImplTransitive(this, config, impl);
+  }
+
   // Get the list of configurations considered to be DEBUG.
   std::vector<std::string> debugConfigs =
     this->Makefile->GetCMakeInstance()->GetDebugConfigs();

+ 20 - 3
Source/cmGeneratorTarget.h

@@ -667,6 +667,9 @@ public:
    */
   void ClearSourcesCache();
 
+  // Do not use.  This is only for a specific call site with a FIXME comment.
+  void ClearLinkInterfaceCache();
+
   void AddSource(const std::string& src, bool before = false);
   void AddTracedSources(std::vector<std::string> const& srcs);
 
@@ -1001,8 +1004,10 @@ private:
     std::string ImportLibrary;
     std::string LibName;
     std::string Languages;
-    std::vector<BT<std::string>> Libraries;
     std::string LibrariesProp;
+    std::vector<BT<std::string>> Libraries;
+    std::vector<BT<std::string>> LibrariesHeadInclude;
+    std::vector<BT<std::string>> LibrariesHeadExclude;
     std::string SharedDeps;
   };
 
@@ -1063,19 +1068,31 @@ private:
   bool IsLinkLookupScope(std::string const& n,
                          cmLocalGenerator const*& lg) const;
 
+  enum class LinkInterfaceField
+  {
+    Libraries,
+    HeadExclude,
+    HeadInclude,
+  };
   void ExpandLinkItems(std::string const& prop, cmBTStringRange entries,
                        std::string const& config,
                        const cmGeneratorTarget* headTarget,
-                       LinkInterfaceFor interfaceFor,
+                       LinkInterfaceFor interfaceFor, LinkInterfaceField field,
                        cmLinkInterface& iface) const;
 
   struct LookupLinkItemScope
   {
     cmLocalGenerator const* LG;
   };
+  enum class LookupSelf
+  {
+    No,
+    Yes,
+  };
   cm::optional<cmLinkItem> LookupLinkItem(std::string const& n,
                                           cmListFileBacktrace const& bt,
-                                          LookupLinkItemScope* scope) const;
+                                          LookupLinkItemScope* scope,
+                                          LookupSelf lookupSelf) const;
 
   std::vector<BT<std::string>> GetSourceFilePaths(
     std::string const& config) const;

+ 2 - 2
Source/cmLinkItem.cxx

@@ -68,8 +68,8 @@ cmLinkImplItem::cmLinkImplItem()
 {
 }
 
-cmLinkImplItem::cmLinkImplItem(cmLinkItem item, bool fromGenex)
+cmLinkImplItem::cmLinkImplItem(cmLinkItem item, bool checkCMP0027)
   : cmLinkItem(std::move(item))
-  , FromGenex(fromGenex)
+  , CheckCMP0027(checkCMP0027)
 {
 }

+ 8 - 2
Source/cmLinkItem.h

@@ -40,8 +40,8 @@ class cmLinkImplItem : public cmLinkItem
 {
 public:
   cmLinkImplItem();
-  cmLinkImplItem(cmLinkItem item, bool fromGenex);
-  bool FromGenex = false;
+  cmLinkImplItem(cmLinkItem item, bool checkCMP0027);
+  bool CheckCMP0027 = false;
 };
 
 /** The link implementation specifies the direct library
@@ -70,6 +70,12 @@ struct cmLinkInterfaceLibraries
   // Object files listed in the interface.
   std::vector<cmLinkItem> Objects;
 
+  // Items to be included as if directly linked by the head target.
+  std::vector<cmLinkItem> HeadInclude;
+
+  // Items to be excluded from direct linking by the head target.
+  std::vector<cmLinkItem> HeadExclude;
+
   // Whether the list depends on a genex referencing the head target.
   bool HadHeadSensitiveCondition = false;
 

+ 5 - 0
Source/cmLocalGenerator.cxx

@@ -2615,10 +2615,15 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
                   true);
               } else if (reuseTarget->GetType() ==
                          cmStateEnums::OBJECT_LIBRARY) {
+                // FIXME: This can propagate more than one level, unlike
+                // the rest of the object files in an object library.
+                // Find another way to do this.
                 target->Target->AppendProperty(
                   "INTERFACE_LINK_LIBRARIES",
                   cmStrCat("$<$<CONFIG:", config,
                            ">:$<LINK_ONLY:", pchSourceObj, ">>"));
+                // We updated the link interface, so ensure it is recomputed.
+                target->ClearLinkInterfaceCache();
               }
             }
           } else {

+ 59 - 0
Source/cmTarget.cxx

@@ -202,6 +202,8 @@ public:
   std::vector<BT<std::string>> LinkDirectoriesEntries;
   std::vector<BT<std::string>> LinkImplementationPropertyEntries;
   std::vector<BT<std::string>> LinkInterfacePropertyEntries;
+  std::vector<BT<std::string>> LinkInterfaceDirectPropertyEntries;
+  std::vector<BT<std::string>> LinkInterfaceDirectExcludePropertyEntries;
   std::vector<BT<std::string>> HeaderSetsEntries;
   std::vector<BT<std::string>> InterfaceHeaderSetsEntries;
   std::vector<std::pair<cmTarget::TLLSignature, cmListFileContext>>
@@ -1138,6 +1140,16 @@ cmBTStringRange cmTarget::GetLinkInterfaceEntries() const
   return cmMakeRange(this->impl->LinkInterfacePropertyEntries);
 }
 
+cmBTStringRange cmTarget::GetLinkInterfaceDirectEntries() const
+{
+  return cmMakeRange(this->impl->LinkInterfaceDirectPropertyEntries);
+}
+
+cmBTStringRange cmTarget::GetLinkInterfaceDirectExcludeEntries() const
+{
+  return cmMakeRange(this->impl->LinkInterfaceDirectExcludePropertyEntries);
+}
+
 cmBTStringRange cmTarget::GetHeaderSetsEntries() const
 {
   return cmMakeRange(this->impl->HeaderSetsEntries);
@@ -1182,6 +1194,8 @@ MAKE_PROP(HEADER_SET);
 MAKE_PROP(HEADER_SETS);
 MAKE_PROP(INTERFACE_HEADER_SETS);
 MAKE_PROP(INTERFACE_LINK_LIBRARIES);
+MAKE_PROP(INTERFACE_LINK_LIBRARIES_DIRECT);
+MAKE_PROP(INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE);
 #undef MAKE_PROP
 }
 
@@ -1313,6 +1327,19 @@ void cmTarget::StoreProperty(const std::string& prop, ValueType value)
       cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace();
       this->impl->LinkInterfacePropertyEntries.emplace_back(value, lfbt);
     }
+  } else if (prop == propINTERFACE_LINK_LIBRARIES_DIRECT) {
+    this->impl->LinkInterfaceDirectPropertyEntries.clear();
+    if (value) {
+      cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace();
+      this->impl->LinkInterfaceDirectPropertyEntries.emplace_back(value, lfbt);
+    }
+  } else if (prop == propINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE) {
+    this->impl->LinkInterfaceDirectExcludePropertyEntries.clear();
+    if (value) {
+      cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace();
+      this->impl->LinkInterfaceDirectExcludePropertyEntries.emplace_back(value,
+                                                                         lfbt);
+    }
   } else if (prop == propSOURCES) {
     this->impl->SourceEntries.clear();
     if (value) {
@@ -1571,6 +1598,17 @@ void cmTarget::AppendProperty(const std::string& prop,
       cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace();
       this->impl->LinkInterfacePropertyEntries.emplace_back(value, lfbt);
     }
+  } else if (prop == propINTERFACE_LINK_LIBRARIES_DIRECT) {
+    if (!value.empty()) {
+      cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace();
+      this->impl->LinkInterfaceDirectPropertyEntries.emplace_back(value, lfbt);
+    }
+  } else if (prop == propINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE) {
+    if (!value.empty()) {
+      cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace();
+      this->impl->LinkInterfaceDirectExcludePropertyEntries.emplace_back(value,
+                                                                         lfbt);
+    }
   } else if (prop == "SOURCES") {
     cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace();
     this->impl->SourceEntries.emplace_back(value, lfbt);
@@ -1881,6 +1919,8 @@ cmValue cmTarget::GetProperty(const std::string& prop) const
     propHEADER_SETS,
     propINTERFACE_HEADER_SETS,
     propINTERFACE_LINK_LIBRARIES,
+    propINTERFACE_LINK_LIBRARIES_DIRECT,
+    propINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE,
   };
   if (specialProps.count(prop)) {
     if (prop == propC_STANDARD || prop == propCXX_STANDARD ||
@@ -1910,6 +1950,25 @@ cmValue cmTarget::GetProperty(const std::string& prop) const
       output = cmJoin(this->impl->LinkInterfacePropertyEntries, ";");
       return cmValue(output);
     }
+    if (prop == propINTERFACE_LINK_LIBRARIES_DIRECT) {
+      if (this->impl->LinkInterfaceDirectPropertyEntries.empty()) {
+        return nullptr;
+      }
+
+      static std::string output;
+      output = cmJoin(this->impl->LinkInterfaceDirectPropertyEntries, ";");
+      return cmValue(output);
+    }
+    if (prop == propINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE) {
+      if (this->impl->LinkInterfaceDirectExcludePropertyEntries.empty()) {
+        return nullptr;
+      }
+
+      static std::string output;
+      output =
+        cmJoin(this->impl->LinkInterfaceDirectExcludePropertyEntries, ";");
+      return cmValue(output);
+    }
     // the type property returns what type the target is
     if (prop == propTYPE) {
       return cmValue(cmState::GetTargetTypeName(this->GetType()));

+ 2 - 0
Source/cmTarget.h

@@ -266,6 +266,8 @@ public:
   cmBTStringRange GetLinkImplementationEntries() const;
 
   cmBTStringRange GetLinkInterfaceEntries() const;
+  cmBTStringRange GetLinkInterfaceDirectEntries() const;
+  cmBTStringRange GetLinkInterfaceDirectExcludeEntries() const;
 
   cmBTStringRange GetHeaderSetsEntries() const;
 

+ 2 - 0
Tests/CMakeLists.txt

@@ -3691,6 +3691,8 @@ if(BUILD_TESTING)
     --test-command InterfaceLinkLibraries)
   list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/InterfaceLinkLibraries")
 
+  ADD_TEST_MACRO(InterfaceLinkLibrariesDirect)
+
   if(NOT CMake_TEST_EXTERNAL_CMAKE)
     add_subdirectory(CMakeTests)
   endif()

+ 16 - 0
Tests/ExportImport/Export/CMakeLists.txt

@@ -163,6 +163,16 @@ install(
 
 cmake_policy(PUSH)
 cmake_policy(SET CMP0022 NEW)
+
+# Test control over direct linking.
+include(../../InterfaceLinkLibrariesDirect/testStaticLibPlugin.cmake)
+include(../../InterfaceLinkLibrariesDirect/testSharedLibWithHelper.cmake)
+include(../../InterfaceLinkLibrariesDirect/testExeWithPluginHelper.cmake)
+if(NOT maybe_OBJECTS_DESTINATION)
+  target_compile_definitions(testSharedLibHelperObj INTERFACE testSharedLibHelperObj_NO_OBJECT)
+  target_compile_definitions(testExePluginHelperObj INTERFACE testExePluginHelperObj_NO_OBJECT)
+endif()
+
 # Test exporting dependent libraries into different exports
 add_library(testLibRequired testLibRequired.c)
 add_library(testLibDepends testLibDepends.c)
@@ -544,6 +554,9 @@ install(
   testLibDeprecation
   testLibCycleA testLibCycleB
   testLibNoSONAME
+  testStaticLibWithPlugin testStaticLibPluginExtra testStaticLibPlugin
+  testSharedLibWithHelper testSharedLibHelperObj
+  testExeWithPluginHelper testExePluginHelperObj
   testMod1 testMod2
   cmp0022NEW cmp0022OLD
   TopDirLib SubDirLinkA
@@ -619,6 +632,9 @@ export(TARGETS testExe2 testLib4 testLib5 testLib6 testLib7 testExe3 testExe4 te
   testLib4lib testLib4libdbg testLib4libopt
   testLibCycleA testLibCycleB
   testLibNoSONAME
+  testStaticLibWithPlugin testStaticLibPluginExtra testStaticLibPlugin
+  testSharedLibWithHelper testSharedLibHelperObj
+  testExeWithPluginHelper testExePluginHelperObj
   testMod1 testMod2
   testLibPerConfigDest
   NAMESPACE bld_

+ 14 - 0
Tests/ExportImport/Import/A/CMakeLists.txt

@@ -68,16 +68,23 @@ target_link_libraries(imp_testExe1
   exp_testLib7
   exp_testLibCycleA
   exp_testLibPerConfigDest
+  exp_testStaticLibWithPlugin
   )
 
 add_library(imp_testInterfaceInclude1 STATIC imp_testInterfaceInclude1.c)
 target_include_directories(imp_testInterfaceInclude1 SYSTEM PRIVATE testInterfaceIncludeSystem)
 target_link_libraries(imp_testInterfaceInclude1 PRIVATE exp_testInterfaceIncludeUser)
 
+add_executable(imp_UseSharedLibWithHelper1 ../../../InterfaceLinkLibrariesDirect/UseSharedLibWithHelper.c)
+target_link_libraries(imp_UseSharedLibWithHelper1 PRIVATE exp_testSharedLibWithHelper testSharedLibHelperExclude)
+
 # Try building a plugin to an executable imported from the install tree.
 add_library(imp_mod1 MODULE imp_mod1.c)
 target_link_libraries(imp_mod1 exp_testExe2)
 
+add_library(imp_ExePlugin1 MODULE ../../../InterfaceLinkLibrariesDirect/ExePlugin.c)
+target_link_libraries(imp_ExePlugin1 PRIVATE exp_testExeWithPluginHelper testExePluginHelperExclude)
+
 # Try referencing an executable imported from the build tree.
 add_custom_command(
   OUTPUT ${Import_BINARY_DIR}/bld_generated.c
@@ -112,6 +119,7 @@ target_link_libraries(imp_testExe1b
   bld_testLib7
   bld_testLibCycleA
   bld_testLibPerConfigDest
+  bld_testStaticLibWithPlugin
   )
 
 add_library(imp_testInterfaceInclude1b STATIC imp_testInterfaceInclude1.c)
@@ -183,10 +191,16 @@ target_link_libraries(SubDirLink_bld PRIVATE bld_TopDirLib bld_SubDirLinkA)
 add_executable(SubDirLink_exp SubDirLink.c)
 target_link_libraries(SubDirLink_exp PRIVATE exp_TopDirLib exp_SubDirLinkA)
 
+add_executable(imp_UseSharedLibWithHelper1b ../../../InterfaceLinkLibrariesDirect/UseSharedLibWithHelper.c)
+target_link_libraries(imp_UseSharedLibWithHelper1b PRIVATE bld_testSharedLibWithHelper testSharedLibHelperExclude)
+
 # Try building a plugin to an executable imported from the build tree.
 add_library(imp_mod1b MODULE imp_mod1.c)
 target_link_libraries(imp_mod1b bld_testExe2)
 
+add_library(imp_ExePlugin1b MODULE ../../../InterfaceLinkLibrariesDirect/ExePlugin.c)
+target_link_libraries(imp_ExePlugin1b PRIVATE bld_testExeWithPluginHelper testExePluginHelperExclude)
+
 # Export/CMakeLists.txt pretends the RelWithDebInfo (as well as Debug)
 # configuration should link to debug libs.
 foreach(c DEBUG RELWITHDEBINFO)

+ 4 - 2
Tests/ExportImport/Import/A/imp_testExe1.c

@@ -10,6 +10,7 @@ extern int testLib6(void);
 extern int testLib7(void);
 extern int testLibCycleA1(void);
 extern int testLibPerConfigDest(void);
+extern int testStaticLibPlugin(void);
 
 /* Switch a symbol between debug and optimized builds to make sure the
    proper library is found from the testLib4 link interface.  */
@@ -24,6 +25,7 @@ int main()
 {
   return (testLib2() + generated_by_testExe1() + testLib3() + testLib4() +
           testLib5() + testLib6() + testLib7() + testLibCycleA1() +
-          testLibPerConfigDest() + generated_by_testExe3() +
-          generated_by_testExe4() + testLib4lib() + testLib4libcfg());
+          testLibPerConfigDest() + testStaticLibPlugin() +
+          generated_by_testExe3() + generated_by_testExe4() + testLib4lib() +
+          testLib4libcfg());
 }

+ 156 - 0
Tests/InterfaceLinkLibrariesDirect/CMakeLists.txt

@@ -0,0 +1,156 @@
+cmake_minimum_required(VERSION 3.21)
+project(InterfaceLinkLibrariesDirect C)
+
+include(testStaticLibPlugin.cmake)
+add_executable(InterfaceLinkLibrariesDirect main.c)
+target_link_libraries(InterfaceLinkLibrariesDirect PRIVATE testStaticLibWithPlugin)
+
+include(testSharedLibWithHelper.cmake)
+add_executable(UseSharedLibWithHelper UseSharedLibWithHelper.c)
+target_link_libraries(UseSharedLibWithHelper PRIVATE testSharedLibWithHelper testSharedLibHelperExclude)
+
+include(testExeWithPluginHelper.cmake)
+add_library(ExePlugin MODULE ExePlugin.c)
+target_link_libraries(ExePlugin PRIVATE testExeWithPluginHelper testExePluginHelperExclude)
+
+#----------------------------------------------------------------------------
+
+# Offer usage requirements and symbols to be used through static libs below.
+add_library(A STATIC
+  a_always.c
+
+  # Good symbols that direct_from_A libraries poison if incorrectly used.
+  a_not_direct_from_A.c
+  a_not_direct_from_A_for_exe.c
+  a_not_direct_from_A_optional.c
+
+  # Bad symbols in direct_from_A libraries below to ensure they come first.
+  a_poison_direct_from_A.c
+  a_poison_direct_from_A_for_exe.c
+  a_poison_direct_from_A_optional.c
+  )
+
+# Propagates as usage requirement from A.
+add_library(direct_from_A STATIC direct_from_A.c direct_from_A_poison.c)
+target_compile_definitions(direct_from_A INTERFACE DEF_DIRECT_FROM_A)
+set_property(TARGET A APPEND PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT direct_from_A)
+
+# Propagates as usage requirement from A, but only for executables.
+add_library(direct_from_A_for_exe STATIC direct_from_A_for_exe.c direct_from_A_for_exe_poison.c)
+target_compile_definitions(direct_from_A_for_exe INTERFACE DEF_DIRECT_FROM_A_FOR_EXE)
+set_property(TARGET A APPEND PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT
+  "$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:direct_from_A_for_exe>")
+
+# Propagates as usage requirement from A, but only for targets that opt-in.
+add_library(direct_from_A_optional STATIC direct_from_A_optional.c direct_from_A_optional_poison.c)
+target_compile_definitions(direct_from_A_optional INTERFACE DEF_DIRECT_FROM_A_OPTIONAL)
+set_property(TARGET A APPEND PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT
+  "$<$<BOOL:$<TARGET_PROPERTY:A_LINK_OPTIONAL>>:direct_from_A_optional>")
+
+# Uses and propagates A's usage requirements.
+# Does not use the exe-only or optional usage requirements.
+add_library(static_A_public STATIC static_A_public.c)
+target_link_libraries(static_A_public PUBLIC A)
+
+# Uses A's usage requirements, but does not propagate them.
+# Does not use the exe-only usage requirement.  Does use the optional one.
+add_library(static_A_private STATIC static_A_private.c)
+target_link_libraries(static_A_private PRIVATE A)
+set_property(TARGET static_A_private PROPERTY A_LINK_OPTIONAL 1)
+
+# Uses A's usage requirements, including an optional one.
+add_executable(exe_use_static_A_public exe_use_static_A_public.c)
+target_link_libraries(exe_use_static_A_public PRIVATE static_A_public)
+set_property(TARGET exe_use_static_A_public PROPERTY A_LINK_OPTIONAL 1)
+
+# Does not use A's usage requirements.
+add_executable(exe_use_static_A_private exe_use_static_A_private.c)
+target_link_libraries(exe_use_static_A_private PRIVATE static_A_private)
+
+#----------------------------------------------------------------------------
+
+# Test how original and injected dependencies get ordered.
+
+# A bunch of static libraries that need to be linked in alphabetic order.
+# Each library has an extra source to poison all symbols meant to be
+# provided by earlier libraries.  This enforces ordering on platforms
+# whose linkers re-visit static libraries.
+add_library(order_A STATIC order_A.c)
+add_library(order_B STATIC order_B.c order_B_poison.c)
+add_library(order_C STATIC order_C.c order_C_poison.c)
+add_library(order_D STATIC order_D.c order_D_poison.c)
+add_library(order_E STATIC order_E.c order_E_poison.c)
+add_library(order_F STATIC order_F.c order_F_poison.c)
+add_library(order_G STATIC order_G.c order_G_poison.c)
+add_library(order_H STATIC order_H.c order_H_poison.c)
+add_library(order_I STATIC order_I.c order_I_poison.c)
+add_library(order_J STATIC order_J.c order_J_poison.c)
+
+# An executable to drive linking.
+add_executable(order_main order_main.c)
+
+# In the following diagram, connection by a slash means the top
+# target lists the bottom target in a link interface property:
+#
+#  \ => INTERFACE_LINK_LIBRARIES
+#  / => INTERFACE_LINK_LIBRARIES_DIRECT
+#
+# The top of each tree represents an entry in the exe's LINK_LIBRARIES.
+# CMake should evaluate this graph to generate the proper link order.
+#
+#             D        H
+#            / \      / \
+#           B   J    F   I
+#          /   /    /   / \
+#         A   C    E   G   J
+set_property(TARGET order_main PROPERTY LINK_LIBRARIES order_D order_H)
+set_property(TARGET order_D PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT order_B)
+set_property(TARGET order_D PROPERTY INTERFACE_LINK_LIBRARIES order_J)
+set_property(TARGET order_B PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT order_A)
+set_property(TARGET order_J PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT order_C)
+set_property(TARGET order_H PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT order_F)
+set_property(TARGET order_H PROPERTY INTERFACE_LINK_LIBRARIES order_I)
+set_property(TARGET order_F PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT order_E)
+set_property(TARGET order_I PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT order_G)
+set_property(TARGET order_I PROPERTY INTERFACE_LINK_LIBRARIES order_J)
+
+#----------------------------------------------------------------------------
+
+# Test that the original LINK_LIBRARIES cannot be re-ordered by injection
+# from usage requirements.
+
+# A bunch of static libraries that need to be linked in alphabetic order.
+# Each library has an extra source to poison all symbols meant to be
+# provided by earlier libraries.  This enforces ordering on platforms
+# whose linkers re-visit static libraries.
+add_library(force_A STATIC order_A.c)
+add_library(force_B STATIC order_B.c order_B_poison.c)
+add_library(force_C STATIC order_C.c order_C_poison.c)
+add_library(force_D STATIC order_D.c order_D_poison.c)
+add_library(force_E STATIC order_E.c order_E_poison.c)
+add_library(force_F STATIC order_F.c order_F_poison.c)
+add_library(force_G STATIC order_G.c order_G_poison.c)
+add_library(force_H STATIC order_H.c order_H_poison.c)
+add_library(force_I STATIC order_I.c order_I_poison.c)
+add_library(force_J STATIC order_J.c order_J_poison.c)
+
+# An executable to drive linking.
+add_executable(force_main order_main.c)
+
+# The executable explicitly lists all the libraries in the right order.
+target_link_libraries(force_main PRIVATE force_A force_B force_C force_D force_E force_F force_G force_H)
+
+# Add legitimate normal dependencies.
+set_property(TARGET force_D PROPERTY INTERFACE_LINK_LIBRARIES force_J)
+set_property(TARGET force_H PROPERTY INTERFACE_LINK_LIBRARIES force_I)
+set_property(TARGET force_I PROPERTY INTERFACE_LINK_LIBRARIES force_J)
+
+# Add bogus injected direct dependencies to verify that they do not
+# change the original order of LINK_LIBRARIES.
+set_property(TARGET force_A PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT force_B)
+set_property(TARGET force_B PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT force_C)
+set_property(TARGET force_C PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT force_D)
+set_property(TARGET force_D PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT force_E)
+set_property(TARGET force_E PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT force_F)
+set_property(TARGET force_F PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT force_G)
+set_property(TARGET force_G PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT force_H)

+ 21 - 0
Tests/InterfaceLinkLibrariesDirect/ExePlugin.c

@@ -0,0 +1,21 @@
+extern int testExePluginHelperObj(int n);
+
+#ifdef testExePluginHelperObj_NO_OBJECT
+int testExePluginHelperObj(int n)
+{
+  return n;
+}
+#endif
+
+#if defined(_WIN32)
+__declspec(dllimport)
+#endif
+  int testExePluginAPI(int n);
+
+#if defined(_WIN32)
+__declspec(dllexport)
+#endif
+  int testExePlugin(int n)
+{
+  return testExePluginAPI(n) + testExePluginHelperObj(n);
+}

+ 18 - 0
Tests/InterfaceLinkLibrariesDirect/UseSharedLibWithHelper.c

@@ -0,0 +1,18 @@
+extern int testSharedLibHelperObj(int n);
+
+#ifdef testSharedLibHelperObj_NO_OBJECT
+int testSharedLibHelperObj(int n)
+{
+  return n;
+}
+#endif
+
+#if defined(_WIN32)
+__declspec(dllimport)
+#endif
+  int testSharedLibWithHelper(int n);
+
+int main(void)
+{
+  return testSharedLibWithHelper(0) + testSharedLibHelperObj(0);
+}

+ 3 - 0
Tests/InterfaceLinkLibrariesDirect/a_always.c

@@ -0,0 +1,3 @@
+void a_always(void)
+{
+}

+ 3 - 0
Tests/InterfaceLinkLibrariesDirect/a_not_direct_from_A.c

@@ -0,0 +1,3 @@
+void not_direct_from_A(void)
+{
+}

+ 3 - 0
Tests/InterfaceLinkLibrariesDirect/a_not_direct_from_A_for_exe.c

@@ -0,0 +1,3 @@
+void not_direct_from_A_for_exe(void)
+{
+}

+ 3 - 0
Tests/InterfaceLinkLibrariesDirect/a_not_direct_from_A_optional.c

@@ -0,0 +1,3 @@
+void not_direct_from_A_optional(void)
+{
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/a_poison_direct_from_A.c

@@ -0,0 +1,5 @@
+extern void poison_direct_from_A(void);
+void direct_from_A(void)
+{
+  poison_direct_from_A();
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/a_poison_direct_from_A_for_exe.c

@@ -0,0 +1,5 @@
+extern void poison_direct_from_A_for_exe(void);
+void direct_from_A_for_exe(void)
+{
+  poison_direct_from_A_for_exe();
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/a_poison_direct_from_A_optional.c

@@ -0,0 +1,5 @@
+extern void poison_direct_from_A_optional(void);
+void direct_from_A_optional(void)
+{
+  poison_direct_from_A_optional();
+}

+ 3 - 0
Tests/InterfaceLinkLibrariesDirect/direct_from_A.c

@@ -0,0 +1,3 @@
+void direct_from_A(void)
+{
+}

+ 3 - 0
Tests/InterfaceLinkLibrariesDirect/direct_from_A_for_exe.c

@@ -0,0 +1,3 @@
+void direct_from_A_for_exe(void)
+{
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/direct_from_A_for_exe_poison.c

@@ -0,0 +1,5 @@
+extern void poison_not_direct_from_A_for_exe(void);
+void not_direct_from_A_for_exe(void)
+{
+  poison_not_direct_from_A_for_exe();
+}

+ 3 - 0
Tests/InterfaceLinkLibrariesDirect/direct_from_A_optional.c

@@ -0,0 +1,3 @@
+void direct_from_A_optional(void)
+{
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/direct_from_A_optional_poison.c

@@ -0,0 +1,5 @@
+extern void poison_not_direct_from_A_optional(void);
+void not_direct_from_A_optional(void)
+{
+  poison_not_direct_from_A_optional();
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/direct_from_A_poison.c

@@ -0,0 +1,5 @@
+extern void poison_not_direct_from_A(void);
+void not_direct_from_A(void)
+{
+  poison_not_direct_from_A();
+}

+ 23 - 0
Tests/InterfaceLinkLibrariesDirect/exe_use_static_A_private.c

@@ -0,0 +1,23 @@
+#ifdef DEF_DIRECT_FROM_A
+#  error "DEF_DIRECT_FROM_A incorrectly defined"
+#endif
+#ifdef DEF_DIRECT_FROM_A_FOR_EXE
+#  error "DEF_DIRECT_FROM_A_FOR_EXE incorrectly defined"
+#endif
+#ifdef DEF_DIRECT_FROM_A_OPTIONAL
+#  error "DEF_DIRECT_FROM_A_OPTIONAL incorrectly defined"
+#endif
+
+extern void static_A_private(void);
+extern void not_direct_from_A(void);
+extern void not_direct_from_A_for_exe(void);
+extern void not_direct_from_A_optional(void);
+
+int main(void)
+{
+  static_A_private();
+  not_direct_from_A();
+  not_direct_from_A_for_exe();
+  not_direct_from_A_optional();
+  return 0;
+}

+ 23 - 0
Tests/InterfaceLinkLibrariesDirect/exe_use_static_A_public.c

@@ -0,0 +1,23 @@
+#ifndef DEF_DIRECT_FROM_A
+#  error "DEF_DIRECT_FROM_A incorrectly not defined"
+#endif
+#ifndef DEF_DIRECT_FROM_A_FOR_EXE
+#  error "DEF_DIRECT_FROM_A_FOR_EXE incorrectly not defined"
+#endif
+#ifndef DEF_DIRECT_FROM_A_OPTIONAL
+#  error "DEF_DIRECT_FROM_A_OPTIONAL incorrectly not defined"
+#endif
+
+extern void static_A_public(void);
+extern void direct_from_A(void);
+extern void direct_from_A_for_exe(void);
+extern void direct_from_A_optional(void);
+
+int main(void)
+{
+  static_A_public();
+  direct_from_A();
+  direct_from_A_for_exe();
+  direct_from_A_optional();
+  return 0;
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/main.c

@@ -0,0 +1,5 @@
+extern int testStaticLibPlugin(void);
+int main(void)
+{
+  return testStaticLibPlugin();
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/order_A.c

@@ -0,0 +1,5 @@
+extern void order_B(void);
+void order_A(void)
+{
+  order_B();
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/order_B.c

@@ -0,0 +1,5 @@
+extern void order_C(void);
+void order_B(void)
+{
+  order_C();
+}

+ 6 - 0
Tests/InterfaceLinkLibrariesDirect/order_B_poison.c

@@ -0,0 +1,6 @@
+extern void order_B_poison(void);
+
+void order_A(void)
+{
+  order_B_poison();
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/order_C.c

@@ -0,0 +1,5 @@
+extern void order_D(void);
+void order_C(void)
+{
+  order_D();
+}

+ 11 - 0
Tests/InterfaceLinkLibrariesDirect/order_C_poison.c

@@ -0,0 +1,11 @@
+extern void order_C_poison(void);
+
+void order_A(void)
+{
+  order_C_poison();
+}
+
+void order_B(void)
+{
+  order_C_poison();
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/order_D.c

@@ -0,0 +1,5 @@
+extern void order_E(void);
+void order_D(void)
+{
+  order_E();
+}

+ 16 - 0
Tests/InterfaceLinkLibrariesDirect/order_D_poison.c

@@ -0,0 +1,16 @@
+extern void order_D_poison(void);
+
+void order_A(void)
+{
+  order_D_poison();
+}
+
+void order_B(void)
+{
+  order_D_poison();
+}
+
+void order_C(void)
+{
+  order_D_poison();
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/order_E.c

@@ -0,0 +1,5 @@
+extern void order_F(void);
+void order_E(void)
+{
+  order_F();
+}

+ 21 - 0
Tests/InterfaceLinkLibrariesDirect/order_E_poison.c

@@ -0,0 +1,21 @@
+extern void order_E_poison(void);
+
+void order_A(void)
+{
+  order_E_poison();
+}
+
+void order_B(void)
+{
+  order_E_poison();
+}
+
+void order_C(void)
+{
+  order_E_poison();
+}
+
+void order_D(void)
+{
+  order_E_poison();
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/order_F.c

@@ -0,0 +1,5 @@
+extern void order_G(void);
+void order_F(void)
+{
+  order_G();
+}

+ 26 - 0
Tests/InterfaceLinkLibrariesDirect/order_F_poison.c

@@ -0,0 +1,26 @@
+extern void order_F_poison(void);
+
+void order_A(void)
+{
+  order_F_poison();
+}
+
+void order_B(void)
+{
+  order_F_poison();
+}
+
+void order_C(void)
+{
+  order_F_poison();
+}
+
+void order_D(void)
+{
+  order_F_poison();
+}
+
+void order_E(void)
+{
+  order_F_poison();
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/order_G.c

@@ -0,0 +1,5 @@
+extern void order_H(void);
+void order_G(void)
+{
+  order_H();
+}

+ 31 - 0
Tests/InterfaceLinkLibrariesDirect/order_G_poison.c

@@ -0,0 +1,31 @@
+extern void order_G_poison(void);
+
+void order_A(void)
+{
+  order_G_poison();
+}
+
+void order_B(void)
+{
+  order_G_poison();
+}
+
+void order_C(void)
+{
+  order_G_poison();
+}
+
+void order_D(void)
+{
+  order_G_poison();
+}
+
+void order_E(void)
+{
+  order_G_poison();
+}
+
+void order_F(void)
+{
+  order_G_poison();
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/order_H.c

@@ -0,0 +1,5 @@
+extern void order_I(void);
+void order_H(void)
+{
+  order_I();
+}

+ 36 - 0
Tests/InterfaceLinkLibrariesDirect/order_H_poison.c

@@ -0,0 +1,36 @@
+extern void order_H_poison(void);
+
+void order_A(void)
+{
+  order_H_poison();
+}
+
+void order_B(void)
+{
+  order_H_poison();
+}
+
+void order_C(void)
+{
+  order_H_poison();
+}
+
+void order_D(void)
+{
+  order_H_poison();
+}
+
+void order_E(void)
+{
+  order_H_poison();
+}
+
+void order_F(void)
+{
+  order_H_poison();
+}
+
+void order_G(void)
+{
+  order_H_poison();
+}

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/order_I.c

@@ -0,0 +1,5 @@
+extern void order_J(void);
+void order_I(void)
+{
+  order_J();
+}

+ 41 - 0
Tests/InterfaceLinkLibrariesDirect/order_I_poison.c

@@ -0,0 +1,41 @@
+extern void order_I_poison(void);
+
+void order_A(void)
+{
+  order_I_poison();
+}
+
+void order_B(void)
+{
+  order_I_poison();
+}
+
+void order_C(void)
+{
+  order_I_poison();
+}
+
+void order_D(void)
+{
+  order_I_poison();
+}
+
+void order_E(void)
+{
+  order_I_poison();
+}
+
+void order_F(void)
+{
+  order_I_poison();
+}
+
+void order_G(void)
+{
+  order_I_poison();
+}
+
+void order_H(void)
+{
+  order_I_poison();
+}

+ 3 - 0
Tests/InterfaceLinkLibrariesDirect/order_J.c

@@ -0,0 +1,3 @@
+void order_J(void)
+{
+}

+ 46 - 0
Tests/InterfaceLinkLibrariesDirect/order_J_poison.c

@@ -0,0 +1,46 @@
+extern void order_J_poison(void);
+
+void order_A(void)
+{
+  order_J_poison();
+}
+
+void order_B(void)
+{
+  order_J_poison();
+}
+
+void order_C(void)
+{
+  order_J_poison();
+}
+
+void order_D(void)
+{
+  order_J_poison();
+}
+
+void order_E(void)
+{
+  order_J_poison();
+}
+
+void order_F(void)
+{
+  order_J_poison();
+}
+
+void order_G(void)
+{
+  order_J_poison();
+}
+
+void order_H(void)
+{
+  order_J_poison();
+}
+
+void order_I(void)
+{
+  order_J_poison();
+}

+ 6 - 0
Tests/InterfaceLinkLibrariesDirect/order_main.c

@@ -0,0 +1,6 @@
+extern void order_A(void);
+int main(void)
+{
+  order_A();
+  return 0;
+}

+ 16 - 0
Tests/InterfaceLinkLibrariesDirect/static_A_private.c

@@ -0,0 +1,16 @@
+#ifndef DEF_DIRECT_FROM_A
+#  error "DEF_DIRECT_FROM_A incorrectly not defined"
+#endif
+#ifdef DEF_DIRECT_FROM_A_FOR_EXE
+#  error "DEF_DIRECT_FROM_A_FOR_EXE incorrectly defined"
+#endif
+#ifndef DEF_DIRECT_FROM_A_OPTIONAL
+#  error "DEF_DIRECT_FROM_A_OPTIONAL incorrectly not defined"
+#endif
+
+extern void a_always(void);
+
+void static_A_private(void)
+{
+  a_always();
+}

+ 16 - 0
Tests/InterfaceLinkLibrariesDirect/static_A_public.c

@@ -0,0 +1,16 @@
+#ifndef DEF_DIRECT_FROM_A
+#  error "DEF_DIRECT_FROM_A incorrectly not defined"
+#endif
+#ifdef DEF_DIRECT_FROM_A_FOR_EXE
+#  error "DEF_DIRECT_FROM_A_FOR_EXE incorrectly defined"
+#endif
+#ifdef DEF_DIRECT_FROM_A_OPTIONAL
+#  error "DEF_DIRECT_FROM_A_OPTIONAL incorrectly defined"
+#endif
+
+extern void a_always(void);
+
+void static_A_public(void)
+{
+  a_always();
+}

+ 4 - 0
Tests/InterfaceLinkLibrariesDirect/testExePluginHelperObj.c

@@ -0,0 +1,4 @@
+int testExePluginHelperObj(int n)
+{
+  return n;
+}

+ 12 - 0
Tests/InterfaceLinkLibrariesDirect/testExeWithPluginHelper.c

@@ -0,0 +1,12 @@
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+  int testExePluginAPI(int n)
+{
+  return n;
+}
+
+int main(void)
+{
+  return 0;
+}

+ 7 - 0
Tests/InterfaceLinkLibrariesDirect/testExeWithPluginHelper.cmake

@@ -0,0 +1,7 @@
+# Logic common to InterfaceLinkLibrariesDirect and ExportImport tests.
+set(src ${CMAKE_CURRENT_LIST_DIR})
+add_executable(testExeWithPluginHelper ${src}/testExeWithPluginHelper.c)
+add_library(testExePluginHelperObj OBJECT ${src}/testExePluginHelperObj.c)
+set_property(TARGET testExeWithPluginHelper PROPERTY ENABLE_EXPORTS 1)
+set_property(TARGET testExeWithPluginHelper PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT $<TARGET_NAME:testExePluginHelperObj>)
+set_property(TARGET testExeWithPluginHelper PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE $<1:testExePluginHelperExclude>)

+ 4 - 0
Tests/InterfaceLinkLibrariesDirect/testSharedLibHelperObj.c

@@ -0,0 +1,4 @@
+int testSharedLibHelperObj(int n)
+{
+  return n;
+}

+ 7 - 0
Tests/InterfaceLinkLibrariesDirect/testSharedLibWithHelper.c

@@ -0,0 +1,7 @@
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+  int testSharedLibWithHelper(int n)
+{
+  return n;
+}

+ 6 - 0
Tests/InterfaceLinkLibrariesDirect/testSharedLibWithHelper.cmake

@@ -0,0 +1,6 @@
+# Logic common to InterfaceLinkLibrariesDirect and ExportImport tests.
+set(src ${CMAKE_CURRENT_LIST_DIR})
+add_library(testSharedLibWithHelper SHARED ${src}/testSharedLibWithHelper.c)
+add_library(testSharedLibHelperObj OBJECT ${src}/testSharedLibHelperObj.c)
+set_property(TARGET testSharedLibWithHelper PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT $<TARGET_NAME:testSharedLibHelperObj>)
+set_property(TARGET testSharedLibWithHelper PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE $<1:testSharedLibHelperExclude>)

+ 6 - 0
Tests/InterfaceLinkLibrariesDirect/testStaticLibPlugin.c

@@ -0,0 +1,6 @@
+extern int testStaticLibWithPlugin1(void);
+extern int testStaticLibPluginExtra(void);
+int testStaticLibPlugin(void)
+{
+  return testStaticLibWithPlugin1() + testStaticLibPluginExtra();
+}

+ 14 - 0
Tests/InterfaceLinkLibrariesDirect/testStaticLibPlugin.cmake

@@ -0,0 +1,14 @@
+# Logic common to InterfaceLinkLibrariesDirect and ExportImport tests.
+set(src ${CMAKE_CURRENT_LIST_DIR})
+add_library(testStaticLibWithPlugin STATIC
+  ${src}/testStaticLibWithPlugin1.c    # used by testStaticLibPlugin
+  ${src}/testStaticLibWithPlugin2.c    # used by testStaticLibPluginExtra
+  ${src}/testStaticLibWithPluginBad1.c # link error if not after testStaticLibPlugin
+  ${src}/testStaticLibWithPluginBad2.c # link error if not after testStaticLibPluginExtra
+  )
+add_library(testStaticLibPluginExtra STATIC ${src}/testStaticLibPluginExtra.c)
+add_library(testStaticLibPlugin STATIC ${src}/testStaticLibPlugin.c)
+target_link_libraries(testStaticLibPlugin PUBLIC testStaticLibWithPlugin testStaticLibPluginExtra)
+target_link_libraries(testStaticLibPluginExtra PUBLIC testStaticLibWithPlugin)
+set_property(TARGET testStaticLibWithPlugin PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT testStaticLibPlugin)
+set_property(TARGET testStaticLibWithPlugin PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE testStaticLibWithPlugin)

+ 5 - 0
Tests/InterfaceLinkLibrariesDirect/testStaticLibPluginExtra.c

@@ -0,0 +1,5 @@
+extern int testStaticLibWithPlugin2(void);
+int testStaticLibPluginExtra(void)
+{
+  return testStaticLibWithPlugin2();
+}

+ 4 - 0
Tests/InterfaceLinkLibrariesDirect/testStaticLibWithPlugin1.c

@@ -0,0 +1,4 @@
+int testStaticLibWithPlugin1(void)
+{
+  return 0;
+}

+ 4 - 0
Tests/InterfaceLinkLibrariesDirect/testStaticLibWithPlugin2.c

@@ -0,0 +1,4 @@
+int testStaticLibWithPlugin2(void)
+{
+  return 0;
+}

+ 6 - 0
Tests/InterfaceLinkLibrariesDirect/testStaticLibWithPluginBad1.c

@@ -0,0 +1,6 @@
+/* Produce an error if if the object compiled from this source is used.  */
+extern int testStaticLibWithPlugin_linked_before_testStaticLibPlugin(void);
+int testStaticLibPlugin(void)
+{
+  return testStaticLibWithPlugin_linked_before_testStaticLibPlugin();
+}

+ 7 - 0
Tests/InterfaceLinkLibrariesDirect/testStaticLibWithPluginBad2.c

@@ -0,0 +1,7 @@
+/* Produce an error if if the object compiled from this source is used.  */
+extern int testStaticLibWithPlugin_linked_before_testStaticLibPluginExtra(
+  void);
+int testStaticLibPluginExtra(void)
+{
+  return testStaticLibWithPlugin_linked_before_testStaticLibPluginExtra();
+}

+ 6 - 0
Tests/ObjectLibrary/Transitive/BarMain.c

@@ -0,0 +1,6 @@
+extern int BarObject1(void);
+
+int main(void)
+{
+  return BarObject1();
+}

+ 5 - 0
Tests/ObjectLibrary/Transitive/BarObject1.c

@@ -0,0 +1,5 @@
+extern int BarObject2(void);
+int BarObject1(void)
+{
+  return BarObject2();
+}

+ 5 - 0
Tests/ObjectLibrary/Transitive/BarObject2.c

@@ -0,0 +1,5 @@
+extern int BarObject3(void);
+int BarObject2(void)
+{
+  return BarObject3();
+}

+ 4 - 0
Tests/ObjectLibrary/Transitive/BarObject3.c

@@ -0,0 +1,4 @@
+int BarObject3(void)
+{
+  return 0;
+}

+ 14 - 1
Tests/ObjectLibrary/Transitive/CMakeLists.txt

@@ -1,4 +1,3 @@
-cmake_policy(SET CMP0022 NEW)
 add_library(FooStatic STATIC FooStatic.c)
 
 add_library(FooObject1 OBJECT FooObject.c)
@@ -10,3 +9,17 @@ add_library(FooObject2 OBJECT FooObject.c)
 target_link_libraries(FooObject2 INTERFACE FooStatic)
 add_executable(Transitive2 Transitive.c)
 target_link_libraries(Transitive2 PRIVATE FooObject2)
+
+add_library(FooObjectDirect OBJECT FooObject.c)
+add_library(FooStaticDirect STATIC FooStatic.c)
+set_property(TARGET FooStaticDirect PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT FooObjectDirect)
+add_executable(TransitiveDirect Transitive.c)
+target_link_libraries(TransitiveDirect PRIVATE FooStaticDirect)
+
+add_library(BarObject1 OBJECT BarObject1.c)
+add_library(BarObject2 OBJECT BarObject2.c)
+add_library(BarObject3 OBJECT BarObject3.c)
+set_property(TARGET BarObject1 PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT BarObject2)
+set_property(TARGET BarObject2 PROPERTY INTERFACE_LINK_LIBRARIES_DIRECT BarObject3)
+add_executable(BarMain BarMain.c)
+target_link_libraries(BarMain PRIVATE BarObject1)