summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarc Chevrier <marc.chevrier@gmail.com>2022-01-31 15:43:41 (GMT)
committerMarc Chevrier <marc.chevrier@gmail.com>2022-02-28 09:26:26 (GMT)
commit0a81ea1f12cbaf60ec60b8e4a27c5ea476a655de (patch)
tree8491b1ae05b5c252b1165244c6976d81c87397ee
parenta9928eb4a54431780d589f70460e5715258f1d27 (diff)
downloadCMake-0a81ea1f12cbaf60ec60b8e4a27c5ea476a655de.zip
CMake-0a81ea1f12cbaf60ec60b8e4a27c5ea476a655de.tar.gz
CMake-0a81ea1f12cbaf60ec60b8e4a27c5ea476a655de.tar.bz2
Genex-LINK_GROUP: Add possibility to group libraries at link step
Fixes: #23121
-rw-r--r--Help/manual/cmake-generator-expressions.7.rst151
-rw-r--r--Help/manual/cmake-variables.7.rst4
-rw-r--r--Help/release/dev/Genex-LINK_GROUP.rst8
-rw-r--r--Help/variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE.rst20
-rw-r--r--Help/variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE_SUPPORTED.rst13
-rw-r--r--Help/variable/CMAKE_LANG_LINK_LIBRARY_USING_FEATURE.rst3
-rw-r--r--Help/variable/CMAKE_LINK_GROUP_USING_FEATURE.rst25
-rw-r--r--Help/variable/CMAKE_LINK_GROUP_USING_FEATURE.txt54
-rw-r--r--Help/variable/CMAKE_LINK_GROUP_USING_FEATURE_SUPPORTED.rst14
-rw-r--r--Help/variable/CMAKE_LINK_LIBRARY_USING_FEATURE.rst3
-rw-r--r--Source/Checks/cm_cxx_filesystem.cxx2
-rw-r--r--Source/cmComputeLinkDepends.cxx433
-rw-r--r--Source/cmComputeLinkDepends.h16
-rw-r--r--Source/cmComputeLinkInformation.cxx284
-rw-r--r--Source/cmComputeLinkInformation.h37
-rw-r--r--Source/cmGeneratorExpressionNode.cxx85
-rw-r--r--Source/cmGeneratorTarget.cxx6
-rw-r--r--Source/cmMakefile.cxx18
-rw-r--r--Source/cmTarget.cxx17
-rw-r--r--Tests/RunCMake/CMakeLists.txt13
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/CMakeLists.txt3
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/RunCMakeTest.cmake35
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command-stderr.txt9
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command.cmake4
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target-stderr.txt9
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target.cmake3
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/add_link_options-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/add_link_options-stderr.txt9
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/add_link_options.cmake5
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1-stderr.txt5
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1.cmake6
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2-stderr.txt5
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2.cmake8
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3-stderr.txt6
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3.cmake9
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4-stderr.txt6
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4.cmake9
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5-stderr.txt6
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5.cmake9
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1-stderr.txt11
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1.cmake17
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2-stderr.txt11
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2.cmake18
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/compatible-features1.cmake18
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/compatible-features2.cmake13
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/compatible-features3.cmake13
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments-stderr.txt8
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments.cmake4
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/empty.c0
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported-stderr.txt5
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported.cmake9
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments-stderr.txt16
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments.cmake6
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1-stderr.txt6
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1.cmake15
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1-stderr.txt6
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1.cmake17
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2-stderr.txt6
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2.cmake17
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature-stderr.txt8
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature.cmake6
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/library-ignored-stderr.txt13
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/library-ignored.cmake15
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/link_directories-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/link_directories-stderr.txt9
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/link_directories.cmake5
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1-stderr.txt8
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1.cmake11
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2-stderr.txt8
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2.cmake14
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex-stderr.txt18
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex.cmake6
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/no-arguments-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/no-arguments-stderr.txt8
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/no-arguments.cmake4
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/only-targets-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/only-targets-stderr.txt13
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/only-targets.cmake16
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/override-library-features1.cmake4
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/override-library-features2.cmake4
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories-stderr.txt9
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories.cmake4
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/target_link_options-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/target_link_options-stderr.txt9
-rw-r--r--Tests/RunCMake/GenEx-LINK_GROUP/target_link_options.cmake4
-rw-r--r--Tests/RunCMake/GenEx-LINK_LIBRARY/RunCMakeTest.cmake1
-rw-r--r--Tests/RunCMake/GenEx-LINK_LIBRARY/invalid-feature-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-LINK_LIBRARY/invalid-feature-stderr.txt8
-rw-r--r--Tests/RunCMake/GenEx-LINK_LIBRARY/invalid-feature.cmake6
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/CMakeLists.txt3
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-group-and-single-check.cmake4
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-group-and-single-result.txt1
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-multiple-groups-check.cmake4
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-multiple-groups-result.txt1
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple1-check.cmake4
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple1-result.txt1
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple2-check.cmake4
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple2-result.txt1
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY-check.cmake4
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY-result.txt1
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY_OVERRIDE-check.cmake4
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY_OVERRIDE-result.txt1
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP.cmake52
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/RunCMakeTest.cmake63
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/base.c9
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/cross_refs.cmake22
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/func1.c7
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/func2.c7
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/func3.c6
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/imported-target-result.txt1
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/imported-target-stdout.txt16
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/imported-target.cmake22
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/lib.c15
-rw-r--r--Tests/RunCMake/target_link_libraries-LINK_GROUP/main.c7
134 files changed, 1881 insertions, 184 deletions
diff --git a/Help/manual/cmake-generator-expressions.7.rst b/Help/manual/cmake-generator-expressions.7.rst
index 0e9060d..403c06e 100644
--- a/Help/manual/cmake-generator-expressions.7.rst
+++ b/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)`,
diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index b38f56c..ffebfff 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/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
diff --git a/Help/release/dev/Genex-LINK_GROUP.rst b/Help/release/dev/Genex-LINK_GROUP.rst
new file mode 100644
index 0000000..aa9e318
--- /dev/null
+++ b/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.
diff --git a/Help/variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE.rst b/Help/variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE.rst
new file mode 100644
index 0000000..b68c94a
--- /dev/null
+++ b/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
diff --git a/Help/variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE_SUPPORTED.rst b/Help/variable/CMAKE_LANG_LINK_GROUP_USING_FEATURE_SUPPORTED.rst
new file mode 100644
index 0000000..533eee7
--- /dev/null
+++ b/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`.
diff --git a/Help/variable/CMAKE_LANG_LINK_LIBRARY_USING_FEATURE.rst b/Help/variable/CMAKE_LANG_LINK_LIBRARY_USING_FEATURE.rst
index 45b8aa8..a9fa9a7 100644
--- a/Help/variable/CMAKE_LANG_LINK_LIBRARY_USING_FEATURE.rst
+++ b/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
diff --git a/Help/variable/CMAKE_LINK_GROUP_USING_FEATURE.rst b/Help/variable/CMAKE_LINK_GROUP_USING_FEATURE.rst
new file mode 100644
index 0000000..8aade01
--- /dev/null
+++ b/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
diff --git a/Help/variable/CMAKE_LINK_GROUP_USING_FEATURE.txt b/Help/variable/CMAKE_LINK_GROUP_USING_FEATURE.txt
new file mode 100644
index 0000000..ecd9cb5
--- /dev/null
+++ b/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``
diff --git a/Help/variable/CMAKE_LINK_GROUP_USING_FEATURE_SUPPORTED.rst b/Help/variable/CMAKE_LINK_GROUP_USING_FEATURE_SUPPORTED.rst
new file mode 100644
index 0000000..249ccbc
--- /dev/null
+++ b/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``.
diff --git a/Help/variable/CMAKE_LINK_LIBRARY_USING_FEATURE.rst b/Help/variable/CMAKE_LINK_LIBRARY_USING_FEATURE.rst
index eae60c5..7aace32 100644
--- a/Help/variable/CMAKE_LINK_LIBRARY_USING_FEATURE.rst
+++ b/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
diff --git a/Source/Checks/cm_cxx_filesystem.cxx b/Source/Checks/cm_cxx_filesystem.cxx
index ae8acc5..b7d5be5 100644
--- a/Source/Checks/cm_cxx_filesystem.cxx
+++ b/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");
diff --git a/Source/cmComputeLinkDepends.cxx b/Source/cmComputeLinkDepends.cxx
index 7c8de9e..a4dc01b 100644
--- a/Source/cmComputeLinkDepends.cxx
+++ b/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());
diff --git a/Source/cmComputeLinkDepends.h b/Source/cmComputeLinkDepends.h
index 3c83f5a..8cc916a 100644
--- a/Source/cmComputeLinkDepends.h
+++ b/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.
diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx
index fe4491d..67214f1 100644
--- a/Source/cmComputeLinkInformation.cxx
+++ b/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()
diff --git a/Source/cmComputeLinkInformation.h b/Source/cmComputeLinkInformation.h
index 4b7fb1a..a4ada1f 100644
--- a/Source/cmComputeLinkInformation.h
+++ b/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);
};
diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx
index b63b90b..db043ec 100644
--- a/Source/cmGeneratorExpressionNode.cxx
+++ b/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 }
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index a246c12..e893b44 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/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;
}
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 6a5d518..be189a6 100644
--- a/Source/cmMakefile.cxx
+++ b/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."));
}
}
}
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index 92a0ac4..4ca1b9b 100644
--- a/Source/cmTarget.cxx
+++ b/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,
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index eda857b..82bc8c3 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/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})
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/CMakeLists.txt b/Tests/RunCMake/GenEx-LINK_GROUP/CMakeLists.txt
new file mode 100644
index 0000000..612169c
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/RunCMakeTest.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/RunCMakeTest.cmake
new file mode 100644
index 0000000..98eef35
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command-stderr.txt
new file mode 100644
index 0000000..a80a11b
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_command.cmake
new file mode 100644
index 0000000..1efe276
--- /dev/null
+++ b/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>"
+)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target-stderr.txt
new file mode 100644
index 0000000..deb246a
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/add_custom_target.cmake
new file mode 100644
index 0000000..cbb5ff0
--- /dev/null
+++ b/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>"
+)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/add_link_options-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/add_link_options-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/add_link_options-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/add_link_options-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/add_link_options-stderr.txt
new file mode 100644
index 0000000..17c348c
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/add_link_options.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/add_link_options.cmake
new file mode 100644
index 0000000..2e31b34
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1-stderr.txt
new file mode 100644
index 0000000..8c95452
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature1.cmake
new file mode 100644
index 0000000..0161221
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2-stderr.txt
new file mode 100644
index 0000000..3f057c4
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature2.cmake
new file mode 100644
index 0000000..7e56194
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3-stderr.txt
new file mode 100644
index 0000000..43e2700
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature3.cmake
new file mode 100644
index 0000000..58b422a
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4-stderr.txt
new file mode 100644
index 0000000..0a2ba60
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature4.cmake
new file mode 100644
index 0000000..dcb8236
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5-stderr.txt
new file mode 100644
index 0000000..4b1f01d
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/bad-feature5.cmake
new file mode 100644
index 0000000..cb10996
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1-stderr.txt
new file mode 100644
index 0000000..94008e1
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies1.cmake
new file mode 100644
index 0000000..a1d7575
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2-stderr.txt
new file mode 100644
index 0000000..365e98f
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/circular-dependencies2.cmake
new file mode 100644
index 0000000..99fda4d
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/compatible-features1.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/compatible-features1.cmake
new file mode 100644
index 0000000..9d10f95
--- /dev/null
+++ b/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>)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/compatible-features2.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/compatible-features2.cmake
new file mode 100644
index 0000000..1fe2880
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/compatible-features3.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/compatible-features3.cmake
new file mode 100644
index 0000000..ac486ce
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments-stderr.txt
new file mode 100644
index 0000000..27101cb
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/empty-arguments.cmake
new file mode 100644
index 0000000..6093935
--- /dev/null
+++ b/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:,>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/empty.c b/Tests/RunCMake/GenEx-LINK_GROUP/empty.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/empty.c
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported-stderr.txt
new file mode 100644
index 0000000..3507f4d
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/feature-not-supported.cmake
new file mode 100644
index 0000000..c4739bb
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments-stderr.txt
new file mode 100644
index 0000000..bae6505
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/forbidden-arguments.cmake
new file mode 100644
index 0000000..dcbf8dd
--- /dev/null
+++ b/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>)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1-stderr.txt
new file mode 100644
index 0000000..932245d
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-features1.cmake
new file mode 100644
index 0000000..efac0f8
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1-stderr.txt
new file mode 100644
index 0000000..e3a4250
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features1.cmake
new file mode 100644
index 0000000..203f071
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2-stderr.txt
new file mode 100644
index 0000000..889cba0
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/incompatible-library-features2.cmake
new file mode 100644
index 0000000..6490819
--- /dev/null
+++ b/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>>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature-stderr.txt
new file mode 100644
index 0000000..793b393
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/invalid-feature.cmake
new file mode 100644
index 0000000..34a319c
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/library-ignored-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/library-ignored-stderr.txt
new file mode 100644
index 0000000..b29c4ec
--- /dev/null
+++ b/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.
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/library-ignored.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/library-ignored.cmake
new file mode 100644
index 0000000..b3f19a7
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/link_directories-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/link_directories-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/link_directories-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/link_directories-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/link_directories-stderr.txt
new file mode 100644
index 0000000..51194a4
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/link_directories.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/link_directories.cmake
new file mode 100644
index 0000000..e356e91
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1-stderr.txt
new file mode 100644
index 0000000..78631ab
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features1.cmake
new file mode 100644
index 0000000..50e0c64
--- /dev/null
+++ b/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>>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2-stderr.txt
new file mode 100644
index 0000000..1a27c37
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-features2.cmake
new file mode 100644
index 0000000..c6ea14c
--- /dev/null
+++ b/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>>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex-stderr.txt
new file mode 100644
index 0000000..87eeb4d
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/nested-incompatible-genex.cmake
new file mode 100644
index 0000000..e3f2ade
--- /dev/null
+++ b/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>>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/no-arguments-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/no-arguments-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/no-arguments-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/no-arguments-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/no-arguments-stderr.txt
new file mode 100644
index 0000000..63c2648
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/no-arguments.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/no-arguments.cmake
new file mode 100644
index 0000000..ffc1381
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/only-targets-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/only-targets-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/only-targets-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/only-targets-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/only-targets-stderr.txt
new file mode 100644
index 0000000..6b770f0
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/only-targets.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/only-targets.cmake
new file mode 100644
index 0000000..8501f1d
--- /dev/null
+++ b/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>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/override-library-features1.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/override-library-features1.cmake
new file mode 100644
index 0000000..127e73f
--- /dev/null
+++ b/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")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/override-library-features2.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/override-library-features2.cmake
new file mode 100644
index 0000000..9ad0bfe
--- /dev/null
+++ b/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")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories-stderr.txt
new file mode 100644
index 0000000..042dd0b
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at target_link_directories.cmake:[0-9]+ \(target_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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories.cmake
new file mode 100644
index 0000000..47a5f9c
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/target_link_directories.cmake
@@ -0,0 +1,4 @@
+enable_language(C)
+
+add_library(empty SHARED empty.c)
+target_link_directories(empty PRIVATE "$<LINK_GROUP:feat>")
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/target_link_options-result.txt b/Tests/RunCMake/GenEx-LINK_GROUP/target_link_options-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/target_link_options-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/target_link_options-stderr.txt b/Tests/RunCMake/GenEx-LINK_GROUP/target_link_options-stderr.txt
new file mode 100644
index 0000000..7030b9c
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/target_link_options-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at target_link_options.cmake:[0-9]+ \(target_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\)
diff --git a/Tests/RunCMake/GenEx-LINK_GROUP/target_link_options.cmake b/Tests/RunCMake/GenEx-LINK_GROUP/target_link_options.cmake
new file mode 100644
index 0000000..d7dd876
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_GROUP/target_link_options.cmake
@@ -0,0 +1,4 @@
+enable_language(C)
+
+add_library(empty SHARED empty.c)
+target_link_options(empty PRIVATE $<LINK_GROUP:FEAT>)
diff --git a/Tests/RunCMake/GenEx-LINK_LIBRARY/RunCMakeTest.cmake b/Tests/RunCMake/GenEx-LINK_LIBRARY/RunCMakeTest.cmake
index d5438b8..9c266fe 100644
--- a/Tests/RunCMake/GenEx-LINK_LIBRARY/RunCMakeTest.cmake
+++ b/Tests/RunCMake/GenEx-LINK_LIBRARY/RunCMakeTest.cmake
@@ -9,6 +9,7 @@ run_cmake(target_link_directories)
run_cmake(no-arguments)
run_cmake(empty-arguments)
run_cmake(forbidden-arguments)
+run_cmake(invalid-feature)
run_cmake(bad-feature1)
run_cmake(bad-feature2)
run_cmake(bad-feature3)
diff --git a/Tests/RunCMake/GenEx-LINK_LIBRARY/invalid-feature-result.txt b/Tests/RunCMake/GenEx-LINK_LIBRARY/invalid-feature-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_LIBRARY/invalid-feature-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LINK_LIBRARY/invalid-feature-stderr.txt b/Tests/RunCMake/GenEx-LINK_LIBRARY/invalid-feature-stderr.txt
new file mode 100644
index 0000000..fb5cdab
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_LIBRARY/invalid-feature-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at invalid-feature.cmake:[0-9]+ \(target_link_libraries\):
+ Error evaluating generator expression:
+
+ \$<LINK_LIBRARY:feat:invalid,dep>
+
+ The feature name 'feat:invalid' contains invalid characters.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LINK_LIBRARY/invalid-feature.cmake b/Tests/RunCMake/GenEx-LINK_LIBRARY/invalid-feature.cmake
new file mode 100644
index 0000000..c49e4cd
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LINK_LIBRARY/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_LIBRARY:feat:invalid,dep>")
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/CMakeLists.txt b/Tests/RunCMake/target_link_libraries-LINK_GROUP/CMakeLists.txt
new file mode 100644
index 0000000..915fc41
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.1...3.22)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake NO_POLICY_SCOPE)
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-group-and-single-check.cmake b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-group-and-single-check.cmake
new file mode 100644
index 0000000..3b8f3ba
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-group-and-single-check.cmake
@@ -0,0 +1,4 @@
+
+if (NOT actual_stdout MATCHES "(/|-)-START_GROUP\"? +.*${LINK_SHARED_LIBRARY_PREFIX}base1${LINK_SHARED_LIBRARY_SUFFIX} +.*${LINK_SHARED_LIBRARY_PREFIX}base3${LINK_SHARED_LIBRARY_SUFFIX} +\"?(/|-)-END_GROUP\"? +.*${LINK_SHARED_LIBRARY_PREFIX}base5${LINK_SHARED_LIBRARY_SUFFIX} +\"?(/|-)-START_GROUP\"? +.*${LINK_SHARED_LIBRARY_PREFIX}base1${LINK_SHARED_LIBRARY_SUFFIX} +.*${LINK_SHARED_LIBRARY_PREFIX}base3${LINK_SHARED_LIBRARY_SUFFIX} +\"?(/|-)-END_GROUP")
+ set (RunCMake_TEST_FAILED "Not found expected '--START_GROUP <base1> <base3> --END_GROUP <base5> --START_GROUP <base1> <base3> --END_GROUP'.")
+endif()
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-group-and-single-result.txt b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-group-and-single-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-group-and-single-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-multiple-groups-check.cmake b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-multiple-groups-check.cmake
new file mode 100644
index 0000000..97732a5
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-multiple-groups-check.cmake
@@ -0,0 +1,4 @@
+
+if (NOT actual_stdout MATCHES "(/|-)-START_GROUP\"? +.*${LINK_SHARED_LIBRARY_PREFIX}base2${LINK_SHARED_LIBRARY_SUFFIX} +.*${LINK_SHARED_LIBRARY_PREFIX}base4${LINK_SHARED_LIBRARY_SUFFIX} +\"?(/|-)-END_GROUP\"? +\"?(/|-)-START_GROUP\"? +.*${LINK_SHARED_LIBRARY_PREFIX}base1${LINK_SHARED_LIBRARY_SUFFIX} +.*${LINK_SHARED_LIBRARY_PREFIX}base2${LINK_SHARED_LIBRARY_SUFFIX} +\"?(/|-)-END_GROUP")
+ set (RunCMake_TEST_FAILED "Not found expected '--START_GROUP <base2> <base4> --END_GROUP --START_GROUP <base1> <base2> --END_GROUP'.")
+endif()
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-multiple-groups-result.txt b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-multiple-groups-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-multiple-groups-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple1-check.cmake b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple1-check.cmake
new file mode 100644
index 0000000..3e53d26
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple1-check.cmake
@@ -0,0 +1,4 @@
+
+if (NOT actual_stdout MATCHES "(/|-)-START_GROUP\"? +.*${LINK_SHARED_LIBRARY_PREFIX}base1${LINK_SHARED_LIBRARY_SUFFIX} +.*${LINK_SHARED_LIBRARY_PREFIX}base2${LINK_SHARED_LIBRARY_SUFFIX} +\"?(/|-)-END_GROUP")
+ set (RunCMake_TEST_FAILED "Not found expected '--START_GROUP <base1> <base2> --END_GROUP'.")
+endif()
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple1-result.txt b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple1-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple1-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple2-check.cmake b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple2-check.cmake
new file mode 100644
index 0000000..475a0e2
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple2-check.cmake
@@ -0,0 +1,4 @@
+
+if (NOT actual_stdout MATCHES "(/|-)-START_GROUP\"? +.*${LINK_SHARED_LIBRARY_PREFIX}base2${LINK_SHARED_LIBRARY_SUFFIX} +.*${LINK_SHARED_LIBRARY_PREFIX}base3${LINK_SHARED_LIBRARY_SUFFIX} +\"?(/|-)-END_GROUP\"? +.*${LINK_SHARED_LIBRARY_PREFIX}base1${LINK_SHARED_LIBRARY_SUFFIX}")
+ set (RunCMake_TEST_FAILED "Not found expected '--START_GROUP <base2> <base3> --END_GROUP <base1>'.")
+endif()
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple2-result.txt b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple2-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-simple2-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY-check.cmake b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY-check.cmake
new file mode 100644
index 0000000..2b2460e
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY-check.cmake
@@ -0,0 +1,4 @@
+
+if (NOT actual_stdout MATCHES "(/|-)-START_GROUP\"? +\"?(/|-)-LIBFLAG.*${LINK_SHARED_LIBRARY_PREFIX}base1${LINK_SHARED_LIBRARY_SUFFIX}\"? +.*${LINK_SHARED_LIBRARY_PREFIX}base2${LINK_SHARED_LIBRARY_SUFFIX} +\"?(/|-)-END_GROUP")
+ set (RunCMake_TEST_FAILED "Not found expected '--START_GROUP --LIBFLAG<base1> <base2> --END_GROUP'.")
+endif()
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY-result.txt b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY_OVERRIDE-check.cmake b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY_OVERRIDE-check.cmake
new file mode 100644
index 0000000..5ef830c
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY_OVERRIDE-check.cmake
@@ -0,0 +1,4 @@
+
+if (NOT actual_stdout MATCHES "(/|-)-START_GROUP\"? +\"?(/|-)-LIBFLAG.*${LINK_SHARED_LIBRARY_PREFIX}base1${LINK_SHARED_LIBRARY_SUFFIX}\"? +\"?(/|-)-LIBFLAG.*${LINK_SHARED_LIBRARY_PREFIX}base3${LINK_SHARED_LIBRARY_SUFFIX}\"? +.*${LINK_SHARED_LIBRARY_PREFIX}base2${LINK_SHARED_LIBRARY_SUFFIX} +\"?(/|-)-END_GROUP")
+ set (RunCMake_TEST_FAILED "Not found expected '--START_GROUP --LIBFLAG<base1> --LIBFLAG<base3> <base2> --END_GROUP'.")
+endif()
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY_OVERRIDE-result.txt b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY_OVERRIDE-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP-with-LINK_LIBRARY_OVERRIDE-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP.cmake b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP.cmake
new file mode 100644
index 0000000..d08db16
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/LINK_GROUP.cmake
@@ -0,0 +1,52 @@
+enable_language(C)
+
+# ensure command line is always displayed and do not use any response file
+set(CMAKE_VERBOSE_MAKEFILE TRUE)
+set(CMAKE_C_USE_RESPONSE_FILE_FOR_LIBRARIES FALSE)
+
+if (CMAKE_GENERATOR MATCHES "Borland|NMake")
+ string(REPLACE "${CMAKE_START_TEMP_FILE}" "" CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_C_CREATE_SHARED_LIBRARY}")
+ string(REPLACE "${CMAKE_END_TEMP_FILE}" "" CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_C_CREATE_SHARED_LIBRARY}")
+endif()
+
+
+add_library(base1 SHARED base.c)
+add_library(base2 SHARED base.c)
+
+
+set(CMAKE_C_LINK_GROUP_USING_feat1 "--START_GROUP" "--END_GROUP")
+set(CMAKE_C_LINK_GROUP_USING_feat1_SUPPORTED TRUE)
+
+set(CMAKE_C_LINK_LIBRARY_USING_feat1 "--LIBFLAG<LIBRARY>")
+set(CMAKE_C_LINK_LIBRARY_USING_feat1_SUPPORTED TRUE)
+
+
+add_library(LinkGroup_simple1 SHARED lib.c)
+target_link_libraries(LinkGroup_simple1 PRIVATE "$<LINK_GROUP:feat1,base1,base2>")
+
+
+add_library(base3 SHARED base.c)
+target_link_libraries(base3 PUBLIC base1)
+add_library(LinkGroup_simple2 SHARED lib.c)
+target_link_libraries(LinkGroup_simple2 PRIVATE "$<LINK_GROUP:feat1,base2,base3>")
+
+
+add_library(base4 SHARED base.c)
+target_link_libraries(base4 INTERFACE "$<LINK_GROUP:feat1,base1,base2>")
+add_library(LinkGroup_multiple-groups SHARED lib.c)
+target_link_libraries(LinkGroup_multiple-groups PRIVATE "$<LINK_GROUP:feat1,base2,base4>")
+
+
+add_library(base5 SHARED base.c)
+target_link_libraries(base5 PUBLIC base1)
+add_library(LinkGroup_group-and-single SHARED lib.c)
+target_link_libraries(LinkGroup_group-and-single PRIVATE "$<LINK_GROUP:feat1,base1,base3>" base5)
+
+
+add_library(LinkGroup_with-LINK_LIBRARY SHARED lib.c)
+target_link_libraries(LinkGroup_with-LINK_LIBRARY PRIVATE "$<LINK_GROUP:feat1,$<LINK_LIBRARY:feat1,base1>,base2>")
+
+
+add_library(LinkGroup_with-LINK_LIBRARY_OVERRIDE SHARED lib.c)
+target_link_libraries(LinkGroup_with-LINK_LIBRARY_OVERRIDE PRIVATE "$<LINK_GROUP:feat1,$<LINK_LIBRARY:feat1,base1,base3>,base2>")
+set_property(TARGET LinkGroup_with-LINK_LIBRARY_OVERRIDE PROPERTY LINK_LIBRARY_OVERRIDE_base1 feat1)
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/RunCMakeTest.cmake b/Tests/RunCMake/target_link_libraries-LINK_GROUP/RunCMakeTest.cmake
new file mode 100644
index 0000000..eedc5b9
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/RunCMakeTest.cmake
@@ -0,0 +1,63 @@
+
+include(RunCMake)
+
+cmake_policy(SET CMP0054 NEW)
+
+macro(run_cmake_target test subtest target)
+ set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build)
+ set(RunCMake_TEST_NO_CLEAN 1)
+ run_cmake_command(${test}-${subtest} ${CMAKE_COMMAND} --build . --target ${target} --config Release --verbose ${ARGN})
+
+ unset(RunCMake_TEST_BINARY_DIR)
+ unset(RunCMake_TEST_NO_CLEAN)
+endmacro()
+
+
+# Some environments are excluded because they are not able to honor verbose mode
+if ((RunCMake_GENERATOR MATCHES "Makefiles|Ninja|Xcode"
+ OR (RunCMake_GENERATOR MATCHES "Visual Studio" AND MSVC_VERSION GREATER_EQUAL "1600"))
+ AND NOT CMAKE_C_COMPILER_ID STREQUAL "Intel")
+
+ set(RunCMake_TEST_OUTPUT_MERGE TRUE)
+ if (NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
+ set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Release)
+ endif()
+
+ if (CMAKE_SYSTEM_NAME STREQUAL "Windows"
+ OR CMAKE_SYSTEM_NAME STREQUAL "CYGWIN"
+ OR CMAKE_SYSTEM_NAME STREQUAL "MSYS")
+ set(LINK_SHARED_LIBRARY_PREFIX ${CMAKE_IMPORT_LIBRARY_PREFIX})
+ set(LINK_SHARED_LIBRARY_SUFFIX ${CMAKE_IMPORT_LIBRARY_SUFFIX})
+ else()
+ set(LINK_SHARED_LIBRARY_PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX})
+ set(LINK_SHARED_LIBRARY_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX})
+ endif()
+ if (MINGW OR MSYS OR CYGWIN)
+ set(LINK_EXTERN_LIBRARY_SUFFIX "")
+ else()
+ set(LINK_EXTERN_LIBRARY_SUFFIX "${CMAKE_IMPORT_LIBRARY_SUFFIX}")
+ endif()
+
+ run_cmake(LINK_GROUP)
+
+ run_cmake_target(LINK_GROUP simple1 LinkGroup_simple1)
+ run_cmake_target(LINK_GROUP simple2 LinkGroup_simple2)
+ run_cmake_target(LINK_GROUP multiple-groups LinkGroup_multiple-groups)
+ run_cmake_target(LINK_GROUP group-and-single LinkGroup_group-and-single)
+ run_cmake_target(LINK_GROUP with-LINK_LIBRARY LinkGroup_with-LINK_LIBRARY)
+ run_cmake_target(LINK_GROUP with-LINK_LIBRARY_OVERRIDE LinkGroup_with-LINK_LIBRARY_OVERRIDE)
+
+ run_cmake(imported-target)
+
+ # tests using features as described in the documentation
+ if((CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ OR (CMAKE_C_COMPILER_ID STREQUAL "SunPro" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER "5.9"
+ AND CMAKE_SYSTEM_NAME STREQUAL "SunOS"))
+ run_cmake(cross_refs)
+ run_cmake_target(cross_refs link main)
+ endif()
+
+ unset(RunCMake_TEST_OPTIONS)
+ unset(RunCMake_TEST_OUTPUT_MERGE)
+
+endif()
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/base.c b/Tests/RunCMake/target_link_libraries-LINK_GROUP/base.c
new file mode 100644
index 0000000..a5075d4
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/base.c
@@ -0,0 +1,9 @@
+
+#if !defined(STATIC_BASE)
+# if defined(_WIN32)
+__declspec(dllexport)
+# endif
+#endif
+ void base()
+{
+}
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/cross_refs.cmake b/Tests/RunCMake/target_link_libraries-LINK_GROUP/cross_refs.cmake
new file mode 100644
index 0000000..4a2a08c
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/cross_refs.cmake
@@ -0,0 +1,22 @@
+
+enable_language(C)
+
+ 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(func1 STATIC func1.c func3.c)
+add_library(func2 STATIC func2.c)
+
+add_executable(main main.c)
+target_link_libraries(main PRIVATE "$<LINK_GROUP:cross_refs,func1,func2>")
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/func1.c b/Tests/RunCMake/target_link_libraries-LINK_GROUP/func1.c
new file mode 100644
index 0000000..3399e00
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/func1.c
@@ -0,0 +1,7 @@
+
+extern void func2();
+
+void func1()
+{
+ func2();
+}
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/func2.c b/Tests/RunCMake/target_link_libraries-LINK_GROUP/func2.c
new file mode 100644
index 0000000..0f9aa64
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/func2.c
@@ -0,0 +1,7 @@
+
+extern void func3();
+
+void func2()
+{
+ func3();
+}
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/func3.c b/Tests/RunCMake/target_link_libraries-LINK_GROUP/func3.c
new file mode 100644
index 0000000..0b7df64
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/func3.c
@@ -0,0 +1,6 @@
+
+extern void func3();
+
+void func3()
+{
+}
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/imported-target-result.txt b/Tests/RunCMake/target_link_libraries-LINK_GROUP/imported-target-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/imported-target-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/imported-target-stdout.txt b/Tests/RunCMake/target_link_libraries-LINK_GROUP/imported-target-stdout.txt
new file mode 100644
index 0000000..16b93d1
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/imported-target-stdout.txt
@@ -0,0 +1,16 @@
+CMake Warning \(dev\) at imported-target.cmake:[0-9]+ \(add_library\):
+ The 'IMPORTED' target 'NS::lib2' uses the generator-expression
+ '\$<LINK_GROUP>' with the feature 'feat', which is undefined or unsupported.
+
+ Did you miss to define it by setting variables
+ "CMAKE_C_LINK_GROUP_USING_feat" and
+ "CMAKE_C_LINK_GROUP_USING_feat_SUPPORTED"\?
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers. Use -Wno-dev to suppress it.
+
+CMake Error at imported-target.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\)
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/imported-target.cmake b/Tests/RunCMake/target_link_libraries-LINK_GROUP/imported-target.cmake
new file mode 100644
index 0000000..bd83f97
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/imported-target.cmake
@@ -0,0 +1,22 @@
+
+enable_language(C)
+
+# Create imported target NS::lib
+add_library(NS::lib STATIC IMPORTED)
+set_target_properties(NS::lib PROPERTIES
+ IMPORTED_LOCATION "/path/to/lib"
+ IMPORTED_IMPLIB "/path/to/import.lib"
+)
+
+# Create imported target NS::lib2
+add_library(NS::lib2 SHARED IMPORTED)
+
+set_target_properties(NS::lib2 PROPERTIES
+ IMPORTED_LOCATION "/path/to/lib"
+ IMPORTED_IMPLIB "/path/to/import.lib"
+ INTERFACE_LINK_LIBRARIES "$<LINK_GROUP:feat,NS::lib>"
+)
+
+
+add_library(lib SHARED lib.c)
+target_link_libraries(lib PRIVATE NS::lib2)
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/lib.c b/Tests/RunCMake/target_link_libraries-LINK_GROUP/lib.c
new file mode 100644
index 0000000..35ab367
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/lib.c
@@ -0,0 +1,15 @@
+
+#if !defined(STATIC_BASE)
+# if defined(_WIN32)
+__declspec(dllimport)
+# endif
+#endif
+ void base();
+
+#if defined(_WIN32)
+__declspec(dllexport)
+#endif
+ void lib()
+{
+ base();
+}
diff --git a/Tests/RunCMake/target_link_libraries-LINK_GROUP/main.c b/Tests/RunCMake/target_link_libraries-LINK_GROUP/main.c
new file mode 100644
index 0000000..403583d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINK_GROUP/main.c
@@ -0,0 +1,7 @@
+
+extern void func1();
+
+int main()
+{
+ func1();
+}