1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-22 07:25:02 +08:00

Linking: Optionally reorder direct dependencies from LINK_LIBRARIES

Traditionally CMake generates link lines by starting with the direct
link dependencies specified by `LINK_LIBRARIES` in their original order
and then appending indirect dependencies that the direct dependencies
do not express.  This gives projects control over ordering among
independent entries, which can be important when intermixing flags
and libraries, or when multiple libraries provide the same symbol.
However, it may also result in inefficient link lines.

Add support for an alternative strategy that can reorder direct link
dependencies to produce more efficient link lines.  This is useful
for projects that cannot easily specify their targets' direct
dependencies in an order that satisfies indirect dependencies.

Add a `CMAKE_LINK_LIBRARIES_STRATEGY` variable and corresponding
`LINK_LIBRARIES_STRATEGY` target property to select a strategy.

Fixes: #26271
This commit is contained in:
Brad King
2024-09-19 13:10:04 -04:00
parent 9285a9dc9a
commit 7abd3137b7
38 changed files with 323 additions and 5 deletions

View File

@@ -140,6 +140,11 @@ Items containing ``::``, such as ``Foo::Bar``, are assumed to be
target names and will cause an error if no such target exists.
See policy :policy:`CMP0028`.
See the :variable:`CMAKE_LINK_LIBRARIES_STRATEGY` variable and
corresponding :prop_tgt:`LINK_LIBRARIES_STRATEGY` target property
for details on how CMake orders direct link dependencies on linker
command lines.
See the :manual:`cmake-buildsystem(7)` manual for more on defining
buildsystem properties.

View File

@@ -336,6 +336,7 @@ Properties on Targets
/prop_tgt/LINK_INTERFACE_MULTIPLICITY_CONFIG
/prop_tgt/LINK_LIBRARIES
/prop_tgt/LINK_LIBRARIES_ONLY_TARGETS
/prop_tgt/LINK_LIBRARIES_STRATEGY
/prop_tgt/LINK_LIBRARY_OVERRIDE
/prop_tgt/LINK_LIBRARY_OVERRIDE_LIBRARY
/prop_tgt/LINK_OPTIONS

View File

@@ -491,6 +491,7 @@ Variables that Control the Build
/variable/CMAKE_LINK_GROUP_USING_FEATURE
/variable/CMAKE_LINK_GROUP_USING_FEATURE_SUPPORTED
/variable/CMAKE_LINK_INTERFACE_LIBRARIES
/variable/CMAKE_LINK_LIBRARIES_STRATEGY
/variable/CMAKE_LINK_LIBRARY_FEATURE_ATTRIBUTES
/variable/CMAKE_LINK_LIBRARY_FILE_FLAG
/variable/CMAKE_LINK_LIBRARY_FLAG

View File

@@ -28,3 +28,8 @@ In advanced use cases, the list of direct link dependencies specified
by this property may be updated by usage requirements from dependencies.
See the :prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT` and
:prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE` target properties.
See the :variable:`CMAKE_LINK_LIBRARIES_STRATEGY` variable and
corresponding :prop_tgt:`LINK_LIBRARIES_STRATEGY` target property
for details on how CMake orders direct link dependencies on linker
command lines.

View File

@@ -0,0 +1,11 @@
LINK_LIBRARIES_STRATEGY
-----------------------
.. versionadded:: 3.31
Specify a strategy for ordering a target's direct link dependencies
on linker command lines.
See the :variable:`CMAKE_LINK_LIBRARIES_STRATEGY` variable for details
and supported values. This property is initialized by the value of that
variable when a target is created.

View File

@@ -0,0 +1,7 @@
link-strategy
-------------
* The :variable:`CMAKE_LINK_LIBRARIES_STRATEGY` variable and
corresponding :prop_tgt:`LINK_LIBRARIES_STRATEGY` target
property were added to optionally specify the strategy
CMake uses to generate link lines.

View File

@@ -0,0 +1,68 @@
CMAKE_LINK_LIBRARIES_STRATEGY
-----------------------------
.. versionadded:: 3.31
Specify a strategy for ordering targets' direct link dependencies
on linker command lines.
The value of this variable initializes the :prop_tgt:`LINK_LIBRARIES_STRATEGY`
target property of targets as they are created. Set that property directly
to specify a strategy for a single target.
CMake generates a target's link line using its :ref:`Target Link Properties`.
In particular, the :prop_tgt:`LINK_LIBRARIES` target property records the
target's direct link dependencies, typically populated by calls to
:command:`target_link_libraries`. Indirect link dependencies are
propagated from those entries of :prop_tgt:`LINK_LIBRARIES` that name
library targets by following the transitive closure of their
:prop_tgt:`INTERFACE_LINK_LIBRARIES` properties. CMake supports multiple
strategies for passing direct and indirect link dependencies to the linker.
Consider this example for the strategies below:
.. code-block:: cmake
add_library(A STATIC ...)
add_library(B STATIC ...)
add_library(C STATIC ...)
add_executable(main ...)
target_link_libraries(B PRIVATE A)
target_link_libraries(C PRIVATE A)
target_link_libraries(main PRIVATE A B C)
The supported strategies are:
``PRESERVE_ORDER``
Entries of :prop_tgt:`LINK_LIBRARIES` always appear first and in their
original order. Indirect link dependencies not satisfied by the
original entries may be reordered and de-duplicated with respect to
one another, but are always appended after the original entries.
This may result in less efficient link lines, but gives projects
control of ordering among independent entries. Such control may be
important when intermixing link flags with libraries, or when multiple
libraries provide a given symbol.
This is the default.
In the above example, this strategy computes a link line for ``main``
by starting with its original entries ``A B C``, and then appends
another ``A`` to satisfy the dependencies of ``B`` and ``C`` on ``A``.
The final order is ``A B C A``.
``REORDER``
Entries of :prop_tgt:`LINK_LIBRARIES` may be reordered, de-duplicated,
and intermixed with indirect link dependencies. This may result in
more efficient link lines, but does not give projects any control of
ordering among independent entries.
In the above example, this strategy computes a link line for ``main``
by re-ordering its original entries ``A B C`` to satisfy the
dependencies of ``B`` and ``C`` on ``A``.
The final order is ``B C A``.
.. note::
Regardless of the strategy used, the actual linker invocation for
some platforms may de-duplicate entries based on linker capabilities.
See policy :policy:`CMP0156`.

View File

@@ -581,7 +581,8 @@ std::string const& cmComputeLinkDepends::LinkEntry::DEFAULT =
cmComputeLinkDepends::cmComputeLinkDepends(const cmGeneratorTarget* target,
const std::string& config,
const std::string& linkLanguage)
const std::string& linkLanguage,
LinkLibrariesStrategy strategy)
: Target(target)
, Makefile(this->Target->Target->GetMakefile())
, GlobalGenerator(this->Target->GetLocalGenerator()->GetGlobalGenerator())
@@ -592,6 +593,7 @@ cmComputeLinkDepends::cmComputeLinkDepends(const cmGeneratorTarget* target,
, LinkLanguage(linkLanguage)
, LinkType(CMP0003_ComputeLinkType(
this->Config, this->Makefile->GetCMakeInstance()->GetDebugConfigs()))
, Strategy(strategy)
{
// target oriented feature override property takes precedence over
@@ -1488,8 +1490,22 @@ void cmComputeLinkDepends::OrderLinkEntries()
}
// Start with the original link line.
for (size_t originalEntry : this->OriginalEntries) {
this->VisitEntry(originalEntry);
switch (this->Strategy) {
case LinkLibrariesStrategy::PRESERVE_ORDER: {
// Emit the direct dependencies in their original order.
// This gives projects control over ordering.
for (size_t originalEntry : this->OriginalEntries) {
this->VisitEntry(originalEntry);
}
} break;
case LinkLibrariesStrategy::REORDER: {
// Schedule the direct dependencies for emission in topo order.
// This may produce more efficient link lines.
for (size_t originalEntry : this->OriginalEntries) {
this->MakePendingComponent(
this->CCG->GetComponentMap()[originalEntry]);
}
} break;
}
// Now explore anything left pending. Since the component graph is

View File

@@ -27,6 +27,12 @@ class cmMakefile;
class cmSourceFile;
class cmake;
enum class LinkLibrariesStrategy
{
PRESERVE_ORDER,
REORDER,
};
/** \class cmComputeLinkDepends
* \brief Compute link dependencies for targets.
*/
@@ -35,7 +41,8 @@ class cmComputeLinkDepends
public:
cmComputeLinkDepends(cmGeneratorTarget const* target,
const std::string& config,
const std::string& linkLanguage);
const std::string& linkLanguage,
LinkLibrariesStrategy strategy);
~cmComputeLinkDepends();
cmComputeLinkDepends(const cmComputeLinkDepends&) = delete;
@@ -94,6 +101,7 @@ private:
bool DebugMode = false;
std::string LinkLanguage;
cmTargetLinkLibraryType LinkType;
LinkLibrariesStrategy Strategy;
EntryVector FinalLinkEntries;
std::map<std::string, std::string> LinkLibraryOverride;

View File

@@ -9,6 +9,7 @@
#include <cm/memory>
#include <cm/optional>
#include <cm/string_view>
#include <cmext/algorithm>
#include <cmext/string_view>
@@ -567,8 +568,25 @@ bool cmComputeLinkInformation::Compute()
return false;
}
LinkLibrariesStrategy strategy = LinkLibrariesStrategy::PRESERVE_ORDER;
if (cmValue s = this->Target->GetProperty("LINK_LIBRARIES_STRATEGY")) {
if (*s == "PRESERVE_ORDER"_s) {
strategy = LinkLibrariesStrategy::PRESERVE_ORDER;
} else if (*s == "REORDER"_s) {
strategy = LinkLibrariesStrategy::REORDER;
} else {
this->CMakeInstance->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("LINK_LIBRARIES_STRATEGY value '", *s,
"' is not recognized."),
this->Target->GetBacktrace());
return false;
}
}
// Compute the ordered link line items.
cmComputeLinkDepends cld(this->Target, this->Config, this->LinkLanguage);
cmComputeLinkDepends cld(this->Target, this->Config, this->LinkLanguage,
strategy);
cld.SetOldLinkDirMode(this->OldLinkDirMode);
cmComputeLinkDepends::EntryVector const& linkEntries = cld.Compute();
FeatureDescriptor const* currentFeature = nullptr;

View File

@@ -466,6 +466,7 @@ TargetProperty const StaticTargetProperties[] = {
{ "LINKER_TYPE"_s, IC::CanCompileSources },
{ "ENABLE_EXPORTS"_s, IC::TargetWithSymbolExports },
{ "LINK_LIBRARIES_ONLY_TARGETS"_s, IC::NormalNonImportedTarget },
{ "LINK_LIBRARIES_STRATEGY"_s, IC::NormalNonImportedTarget },
{ "LINK_SEARCH_START_STATIC"_s, IC::CanCompileSources },
{ "LINK_SEARCH_END_STATIC"_s, IC::CanCompileSources },
// Initialize per-configuration name postfix property from the variable only

View File

@@ -836,6 +836,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "(Linux|Darwin|Windows)"
endif()
add_RunCMake_test(LinkLibrariesProcessing)
add_RunCMake_test(LinkLibrariesStrategy)
add_RunCMake_test(File_Archive)
add_RunCMake_test(File_Configure)
add_RunCMake_test(File_Generate)

View File

@@ -0,0 +1 @@
^Library 'B' was linked first\.$

View File

@@ -0,0 +1 @@
^Library 'A' was linked first\.$

View File

@@ -0,0 +1,7 @@
target \[main\] link dependency ordering:
target \[A\]
target \[B\]
target \[C\]
target \[A\]
target \[main\] link line:

View File

@@ -0,0 +1,2 @@
set(CMAKE_LINK_LIBRARIES_STRATEGY PRESERVE_ORDER)
include(Basic-common.cmake)

View File

@@ -0,0 +1 @@
^Library 'B' was linked first\.$

View File

@@ -0,0 +1,6 @@
target \[main\] link dependency ordering:
target \[B\]
target \[C\]
target \[A\]
target \[main\] link line:

View File

@@ -0,0 +1,2 @@
set(CMAKE_LINK_LIBRARIES_STRATEGY REORDER)
include(Basic-common.cmake)

View File

@@ -0,0 +1,15 @@
enable_language(C)
add_library(A STATIC BasicA.c BasicX.c)
add_library(B STATIC BasicB.c BasicX.c)
add_library(C STATIC BasicC.c BasicX.c)
target_link_libraries(B PRIVATE A)
target_link_libraries(C PRIVATE A)
target_compile_definitions(A PRIVATE BASIC_ID="A")
target_compile_definitions(B PRIVATE BASIC_ID="B")
target_compile_definitions(C PRIVATE BASIC_ID="C")
add_executable(main Basic.c)
target_link_libraries(main PRIVATE A B C)
set_property(TARGET main PROPERTY LINK_DEPENDS_DEBUG_MODE 1) # undocumented
set_property(TARGET main PROPERTY RUNTIME_OUTPUT_DIRECTORY "$<1:${CMAKE_BINARY_DIR}>")

View File

@@ -0,0 +1,11 @@
extern int BasicB(void);
extern int BasicC(void);
/* Use a symbol provided by a dedicated object file in A, B, and C.
The first library linked will determine the return value. */
extern int BasicX(void);
int main(void)
{
return BasicB() + BasicC() + BasicX();
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
#include <stdio.h>
int BasicX(void)
{
printf("Library '%s' was linked first.\n", BASIC_ID);
return 0;
}

View File

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

View File

@@ -0,0 +1,15 @@
target \[main\] link dependency ordering:
item \[-Flag1\]
target \[A\]
item \[-Flag1\]
target \[B\]
item \[-Flag2\]
target \[C\]
item \[-Flag2\]
target \[A\]
item \[-Flag2\]
target \[B\]
item \[-Flag3\]
target \[C\]
target \[main\] link line:

View File

@@ -0,0 +1,2 @@
set(CMAKE_LINK_LIBRARIES_STRATEGY PRESERVE_ORDER)
include(Duplicate-common.cmake)

View File

@@ -0,0 +1,9 @@
target \[main\] link dependency ordering:
item \[-Flag1\]
target \[A\]
target \[B\]
item \[-Flag2\]
target \[C\]
item \[-Flag3\]
target \[main\] link line:

View File

@@ -0,0 +1,2 @@
set(CMAKE_LINK_LIBRARIES_STRATEGY REORDER)
include(Duplicate-common.cmake)

View File

@@ -0,0 +1,12 @@
enable_language(C)
add_library(A INTERFACE IMPORTED)
add_library(B INTERFACE IMPORTED)
add_library(C INTERFACE IMPORTED)
set_property(TARGET A PROPERTY IMPORTED_LIBNAME A)
set_property(TARGET B PROPERTY IMPORTED_LIBNAME B)
set_property(TARGET C PROPERTY IMPORTED_LIBNAME C)
add_executable(main Duplicate.c)
target_link_libraries(main PRIVATE -Flag1 A -Flag1 B -Flag2 C -Flag2 A -Flag2 B -Flag3 C)
set_property(TARGET main PROPERTY LINK_DEPENDS_DEBUG_MODE 1) # undocumented

View File

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

View File

@@ -0,0 +1,12 @@
enable_language(C)
set(info "")
foreach(var
CMAKE_C_LINK_LIBRARIES_PROCESSING
)
if(DEFINED ${var})
string(APPEND info "set(${var} \"${${var}}\")\n")
endif()
endforeach()
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/info.cmake" "${info}")

View File

@@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.30)
include(RunCMake)
if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(RunCMake_TEST_OPTIONS -DCMAKE_CONFIGURATION_TYPES=Debug)
else()
set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
endif()
# Detect information from the toolchain:
# - CMAKE_C_LINK_LIBRARIES_PROCESSING
run_cmake(Inspect)
include("${RunCMake_BINARY_DIR}/Inspect-build/info.cmake")
run_cmake(Unknown)
function(run_strategy case exe)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build)
run_cmake(${case})
set(RunCMake_TEST_NO_CLEAN 1)
run_cmake_command(${case}-build ${CMAKE_COMMAND} --build . --config Debug)
if(exe)
if("ORDER=REVERSE" IN_LIST CMAKE_C_LINK_LIBRARIES_PROCESSING)
set(RunCMake-stdout-file ${case}-run-stdout-reverse.txt)
endif()
run_cmake_command(${case}-run ${RunCMake_TEST_BINARY_DIR}/${exe})
unset(RunCMake-stdout-file)
endif()
endfunction()
run_strategy(Basic-PRESERVE_ORDER "main")
run_strategy(Basic-REORDER "main")
run_cmake(Duplicate-PRESERVE_ORDER)
run_cmake(Duplicate-REORDER)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,4 @@
^CMake Error at Unknown.cmake:5 \(add_executable\):
LINK_LIBRARIES_STRATEGY value 'unknown' is not recognized\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9] \(include\)

View File

@@ -0,0 +1,5 @@
enable_language(C)
set(CMAKE_LINK_LIBRARIES_STRATEGY unknown)
add_executable(main main.c)

View File

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