Browse Source

Genex-LINK_GROUP: Add possibility to group libraries at link step

Fixes: #23121
Marc Chevrier 3 years ago
parent
commit
0a81ea1f12
100 changed files with 1578 additions and 184 deletions
  1. 149 2
      Help/manual/cmake-generator-expressions.7.rst
  2. 4 0
      Help/manual/cmake-variables.7.rst
  3. 8 0
      Help/release/dev/Genex-LINK_GROUP.rst
  4. 20 0
      Help/variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE.rst
  5. 13 0
      Help/variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE_SUPPORTED.rst
  6. 2 1
      Help/variable/CMAKE_LANG_LINK_LIBRARY_USING_FEATURE.rst
  7. 25 0
      Help/variable/CMAKE_LINK_GROUP_USING_FEATURE.rst
  8. 54 0
      Help/variable/CMAKE_LINK_GROUP_USING_FEATURE.txt
  9. 14 0
      Help/variable/CMAKE_LINK_GROUP_USING_FEATURE_SUPPORTED.rst
  10. 2 1
      Help/variable/CMAKE_LINK_LIBRARY_USING_FEATURE.rst
  11. 2 0
      Source/Checks/cm_cxx_filesystem.cxx
  12. 363 70
      Source/cmComputeLinkDepends.cxx
  13. 14 2
      Source/cmComputeLinkDepends.h
  14. 201 83
      Source/cmComputeLinkInformation.cxx
  15. 31 6
      Source/cmComputeLinkInformation.h
  16. 85 0
      Source/cmGeneratorExpressionNode.cxx
  17. 4 2
      Source/cmGeneratorTarget.cxx
  18. 9 9
      Source/cmMakefile.cxx
  19. 9 8
      Source/cmTarget.cxx
  20. 13 0
      Tests/RunCMake/CMakeLists.txt
  21. 3 0
      Tests/RunCMake/GenEx-LINK_GROUP/CMakeLists.txt
  22. 35 0
      Tests/RunCMake/GenEx-LINK_GROUP/RunCMakeTest.cmake
  23. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command-result.txt
  24. 9 0
      Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command-stderr.txt
  25. 4 0
      Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command.cmake
  26. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target-result.txt
  27. 9 0
      Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target-stderr.txt
  28. 3 0
      Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target.cmake
  29. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/add_link_options-result.txt
  30. 9 0
      Tests/RunCMake/GenEx-LINK_GROUP/add_link_options-stderr.txt
  31. 5 0
      Tests/RunCMake/GenEx-LINK_GROUP/add_link_options.cmake
  32. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1-result.txt
  33. 5 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1-stderr.txt
  34. 6 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1.cmake
  35. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2-result.txt
  36. 5 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2-stderr.txt
  37. 8 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2.cmake
  38. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3-result.txt
  39. 6 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3-stderr.txt
  40. 9 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3.cmake
  41. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4-result.txt
  42. 6 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4-stderr.txt
  43. 9 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4.cmake
  44. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5-result.txt
  45. 6 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5-stderr.txt
  46. 9 0
      Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5.cmake
  47. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1-result.txt
  48. 11 0
      Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1-stderr.txt
  49. 17 0
      Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1.cmake
  50. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2-result.txt
  51. 11 0
      Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2-stderr.txt
  52. 18 0
      Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2.cmake
  53. 18 0
      Tests/RunCMake/GenEx-LINK_GROUP/compatible-features1.cmake
  54. 13 0
      Tests/RunCMake/GenEx-LINK_GROUP/compatible-features2.cmake
  55. 13 0
      Tests/RunCMake/GenEx-LINK_GROUP/compatible-features3.cmake
  56. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments-result.txt
  57. 8 0
      Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments-stderr.txt
  58. 4 0
      Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments.cmake
  59. 0 0
      Tests/RunCMake/GenEx-LINK_GROUP/empty.c
  60. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported-result.txt
  61. 5 0
      Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported-stderr.txt
  62. 9 0
      Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported.cmake
  63. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments-result.txt
  64. 16 0
      Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments-stderr.txt
  65. 6 0
      Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments.cmake
  66. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1-result.txt
  67. 6 0
      Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1-stderr.txt
  68. 15 0
      Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1.cmake
  69. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1-result.txt
  70. 6 0
      Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1-stderr.txt
  71. 17 0
      Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1.cmake
  72. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2-result.txt
  73. 6 0
      Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2-stderr.txt
  74. 17 0
      Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2.cmake
  75. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature-result.txt
  76. 8 0
      Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature-stderr.txt
  77. 6 0
      Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature.cmake
  78. 13 0
      Tests/RunCMake/GenEx-LINK_GROUP/library-ignored-stderr.txt
  79. 15 0
      Tests/RunCMake/GenEx-LINK_GROUP/library-ignored.cmake
  80. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/link_directories-result.txt
  81. 9 0
      Tests/RunCMake/GenEx-LINK_GROUP/link_directories-stderr.txt
  82. 5 0
      Tests/RunCMake/GenEx-LINK_GROUP/link_directories.cmake
  83. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1-result.txt
  84. 8 0
      Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1-stderr.txt
  85. 11 0
      Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1.cmake
  86. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2-result.txt
  87. 8 0
      Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2-stderr.txt
  88. 14 0
      Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2.cmake
  89. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex-result.txt
  90. 18 0
      Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex-stderr.txt
  91. 6 0
      Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex.cmake
  92. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/no-arguments-result.txt
  93. 8 0
      Tests/RunCMake/GenEx-LINK_GROUP/no-arguments-stderr.txt
  94. 4 0
      Tests/RunCMake/GenEx-LINK_GROUP/no-arguments.cmake
  95. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/only-targets-result.txt
  96. 13 0
      Tests/RunCMake/GenEx-LINK_GROUP/only-targets-stderr.txt
  97. 16 0
      Tests/RunCMake/GenEx-LINK_GROUP/only-targets.cmake
  98. 4 0
      Tests/RunCMake/GenEx-LINK_GROUP/override-library-features1.cmake
  99. 4 0
      Tests/RunCMake/GenEx-LINK_GROUP/override-library-features2.cmake
  100. 1 0
      Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories-result.txt

+ 149 - 2
Help/manual/cmake-generator-expressions.7.rst

@@ -1183,13 +1183,160 @@ Output-Related Expressions
   .. note::
 
     This expression does not guarantee that the list of specified libraries
-    will be kept grouped. So, constructs like ``start-group`` and
-    ``end-group``, as supported by ``GNU ld``, cannot be used.
+    will be kept grouped. So, to manage constructs like ``start-group`` and
+    ``end-group``, as supported by ``GNU ld``, the :genex:`LINK_GROUP`
+    generator expression can be used.
 
   ``CMake`` pre-defines some features of general interest:
 
   .. include:: ../variable/LINK_LIBRARY_PREDEFINED_FEATURES.txt
 
+.. genex:: $<LINK_GROUP:feature,library-list>
+
+  .. versionadded:: 3.24
+
+  Manage the grouping of libraries during the link step.
+  This expression may be used to specify how to kept groups of libraries during
+  the link of a target.
+  For example:
+
+  .. code-block:: cmake
+
+    add_library(lib1 STATIC ...)
+    add_library(lib2 ...)
+    target_link_libraries(lib2 PRIVATE "$<LINK_GROUP:cross_refs,lib1,external>")
+
+  This specify to use the ``lib1`` target and ``external`` library  with the
+  group feature ``cross_refs`` for linking target ``lib2``. The feature must
+  have be defined by :variable:`CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>`
+  variable or, if :variable:`CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>_SUPPORTED`
+  is false, by :variable:`CMAKE_LINK_GROUP_USING_<FEATURE>` variable.
+
+  .. note::
+
+    The evaluation of this generator expression will use, for the following
+    variables, the values defined at the level of the creation of the target:
+
+    * :variable:`CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>_SUPPORTED`
+    * :variable:`CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>`
+    * :variable:`CMAKE_LINK_GROUP_USING_<FEATURE>_SUPPORTED`
+    * :variable:`CMAKE_LINK_GROUP_USING_<FEATURE>`
+
+  This expression can only be used to specify link libraries (i.e. part of
+  :command:`link_libraries` or :command:`target_link_libraries` commands and
+  :prop_tgt:`LINK_LIBRARIES` or :prop_tgt:`INTERFACE_LINK_LIBRARIES` target
+  properties).
+
+  .. note::
+
+    If this expression appears in the :prop_tgt:`INTERFACE_LINK_LIBRARIES`
+    property of a target, it will be included in the imported target generated
+    by :command:`install(EXPORT)` command. It is the responsibility of the
+    environment consuming this import to define the link feature used by this
+    expression.
+
+  The ``library-list`` argument can hold CMake targets or external libraries.
+  Any ``CMake`` target of type :ref:`OBJECT <Object Libraries>` or
+  :ref:`INTERFACE <Interface Libraries>` will be ignored by this expression and
+  will be handled in the standard way.
+
+  .. note::
+
+    This expression is compatible with the :genex:`LINK_LIBRARY` generator
+    expression. The libraries involved in a group can be specified using the
+    :genex:`LINK_LIBRARY` generator expression.
+
+  Each target or external library involved in the link step can be part of
+  different groups as far as these groups use the same feature, so mixing
+  different group features for the same target or library is forbidden. The
+  different groups will be part of the link step.
+
+  .. code-block:: cmake
+
+    add_library(lib1 ...)
+    add_library(lib2 ...)
+
+    add_library(lib3 ...)
+    target_link_libraries(lib3 PUBLIC "$<LINK_GROUP:feature1,lib1,lib2>")
+
+    add_library(lib4 ...)
+    target_link_libraries(lib4 PRIVATE "$<LINK_GROUP:feature1,lib1,lib3>")
+    # lib4 will be linked with the groups {lib1,lib2} and {lib1,lib3}
+
+    add_library(lib5 ...)
+    target_link_libraries(lib5 PRIVATE "$<LINK_GROUP:feature2,lib1,lib3>")
+    # an error will be raised here because lib1 is part of two groups with
+    # different features
+
+  When a target or an external library is involved in the link step as part of
+  a group and also as standalone, any occurrence of the standalone link item
+  will be replaced by the group or groups it belong to.
+
+  .. code-block:: cmake
+
+    add_library(lib1 ...)
+    add_library(lib2 ...)
+
+    add_library(lib3 ...)
+    target_link_libraries(lib3 PUBLIC lib1)
+
+    add_library(lib4 ...)
+    target_link_libraries(lib4 PRIVATE lib3 "$<LINK_GROUP:feature1,lib1,lib2>")
+    # lib4 will only be linked with lib3 and the group {lib1,lib2}
+
+  This example will be "re-written" by ``CMake`` in the following form:
+
+  .. code-block:: cmake
+
+    add_library(lib1 ...)
+    add_library(lib2 ...)
+
+    add_library(lib3 ...)
+    target_link_libraries(lib3 PUBLIC "$<LINK_GROUP:feature1,lib1,lib2>")
+
+    add_library(lib4 ...)
+    target_link_libraries(lib4 PRIVATE lib3 "$<LINK_GROUP:feature1,lib1,lib2>")
+    # lib4 will only be linked with lib3 and the group {lib1,lib2}
+
+  Be aware that the precedence of the group over the standalone link item can
+  result in some circular dependency between groups, which will raise an
+  error because circular dependencies are not allowed for groups.
+
+  .. code-block:: cmake
+
+    add_library(lib1A ...)
+    add_library(lib1B ...)
+
+    add_library(lib2A ...)
+    add_library(lib2B ...)
+
+    target_link_libraries(lib1A PUBLIC lib2A)
+    target_link_libraries(lib2B PUBLIC lib1B)
+
+    add_library(lib ...)
+    target_link_libraries(lib3 PRIVATE "$<LINK_GROUP:feat,lib1A,lib1B>"
+                                       "$<LINK_GROUP:feat,lib2A,lib2B>")
+
+  This example will be "re-written" by ``CMake`` in the following form:
+
+  .. code-block:: cmake
+
+    add_library(lib1A ...)
+    add_library(lib1B ...)
+
+    add_library(lib2A ...)
+    add_library(lib2B ...)
+
+    target_link_libraries(lib1A PUBLIC "$<LINK_GROUP:feat,lib2A,lib2B>")
+    target_link_libraries(lib2B PUBLIC "$<LINK_GROUP:feat,lib1A,lib1B>")
+
+    add_library(lib ...)
+    target_link_libraries(lib3 PRIVATE "$<LINK_GROUP:feat,lib1A,lib1B>"
+                                       "$<LINK_GROUP:feat,lib2A,lib2B>")
+
+  So, we have a circular dependency between groups ``{lib1A,lib1B}`` and
+  ``{lib2A,lib2B}``.
+
 .. genex:: $<INSTALL_INTERFACE:...>
 
   Content of ``...`` when the property is exported using :command:`install(EXPORT)`,

+ 4 - 0
Help/manual/cmake-variables.7.rst

@@ -440,6 +440,8 @@ Variables that Control the Build
    /variable/CMAKE_LANG_LINK_LIBRARY_USING_FEATURE
    /variable/CMAKE_LANG_LINK_LIBRARY_USING_FEATURE_SUPPORTED
    /variable/CMAKE_LANG_LINKER_LAUNCHER
+   /variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE
+   /variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE_SUPPORTED
    /variable/CMAKE_LANG_LINK_LIBRARY_FILE_FLAG
    /variable/CMAKE_LANG_LINK_LIBRARY_FLAG
    /variable/CMAKE_LANG_LINK_WHAT_YOU_USE_FLAG
@@ -449,6 +451,8 @@ Variables that Control the Build
    /variable/CMAKE_LIBRARY_PATH_FLAG
    /variable/CMAKE_LINK_DEF_FILE_FLAG
    /variable/CMAKE_LINK_DEPENDS_NO_SHARED
+   /variable/CMAKE_LINK_GROUP_USING_FEATURE
+   /variable/CMAKE_LINK_GROUP_USING_FEATURE_SUPPORTED
    /variable/CMAKE_LINK_INTERFACE_LIBRARIES
    /variable/CMAKE_LINK_LIBRARY_FILE_FLAG
    /variable/CMAKE_LINK_LIBRARY_FLAG

+ 8 - 0
Help/release/dev/Genex-LINK_GROUP.rst

@@ -0,0 +1,8 @@
+Genex-LINK_GROUP
+----------------
+
+* The :genex:`LINK_GROUP` generator expression was added to manage the grouping
+  of libraries during the link step. The variables
+  :variable:`CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>` and
+  :variable:`CMAKE_LINK_GROUP_USING_<FEATURE>` are used to define features
+  usable by the :genex:`LINK_GROUP` generator expression.

+ 20 - 0
Help/variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE.rst

@@ -0,0 +1,20 @@
+CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>
+---------------------------------------
+
+.. versionadded:: 3.24
+
+This variable defines, for the specified ``<FEATURE>`` and the linker language
+``<LANG>``, the expression expected by the linker when libraries are specified
+using :genex:`LINK_GROUP` generator expression.
+
+.. note::
+
+  * Feature names can contain Latin letters, digits and undercores.
+  * Feature names defined in all uppercase are reserved to CMake.
+
+See also the associated variable
+:variable:`CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>_SUPPORTED` and
+:variable:`CMAKE_LINK_GROUP_USING_<FEATURE>` variable for the definition of
+features independent from the link language.
+
+.. include:: CMAKE_LINK_GROUP_USING_FEATURE.txt

+ 13 - 0
Help/variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE_SUPPORTED.rst

@@ -0,0 +1,13 @@
+CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>_SUPPORTED
+-------------------------------------------------
+
+.. versionadded:: 3.24
+
+Set to ``TRUE`` if the ``<FEATURE>``, as defined by variable
+:variable:`CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>`, is supported for the
+linker language ``<LANG>``.
+
+.. note::
+
+  This variable is evaluated before the more generic variable
+  :variable:`CMAKE_LINK_GROUP_USING_<FEATURE>_SUPPORTED`.

+ 2 - 1
Help/variable/CMAKE_LANG_LINK_LIBRARY_USING_FEATURE.rst

@@ -9,7 +9,8 @@ using :genex:`LINK_LIBRARY` generator expression.
 
 .. note::
 
-  Feature names defined in all uppercase are reserved to CMake.
+  * Feature names can contain Latin letters, digits and undercores.
+  * Feature names defined in all uppercase are reserved to CMake.
 
 See also the associated variable
 :variable:`CMAKE_<LANG>_LINK_LIBRARY_USING_<FEATURE>_SUPPORTED` and

+ 25 - 0
Help/variable/CMAKE_LINK_GROUP_USING_FEATURE.rst

@@ -0,0 +1,25 @@
+CMAKE_LINK_GROUP_USING_<FEATURE>
+--------------------------------
+
+.. versionadded:: 3.24
+
+This variable defines, for the specified ``<FEATURE>``, the expression expected
+by the linker when libraries are specified using :genex:`LINK_GROUP` generator
+expression.
+
+.. note::
+
+  * Feature names can contain Latin letters, digits and undercores.
+  * Feature names defined in all uppercase are reserved to CMake.
+
+See also the associated variable
+:variable:`CMAKE_LINK_GROUP_USING_<FEATURE>_SUPPORTED` and
+:variable:`CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>` variable for the definition
+of features dependent from the link language.
+
+This variable will be used by :genex:`LINK_GROUP` generator expression if,
+for the linker language, the variable
+:variable:`CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>_SUPPORTED` is false or not
+set.
+
+.. include:: CMAKE_LINK_GROUP_USING_FEATURE.txt

+ 54 - 0
Help/variable/CMAKE_LINK_GROUP_USING_FEATURE.txt

@@ -0,0 +1,54 @@
+
+It must contain two elements.
+
+::
+
+  <PREFIX> <SUFFIX>
+
+``<PREFIX>`` and ``<SUFFIX>`` will be used to encapsulate the list of
+libraries.
+
+For the elements of this variable, the ``LINKER:`` prefix can be used:
+
+  .. include:: ../command/LINK_OPTIONS_LINKER.txt
+    :start-line: 3
+
+Examples
+^^^^^^^^
+
+Solving cross-references between two static libraries
+"""""""""""""""""""""""""""""""""""""""""""""""""""""
+
+A common need is the capability to search repeatedly in a group of static
+libraries until no new undefined references are created. This capability is
+offered by different environments but with a specific syntax:
+
+.. code-block:: cmake
+
+  set(CMAKE_C_LINK_GROUP_USING_cross_refs_SUPPORTED TRUE)
+  if(CMAKE_C_COMPILER_ID STREQUAL "GNU"
+     AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
+    set(CMAKE_C_LINK_GROUP_USING_cross_refs "LINKER:--start-group"
+                                            "LINKER:--end-group")
+  elseif(CMAKE_C_COMPILER_ID STREQUAL "SunPro"
+         AND CMAKE_SYSTEM_NAME STREQUAL "SunOS")
+    set(CMAKE_C_LINK_GROUP_USING_cross_refs "LINKER:-z,rescan-start"
+                                            "LINKER:-z,rescan-end")
+  else()
+    # feature not yet supported for the other environments
+    set(CMAKE_C_LINK_GROUP_USING_cross_refs_SUPPORTED FALSE)
+  endif()
+
+  add_library(lib1 STATIC ...)
+
+  add_library(lib3 SHARED ...)
+  if(CMAKE_C_LINK_GROUP_USING_cross_refs_SUPPORTED)
+    target_link_libraries(lib3 PRIVATE "$<LINK_GROUP:cross_refs,lib1,external>")
+  else()
+    target_link_libraries(lib3 PRIVATE lib1 external)
+  endif()
+
+CMake will generate the following link expressions:
+
+* ``GNU``: ``-Wl,--start-group /path/to/lib1.a -lexternal -Wl,--end-group``
+* ``SunPro``: ``-Wl,-z,rescan-start /path/to/lib1.a -lexternal -Wl,-z,rescan-end``

+ 14 - 0
Help/variable/CMAKE_LINK_GROUP_USING_FEATURE_SUPPORTED.rst

@@ -0,0 +1,14 @@
+CMAKE_LINK_GROUP_USING_<FEATURE>_SUPPORTED
+------------------------------------------
+
+.. versionadded:: 3.24
+
+Set to ``TRUE`` if the ``<FEATURE>``, as defined by variable
+:variable:`CMAKE_LINK_GROUP_USING_<FEATURE>`, is supported regardless the
+linker language.
+
+.. note::
+
+  This variable is evaluated if, and only if, the variable
+  :variable:`CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>_SUPPORTED` evaluates to
+  ``FALSE``.

+ 2 - 1
Help/variable/CMAKE_LINK_LIBRARY_USING_FEATURE.rst

@@ -9,7 +9,8 @@ using :genex:`LINK_LIBRARY` generator expression.
 
 .. note::
 
-  Feature names defined in all uppercase are reserved to CMake.
+  * Feature names can contain Latin letters, digits and undercores.
+  * Feature names defined in all uppercase are reserved to CMake.
 
 See also the associated variable
 :variable:`CMAKE_LINK_LIBRARY_USING_<FEATURE>_SUPPORTED` and

+ 2 - 0
Source/Checks/cm_cxx_filesystem.cxx

@@ -3,6 +3,8 @@
 
 int main()
 {
+  return 1;
+
   std::filesystem::path p0(L"/a/b/c");
 
   std::filesystem::path p1("/a/b/c");

+ 363 - 70
Source/cmComputeLinkDepends.cxx

@@ -180,6 +180,7 @@ items that we know the linker will re-use automatically (shared libs).
 */
 
 namespace {
+// LINK_LIBRARY helpers
 const auto LL_BEGIN = "<LINK_LIBRARY:"_s;
 const auto LL_END = "</LINK_LIBRARY:"_s;
 
@@ -202,6 +203,31 @@ bool IsFeatureSupported(cmMakefile* makefile, std::string const& linkLanguage,
     cmStrCat("CMAKE_LINK_LIBRARY_USING_", feature, "_SUPPORTED");
   return makefile->GetDefinition(featureSupported).IsOn();
 }
+
+// LINK_GROUP helpers
+const auto LG_BEGIN = "<LINK_GROUP:"_s;
+const auto LG_END = "</LINK_GROUP:"_s;
+
+inline std::string ExtractGroupFeature(std::string const& item)
+{
+  return item.substr(LG_BEGIN.length(),
+                     item.find(':', LG_BEGIN.length()) - LG_BEGIN.length());
+}
+
+bool IsGroupFeatureSupported(cmMakefile* makefile,
+                             std::string const& linkLanguage,
+                             std::string const& feature)
+{
+  auto featureSupported = cmStrCat(
+    "CMAKE_", linkLanguage, "_LINK_GROUP_USING_", feature, "_SUPPORTED");
+  if (makefile->GetDefinition(featureSupported).IsOn()) {
+    return true;
+  }
+
+  featureSupported =
+    cmStrCat("CMAKE_LINK_GROUP_USING_", feature, "_SUPPORTED");
+  return makefile->GetDefinition(featureSupported).IsOn();
+}
 }
 
 const std::string cmComputeLinkDepends::LinkEntry::DEFAULT = "DEFAULT";
@@ -311,6 +337,11 @@ cmComputeLinkDepends::Compute()
   // Infer dependencies of targets for which they were not known.
   this->InferDependencies();
 
+  // finalize groups dependencies
+  // All dependencies which are raw items must be replaced by the group
+  // it belongs to, if any.
+  this->UpdateGroupDependencies();
+
   // Cleanup the constraint graph.
   this->CleanConstraintGraph();
 
@@ -325,6 +356,19 @@ cmComputeLinkDepends::Compute()
     this->DisplayConstraintGraph();
   }
 
+  // Compute the DAG of strongly connected components.  The algorithm
+  // used by cmComputeComponentGraph should identify the components in
+  // the same order in which the items were originally discovered in
+  // the BFS.  This should preserve the original order when no
+  // constraints disallow it.
+  this->CCG =
+    cm::make_unique<cmComputeComponentGraph>(this->EntryConstraintGraph);
+  this->CCG->Compute();
+
+  if (!this->CheckCircularDependencies()) {
+    return this->FinalLinkEntries;
+  }
+
   // Compute the final ordering.
   this->OrderLinkEntries();
 
@@ -350,6 +394,29 @@ cmComputeLinkDepends::Compute()
   // Reverse the resulting order since we iterated in reverse.
   std::reverse(this->FinalLinkEntries.begin(), this->FinalLinkEntries.end());
 
+  // Expand group items
+  if (!this->GroupItems.empty()) {
+    for (const auto& group : this->GroupItems) {
+      const LinkEntry& groupEntry = this->EntryList[group.first];
+      auto it = this->FinalLinkEntries.begin();
+      while (true) {
+        it = std::find_if(it, this->FinalLinkEntries.end(),
+                          [&groupEntry](const LinkEntry& entry) -> bool {
+                            return groupEntry.Item == entry.Item;
+                          });
+        if (it == this->FinalLinkEntries.end()) {
+          break;
+        }
+        it->Item.Value = "</LINK_GROUP>";
+        for (auto i = group.second.rbegin(); i != group.second.rend(); ++i) {
+          it = this->FinalLinkEntries.insert(it, this->EntryList[*i]);
+        }
+        it = this->FinalLinkEntries.insert(it, groupEntry);
+        it->Item.Value = "<LINK_GROUP>";
+      }
+    }
+  }
+
   // Display the final set.
   if (this->DebugMode) {
     this->DisplayFinalEntries();
@@ -379,7 +446,8 @@ cmComputeLinkDepends::AllocateLinkEntry(cmLinkItem const& item)
   return lei;
 }
 
-std::pair<int, bool> cmComputeLinkDepends::AddLinkEntry(cmLinkItem const& item)
+std::pair<int, bool> cmComputeLinkDepends::AddLinkEntry(cmLinkItem const& item,
+                                                        int groupIndex)
 {
   // Allocate a spot for the item entry.
   auto lei = this->AllocateLinkEntry(item);
@@ -399,23 +467,28 @@ std::pair<int, bool> cmComputeLinkDepends::AddLinkEntry(cmLinkItem const& item)
       entry.Item.Value[1] != 'l' &&
       entry.Item.Value.substr(0, 10) != "-framework") {
     entry.Kind = LinkEntry::Flag;
+  } else if (cmHasPrefix(entry.Item.Value, LG_BEGIN) &&
+             cmHasSuffix(entry.Item.Value, '>')) {
+    entry.Kind = LinkEntry::Group;
   }
 
-  // If the item has dependencies queue it to follow them.
-  if (entry.Target) {
-    // Target dependencies are always known.  Follow them.
-    BFSEntry qe = { index, nullptr };
-    this->BFSQueue.push(qe);
-  } else {
-    // Look for an old-style <item>_LIB_DEPENDS variable.
-    std::string var = cmStrCat(entry.Item.Value, "_LIB_DEPENDS");
-    if (cmValue val = this->Makefile->GetDefinition(var)) {
-      // The item dependencies are known.  Follow them.
-      BFSEntry qe = { index, val->c_str() };
+  if (entry.Kind != LinkEntry::Group) {
+    // If the item has dependencies queue it to follow them.
+    if (entry.Target) {
+      // Target dependencies are always known.  Follow them.
+      BFSEntry qe = { index, groupIndex, nullptr };
       this->BFSQueue.push(qe);
-    } else if (entry.Kind != LinkEntry::Flag) {
-      // The item dependencies are not known.  We need to infer them.
-      this->InferredDependSets[index].Initialized = true;
+    } else {
+      // Look for an old-style <item>_LIB_DEPENDS variable.
+      std::string var = cmStrCat(entry.Item.Value, "_LIB_DEPENDS");
+      if (cmValue val = this->Makefile->GetDefinition(var)) {
+        // The item dependencies are known.  Follow them.
+        BFSEntry qe = { index, groupIndex, val->c_str() };
+        this->BFSQueue.push(qe);
+      } else if (entry.Kind != LinkEntry::Flag) {
+        // The item dependencies are not known.  We need to infer them.
+        this->InferredDependSets[index].Initialized = true;
+      }
     }
   }
 
@@ -445,8 +518,8 @@ void cmComputeLinkDepends::AddLinkObject(cmLinkItem const& item)
 void cmComputeLinkDepends::FollowLinkEntry(BFSEntry qe)
 {
   // Get this entry representation.
-  int depender_index = qe.Index;
-  LinkEntry const& entry = this->EntryList[depender_index];
+  int depender_index = qe.GroupIndex == -1 ? qe.Index : qe.GroupIndex;
+  LinkEntry const& entry = this->EntryList[qe.Index];
 
   // Follow the item's dependencies.
   if (entry.Target) {
@@ -628,6 +701,10 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index,
   std::map<int, DependSet> dependSets;
   std::string feature = LinkEntry::DEFAULT;
 
+  bool inGroup = false;
+  std::pair<int, bool> groupIndex{ -1, false };
+  std::vector<int> groupItems;
+
   // Loop over the libraries linked directly by the depender.
   for (T const& l : libs) {
     // Skip entries that will resolve to the target getting linked or
@@ -636,6 +713,7 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index,
     if (item.AsStr() == this->Target->GetName() || item.AsStr().empty()) {
       continue;
     }
+
     if (cmHasPrefix(item.AsStr(), LL_BEGIN) &&
         cmHasSuffix(item.AsStr(), '>')) {
       feature = ExtractFeature(item.AsStr());
@@ -666,12 +744,82 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index,
       continue;
     }
 
+    if (cmHasPrefix(item.AsStr(), LG_BEGIN) &&
+        cmHasSuffix(item.AsStr(), '>')) {
+      groupIndex = this->AddLinkEntry(item);
+      if (groupIndex.second) {
+        LinkEntry& entry = this->EntryList[groupIndex.first];
+        entry.Feature = ExtractGroupFeature(item.AsStr());
+      }
+      inGroup = true;
+      if (depender_index >= 0) {
+        this->EntryConstraintGraph[depender_index].emplace_back(
+          groupIndex.first, false, false, cmListFileBacktrace());
+      } else {
+        // This is a direct dependency of the target being linked.
+        this->OriginalEntries.push_back(groupIndex.first);
+      }
+      continue;
+    }
+
+    int dependee_index;
+
+    if (cmHasPrefix(item.AsStr(), LG_END) && cmHasSuffix(item.AsStr(), '>')) {
+      dependee_index = groupIndex.first;
+      if (groupIndex.second) {
+        this->GroupItems.emplace(groupIndex.first, groupItems);
+      }
+      inGroup = false;
+      groupIndex = std::make_pair(-1, false);
+      groupItems.clear();
+      continue;
+    }
+
+    if (depender_index >= 0 && inGroup) {
+      const auto& depender = this->EntryList[depender_index];
+      const auto& groupFeature = this->EntryList[groupIndex.first].Feature;
+      if (depender.Target != nullptr && depender.Target->IsImported() &&
+          !IsGroupFeatureSupported(this->Makefile, this->LinkLanguage,
+                                   groupFeature)) {
+        this->CMakeInstance->IssueMessage(
+          MessageType::AUTHOR_ERROR,
+          cmStrCat("The 'IMPORTED' target '", depender.Target->GetName(),
+                   "' uses the generator-expression '$<LINK_GROUP>' with "
+                   "the feature '",
+                   groupFeature,
+                   "', which is undefined or unsupported.\nDid you miss to "
+                   "define it by setting variables \"CMAKE_",
+                   this->LinkLanguage, "_LINK_GROUP_USING_", groupFeature,
+                   "\" and \"CMAKE_", this->LinkLanguage, "_LINK_GROUP_USING_",
+                   groupFeature, "_SUPPORTED\"?"),
+          this->Target->GetBacktrace());
+      }
+    }
+
     // Add a link entry for this item.
-    auto ale = this->AddLinkEntry(item);
-    int dependee_index = ale.first;
+    auto ale = this->AddLinkEntry(item, groupIndex.first);
+    dependee_index = ale.first;
     LinkEntry& entry = this->EntryList[dependee_index];
     auto const& itemFeature =
       this->GetCurrentFeature(entry.Item.Value, feature);
+    if (inGroup && ale.second && entry.Target != nullptr &&
+        (entry.Target->GetType() == cmStateEnums::TargetType::OBJECT_LIBRARY ||
+         entry.Target->GetType() ==
+           cmStateEnums::TargetType::INTERFACE_LIBRARY)) {
+      const auto& groupFeature = this->EntryList[groupIndex.first].Feature;
+      this->CMakeInstance->IssueMessage(
+        MessageType::AUTHOR_WARNING,
+        cmStrCat(
+          "The feature '", groupFeature,
+          "', specified as part of a generator-expression "
+          "'$",
+          LG_BEGIN, groupFeature, ">', will not be applied to the ",
+          (entry.Target->GetType() == cmStateEnums::TargetType::OBJECT_LIBRARY
+             ? "OBJECT"
+             : "INTERFACE"),
+          " library '", entry.Item.Value, "'."),
+        this->Target->GetBacktrace());
+    }
     if (itemFeature != LinkEntry::DEFAULT) {
       if (ale.second) {
         // current item not yet defined
@@ -702,50 +850,97 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index,
       (entry.Target->GetType() != cmStateEnums::TargetType::OBJECT_LIBRARY &&
        entry.Target->GetType() != cmStateEnums::TargetType::INTERFACE_LIBRARY);
 
-    if (supportedItem && entry.Feature != itemFeature) {
-      // incompatibles features occurred
-      this->CMakeInstance->IssueMessage(
-        MessageType::FATAL_ERROR,
-        cmStrCat("Impossible to link target '", this->Target->GetName(),
-                 "' because the link item '", entry.Item.Value,
-                 "', specified ",
-                 (itemFeature == LinkEntry::DEFAULT
-                    ? "without any feature or 'DEFAULT' feature"
-                    : cmStrCat("with the feature '", itemFeature, '\'')),
-                 ", has already occurred ",
-                 (entry.Feature == LinkEntry::DEFAULT
-                    ? "without any feature or 'DEFAULT' feature"
-                    : cmStrCat("with the feature '", entry.Feature, '\'')),
-                 ", which is not allowed."),
-        this->Target->GetBacktrace());
+    if (supportedItem) {
+      if (inGroup) {
+        const auto& currentFeature = this->EntryList[groupIndex.first].Feature;
+        for (const auto& g : this->GroupItems) {
+          const auto& groupFeature = this->EntryList[g.first].Feature;
+          if (groupFeature == currentFeature) {
+            continue;
+          }
+          if (std::find(g.second.cbegin(), g.second.cend(), dependee_index) !=
+              g.second.cend()) {
+            this->CMakeInstance->IssueMessage(
+              MessageType::FATAL_ERROR,
+              cmStrCat("Impossible to link target '", this->Target->GetName(),
+                       "' because the link item '", entry.Item.Value,
+                       "', specified with the group feature '", currentFeature,
+                       '\'', ", has already occurred with the feature '",
+                       groupFeature, '\'', ", which is not allowed."),
+              this->Target->GetBacktrace());
+            continue;
+          }
+        }
+      }
+      if (entry.Feature != itemFeature) {
+        // incompatibles features occurred
+        this->CMakeInstance->IssueMessage(
+          MessageType::FATAL_ERROR,
+          cmStrCat("Impossible to link target '", this->Target->GetName(),
+                   "' because the link item '", entry.Item.Value,
+                   "', specified ",
+                   (itemFeature == LinkEntry::DEFAULT
+                      ? "without any feature or 'DEFAULT' feature"
+                      : cmStrCat("with the feature '", itemFeature, '\'')),
+                   ", has already occurred ",
+                   (entry.Feature == LinkEntry::DEFAULT
+                      ? "without any feature or 'DEFAULT' feature"
+                      : cmStrCat("with the feature '", entry.Feature, '\'')),
+                   ", which is not allowed."),
+          this->Target->GetBacktrace());
+      }
     }
 
-    // The dependee must come after the depender.
-    if (depender_index >= 0) {
-      this->EntryConstraintGraph[depender_index].emplace_back(
-        dependee_index, false, false, cmListFileBacktrace());
+    if (inGroup) {
+      // store item index for dependencies handling
+      groupItems.push_back(dependee_index);
     } else {
-      // This is a direct dependency of the target being linked.
-      this->OriginalEntries.push_back(dependee_index);
-    }
-
-    // Update the inferred dependencies for earlier items.
-    for (auto& dependSet : dependSets) {
-      // Add this item to the inferred dependencies of other items.
-      // Target items are never inferred dependees because unknown
-      // items are outside libraries that should not be depending on
-      // targets.
-      if (!this->EntryList[dependee_index].Target &&
-          this->EntryList[dependee_index].Kind != LinkEntry::Flag &&
-          dependee_index != dependSet.first) {
-        dependSet.second.insert(dependee_index);
+      std::vector<int> indexes;
+      bool entryHandled = false;
+      // search any occurrence of the library in already defined groups
+      for (const auto& group : this->GroupItems) {
+        for (auto index : group.second) {
+          if (entry.Item.Value == this->EntryList[index].Item.Value) {
+            indexes.push_back(group.first);
+            entryHandled = true;
+            break;
+          }
+        }
       }
-    }
+      if (!entryHandled) {
+        indexes.push_back(dependee_index);
+      }
+
+      for (auto index : indexes) {
+        // The dependee must come after the depender.
+        if (depender_index >= 0) {
+          this->EntryConstraintGraph[depender_index].emplace_back(
+            index, false, false, cmListFileBacktrace());
+        } else {
+          // This is a direct dependency of the target being linked.
+          this->OriginalEntries.push_back(index);
+        }
+
+        // Update the inferred dependencies for earlier items.
+        for (auto& dependSet : dependSets) {
+          // Add this item to the inferred dependencies of other items.
+          // Target items are never inferred dependees because unknown
+          // items are outside libraries that should not be depending on
+          // targets.
+          if (!this->EntryList[index].Target &&
+              this->EntryList[index].Kind != LinkEntry::Flag &&
+              this->EntryList[index].Kind != LinkEntry::Group &&
+              dependee_index != dependSet.first) {
+            dependSet.second.insert(index);
+          }
+        }
 
-    // If this item needs to have dependencies inferred, do so.
-    if (this->InferredDependSets[dependee_index].Initialized) {
-      // Make sure an entry exists to hold the set for the item.
-      dependSets[dependee_index];
+        // If this item needs to have dependencies inferred, do so.
+        if (this->InferredDependSets[index].Initialized) {
+          // Make sure an entry exists to hold the set for the item.
+          dependSets[index];
+        }
+      }
     }
   }
 
@@ -808,6 +1003,36 @@ void cmComputeLinkDepends::InferDependencies()
   }
 }
 
+void cmComputeLinkDepends::UpdateGroupDependencies()
+{
+  if (this->GroupItems.empty()) {
+    return;
+  }
+
+  // Walks through all entries of the constraint graph to replace dependencies
+  // over raw items by the group it belongs to, if any.
+  for (auto& edgeList : this->EntryConstraintGraph) {
+    for (auto& edge : edgeList) {
+      int index = edge;
+      if (this->EntryList[index].Kind == LinkEntry::Group ||
+          this->EntryList[index].Kind == LinkEntry::Flag ||
+          this->EntryList[index].Kind == LinkEntry::Object) {
+        continue;
+      }
+      // search the item in the defined groups
+      for (const auto& groupItems : this->GroupItems) {
+        auto pos = std::find(groupItems.second.cbegin(),
+                             groupItems.second.cend(), index);
+        if (pos != groupItems.second.cend()) {
+          // replace lib dependency by the group it belongs to
+          edge = cmGraphEdge{ groupItems.first, false, false,
+                              cmListFileBacktrace() };
+        }
+      }
+    }
+  }
+}
+
 void cmComputeLinkDepends::CleanConstraintGraph()
 {
   for (cmGraphEdgeList& edgeList : this->EntryConstraintGraph) {
@@ -821,6 +1046,76 @@ void cmComputeLinkDepends::CleanConstraintGraph()
   }
 }
 
+bool cmComputeLinkDepends::CheckCircularDependencies() const
+{
+  std::vector<NodeList> const& components = this->CCG->GetComponents();
+  int nc = static_cast<int>(components.size());
+  for (int c = 0; c < nc; ++c) {
+    // Get the current component.
+    NodeList const& nl = components[c];
+
+    // Skip trivial components.
+    if (nl.size() < 2) {
+      continue;
+    }
+
+    // no group must be evolved
+    bool cycleDetected = false;
+    for (int ni : nl) {
+      if (this->EntryList[ni].Kind == LinkEntry::Group) {
+        cycleDetected = true;
+        break;
+      }
+    }
+    if (!cycleDetected) {
+      continue;
+    }
+
+    // Construct the error message.
+    auto formatItem = [](LinkEntry const& entry) -> std::string {
+      if (entry.Kind == LinkEntry::Group) {
+        auto items =
+          entry.Item.Value.substr(entry.Item.Value.find(':', 12) + 1);
+        items.pop_back();
+        std::replace(items.begin(), items.end(), '|', ',');
+        return cmStrCat("group \"", ExtractGroupFeature(entry.Item.Value),
+                        ":{", items, "}\"");
+      }
+      return cmStrCat('"', entry.Item.Value, '"');
+    };
+
+    std::ostringstream e;
+    e << "The inter-target dependency graph, for the target \""
+      << this->Target->GetName()
+      << "\", contains the following strongly connected component "
+         "(cycle):\n";
+    std::vector<int> const& cmap = this->CCG->GetComponentMap();
+    for (int i : nl) {
+      // Get the depender.
+      LinkEntry const& depender = this->EntryList[i];
+
+      // Describe the depender.
+      e << "  " << formatItem(depender) << "\n";
+
+      // List its dependencies that are inside the component.
+      EdgeList const& el = this->EntryConstraintGraph[i];
+      for (cmGraphEdge const& ni : el) {
+        int j = ni;
+        if (cmap[j] == c) {
+          LinkEntry const& dependee = this->EntryList[j];
+          e << "    depends on " << formatItem(dependee) << "\n";
+        }
+      }
+    }
+    this->CMakeInstance->IssueMessage(MessageType::FATAL_ERROR, e.str(),
+                                      this->Target->GetBacktrace());
+
+    return false;
+  }
+
+  return true;
+}
+
 void cmComputeLinkDepends::DisplayConstraintGraph()
 {
   // Display the graph nodes and their edges.
@@ -835,15 +1130,6 @@ void cmComputeLinkDepends::DisplayConstraintGraph()
 
 void cmComputeLinkDepends::OrderLinkEntries()
 {
-  // Compute the DAG of strongly connected components.  The algorithm
-  // used by cmComputeComponentGraph should identify the components in
-  // the same order in which the items were originally discovered in
-  // the BFS.  This should preserve the original order when no
-  // constraints disallow it.
-  this->CCG =
-    cm::make_unique<cmComputeComponentGraph>(this->EntryConstraintGraph);
-  this->CCG->Compute();
-
   // The component graph is guaranteed to be acyclic.  Start a DFS
   // from every entry to compute a topological order for the
   // components.
@@ -1033,11 +1319,18 @@ int cmComputeLinkDepends::ComputeComponentCount(NodeList const& nl)
 void cmComputeLinkDepends::DisplayFinalEntries()
 {
   fprintf(stderr, "target [%s] links to:\n", this->Target->GetName().c_str());
+  char space[] = "  ";
+  int count = 2;
   for (LinkEntry const& lei : this->FinalLinkEntries) {
-    if (lei.Target) {
-      fprintf(stderr, "  target [%s]", lei.Target->GetName().c_str());
+    if (lei.Kind == LinkEntry::Group) {
+      fprintf(stderr, "  %s group",
+              lei.Item.Value == "<LINK_GROUP>" ? "start" : "end");
+      count = lei.Item.Value == "<LINK_GROUP>" ? 4 : 2;
+    } else if (lei.Target) {
+      fprintf(stderr, "%*starget [%s]", count, space,
+              lei.Target->GetName().c_str());
     } else {
-      fprintf(stderr, "  item [%s]", lei.Item.Value.c_str());
+      fprintf(stderr, "%*sitem [%s]", count, space, lei.Item.Value.c_str());
     }
     if (lei.Feature != LinkEntry::DEFAULT) {
       fprintf(stderr, ", feature [%s]", lei.Feature.c_str());

+ 14 - 2
Source/cmComputeLinkDepends.h

@@ -54,7 +54,10 @@ public:
       Library,
       Object,
       SharedDep,
-      Flag
+      Flag,
+      // The following member is for the management of items specified
+      // through genex $<LINK_GROUP:...>
+      Group
     };
 
     BT<std::string> Item;
@@ -90,7 +93,8 @@ private:
 
   std::pair<std::map<cmLinkItem, int>::iterator, bool> AllocateLinkEntry(
     cmLinkItem const& item);
-  std::pair<int, bool> AddLinkEntry(cmLinkItem const& item);
+  std::pair<int, bool> AddLinkEntry(cmLinkItem const& item,
+                                    int groupIndex = -1);
   void AddLinkObject(cmLinkItem const& item);
   void AddVarLinkEntries(int depender_index, const char* value);
   void AddDirectLinkEntries();
@@ -103,10 +107,14 @@ private:
   std::vector<LinkEntry> EntryList;
   std::map<cmLinkItem, int> LinkEntryIndex;
 
+  // map storing, for each group, the list of items
+  std::map<int, std::vector<int>> GroupItems;
+
   // BFS of initial dependencies.
   struct BFSEntry
   {
     int Index;
+    int GroupIndex;
     const char* LibDepends;
   };
   std::queue<BFSEntry> BFSQueue;
@@ -139,12 +147,16 @@ private:
   std::vector<DependSetList> InferredDependSets;
   void InferDependencies();
 
+  // To finalize dependencies over groups in place of raw items
+  void UpdateGroupDependencies();
+
   // Ordering constraint graph adjacency list.
   using NodeList = cmGraphNodeList;
   using EdgeList = cmGraphEdgeList;
   using Graph = cmGraphAdjacencyList;
   Graph EntryConstraintGraph;
   void CleanConstraintGraph();
+  bool CheckCircularDependencies() const;
   void DisplayConstraintGraph();
 
   // Ordering algorithm.

+ 201 - 83
Source/cmComputeLinkInformation.cxx

@@ -350,21 +350,23 @@ cmComputeLinkInformation::cmComputeLinkInformation(
   if (!this->GetLibLinkFileFlag().empty()) {
     this->LibraryFeatureDescriptors.emplace(
       "__CMAKE_LINK_LIBRARY",
-      FeatureDescriptor{ "__CMAKE_LINK_LIBRARY",
-                         cmStrCat(this->GetLibLinkFileFlag(), "<LIBRARY>") });
+      LibraryFeatureDescriptor{
+        "__CMAKE_LINK_LIBRARY",
+        cmStrCat(this->GetLibLinkFileFlag(), "<LIBRARY>") });
   }
   if (!this->GetObjLinkFileFlag().empty()) {
     this->LibraryFeatureDescriptors.emplace(
       "__CMAKE_LINK_OBJECT",
-      FeatureDescriptor{ "__CMAKE_LINK_OBJECT",
-                         cmStrCat(this->GetObjLinkFileFlag(), "<LIBRARY>") });
+      LibraryFeatureDescriptor{
+        "__CMAKE_LINK_OBJECT",
+        cmStrCat(this->GetObjLinkFileFlag(), "<LIBRARY>") });
   }
   if (!this->LoaderFlag->empty()) {
     // Define a Feature descriptor for the link of an executable with exports
     this->LibraryFeatureDescriptors.emplace(
       "__CMAKE_LINK_EXECUTABLE",
-      FeatureDescriptor{ "__CMAKE_LINK_EXECUTABLE",
-                         cmStrCat(this->LoaderFlag, "<LIBRARY>") });
+      LibraryFeatureDescriptor{ "__CMAKE_LINK_EXECUTABLE",
+                                cmStrCat(this->LoaderFlag, "<LIBRARY>") });
   }
 
   // Check the platform policy for missing soname case.
@@ -544,6 +546,19 @@ bool cmComputeLinkInformation::Compute()
 
   // Add the link line items.
   for (cmComputeLinkDepends::LinkEntry const& linkEntry : linkEntries) {
+    if (linkEntry.Kind == cmComputeLinkDepends::LinkEntry::Group) {
+      const auto& groupFeature = this->GetGroupFeature(linkEntry.Feature);
+      if (groupFeature.Supported) {
+        this->Items.emplace_back(
+          BT<std::string>{ linkEntry.Item.Value == "<LINK_GROUP>"
+                             ? groupFeature.Prefix
+                             : groupFeature.Suffix,
+                           linkEntry.Item.Backtrace },
+          ItemIsPath::No);
+      }
+      continue;
+    }
+
     if (currentFeature != nullptr &&
         linkEntry.Feature != currentFeature->Name) {
       // emit feature suffix, if any
@@ -664,6 +679,117 @@ bool IsValidFeatureFormat(const std::string& format)
     format.find("<LIB_ITEM>") != std::string::npos ||
     format.find("<LINK_ITEM>") != std::string::npos;
 }
+
+class FeaturePlaceHolderExpander : public cmPlaceholderExpander
+{
+public:
+  FeaturePlaceHolderExpander(const std::string* library,
+                             const std::string* libItem = nullptr,
+                             const std::string* linkItem = nullptr)
+    : Library(library)
+    , LibItem(libItem)
+    , LinkItem(linkItem)
+  {
+  }
+
+private:
+  std::string ExpandVariable(std::string const& variable) override
+  {
+    if (this->Library != nullptr && variable == "LIBRARY") {
+      return *this->Library;
+    }
+    if (this->LibItem != nullptr && variable == "LIB_ITEM") {
+      return *this->LibItem;
+    }
+    if (this->LinkItem != nullptr && variable == "LINK_ITEM") {
+      return *this->LinkItem;
+    }
+
+    return variable;
+  }
+
+  const std::string* Library = nullptr;
+  const std::string* LibItem = nullptr;
+  const std::string* LinkItem = nullptr;
+};
+}
+
+cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor(
+  std::string name, std::string itemFormat)
+  : Name(std::move(name))
+  , Supported(true)
+  , ItemPathFormat(std::move(itemFormat))
+  , ItemNameFormat(this->ItemPathFormat)
+{
+}
+cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor(
+  std::string name, std::string itemPathFormat, std::string itemNameFormat)
+  : Name(std::move(name))
+  , Supported(true)
+  , ItemPathFormat(std::move(itemPathFormat))
+  , ItemNameFormat(std::move(itemNameFormat))
+{
+}
+cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor(
+  std::string name, std::string prefix, std::string itemPathFormat,
+  std::string itemNameFormat, std::string suffix)
+  : Name(std::move(name))
+  , Supported(true)
+  , Prefix(std::move(prefix))
+  , Suffix(std::move(suffix))
+  , ItemPathFormat(std::move(itemPathFormat))
+  , ItemNameFormat(std::move(itemNameFormat))
+{
+}
+cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor(
+  std::string name, std::string prefix, std::string suffix, bool)
+  : Name(std::move(name))
+  , Supported(true)
+  , Prefix(std::move(prefix))
+  , Suffix(std::move(suffix))
+{
+}
+
+std::string cmComputeLinkInformation::FeatureDescriptor::GetDecoratedItem(
+  std::string const& library, ItemIsPath isPath) const
+{
+  auto format =
+    isPath == ItemIsPath::Yes ? this->ItemPathFormat : this->ItemNameFormat;
+
+  // replace <LIBRARY>, <LIB_ITEM> and <LINK_ITEM> patterns with library path
+  FeaturePlaceHolderExpander expander(&library, &library, &library);
+  return expander.ExpandVariables(format);
+}
+std::string cmComputeLinkInformation::FeatureDescriptor::GetDecoratedItem(
+  std::string const& library, std::string const& libItem,
+  std::string const& linkItem, ItemIsPath isPath) const
+{
+  auto format =
+    isPath == ItemIsPath::Yes ? this->ItemPathFormat : this->ItemNameFormat;
+
+  // replace <LIBRARY>, <LIB_ITEM> and <LINK_ITEM> patterns
+  FeaturePlaceHolderExpander expander(&library, &libItem, &linkItem);
+  return expander.ExpandVariables(format);
+}
+
+cmComputeLinkInformation::LibraryFeatureDescriptor::LibraryFeatureDescriptor(
+  std::string name, std::string itemFormat)
+  : FeatureDescriptor(std::move(name), std::move(itemFormat))
+{
+}
+cmComputeLinkInformation::LibraryFeatureDescriptor::LibraryFeatureDescriptor(
+  std::string name, std::string itemPathFormat, std::string itemNameFormat)
+  : FeatureDescriptor(std::move(name), std::move(itemPathFormat),
+                      std::move(itemNameFormat))
+{
+}
+cmComputeLinkInformation::LibraryFeatureDescriptor::LibraryFeatureDescriptor(
+  std::string name, std::string prefix, std::string itemPathFormat,
+  std::string itemNameFormat, std::string suffix)
+  : FeatureDescriptor(std::move(name), std::move(prefix),
+                      std::move(itemPathFormat), std::move(itemNameFormat),
+                      std::move(suffix))
+{
 }
 
 bool cmComputeLinkInformation::AddLibraryFeature(std::string const& feature)
@@ -792,12 +918,13 @@ bool cmComputeLinkInformation::AddLibraryFeature(std::string const& feature)
 
   if (items.size() == 2) {
     this->LibraryFeatureDescriptors.emplace(
-      feature, FeatureDescriptor{ feature, items[0].Value, items[1].Value });
+      feature,
+      LibraryFeatureDescriptor{ feature, items[0].Value, items[1].Value });
   } else {
     this->LibraryFeatureDescriptors.emplace(
       feature,
-      FeatureDescriptor{ feature, items[0].Value, items[1].Value,
-                         items[2].Value, items[3].Value });
+      LibraryFeatureDescriptor{ feature, items[0].Value, items[1].Value,
+                                items[2].Value, items[3].Value });
   }
 
   return true;
@@ -819,89 +946,80 @@ cmComputeLinkInformation::FindLibraryFeature(std::string const& feature) const
   return &it->second;
 }
 
-namespace {
-class FeaturePlaceHolderExpander : public cmPlaceholderExpander
+cmComputeLinkInformation::GroupFeatureDescriptor::GroupFeatureDescriptor(
+  std::string name, std::string prefix, std::string suffix)
+  : FeatureDescriptor(std::move(name), std::move(prefix), std::move(suffix),
+                      true)
 {
-public:
-  FeaturePlaceHolderExpander(const std::string* library,
-                             const std::string* libItem = nullptr,
-                             const std::string* linkItem = nullptr)
-    : Library(library)
-    , LibItem(libItem)
-    , LinkItem(linkItem)
-  {
-  }
+}
 
-private:
-  std::string ExpandVariable(std::string const& variable) override
-  {
-    if (this->Library != nullptr && variable == "LIBRARY") {
-      return *this->Library;
-    }
-    if (this->LibItem != nullptr && variable == "LIB_ITEM") {
-      return *this->LibItem;
-    }
-    if (this->LinkItem != nullptr && variable == "LINK_ITEM") {
-      return *this->LinkItem;
-    }
+cmComputeLinkInformation::FeatureDescriptor const&
+cmComputeLinkInformation::GetGroupFeature(std::string const& feature)
+{
+  auto it = this->GroupFeatureDescriptors.find(feature);
+  if (it != this->GroupFeatureDescriptors.end()) {
+    return it->second;
+  }
 
-    return variable;
+  auto featureName =
+    cmStrCat("CMAKE_", this->LinkLanguage, "_LINK_GROUP_USING_", feature);
+  cmValue featureSupported =
+    this->Makefile->GetDefinition(cmStrCat(featureName, "_SUPPORTED"));
+  if (!featureSupported.IsOn()) {
+    featureName = cmStrCat("CMAKE_LINK_GROUP_USING_", feature);
+    featureSupported =
+      this->Makefile->GetDefinition(cmStrCat(featureName, "_SUPPORTED"));
+  }
+  if (!featureSupported.IsOn()) {
+    this->CMakeInstance->IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat("Feature '", feature,
+               "', specified through generator-expression '$<LINK_GROUP>' to "
+               "link target '",
+               this->Target->GetName(), "', is not supported for the '",
+               this->LinkLanguage, "' link language."),
+      this->Target->GetBacktrace());
+    return this->GroupFeatureDescriptors.emplace(feature, FeatureDescriptor{})
+      .first->second;
   }
 
-  const std::string* Library = nullptr;
-  const std::string* LibItem = nullptr;
-  const std::string* LinkItem = nullptr;
-};
-}
+  cmValue langFeature = this->Makefile->GetDefinition(featureName);
+  if (!langFeature) {
+    this->CMakeInstance->IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat("Feature '", feature,
+               "', specified through generator-expression '$<LINK_GROUP>' to "
+               "link target '",
+               this->Target->GetName(), "', is not defined for the '",
+               this->LinkLanguage, "' link language."),
+      this->Target->GetBacktrace());
+    return this->GroupFeatureDescriptors.emplace(feature, FeatureDescriptor{})
+      .first->second;
+  }
 
-cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor(
-  std::string name, std::string itemFormat)
-  : Name(std::move(name))
-  , Supported(true)
-  , ItemPathFormat(std::move(itemFormat))
-  , ItemNameFormat(this->ItemPathFormat)
-{
-}
-cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor(
-  std::string name, std::string itemPathFormat, std::string itemNameFormat)
-  : Name(std::move(name))
-  , Supported(true)
-  , ItemPathFormat(std::move(itemPathFormat))
-  , ItemNameFormat(std::move(itemNameFormat))
-{
-}
-cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor(
-  std::string name, std::string prefix, std::string itemPathFormat,
-  std::string itemNameFormat, std::string suffix)
-  : Name(std::move(name))
-  , Supported(true)
-  , Prefix(std::move(prefix))
-  , Suffix(std::move(suffix))
-  , ItemPathFormat(std::move(itemPathFormat))
-  , ItemNameFormat(std::move(itemNameFormat))
-{
-}
+  auto items =
+    cmExpandListWithBacktrace(langFeature, this->Target->GetBacktrace(), true);
 
-std::string cmComputeLinkInformation::FeatureDescriptor::GetDecoratedItem(
-  std::string const& library, ItemIsPath isPath) const
-{
-  auto format =
-    isPath == ItemIsPath::Yes ? this->ItemPathFormat : this->ItemNameFormat;
+  // replace LINKER: pattern
+  this->Target->ResolveLinkerWrapper(items, this->LinkLanguage, true);
 
-  // replace <LIBRARY>, <LIB_ITEM> and <LINK_ITEM> patterns with library path
-  FeaturePlaceHolderExpander expander(&library, &library, &library);
-  return expander.ExpandVariables(format);
-}
-std::string cmComputeLinkInformation::FeatureDescriptor::GetDecoratedItem(
-  std::string const& library, std::string const& libItem,
-  std::string const& linkItem, ItemIsPath isPath) const
-{
-  auto format =
-    isPath == ItemIsPath::Yes ? this->ItemPathFormat : this->ItemNameFormat;
+  if (items.size() == 2) {
+    return this->GroupFeatureDescriptors
+      .emplace(
+        feature,
+        GroupFeatureDescriptor{ feature, items[0].Value, items[1].Value })
+      .first->second;
+  }
 
-  // replace <LIBRARY>, <LIB_ITEM> and <LINK_ITEM> patterns
-  FeaturePlaceHolderExpander expander(&library, &libItem, &linkItem);
-  return expander.ExpandVariables(format);
+  this->CMakeInstance->IssueMessage(
+    MessageType::FATAL_ERROR,
+    cmStrCat("Feature '", feature, "', specified by variable '", featureName,
+             "', is malformed (wrong number of elements) and cannot be used "
+             "to link target '",
+             this->Target->GetName(), "'."),
+    this->Target->GetBacktrace());
+  return this->GroupFeatureDescriptors.emplace(feature, FeatureDescriptor{})
+    .first->second;
 }
 
 void cmComputeLinkInformation::AddImplicitLinkInfo()

+ 31 - 6
Source/cmComputeLinkInformation.h

@@ -255,12 +255,6 @@ private:
   {
   public:
     FeatureDescriptor() = default;
-    FeatureDescriptor(std::string name, std::string itemFormat);
-    FeatureDescriptor(std::string name, std::string itemPathFormat,
-                      std::string itemNameFormat);
-    FeatureDescriptor(std::string name, std::string prefix,
-                      std::string itemPathFormat, std::string itemNameFormat,
-                      std::string suffix);
 
     const std::string Name;
     const bool Supported = false;
@@ -273,13 +267,44 @@ private:
                                  std::string const& defaultValue,
                                  ItemIsPath isPath) const;
 
+  protected:
+    FeatureDescriptor(std::string name, std::string itemFormat);
+    FeatureDescriptor(std::string name, std::string itemPathFormat,
+                      std::string itemNameFormat);
+    FeatureDescriptor(std::string name, std::string prefix,
+                      std::string itemPathFormat, std::string itemNameFormat,
+                      std::string suffix);
+
+    FeatureDescriptor(std::string name, std::string prefix, std::string suffix,
+                      bool isGroup);
+
   private:
     std::string ItemPathFormat;
     std::string ItemNameFormat;
   };
+
+  class LibraryFeatureDescriptor : public FeatureDescriptor
+  {
+  public:
+    LibraryFeatureDescriptor(std::string name, std::string itemFormat);
+    LibraryFeatureDescriptor(std::string name, std::string itemPathFormat,
+                             std::string itemNameFormat);
+    LibraryFeatureDescriptor(std::string name, std::string prefix,
+                             std::string itemPathFormat,
+                             std::string itemNameFormat, std::string suffix);
+  };
   std::map<std::string, FeatureDescriptor> LibraryFeatureDescriptors;
   bool AddLibraryFeature(std::string const& feature);
   FeatureDescriptor const& GetLibraryFeature(std::string const& feature) const;
   FeatureDescriptor const* FindLibraryFeature(
     std::string const& feature) const;
+
+  class GroupFeatureDescriptor : public FeatureDescriptor
+  {
+  public:
+    GroupFeatureDescriptor(std::string name, std::string prefix,
+                           std::string suffix);
+  };
+  std::map<std::string, FeatureDescriptor> GroupFeatureDescriptors;
+  FeatureDescriptor const& GetGroupFeature(std::string const& feature);
 };

+ 85 - 0
Source/cmGeneratorExpressionNode.cxx

@@ -1231,7 +1231,15 @@ static const struct LinkLibraryNode : public cmGeneratorExpressionNode
       return std::string();
     }
 
+    static cmsys::RegularExpression featureNameValidator("^[A-Za-z0-9_]+$");
     auto const& feature = list.front();
+    if (!featureNameValidator.find(feature)) {
+      reportError(context, content->GetOriginalExpression(),
+                  cmStrCat("The feature name '", feature,
+                           "' contains invalid characters."));
+      return std::string();
+    }
+
     const auto LL_BEGIN = cmStrCat("<LINK_LIBRARY:", feature, '>');
     const auto LL_END = cmStrCat("</LINK_LIBRARY:", feature, '>');
 
@@ -1252,6 +1260,17 @@ static const struct LinkLibraryNode : public cmGeneratorExpressionNode
         "$<LINK_LIBRARY:...> with different features cannot be nested.");
       return std::string();
     }
+    // $<LINK_GROUP:...> must not appear as part of $<LINK_LIBRARY:...>
+    it = std::find_if(list.cbegin() + 1, list.cend(),
+                      [](const std::string& item) -> bool {
+                        return cmHasPrefix(item, "<LINK_GROUP:"_s);
+                      });
+    if (it != list.cend()) {
+      reportError(context, content->GetOriginalExpression(),
+                  "$<LINK_GROUP:...> cannot be nested inside a "
+                  "$<LINK_LIBRARY:...> expression.");
+      return std::string();
+    }
 
     list.front() = LL_BEGIN;
     list.push_back(LL_END);
@@ -1260,6 +1279,71 @@ static const struct LinkLibraryNode : public cmGeneratorExpressionNode
   }
 } linkLibraryNode;
 
+static const struct LinkGroupNode : public cmGeneratorExpressionNode
+{
+  LinkGroupNode() {} // NOLINT(modernize-use-equals-default)
+
+  int NumExpectedParameters() const override { return OneOrMoreParameters; }
+
+  std::string Evaluate(
+    const std::vector<std::string>& parameters,
+    cmGeneratorExpressionContext* context,
+    const GeneratorExpressionContent* content,
+    cmGeneratorExpressionDAGChecker* dagChecker) const override
+  {
+    if (!context->HeadTarget || !dagChecker ||
+        !dagChecker->EvaluatingLinkLibraries()) {
+      reportError(context, content->GetOriginalExpression(),
+                  "$<LINK_GROUP:...> may only be used with binary targets "
+                  "to specify group of link libraries.");
+      return std::string();
+    }
+
+    std::vector<std::string> list;
+    cmExpandLists(parameters.begin(), parameters.end(), list);
+    if (list.empty()) {
+      reportError(
+        context, content->GetOriginalExpression(),
+        "$<LINK_GROUP:...> expects a feature name as first argument.");
+      return std::string();
+    }
+    // $<LINK_GROUP:..> cannot be nested
+    if (std::find_if(list.cbegin(), list.cend(),
+                     [](const std::string& item) -> bool {
+                       return cmHasPrefix(item, "<LINK_GROUP"_s);
+                     }) != list.cend()) {
+      reportError(context, content->GetOriginalExpression(),
+                  "$<LINK_GROUP:...> cannot be nested.");
+      return std::string();
+    }
+    if (list.size() == 1) {
+      // no libraries specified, ignore this genex
+      return std::string();
+    }
+
+    static cmsys::RegularExpression featureNameValidator("^[A-Za-z0-9_]+$");
+    auto const& feature = list.front();
+    if (!featureNameValidator.find(feature)) {
+      reportError(context, content->GetOriginalExpression(),
+                  cmStrCat("The feature name '", feature,
+                           "' contains invalid characters."));
+      return std::string();
+    }
+
+    const auto LG_BEGIN = cmStrCat(
+      "<LINK_GROUP:", feature, ':',
+      cmJoin(cmRange<decltype(list.cbegin())>(list.cbegin() + 1, list.cend()),
+             "|"_s),
+      '>');
+    const auto LG_END = cmStrCat("</LINK_GROUP:", feature, '>');
+
+    list.front() = LG_BEGIN;
+    list.push_back(LG_END);
+
+    return cmJoin(list, ";"_s);
+  }
+} linkGroupNode;
+
 static const struct HostLinkNode : public cmGeneratorExpressionNode
 {
   HostLinkNode() {} // NOLINT(modernize-use-equals-default)
@@ -2731,6 +2815,7 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode(
     { "LINK_LANG_AND_ID", &linkLanguageAndIdNode },
     { "LINK_LANGUAGE", &linkLanguageNode },
     { "LINK_LIBRARY", &linkLibraryNode },
+    { "LINK_GROUP", &linkGroupNode },
     { "HOST_LINK", &hostLinkNode },
     { "DEVICE_LINK", &deviceLinkNode },
     { "SHELL_PATH", &shellPathNode }

+ 4 - 2
Source/cmGeneratorTarget.cxx

@@ -6341,7 +6341,8 @@ cm::string_view missingTargetPossibleReasons =
 bool cmGeneratorTarget::VerifyLinkItemColons(LinkItemRole role,
                                              cmLinkItem const& item) const
 {
-  if (item.Target || item.AsStr().find("::") == std::string::npos) {
+  if (item.Target || cmHasPrefix(item.AsStr(), "<LINK_GROUP:"_s) ||
+      item.AsStr().find("::") == std::string::npos) {
     return true;
   }
   MessageType messageType = MessageType::FATAL_ERROR;
@@ -6388,7 +6389,8 @@ bool cmGeneratorTarget::VerifyLinkItemIsTarget(LinkItemRole role,
   if (!str.empty() &&
       (str[0] == '-' || str[0] == '$' || str[0] == '`' ||
        str.find_first_of("/\\") != std::string::npos ||
-       cmHasPrefix(str, "<LINK_LIBRARY:"_s))) {
+       cmHasPrefix(str, "<LINK_LIBRARY:"_s) ||
+       cmHasPrefix(str, "<LINK_GROUP:"_s))) {
     return true;
   }
 

+ 9 - 9
Source/cmMakefile.cxx

@@ -3988,21 +3988,21 @@ void cmMakefile::CheckProperty(const std::string& prop) const
   if (prop == "LINK_LIBRARIES") {
     if (cmValue value = this->GetProperty(prop)) {
       // Look for <LINK_LIBRARY:> internal pattern
-      static cmsys::RegularExpression linkLibrary(
-        "(^|;)(</?LINK_LIBRARY:[^;>]*>)(;|$)");
-      if (!linkLibrary.find(value)) {
+      static cmsys::RegularExpression linkPattern(
+        "(^|;)(</?LINK_(LIBRARY|GROUP):[^;>]*>)(;|$)");
+      if (!linkPattern.find(value)) {
         return;
       }
 
       // Report an error.
       this->IssueMessage(
         MessageType::FATAL_ERROR,
-        cmStrCat(
-          "Property ", prop, " contains the invalid item \"",
-          linkLibrary.match(2), "\". The ", prop,
-          " property may contain the generator-expression "
-          "\"$<LINK_LIBRARY:...>\" "
-          "which may be used to specify how the libraries are linked."));
+        cmStrCat("Property ", prop, " contains the invalid item \"",
+                 linkPattern.match(2), "\". The ", prop,
+                 " property may contain the generator-expression \"$<LINK_",
+                 linkPattern.match(3),
+                 ":...>\" which may be used to specify how the libraries are "
+                 "linked."));
     }
   }
 }

+ 9 - 8
Source/cmTarget.cxx

@@ -1788,20 +1788,21 @@ void CheckLinkLibraryPattern(const std::string& property,
                              const std::string& value, cmMakefile* context)
 {
   // Look for <LINK_LIBRARY:> and </LINK_LIBRARY:> internal tags
-  static cmsys::RegularExpression linkLibrary(
-    "(^|;)(</?LINK_LIBRARY:[^;>]*>)(;|$)");
-  if (!linkLibrary.find(value)) {
+  static cmsys::RegularExpression linkPattern(
+    "(^|;)(</?LINK_(LIBRARY|GROUP):[^;>]*>)(;|$)");
+  if (!linkPattern.find(value)) {
     return;
   }
 
   // Report an error.
   context->IssueMessage(
     MessageType::FATAL_ERROR,
-    cmStrCat("Property ", property, " contains the invalid item \"",
-             linkLibrary.match(2), "\". The ", property,
-             " property may contain the generator-expression "
-             "\"$<LINK_LIBRARY:...>\" "
-             "which may be used to specify how the libraries are linked."));
+    cmStrCat(
+      "Property ", property, " contains the invalid item \"",
+      linkPattern.match(2), "\". The ", property,
+      " property may contain the generator-expression \"$<LINK_",
+      linkPattern.match(3),
+      ":...>\" which may be used to specify how the libraries are linked."));
 }
 
 void CheckLINK_INTERFACE_LIBRARIES(const std::string& prop,

+ 13 - 0
Tests/RunCMake/CMakeLists.txt

@@ -314,6 +314,7 @@ add_RunCMake_test(GenEx-LINK_LANG_AND_ID)
 add_RunCMake_test(GenEx-HOST_LINK)
 add_RunCMake_test(GenEx-DEVICE_LINK)
 add_RunCMake_test(GenEx-LINK_LIBRARY)
+add_RunCMake_test(GenEx-LINK_GROUP)
 add_RunCMake_test(GenEx-TARGET_FILE -DLINKER_SUPPORTS_PDB=${LINKER_SUPPORTS_PDB})
 add_RunCMake_test(GenEx-GENEX_EVAL)
 add_RunCMake_test(GenEx-TARGET_RUNTIME_DLLS)
@@ -670,6 +671,18 @@ add_RunCMake_test(target_link_libraries-LINK_LIBRARY -DCMAKE_SYSTEM_NAME=${CMAKE
                                                      -DCMAKE_IMPORT_LIBRARY_PREFIX=${CMAKE_IMPORT_LIBRARY_PREFIX}
                                                      -DCMAKE_IMPORT_LIBRARY_SUFFIX=${CMAKE_IMPORT_LIBRARY_SUFFIX}
                                                      -DCMAKE_LINK_LIBRARY_FLAG=${CMAKE_LINK_LIBRARY_FLAG})
+add_RunCMake_test(target_link_libraries-LINK_GROUP -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
+                                                   -DMINGW=${MINGW}
+                                                   -DMSYS=${MSYS}
+                                                   -DCYGWIN=${CYGWIN}
+                                                   -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
+                                                   -DCMAKE_C_COMPILER_VERSION=${CMAKE_C_COMPILER_VERSION}
+                                                   -DMSVC_VERSION=${MSVC_VERSION}
+                                                   -DCMAKE_SHARED_LIBRARY_PREFIX=${CMAKE_SHARED_LIBRARY_PREFIX}
+                                                   -DCMAKE_SHARED_LIBRARY_SUFFIX=${CMAKE_SHARED_LIBRARY_SUFFIX}
+                                                   -DCMAKE_IMPORT_LIBRARY_PREFIX=${CMAKE_IMPORT_LIBRARY_PREFIX}
+                                                   -DCMAKE_IMPORT_LIBRARY_SUFFIX=${CMAKE_IMPORT_LIBRARY_SUFFIX}
+                                                   -DCMAKE_LINK_LIBRARY_FLAG=${CMAKE_LINK_LIBRARY_FLAG})
 add_RunCMake_test(add_link_options -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID})
 add_RunCMake_test(target_link_options -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
                                       -DCMake_TEST_CUDA=${CMake_TEST_CUDA})

+ 3 - 0
Tests/RunCMake/GenEx-LINK_GROUP/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.18...3.22)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 35 - 0
Tests/RunCMake/GenEx-LINK_GROUP/RunCMakeTest.cmake

@@ -0,0 +1,35 @@
+include(RunCMake)
+
+run_cmake(add_custom_target)
+run_cmake(add_custom_command)
+run_cmake(add_link_options)
+run_cmake(link_directories)
+run_cmake(target_link_options)
+run_cmake(target_link_directories)
+run_cmake(no-arguments)
+run_cmake(empty-arguments)
+run_cmake(forbidden-arguments)
+run_cmake(nested-incompatible-genex)
+run_cmake(invalid-feature)
+run_cmake(bad-feature1)
+run_cmake(bad-feature2)
+run_cmake(bad-feature3)
+run_cmake(bad-feature4)
+run_cmake(bad-feature5)
+run_cmake(feature-not-supported)
+run_cmake(library-ignored)
+run_cmake(compatible-features1)
+run_cmake(compatible-features2)
+run_cmake(compatible-features3)
+run_cmake(incompatible-features1)
+run_cmake(nested-incompatible-features1)
+run_cmake(nested-incompatible-features2)
+run_cmake(circular-dependencies1)
+run_cmake(circular-dependencies2)
+run_cmake(only-targets)
+
+# usage of LINK_LIBRARY with LINK_GROUP
+run_cmake(incompatible-library-features1)
+run_cmake(incompatible-library-features2)
+run_cmake(override-library-features1)
+run_cmake(override-library-features2)

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command-result.txt

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

+ 9 - 0
Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command-stderr.txt

@@ -0,0 +1,9 @@
+CMake Error at add_custom_command.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<LINK_GROUP:feat>
+
+  \$<LINK_GROUP:...> may only be used with binary targets to specify group of
+  link libraries.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 4 - 0
Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command.cmake

@@ -0,0 +1,4 @@
+add_custom_target(drive)
+add_custom_command(TARGET drive PRE_BUILD
+  COMMAND ${CMAKE_COMMAND} -E echo "$<LINK_GROUP:feat>"
+)

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target-result.txt

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

+ 9 - 0
Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target-stderr.txt

@@ -0,0 +1,9 @@
+CMake Error at add_custom_target.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<LINK_GROUP:feat>
+
+  \$<LINK_GROUP:...> may only be used with binary targets to specify group of
+  link libraries.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 3 - 0
Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target.cmake

@@ -0,0 +1,3 @@
+add_custom_target(drive
+  COMMAND ${CMAKE_COMMAND} -E echo "$<LINK_GROUP:feat>"
+)

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/add_link_options-result.txt

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

+ 9 - 0
Tests/RunCMake/GenEx-LINK_GROUP/add_link_options-stderr.txt

@@ -0,0 +1,9 @@
+CMake Error at add_link_options.cmake:[0-9]+ \(add_link_options\):
+  Error evaluating generator expression:
+
+    \$<LINK_GROUP:feat>
+
+  \$<LINK_GROUP:...> may only be used with binary targets to specify group of
+  link libraries.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 5 - 0
Tests/RunCMake/GenEx-LINK_GROUP/add_link_options.cmake

@@ -0,0 +1,5 @@
+enable_language(C)
+
+add_link_options("$<LINK_GROUP:feat>")
+
+add_library(empty SHARED empty.c)

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1-result.txt

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

+ 5 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error at bad-feature1.cmake:[0-9]+ \(add_library\):
+  Feature 'bad_feat', specified through generator-expression '\$<LINK_GROUP>'
+  to link target 'lib', is not supported for the 'C' link language.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 6 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1.cmake

@@ -0,0 +1,6 @@
+enable_language(C)
+
+add_library(dep SHARED empty.c)
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE "$<LINK_GROUP:bad_feat,dep>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2-result.txt

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

+ 5 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error at bad-feature2.cmake:[0-9]+ \(add_library\):
+  Feature 'feat', specified through generator-expression '\$<LINK_GROUP>' to
+  link target 'lib', is not defined for the 'C' link language.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 8 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2.cmake

@@ -0,0 +1,8 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED TRUE)
+
+add_library(dep SHARED empty.c)
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE "$<LINK_GROUP:feat,dep>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3-result.txt

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

+ 6 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3-stderr.txt

@@ -0,0 +1,6 @@
+CMake Error at bad-feature3.cmake:[0-9]+ \(add_library\):
+  Feature 'feat', specified by variable 'CMAKE_C_LINK_GROUP_USING_feat', is
+  malformed \(wrong number of elements\) and cannot be used to link target
+  'lib'.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 9 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3.cmake

@@ -0,0 +1,9 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat "")
+
+add_library(dep SHARED empty.c)
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE "$<LINK_GROUP:feat,dep>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4-result.txt

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

+ 6 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4-stderr.txt

@@ -0,0 +1,6 @@
+CMake Error at bad-feature4.cmake:[0-9]+ \(add_library\):
+  Feature 'feat', specified by variable 'CMAKE_C_LINK_GROUP_USING_feat', is
+  malformed \(wrong number of elements\) and cannot be used to link target
+  'lib'.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 9 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4.cmake

@@ -0,0 +1,9 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat "-opt")
+
+add_library(dep SHARED empty.c)
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE "$<LINK_GROUP:feat,dep>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5-result.txt

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

+ 6 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5-stderr.txt

@@ -0,0 +1,6 @@
+CMake Error at bad-feature5.cmake:[0-9]+ \(add_library\):
+  Feature 'feat', specified by variable 'CMAKE_C_LINK_GROUP_USING_feat', is
+  malformed \(wrong number of elements\) and cannot be used to link target
+  'lib'.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 9 - 0
Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5.cmake

@@ -0,0 +1,9 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat "-start" "<LIBRARY>" "-stop")
+
+add_library(dep SHARED empty.c)
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE "$<LINK_GROUP:feat,dep>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1-result.txt

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

+ 11 - 0
Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1-stderr.txt

@@ -0,0 +1,11 @@
+CMake Error at circular-dependencies1.cmake:[0-9]+ \(add_library\):
+  The inter-target dependency graph, for the target "lib1", contains the
+  following strongly connected component \(cycle\):
+
+    group "feat:{dep1.1,dep1.2}"
+      depends on group "feat:{dep2.1,dep2.2}"
+    group "feat:{dep2.1,dep2.2}"
+      depends on group "feat:{dep1.1,dep1.2}"
+
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 17 - 0
Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1.cmake

@@ -0,0 +1,17 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat "--start" "--stop")
+
+add_library(dep1.1 SHARED empty.c)
+add_library(dep1.2 SHARED empty.c)
+
+add_library(dep2.1 SHARED empty.c)
+add_library(dep2.2 SHARED empty.c)
+
+target_link_libraries(dep1.1 PUBLIC dep2.1)
+target_link_libraries(dep2.2 PUBLIC dep1.2)
+
+add_library(lib1 SHARED empty.c)
+target_link_libraries(lib1 PRIVATE "$<LINK_GROUP:feat,dep1.1,dep1.2>"
+                                   "$<LINK_GROUP:feat,dep2.1,dep2.2>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2-result.txt

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

+ 11 - 0
Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2-stderr.txt

@@ -0,0 +1,11 @@
+CMake Error at circular-dependencies2.cmake:[0-9]+ \(add_library\):
+  The inter-target dependency graph, for the target "lib2", contains the
+  following strongly connected component \(cycle\):
+
+    group "feat:{base3,base4}"
+      depends on group "feat:{base1,base2}"
+    group "feat:{base1,base2}"
+      depends on group "feat:{base3,base4}"
+
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 18 - 0
Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2.cmake

@@ -0,0 +1,18 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat "--start" "--stop")
+
+add_library(base1 SHARED empty.c)
+add_library(base2 SHARED empty.c)
+add_library(base3 SHARED empty.c)
+add_library(base4 SHARED empty.c)
+
+target_link_libraries(base1 PUBLIC base3)
+target_link_libraries(base4 PUBLIC base2)
+
+add_library(lib1 SHARED empty.c)
+target_link_libraries(lib1 PUBLIC "$<LINK_GROUP:feat,base1,base2>")
+
+add_library(lib2 SHARED empty.c)
+target_link_libraries(lib2 PRIVATE "$<LINK_GROUP:feat,base3,base4>" lib1)

+ 18 - 0
Tests/RunCMake/GenEx-LINK_GROUP/compatible-features1.cmake

@@ -0,0 +1,18 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat1_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat1 "--start" "--stop")
+
+set(CMAKE_C_LINK_GROUP_USING_feat2_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat2 "--start" "--stop")
+
+add_library(dep1 SHARED empty.c)
+
+add_library(dep2 SHARED empty.c)
+target_link_libraries(dep2 PRIVATE "$<LINK_GROUP:feat1,dep1>")
+
+add_library(dep3 SHARED empty.c)
+target_link_libraries(dep3 PUBLIC dep2)
+
+add_library(lib1 SHARED empty.c)
+target_link_libraries(lib1 PRIVATE $<LINK_GROUP:feat2,dep1,dep2>)

+ 13 - 0
Tests/RunCMake/GenEx-LINK_GROUP/compatible-features2.cmake

@@ -0,0 +1,13 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat "--start" "--stop")
+
+add_library(dep1 SHARED empty.c)
+add_library(dep2 SHARED empty.c)
+
+add_library(lib1 SHARED empty.c)
+target_link_libraries(lib1 PUBLIC "$<LINK_GROUP:feat,dep1,dep2>")
+
+add_library(lib2 SHARED empty.c)
+target_link_libraries(lib2 PRIVATE "$<LINK_GROUP:feat,dep2,lib1>")

+ 13 - 0
Tests/RunCMake/GenEx-LINK_GROUP/compatible-features3.cmake

@@ -0,0 +1,13 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat "--start" "--stop")
+
+add_library(dep1 SHARED empty.c)
+add_library(dep2 SHARED empty.c)
+target_link_libraries(dep2 PUBLIC dep1)
+add_library(dep3 SHARED empty.c)
+target_link_libraries(dep3 PUBLIC dep1)
+
+add_library(lib1 SHARED empty.c)
+target_link_libraries(lib1 PUBLIC "$<LINK_GROUP:feat,dep1,dep2>" dep3)

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments-result.txt

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

+ 8 - 0
Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at empty-arguments.cmake:[0-9]+ \(target_link_libraries\):
+  Error evaluating generator expression:
+
+    \$<LINK_GROUP:,>
+
+  \$<LINK_GROUP:...> expects a feature name as first argument.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 4 - 0
Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments.cmake

@@ -0,0 +1,4 @@
+enable_language(C)
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE "$<LINK_GROUP:,>")

+ 0 - 0
Tests/RunCMake/GenEx-LINK_GROUP/empty.c


+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported-result.txt

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

+ 5 - 0
Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error at feature-not-supported.cmake:[0-9]+ \(add_library\):
+  Feature 'feat', specified through generator-expression '\$<LINK_GROUP>' to
+  link target 'lib', is not supported for the 'C' link language.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 9 - 0
Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported.cmake

@@ -0,0 +1,9 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED FALSE)
+set(CMAKE_C_LINK_GROUP_USING_feat "--start" "--end")
+
+add_library(dep SHARED empty.c)
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE "$<LINK_GROUP:feat,dep>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments-result.txt

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

+ 16 - 0
Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments-stderr.txt

@@ -0,0 +1,16 @@
+CMake Error at forbidden-arguments.cmake:[0-9]+ \(link_libraries\):
+  Property LINK_LIBRARIES contains the invalid item "<LINK_GROUP:feat>".  The
+  LINK_LIBRARIES property may contain the generator-expression
+  "\$<LINK_GROUP:...>" which may be used to specify how the libraries are
+  linked.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
+
+
+CMake Error at forbidden-arguments.cmake:[0-9]+ \(target_link_libraries\):
+  Property LINK_LIBRARIES contains the invalid item "<LINK_GROUP:feat>".  The
+  LINK_LIBRARIES property may contain the generator-expression
+  "\$<LINK_GROUP:...>" which may be used to specify how the libraries are
+  linked.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 6 - 0
Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments.cmake

@@ -0,0 +1,6 @@
+enable_language(C)
+
+link_libraries(<LINK_GROUP:feat> foo </LINK_GROUP:feat>)
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE <LINK_GROUP:feat> foo </LINK_GROUP:feat>)

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1-result.txt

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

+ 6 - 0
Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1-stderr.txt

@@ -0,0 +1,6 @@
+CMake Error at incompatible-features1.cmake:[0-9]+ \(add_library\):
+  Impossible to link target 'lib1' because the link item 'dep2', specified
+  with the group feature 'feat1', has already occurred with the feature
+  'feat2', which is not allowed.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 15 - 0
Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1.cmake

@@ -0,0 +1,15 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat1_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat1 "--start" "--stop")
+
+set(CMAKE_C_LINK_GROUP_USING_feat2_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat2 "--start" "--stop")
+
+add_library(dep1 SHARED empty.c)
+add_library(dep2 SHARED empty.c)
+add_library(dep3 SHARED empty.c)
+target_link_libraries(dep3 PUBLIC "$<LINK_GROUP:feat1,dep1,dep2>")
+
+add_library(lib1 SHARED empty.c)
+target_link_libraries(lib1 PRIVATE "$<LINK_GROUP:feat2,dep2,dep3>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1-result.txt

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

+ 6 - 0
Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1-stderr.txt

@@ -0,0 +1,6 @@
+CMake Error at incompatible-library-features1.cmake:[0-9]+ \(add_library\):
+  Impossible to link target 'lib1' because the link item 'dep1', specified
+  with the feature 'feat1', has already occurred without any feature or
+  'DEFAULT' feature, which is not allowed.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 17 - 0
Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1.cmake

@@ -0,0 +1,17 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat1_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat1 "--start" "--stop")
+
+set(CMAKE_C_LINK_LIBRARY_USING_feat1_SUPPORTED TRUE)
+set(CMAKE_C_LINK_LIBRARY_USING_feat1 "--libflag1<LIBRARY>")
+
+set(CMAKE_C_LINK_LIBRARY_USING_feat2_SUPPORTED TRUE)
+set(CMAKE_C_LINK_LIBRARY_USING_feat2 "--libflag2<LIBRARY>")
+
+add_library(dep1 SHARED empty.c)
+add_library(dep2 SHARED empty.c)
+target_link_libraries(dep2 PUBLIC "$<LINK_LIBRARY:feat1,dep1>")
+
+add_library(lib1 SHARED empty.c)
+target_link_libraries(lib1 PRIVATE "$<LINK_GROUP:feat1,$<LINK_LIBRARY:feat2,dep2>,dep1>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2-result.txt

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

+ 6 - 0
Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2-stderr.txt

@@ -0,0 +1,6 @@
+CMake Error at incompatible-library-features2.cmake:[0-9]+ \(add_library\):
+  Impossible to link target 'lib1' because the link item 'dep1', specified
+  with the feature 'feat1', has already occurred with the feature 'feat2',
+  which is not allowed.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 17 - 0
Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2.cmake

@@ -0,0 +1,17 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat1_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat1 "--start" "--stop")
+
+set(CMAKE_C_LINK_LIBRARY_USING_feat1_SUPPORTED TRUE)
+set(CMAKE_C_LINK_LIBRARY_USING_feat1 "--libflag1<LIBRARY>")
+
+set(CMAKE_C_LINK_LIBRARY_USING_feat2_SUPPORTED TRUE)
+set(CMAKE_C_LINK_LIBRARY_USING_feat2 "--libflag2<LIBRARY>")
+
+add_library(dep1 SHARED empty.c)
+add_library(dep2 SHARED empty.c)
+target_link_libraries(dep2 PUBLIC "$<LINK_LIBRARY:feat1,dep1>")
+
+add_library(lib1 SHARED empty.c)
+target_link_libraries(lib1 PRIVATE "$<LINK_GROUP:feat1,$<LINK_LIBRARY:feat2,dep2,dep1>>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature-result.txt

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

+ 8 - 0
Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at invalid-feature.cmake:[0-9]+ \(target_link_libraries\):
+  Error evaluating generator expression:
+
+    \$<LINK_GROUP:feat:invalid,dep>
+
+  The feature name 'feat:invalid' contains invalid characters.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 6 - 0
Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature.cmake

@@ -0,0 +1,6 @@
+enable_language(C)
+
+add_library(dep SHARED empty.c)
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE "$<LINK_GROUP:feat:invalid,dep>")

+ 13 - 0
Tests/RunCMake/GenEx-LINK_GROUP/library-ignored-stderr.txt

@@ -0,0 +1,13 @@
+CMake Warning \(dev\) at library-ignored.cmake:[0-9]+ \(add_library\):
+  The feature 'feat', specified as part of a generator-expression
+  '\$<LINK_GROUP:feat>', will not be applied to the INTERFACE library 'front'.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Warning \(dev\) at library-ignored.cmake:[0-9]+ \(add_library\):
+  The feature 'feat', specified as part of a generator-expression
+  '\$<LINK_GROUP:feat>', will not be applied to the OBJECT library 'dep'.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 15 - 0
Tests/RunCMake/GenEx-LINK_GROUP/library-ignored.cmake

@@ -0,0 +1,15 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat "--start" "--end")
+
+add_library(dep OBJECT empty.c)
+
+add_library(lib SHARED empty.c)
+
+add_library(front INTERFACE)
+target_link_libraries(front INTERFACE lib)
+
+
+add_library(lib2 SHARED empty.c)
+target_link_libraries(lib2 PRIVATE "$<LINK_GROUP:feat,front,dep>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/link_directories-result.txt

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

+ 9 - 0
Tests/RunCMake/GenEx-LINK_GROUP/link_directories-stderr.txt

@@ -0,0 +1,9 @@
+CMake Error at link_directories.cmake:[0-9]+ \(link_directories\):
+  Error evaluating generator expression:
+
+    \$<LINK_GROUP:feat>
+
+  \$<LINK_GROUP:...> may only be used with binary targets to specify group of
+  link libraries.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 5 - 0
Tests/RunCMake/GenEx-LINK_GROUP/link_directories.cmake

@@ -0,0 +1,5 @@
+enable_language(C)
+
+link_directories("$<LINK_GROUP:feat>")
+
+add_library(empty SHARED empty.c)

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1-result.txt

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

+ 8 - 0
Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at nested-incompatible-features1.cmake:[0-9]+ \(target_link_libraries\):
+  Error evaluating generator expression:
+
+    \$<LINK_GROUP:feat,dep1,\$<LINK_GROUP:feat,dep2>>
+
+  \$<LINK_GROUP:...> cannot be nested.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 11 - 0
Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1.cmake

@@ -0,0 +1,11 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat "--start" "--end")
+
+add_library(dep1 SHARED empty.c)
+
+add_library(dep2 SHARED empty.c)
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE "$<LINK_GROUP:feat,dep1,$<LINK_GROUP:feat,dep2>>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2-result.txt

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

+ 8 - 0
Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at nested-incompatible-features2.cmake:[0-9]+ \(target_link_libraries\):
+  Error evaluating generator expression:
+
+    \$<LINK_GROUP:feat1,dep1,\$<LINK_GROUP:feat2,dep2>>
+
+  \$<LINK_GROUP:...> cannot be nested.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 14 - 0
Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2.cmake

@@ -0,0 +1,14 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat1_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat1 "--start" "--end")
+
+set(CMAKE_C_LINK_GROUP_USING_feat2_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat2 "--start" "--end")
+
+add_library(dep1 SHARED empty.c)
+
+add_library(dep2 SHARED empty.c)
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE "$<LINK_GROUP:feat1,dep1,$<LINK_GROUP:feat2,dep2>>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex-result.txt

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

+ 18 - 0
Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex-stderr.txt

@@ -0,0 +1,18 @@
+CMake Error at nested-incompatible-genex.cmake:[0-9]+ \(add_library\):
+  Error evaluating generator expression:
+
+    \$<LINK_LIBRARY:feat,\$<LINK_GROUP:feat,foo>>
+
+  \$<LINK_GROUP:...> cannot be nested inside a \$<LINK_LIBRARY:...> expression.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
+
+
+CMake Error at nested-incompatible-genex.cmake:[0-9]+ \(target_link_libraries\):
+  Error evaluating generator expression:
+
+    \$<LINK_LIBRARY:feat,\$<LINK_GROUP:feat,foo>>
+
+  \$<LINK_GROUP:...> cannot be nested inside a \$<LINK_LIBRARY:...> expression.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 6 - 0
Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex.cmake

@@ -0,0 +1,6 @@
+enable_language(C)
+
+link_libraries("$<LINK_LIBRARY:feat,$<LINK_GROUP:feat,foo>>")
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE "$<LINK_LIBRARY:feat,$<LINK_GROUP:feat,foo>>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/no-arguments-result.txt

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

+ 8 - 0
Tests/RunCMake/GenEx-LINK_GROUP/no-arguments-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at no-arguments.cmake:[0-9]+ \(target_link_libraries\):
+  Error evaluating generator expression:
+
+    \$<LINK_GROUP>
+
+  \$<LINK_GROUP> expression requires at least one parameter.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 4 - 0
Tests/RunCMake/GenEx-LINK_GROUP/no-arguments.cmake

@@ -0,0 +1,4 @@
+enable_language(C)
+
+add_library(lib SHARED empty.c)
+target_link_libraries(lib PRIVATE "$<LINK_GROUP>")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/only-targets-result.txt

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

+ 13 - 0
Tests/RunCMake/GenEx-LINK_GROUP/only-targets-stderr.txt

@@ -0,0 +1,13 @@
+CMake Error at only-targets.cmake:[0-9]+ \(target_link_libraries\):
+  Target "lib2" has LINK_LIBRARIES_ONLY_TARGETS enabled, but it links to:
+
+    external
+
+  which is not a target.  Possible reasons include:
+
+    \* There is a typo in the target name.
+    \* A find_package call is missing for an IMPORTED target.
+    \* An ALIAS target is missing.
+
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)

+ 16 - 0
Tests/RunCMake/GenEx-LINK_GROUP/only-targets.cmake

@@ -0,0 +1,16 @@
+enable_language(C)
+
+set(CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED TRUE)
+set(CMAKE_C_LINK_GROUP_USING_feat "--start" "--end")
+
+set(CMAKE_LINK_LIBRARIES_ONLY_TARGETS 1)
+
+add_library(dep1 SHARED empty.c)
+
+add_library(lib1 SHARED empty.c)
+# accepted
+target_link_libraries(lib1 PRIVATE "$<LINK_GROUP:feat,dep1>")
+
+add_library(lib2 SHARED empty.c)
+# invalid
+target_link_libraries(lib2 PRIVATE "$<LINK_GROUP:feat,external>")

+ 4 - 0
Tests/RunCMake/GenEx-LINK_GROUP/override-library-features1.cmake

@@ -0,0 +1,4 @@
+
+include(incompatible-library-features1.cmake)
+
+set_property(TARGET lib1 PROPERTY LINK_LIBRARY_OVERRIDE "feat1,dep1")

+ 4 - 0
Tests/RunCMake/GenEx-LINK_GROUP/override-library-features2.cmake

@@ -0,0 +1,4 @@
+
+include(incompatible-library-features2.cmake)
+
+set_property(TARGET lib1 PROPERTY LINK_LIBRARY_OVERRIDE_dep1 "feat1")

+ 1 - 0
Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories-result.txt

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

Some files were not shown because too many files changed in this diff