target_link_libraries: Place $<TARGET_OBJECTS> before libraries

Linkers always use object files explicitly specified on the command line
regardless of where they appear.  Move them to the front of the list of
linked libraries in so that symbols required by the object files can be
resolved by any library.

Issue: #22149
This commit is contained in:
Brad King 2021-05-26 10:56:10 -04:00
parent f530b3a267
commit 3941555d93
22 changed files with 338 additions and 5 deletions

View File

@ -289,6 +289,91 @@ treated as :ref:`Interface Libraries`, but when they appear in
a target's :prop_tgt:`LINK_LIBRARIES` property their object files
will be included in the link too.
.. _`Linking Object Libraries via $<TARGET_OBJECTS>`:
Linking Object Libraries via $<TARGET_OBJECTS>
""""""""""""""""""""""""""""""""""""""""""""""
.. versionadded:: 3.21
The object files associated with an object library may be referenced
by the :genex:`$<TARGET_OBJECTS>` generator expression. Such object
files are placed on the link line *before* all libraries, regardless
of their relative order. Additionally, an ordering dependency will be
added to the build sysstem to make sure the object library is up-to-date
before the dependent target links. For example, the code
.. code-block:: cmake
add_library(obj3 OBJECT obj3.c)
target_compile_definitions(obj3 PUBLIC OBJ3)
add_executable(main3 main3.c)
target_link_libraries(main3 PRIVATE a3 $<TARGET_OBJECTS:obj3> b3)
links executable ``main3`` with object files from ``main3.c``
and ``obj3.c`` followed by the ``a3`` and ``b3`` libraries.
``main3.c`` is *not* compiled with usage requirements from ``obj3``,
such as ``-DOBJ3``.
This approach can be used to achieve transitive inclusion of object
files in link lines as usage requirements. Continuing the above
example, the code
.. code-block:: cmake
add_library(iface_obj3 INTERFACE)
target_link_libraries(iface_obj3 INTERFACE obj3 $<TARGET_OBJECTS:obj3>)
creates an interface library ``iface_obj3`` that forwards the ``obj3``
usage requirements and adds the ``obj3`` object files to dependents'
link lines. The code
.. code-block:: cmake
add_executable(use_obj3 use_obj3.c)
target_link_libraries(use_obj3 PRIVATE iface_obj3)
compiles ``use_obj3.c`` with ``-DOBJ3`` and links executable ``use_obj3``
with object files from ``use_obj3.c`` and ``obj3.c``.
This also works transitively through a static library. Since a static
library does not link, it does not consume the object files from
object libraries referenced this way. Instead, the object files
become transitive link dependencies of the static library.
Continuing the above example, the code
.. code-block:: cmake
add_library(static3 STATIC static3.c)
target_link_libraries(static3 PRIVATE iface_obj3)
add_executable(use_static3 use_static3.c)
target_link_libraries(use_static3 PRIVATE static3)
compiles ``static3.c`` with ``-DOBJ3`` and creates ``libstatic3.a``
using only its own object file. ``use_static3.c`` is compiled *without*
``-DOBJ3`` because the usage requirement is not transitive through
the private dependency of ``static3``. However, the link dependencies
of ``static3`` are propagated, including the ``iface_obj3`` reference
to ``$<TARGET_OBJECTS:obj3>``. The ``use_static3`` executable is
created with object files from ``use_static3.c`` and ``obj3.c``, and
linked to library ``libstatic3.a``.
When using this approach, it is the project's responsibility to avoid
linking multiple dependent binaries to ``iface_obj3``, because they will
all get the ``obj3`` object files on their link lines.
.. note::
Referencing :genex:`$<TARGET_OBJECTS>` in ``target_link_libraries``
calls worked in versions of CMake prior to 3.21 for some cases,
but was not fully supported:
* It did not place the object files before libraries on link lines.
* It did not add an ordering dependency on the object library.
* It did not work in Xcode with multiple architectures.
Cyclic Dependencies of Static Libraries
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,8 @@
link-objects-first
------------------
* :command:`target_link_libraries` calls referencing object libraries
via the :genex:`TARGET_OBJECTS` generator expression now place the
object files before all libraries on the link line, regardless of
their specified order. See documentation on
:ref:`Linking Object Libraries via \$\<TARGET_OBJECTS\>` for details.

View File

@ -263,6 +263,12 @@ cmComputeLinkDepends::Compute()
this->FinalLinkEntries.push_back(e);
}
}
// Place explicitly linked object files in the front. The linker will
// always use them anyway, and they may depend on symbols from libraries.
// Append in reverse order since we reverse the final order below.
for (int i : cmReverseRange(this->ObjectEntries)) {
this->FinalLinkEntries.emplace_back(this->EntryList[i]);
}
// Reverse the resulting order since we iterated in reverse.
std::reverse(this->FinalLinkEntries.begin(), this->FinalLinkEntries.end());
@ -328,6 +334,27 @@ int cmComputeLinkDepends::AddLinkEntry(cmLinkItem const& item)
return index;
}
void cmComputeLinkDepends::AddLinkObject(cmLinkItem const& item)
{
// Check if the item entry has already been added.
auto lei = this->LinkEntryIndex.find(item);
if (lei != this->LinkEntryIndex.end()) {
return;
}
// Allocate a spot for the item entry.
lei = this->AllocateLinkEntry(item);
// Initialize the item entry.
int index = lei->second;
LinkEntry& entry = this->EntryList[index];
entry.Item = BT<std::string>(item.AsStr(), item.Backtrace);
entry.IsObject = true;
// Record explicitly linked object files separately.
this->ObjectEntries.emplace_back(index);
}
void cmComputeLinkDepends::FollowLinkEntry(BFSEntry qe)
{
// Get this entry representation.
@ -343,6 +370,7 @@ void cmComputeLinkDepends::FollowLinkEntry(BFSEntry qe)
entry.Target->GetType() == cmStateEnums::INTERFACE_LIBRARY;
// This target provides its own link interface information.
this->AddLinkEntries(depender_index, iface->Libraries);
this->AddLinkObjects(iface->Objects);
if (isIface) {
return;
@ -487,6 +515,7 @@ void cmComputeLinkDepends::AddDirectLinkEntries()
cmLinkImplementation const* impl =
this->Target->GetLinkImplementation(this->Config);
this->AddLinkEntries(-1, impl->Libraries);
this->AddLinkObjects(impl->Objects);
for (cmLinkItem const& wi : impl->WrongConfigLibraries) {
this->CheckWrongConfigItem(wi);
}
@ -546,6 +575,13 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index,
}
}
void cmComputeLinkDepends::AddLinkObjects(std::vector<cmLinkItem> const& objs)
{
for (cmLinkItem const& obj : objs) {
this->AddLinkObject(obj);
}
}
cmLinkItem cmComputeLinkDepends::ResolveLinkItem(int depender_index,
const std::string& name)
{

View File

@ -66,10 +66,12 @@ private:
std::map<cmLinkItem, int>::iterator AllocateLinkEntry(
cmLinkItem const& item);
int AddLinkEntry(cmLinkItem const& item);
void AddLinkObject(cmLinkItem const& item);
void AddVarLinkEntries(int depender_index, const char* value);
void AddDirectLinkEntries();
template <typename T>
void AddLinkEntries(int depender_index, std::vector<T> const& libs);
void AddLinkObjects(std::vector<cmLinkItem> const& objs);
cmLinkItem ResolveLinkItem(int depender_index, const std::string& name);
// One entry for each unique item.
@ -154,6 +156,9 @@ private:
std::set<cmGeneratorTarget const*> OldWrongConfigItems;
void CheckWrongConfigItem(cmLinkItem const& item);
// Record of explicitly linked object files.
std::vector<int> ObjectEntries;
int ComponentOrderId;
cmTargetLinkLibraryType LinkType;
bool HasConfig;

View File

@ -20,6 +20,7 @@
#include "cmProperty.h"
#include "cmRange.h"
#include "cmSourceFile.h"
#include "cmSourceFileLocationKind.h"
#include "cmState.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
@ -227,6 +228,12 @@ void cmComputeTargetDepends::CollectTargetDepends(int depender_index)
this->AddInterfaceDepends(depender_index, lib, it, emitted);
}
}
for (cmLinkItem const& obj : impl->Objects) {
if (cmSourceFile const* o = depender->Makefile->GetSource(
obj.AsStr(), cmSourceFileLocationKind::Known)) {
this->AddObjectDepends(depender_index, o, emitted);
}
}
}
// Add dependencies on object libraries not otherwise handled above.
@ -274,6 +281,12 @@ void cmComputeTargetDepends::AddInterfaceDepends(
this->AddInterfaceDepends(depender_index, libBT, config, emitted);
}
}
for (cmLinkItem const& obj : iface->Objects) {
if (cmSourceFile const* o = depender->Makefile->GetSource(
obj.AsStr(), cmSourceFileLocationKind::Known)) {
this->AddObjectDepends(depender_index, o, emitted);
}
}
}
}

View File

@ -6275,8 +6275,8 @@ cm::optional<cmLinkItem> cmGeneratorTarget::LookupLinkItem(
void cmGeneratorTarget::ExpandLinkItems(
std::string const& prop, std::string const& value, std::string const& config,
cmGeneratorTarget const* headTarget, bool usage_requirements_only,
std::vector<cmLinkItem>& items, bool& hadHeadSensitiveCondition,
bool& hadContextSensitiveCondition,
std::vector<cmLinkItem>& items, std::vector<cmLinkItem>& objects,
bool& hadHeadSensitiveCondition, bool& hadContextSensitiveCondition,
bool& hadLinkLanguageSensitiveCondition) const
{
// Keep this logic in sync with ComputeLinkImplementationLibraries.
@ -6294,9 +6294,22 @@ void cmGeneratorTarget::ExpandLinkItems(
cmExpandList(cge->Evaluate(this->LocalGenerator, config, headTarget,
&dagChecker, this, headTarget->LinkerLanguage),
libs);
cmMakefile const* mf = this->LocalGenerator->GetMakefile();
for (std::string const& lib : libs) {
if (cm::optional<cmLinkItem> maybeItem =
this->LookupLinkItem(lib, cge->GetBacktrace())) {
if (!maybeItem->Target) {
// Report explicitly linked object files separately.
std::string const& maybeObj = maybeItem->AsStr();
if (cmSystemTools::FileIsFullPath(maybeObj)) {
cmSourceFile const* sf =
mf->GetSource(maybeObj, cmSourceFileLocationKind::Known);
if (sf && sf->GetPropertyAsBool("EXTERNAL_OBJECT")) {
objects.emplace_back(std::move(*maybeItem));
continue;
}
}
}
items.emplace_back(std::move(*maybeItem));
}
}
@ -6804,7 +6817,7 @@ void cmGeneratorTarget::ComputeLinkInterfaceLibraries(
// The interface libraries have been explicitly set.
this->ExpandLinkItems(linkIfaceProp, *explicitLibraries, config,
headTarget, usage_requirements_only, iface.Libraries,
iface.HadHeadSensitiveCondition,
iface.Objects, iface.HadHeadSensitiveCondition,
iface.HadContextSensitiveCondition,
iface.HadLinkLanguageSensitiveCondition);
return;
@ -6828,6 +6841,7 @@ void cmGeneratorTarget::ComputeLinkInterfaceLibraries(
// Compare the link implementation fallback link interface to the
// preferred new link interface property and warn if different.
std::vector<cmLinkItem> ifaceLibs;
std::vector<cmLinkItem> ifaceObjects;
static const std::string newProp = "INTERFACE_LINK_LIBRARIES";
if (cmProp newExplicitLibraries = this->GetProperty(newProp)) {
bool hadHeadSensitiveConditionDummy = false;
@ -6835,7 +6849,7 @@ void cmGeneratorTarget::ComputeLinkInterfaceLibraries(
bool hadLinkLanguageSensitiveConditionDummy = false;
this->ExpandLinkItems(newProp, *newExplicitLibraries, config,
headTarget, usage_requirements_only, ifaceLibs,
hadHeadSensitiveConditionDummy,
ifaceObjects, hadHeadSensitiveConditionDummy,
hadContextSensitiveConditionDummy,
hadLinkLanguageSensitiveConditionDummy);
}
@ -6903,7 +6917,7 @@ const cmLinkInterface* cmGeneratorTarget::GetImportLinkInterface(
cmExpandList(info->Languages, iface.Languages);
this->ExpandLinkItems(info->LibrariesProp, info->Libraries, config,
headTarget, usage_requirements_only, iface.Libraries,
iface.HadHeadSensitiveCondition,
iface.Objects, iface.HadHeadSensitiveCondition,
iface.HadContextSensitiveCondition,
iface.HadLinkLanguageSensitiveCondition);
std::vector<std::string> deps = cmExpandedList(info->SharedDeps);
@ -7425,6 +7439,7 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries(
cmGeneratorTarget const* head) const
{
cmLocalGenerator const* lg = this->LocalGenerator;
cmMakefile const* mf = lg->GetMakefile();
cmStringRange entryRange = this->Target->GetLinkImplementationEntries();
cmBacktraceRange btRange = this->Target->GetLinkImplementationBacktraces();
cmBacktraceRange::const_iterator btIt = btRange.begin();
@ -7500,6 +7515,19 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries(
// The entry is meant for this configuration.
cmLinkItem item = this->ResolveLinkItem(name, *btIt, lg);
if (!item.Target) {
// Report explicitly linked object files separately.
std::string const& maybeObj = item.AsStr();
if (cmSystemTools::FileIsFullPath(maybeObj)) {
cmSourceFile const* sf =
mf->GetSource(maybeObj, cmSourceFileLocationKind::Known);
if (sf && sf->GetPropertyAsBool("EXTERNAL_OBJECT")) {
impl.Objects.emplace_back(std::move(item));
continue;
}
}
}
impl.Libraries.emplace_back(std::move(item), evaluated != *le);
}

View File

@ -1036,6 +1036,7 @@ private:
const cmGeneratorTarget* headTarget,
bool usage_requirements_only,
std::vector<cmLinkItem>& items,
std::vector<cmLinkItem>& objects,
bool& hadHeadSensitiveCondition,
bool& hadContextSensitiveCondition,
bool& hadLinkLanguageSensitiveCondition) const;

View File

@ -50,6 +50,9 @@ struct cmLinkImplementationLibraries
// Libraries linked directly in this configuration.
std::vector<cmLinkImplItem> Libraries;
// Object files linked directly in this configuration.
std::vector<cmLinkItem> Objects;
// Libraries linked directly in other configurations.
// Needed only for OLD behavior of CMP0003.
std::vector<cmLinkItem> WrongConfigLibraries;
@ -63,6 +66,9 @@ struct cmLinkInterfaceLibraries
// Libraries listed in the interface.
std::vector<cmLinkItem> Libraries;
// Object files listed in the interface.
std::vector<cmLinkItem> Objects;
// Whether the list depends on a genex referencing the head target.
bool HadHeadSensitiveCondition = false;

View File

@ -74,4 +74,6 @@ target_link_libraries(UseABstaticObjs ABstatic)
add_subdirectory(ExportLanguages)
add_subdirectory(LinkObjects)
add_subdirectory(Transitive)

View File

@ -0,0 +1,45 @@
add_executable(LinkObjects main.c)
# Link TARGET_OBJECTS through LINK_LIBRARIES.
add_library(LinkObjectsAObj OBJECT a_obj.c)
add_library(LinkObjectsADep STATIC a_dep.c)
target_compile_definitions(LinkObjectsAObj PUBLIC OBJA)
target_link_libraries(LinkObjects PRIVATE LinkObjectsADep $<TARGET_OBJECTS:LinkObjectsAObj>)
# Link TARGET_OBJECTS through INTERFACE_LINK_LIBRARIES with usage requirements.
add_library(LinkObjectsB INTERFACE)
add_library(LinkObjectsBObj OBJECT b_obj.c)
add_library(LinkObjectsBDep STATIC b_dep.c)
target_compile_definitions(LinkObjectsBObj PUBLIC OBJB)
target_link_libraries(LinkObjectsB INTERFACE LinkObjectsBObj $<TARGET_OBJECTS:LinkObjectsBObj>)
target_link_libraries(LinkObjectsBObj PRIVATE LinkObjectsBDep)
target_link_libraries(LinkObjects PRIVATE LinkObjectsB)
# Link TARGET_OBJECTS through INTERFACE_LINK_LIBRARIES without usage requirements.
add_library(LinkObjectsC INTERFACE)
add_library(LinkObjectsCObj OBJECT c_obj.c)
add_library(LinkObjectsCDep STATIC c_dep.c)
target_compile_definitions(LinkObjectsCObj PUBLIC OBJC)
target_link_libraries(LinkObjectsC INTERFACE LinkObjectsCDep $<TARGET_OBJECTS:LinkObjectsCObj>)
target_link_libraries(LinkObjectsCObj PRIVATE LinkObjectsCDep)
target_link_libraries(LinkObjects PRIVATE LinkObjectsC)
# Link TARGET_OBJECTS through both LINK_LIBRARIES and INTERFACE_LINK_LIBRARIES, deduplicated.
add_library(LinkObjectsD INTERFACE)
add_library(LinkObjectsDObj OBJECT d_obj.c)
add_library(LinkObjectsDDep STATIC d_dep.c)
target_compile_definitions(LinkObjectsDObj PUBLIC OBJD)
target_link_libraries(LinkObjectsD INTERFACE LinkObjectsDObj $<TARGET_OBJECTS:LinkObjectsDObj>)
target_link_libraries(LinkObjectsDObj PRIVATE LinkObjectsDDep)
target_link_libraries(LinkObjects PRIVATE $<TARGET_OBJECTS:LinkObjectsDObj> LinkObjectsD)
# Link TARGET_OBJECTS through STATIC library private dependency.
add_library(LinkObjectsE INTERFACE)
add_library(LinkObjectsEObj OBJECT e_obj.c)
add_library(LinkObjectsEDep STATIC e_dep.c)
add_library(LinkObjectsEStatic STATIC e_lib.c)
target_compile_definitions(LinkObjectsEObj PUBLIC OBJE)
target_link_libraries(LinkObjectsE INTERFACE LinkObjectsEObj $<TARGET_OBJECTS:LinkObjectsEObj>)
target_link_libraries(LinkObjectsEObj PRIVATE LinkObjectsEDep)
target_link_libraries(LinkObjectsEStatic PRIVATE LinkObjectsE)
target_link_libraries(LinkObjects PRIVATE LinkObjectsEStatic)

View File

@ -0,0 +1,7 @@
#ifdef OBJA
# error "OBJA is defined, but should not be"
#endif
int a_dep(void)
{
return 0;
}

View File

@ -0,0 +1,8 @@
#ifndef OBJA
# error "OBJA is not defined, but should be"
#endif
extern int a_dep(void);
int a_obj(void)
{
return a_dep();
}

View File

@ -0,0 +1,7 @@
#ifdef OBJB
# error "OBJB is defined, but should not be"
#endif
int b_dep(void)
{
return 0;
}

View File

@ -0,0 +1,8 @@
#ifndef OBJB
# error "OBJB is not defined, but should be"
#endif
extern int b_dep(void);
int b_obj(void)
{
return b_dep();
}

View File

@ -0,0 +1,7 @@
#ifdef OBJC
# error "OBJC is defined, but should not be"
#endif
int c_dep(void)
{
return 0;
}

View File

@ -0,0 +1,8 @@
#ifndef OBJC
# error "OBJC is not defined, but should be"
#endif
extern int c_dep(void);
int c_obj(void)
{
return c_dep();
}

View File

@ -0,0 +1,7 @@
#ifdef OBJD
# error "OBJD is defined, but should not be"
#endif
int d_dep(void)
{
return 0;
}

View File

@ -0,0 +1,8 @@
#ifndef OBJD
# error "OBJD is not defined, but should be"
#endif
extern int d_dep(void);
int d_obj(void)
{
return d_dep();
}

View File

@ -0,0 +1,7 @@
#ifdef OBJE
# error "OBJE is defined, but should not be"
#endif
int e_dep(void)
{
return 0;
}

View File

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

View File

@ -0,0 +1,8 @@
#ifndef OBJE
# error "OBJE is not defined, but should be"
#endif
extern int e_dep(void);
int e_obj(void)
{
return e_dep();
}

View File

@ -0,0 +1,24 @@
#ifdef OBJA
# error "OBJA is defined, but should not be"
#endif
#ifndef OBJB
# error "OBJB is not defined, but should be"
#endif
#ifdef OBJC
# error "OBJC is defined, but should not be"
#endif
#ifndef OBJD
# error "OBJD is not defined, but should be"
#endif
#ifdef OBJE
# error "OBJE is defined, but should not be"
#endif
extern int a_obj(void);
extern int b_obj(void);
extern int c_obj(void);
extern int d_obj(void);
extern int e_lib(void);
int main(void)
{
return a_obj() + b_obj() + c_obj() + d_obj() + e_lib();
}