mirror of
https://github.com/Kitware/CMake.git
synced 2026-01-06 05:40:54 -06:00
target_link_libraries: Optionally require only target names
Optionally verify that items in `LINK_LIBRARIES` and `INTERFACE_LINK_LIBRARIES` that can be target names are actually target names. Add a `LINK_LIBRARIES_ONLY_TARGETS` target property and corresponding `CMAKE_LINK_LIBRARIES_ONLY_TARGETS` variable to enable this new check. Fixes: #22858
This commit is contained in:
@@ -304,6 +304,7 @@ Properties on Targets
|
||||
/prop_tgt/LINK_INTERFACE_MULTIPLICITY
|
||||
/prop_tgt/LINK_INTERFACE_MULTIPLICITY_CONFIG
|
||||
/prop_tgt/LINK_LIBRARIES
|
||||
/prop_tgt/LINK_LIBRARIES_ONLY_TARGETS
|
||||
/prop_tgt/LINK_OPTIONS
|
||||
/prop_tgt/LINK_SEARCH_END_STATIC
|
||||
/prop_tgt/LINK_SEARCH_START_STATIC
|
||||
|
||||
@@ -220,6 +220,7 @@ Variables that Change Behavior
|
||||
/variable/CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT
|
||||
/variable/CMAKE_LIBRARY_PATH
|
||||
/variable/CMAKE_LINK_DIRECTORIES_BEFORE
|
||||
/variable/CMAKE_LINK_LIBRARIES_ONLY_TARGETS
|
||||
/variable/CMAKE_MFC_FLAG
|
||||
/variable/CMAKE_MAXIMUM_RECURSION_DEPTH
|
||||
/variable/CMAKE_MESSAGE_CONTEXT
|
||||
|
||||
@@ -13,6 +13,8 @@ on disk. Previously, if a target was not found with a matching name, the name
|
||||
was considered to refer to a file on disk. This can lead to confusing error
|
||||
messages if there is a typo in what should be a target name.
|
||||
|
||||
See also the :prop_tgt:`LINK_LIBRARIES_ONLY_TARGETS` target property.
|
||||
|
||||
The ``OLD`` behavior for this policy is to search for targets, then files on
|
||||
disk, even if the search term contains double-colons. The ``NEW`` behavior
|
||||
for this policy is to issue a ``FATAL_ERROR`` if a link dependency contains
|
||||
|
||||
55
Help/prop_tgt/LINK_LIBRARIES_ONLY_TARGETS.rst
Normal file
55
Help/prop_tgt/LINK_LIBRARIES_ONLY_TARGETS.rst
Normal file
@@ -0,0 +1,55 @@
|
||||
LINK_LIBRARIES_ONLY_TARGETS
|
||||
---------------------------
|
||||
|
||||
.. versionadded:: 3.23
|
||||
|
||||
Enforce that link items that can be target names are actually existing targets.
|
||||
|
||||
Set this property to a true value to enable additional checks on the contents
|
||||
of the :prop_tgt:`LINK_LIBRARIES` and :prop_tgt:`INTERFACE_LINK_LIBRARIES`
|
||||
target properties, typically populated by :command:`target_link_libraries`.
|
||||
CMake will verify that link items that might be target names actually name
|
||||
existing targets. An item is considered a possible target name if:
|
||||
|
||||
* it does not contain a ``/`` or ``\``, and
|
||||
* it does not start in ``-``, and
|
||||
* (for historical reasons) it does not start in ``$`` or `````.
|
||||
|
||||
This property is initialized by the value of the
|
||||
:variable:`CMAKE_LINK_LIBRARIES_ONLY_TARGETS` variable when a non-imported
|
||||
target is created. The property may be explicitly enabled on an imported
|
||||
target to check its link interface.
|
||||
|
||||
For example, the following code:
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
set(CMAKE_LINK_LIBRARIES_ONLY_TARGETS ON)
|
||||
add_executable(myLib STATIC myLib.c)
|
||||
add_executable(myExe myExe.c)
|
||||
target_link_libraries(myExe PRIVATE miLib) # typo for myLib
|
||||
|
||||
will produce a CMake-time error that ``miLib`` is not a target.
|
||||
|
||||
In order to link toolchain-provided libraries by name while still
|
||||
enforcing ``LINK_LIBRARIES_ONLY_TARGETS``, use an
|
||||
:ref:`imported <Imported Targets>`
|
||||
:ref:`Interface Library <Interface Libraries>` with the
|
||||
:prop_tgt:`IMPORTED_LIBNAME` target property:
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
add_library(toolchain::m INTERFACE IMPORTED)
|
||||
set_property(TARGET toolchain::m PROPERTY IMPORTED_LIBNAME "m")
|
||||
target_link_libraries(myExe PRIVATE toolchain::m)
|
||||
|
||||
See also policy :policy:`CMP0028`.
|
||||
|
||||
.. note::
|
||||
|
||||
If :prop_tgt:`INTERFACE_LINK_LIBRARIES` contains generator expressions,
|
||||
its actual list of link items may depend on the type and properties of
|
||||
the consuming target. In such cases CMake may not always detect names
|
||||
of missing targets that only appear for specific consumers.
|
||||
A future version of CMake with improved heuristics may start triggering
|
||||
errors on projects accepted by previous versions of CMake.
|
||||
7
Help/release/dev/link-only-targets.rst
Normal file
7
Help/release/dev/link-only-targets.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
link-only-targets
|
||||
-----------------
|
||||
|
||||
* The :variable:`CMAKE_LINK_LIBRARIES_ONLY_TARGETS` variable and
|
||||
corresponding :prop_tgt:`LINK_LIBRARIES_ONLY_TARGETS` target
|
||||
property were added to optionally require that all link items
|
||||
that can be target names are actually names of existing targets.
|
||||
10
Help/variable/CMAKE_LINK_LIBRARIES_ONLY_TARGETS.rst
Normal file
10
Help/variable/CMAKE_LINK_LIBRARIES_ONLY_TARGETS.rst
Normal file
@@ -0,0 +1,10 @@
|
||||
CMAKE_LINK_LIBRARIES_ONLY_TARGETS
|
||||
---------------------------------
|
||||
|
||||
.. versionadded:: 3.23
|
||||
|
||||
Set this variable to initialize the :prop_tgt:`LINK_LIBRARIES_ONLY_TARGETS`
|
||||
property of non-imported targets when they are created. Setting it to true
|
||||
enables an additional check that all items named by
|
||||
:command:`target_link_libraries` that can be target names are actually names
|
||||
of existing targets. See the target property documentation for details.
|
||||
@@ -6247,6 +6247,18 @@ cmComputeLinkInformation* cmGeneratorTarget::GetLinkInformation(
|
||||
|
||||
void cmGeneratorTarget::CheckLinkLibraries() const
|
||||
{
|
||||
bool linkLibrariesOnlyTargets =
|
||||
this->GetPropertyAsBool("LINK_LIBRARIES_ONLY_TARGETS");
|
||||
|
||||
// Evaluate the link interface of this target if needed for extra checks.
|
||||
if (linkLibrariesOnlyTargets) {
|
||||
std::vector<std::string> const& configs =
|
||||
this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
|
||||
for (std::string const& config : configs) {
|
||||
this->GetLinkInterfaceLibraries(config, this, LinkInterfaceFor::Link);
|
||||
}
|
||||
}
|
||||
|
||||
// Check link the implementation for each generated configuration.
|
||||
for (auto const& hmp : this->LinkImplMap) {
|
||||
HeadToLinkImplementationMap const& hm = hmp.second;
|
||||
@@ -6260,6 +6272,10 @@ void cmGeneratorTarget::CheckLinkLibraries() const
|
||||
if (!this->VerifyLinkItemColons(LinkItemRole::Implementation, item)) {
|
||||
return;
|
||||
}
|
||||
if (linkLibrariesOnlyTargets &&
|
||||
!this->VerifyLinkItemIsTarget(LinkItemRole::Implementation, item)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6276,6 +6292,10 @@ void cmGeneratorTarget::CheckLinkLibraries() const
|
||||
if (!this->VerifyLinkItemColons(LinkItemRole::Interface, item)) {
|
||||
return;
|
||||
}
|
||||
if (linkLibrariesOnlyTargets &&
|
||||
!this->VerifyLinkItemIsTarget(LinkItemRole::Interface, item)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6329,6 +6349,35 @@ bool cmGeneratorTarget::VerifyLinkItemColons(LinkItemRole role,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cmGeneratorTarget::VerifyLinkItemIsTarget(LinkItemRole role,
|
||||
cmLinkItem const& item) const
|
||||
{
|
||||
if (item.Target) {
|
||||
return true;
|
||||
}
|
||||
std::string const& str = item.AsStr();
|
||||
if (!str.empty() &&
|
||||
(str[0] == '-' || str[0] == '$' || str[0] == '`' ||
|
||||
str.find_first_of("/\\") != std::string::npos)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string e = cmStrCat("Target \"", this->GetName(),
|
||||
"\" has LINK_LIBRARIES_ONLY_TARGETS enabled, but ",
|
||||
role == LinkItemRole::Implementation
|
||||
? "it links to"
|
||||
: "its link interface contains",
|
||||
":\n ", item.AsStr(), "\nwhich is not a target. ",
|
||||
missingTargetPossibleReasons);
|
||||
cmListFileBacktrace backtrace = item.Backtrace;
|
||||
if (backtrace.Empty()) {
|
||||
backtrace = this->GetBacktrace();
|
||||
}
|
||||
this->LocalGenerator->GetCMakeInstance()->IssueMessage(
|
||||
MessageType::FATAL_ERROR, e, backtrace);
|
||||
return false;
|
||||
}
|
||||
|
||||
void cmGeneratorTarget::GetTargetVersion(int& major, int& minor) const
|
||||
{
|
||||
int patch;
|
||||
|
||||
@@ -982,6 +982,7 @@ private:
|
||||
Implementation,
|
||||
Interface,
|
||||
};
|
||||
bool VerifyLinkItemIsTarget(LinkItemRole role, cmLinkItem const& item) const;
|
||||
bool VerifyLinkItemColons(LinkItemRole role, cmLinkItem const& item) const;
|
||||
|
||||
// Cache import information from properties for each configuration.
|
||||
|
||||
@@ -471,6 +471,9 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
|
||||
initProp(property);
|
||||
}
|
||||
}
|
||||
if (!this->IsImported()) {
|
||||
initProp("LINK_LIBRARIES_ONLY_TARGETS");
|
||||
}
|
||||
}
|
||||
|
||||
// Save the backtrace of target construction.
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
if(NOT RunCMake_TEST MATCHES "^CMP0028")
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
endif()
|
||||
project(${RunCMake_TEST} CXX)
|
||||
include(${RunCMake_TEST}.cmake NO_POLICY_SCOPE) # policy used at end of dir
|
||||
|
||||
1
Tests/RunCMake/LinkItemValidation/OnlyTargets-result.txt
Normal file
1
Tests/RunCMake/LinkItemValidation/OnlyTargets-result.txt
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
40
Tests/RunCMake/LinkItemValidation/OnlyTargets-stderr.txt
Normal file
40
Tests/RunCMake/LinkItemValidation/OnlyTargets-stderr.txt
Normal file
@@ -0,0 +1,40 @@
|
||||
^CMake Error at OnlyTargets\.cmake:11 \(target_link_libraries\):
|
||||
Target "exe" has LINK_LIBRARIES_ONLY_TARGETS enabled, but it links to:
|
||||
|
||||
non_target_in_exe
|
||||
|
||||
which is not a target\. Possible reasons include:
|
||||
(
|
||||
\*[^
|
||||
]+)*
|
||||
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:[0-9]+ \(include\)
|
||||
+
|
||||
CMake Error at OnlyTargets\.cmake:21 \(target_link_libraries\):
|
||||
Target "iface" has LINK_LIBRARIES_ONLY_TARGETS enabled, but its link
|
||||
interface contains:
|
||||
|
||||
non_target_in_iface
|
||||
|
||||
which is not a target\. Possible reasons include:
|
||||
(
|
||||
\*[^
|
||||
]+)*
|
||||
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:[0-9]+ \(include\)
|
||||
+
|
||||
CMake Error at OnlyTargets\.cmake:30 \(target_link_libraries\):
|
||||
Target "iface_imported_checked" has LINK_LIBRARIES_ONLY_TARGETS enabled,
|
||||
but its link interface contains:
|
||||
|
||||
non_target_in_iface_imported_checked
|
||||
|
||||
which is not a target\. Possible reasons include:
|
||||
(
|
||||
\*[^
|
||||
]+)*
|
||||
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:[0-9]+ \(include\)
|
||||
56
Tests/RunCMake/LinkItemValidation/OnlyTargets.cmake
Normal file
56
Tests/RunCMake/LinkItemValidation/OnlyTargets.cmake
Normal file
@@ -0,0 +1,56 @@
|
||||
enable_language(C)
|
||||
|
||||
set(CMAKE_LINK_LIBRARIES_ONLY_TARGETS 1)
|
||||
|
||||
# Use imported interface library to name toolchain-provided libraries.
|
||||
add_library(toolchain::m INTERFACE IMPORTED)
|
||||
set_property(TARGET toolchain::m PROPERTY IMPORTED_LIBNAME "m")
|
||||
|
||||
# Linking directly warns.
|
||||
add_executable(exe main.c)
|
||||
target_link_libraries(exe PRIVATE
|
||||
-lflag_in_exe # accepted
|
||||
/abs/path/in_exe # accepted
|
||||
rel/path/in_exe # accepted
|
||||
toolchain::m # accepted
|
||||
non_target_in_exe # rejected
|
||||
)
|
||||
|
||||
# Link interfaces warn.
|
||||
add_library(iface INTERFACE)
|
||||
target_link_libraries(iface INTERFACE
|
||||
-lflag_in_iface # accepted
|
||||
/abs/path/in_iface # accepted
|
||||
rel/path/in_iface # accepted
|
||||
non_target_in_iface # rejected
|
||||
)
|
||||
|
||||
# Imported target link interfaces warn if explicitly enabled.
|
||||
add_library(iface_imported_checked INTERFACE IMPORTED)
|
||||
target_link_libraries(iface_imported_checked INTERFACE
|
||||
-lflag_iface_imported_checked # accepted
|
||||
/abs/path/in_iface_imported_checked # accepted
|
||||
rel/path/in_iface_imported_checked # accepted
|
||||
non_target_in_iface_imported_checked # rejected
|
||||
)
|
||||
set_property(TARGET iface_imported_checked PROPERTY LINK_LIBRARIES_ONLY_TARGETS 1)
|
||||
|
||||
# Linking directly does not warn if explicitly disabled.
|
||||
add_executable(exe_not_checked main.c)
|
||||
target_link_libraries(exe_not_checked PRIVATE
|
||||
non_target_in_exe_not_checked
|
||||
)
|
||||
set_property(TARGET exe_not_checked PROPERTY LINK_LIBRARIES_ONLY_TARGETS 0)
|
||||
|
||||
# Link interfaces do not warn if explicitly disabled.
|
||||
add_library(iface_not_checked INTERFACE)
|
||||
target_link_libraries(iface_not_checked INTERFACE
|
||||
non_target_in_iface_not_checked
|
||||
)
|
||||
set_property(TARGET iface_not_checked PROPERTY LINK_LIBRARIES_ONLY_TARGETS 0)
|
||||
|
||||
# Imported target link interfaces do not warn if not explicitly enabled.
|
||||
add_library(iface_imported_default INTERFACE IMPORTED)
|
||||
target_link_libraries(iface_imported_default INTERFACE
|
||||
non_target_in_iface_imported_default
|
||||
)
|
||||
@@ -6,3 +6,5 @@ run_cmake(CMP0028-WARN)
|
||||
run_cmake(CMP0028-NEW-iface)
|
||||
run_cmake(CMP0028-OLD-iface)
|
||||
run_cmake(CMP0028-WARN-iface)
|
||||
|
||||
run_cmake(OnlyTargets)
|
||||
|
||||
4
Tests/RunCMake/LinkItemValidation/main.c
Normal file
4
Tests/RunCMake/LinkItemValidation/main.c
Normal file
@@ -0,0 +1,4 @@
|
||||
int main(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user