summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2021-06-01 12:35:49 +0000
committerKitware Robot <kwrobot@kitware.com>2021-06-01 08:35:56 -0400
commit5eb2aa1f57e99f30be5ea145ce9af5fd9652392f (patch)
tree20ec33b57ee8da2737aa53376aa46032bf8860a8
parent54cc20137cb5ea304a298dc63e68c86ebe6a2b89 (diff)
parent3941555d935afad8343c66f39579bfc611201a6f (diff)
downloadcmake-5eb2aa1f57e99f30be5ea145ce9af5fd9652392f.tar.gz
Merge topic 'link-objects-first'
3941555d93 target_link_libraries: Place $<TARGET_OBJECTS> before libraries f530b3a267 OpenWatcom: Add infrastructure to link to object files 8a4ca110e4 cmComputeLinkInformation: Improve type safety of item IsPath member 83ad066ed1 cmComputeTargetDepends: Factor out helper to add object library dependency 7f506b95a7 cmGeneratorTarget: Refactor link item lookup 96809a8541 cmGeneratorTarget: Give temporary link impl item an explicit name ddffbb8adb cmMakefile: Register explicit object sources more efficiently 18e42d3e63 cmGeneratorExpressionNode: Constify local variable Acked-by: Kitware Robot <kwrobot@kitware.com> Merge-request: !6166
-rw-r--r--Help/command/target_link_libraries.rst85
-rw-r--r--Help/release/dev/link-objects-first.rst8
-rw-r--r--Modules/Compiler/OpenWatcom.cmake1
-rw-r--r--Source/cmComputeLinkDepends.cxx36
-rw-r--r--Source/cmComputeLinkDepends.h6
-rw-r--r--Source/cmComputeLinkInformation.cxx44
-rw-r--r--Source/cmComputeLinkInformation.h33
-rw-r--r--Source/cmComputeTargetDepends.cxx62
-rw-r--r--Source/cmComputeTargetDepends.h3
-rw-r--r--Source/cmGeneratorExpressionNode.cxx2
-rw-r--r--Source/cmGeneratorTarget.cxx79
-rw-r--r--Source/cmGeneratorTarget.h6
-rw-r--r--Source/cmGlobalXCodeGenerator.cxx8
-rw-r--r--Source/cmLinkItem.h6
-rw-r--r--Source/cmLinkLineComputer.cxx8
-rw-r--r--Source/cmLinkLineDeviceComputer.cxx2
-rw-r--r--Source/cmLocalVisualStudio7Generator.cxx2
-rw-r--r--Source/cmMakefile.cxx3
-rw-r--r--Source/cmTargetLinkLibrariesCommand.cxx2
-rw-r--r--Source/cmVisualStudio10TargetGenerator.cxx7
-rw-r--r--Tests/ObjectLibrary/CMakeLists.txt2
-rw-r--r--Tests/ObjectLibrary/LinkObjects/CMakeLists.txt45
-rw-r--r--Tests/ObjectLibrary/LinkObjects/a_dep.c7
-rw-r--r--Tests/ObjectLibrary/LinkObjects/a_obj.c8
-rw-r--r--Tests/ObjectLibrary/LinkObjects/b_dep.c7
-rw-r--r--Tests/ObjectLibrary/LinkObjects/b_obj.c8
-rw-r--r--Tests/ObjectLibrary/LinkObjects/c_dep.c7
-rw-r--r--Tests/ObjectLibrary/LinkObjects/c_obj.c8
-rw-r--r--Tests/ObjectLibrary/LinkObjects/d_dep.c7
-rw-r--r--Tests/ObjectLibrary/LinkObjects/d_obj.c8
-rw-r--r--Tests/ObjectLibrary/LinkObjects/e_dep.c7
-rw-r--r--Tests/ObjectLibrary/LinkObjects/e_lib.c5
-rw-r--r--Tests/ObjectLibrary/LinkObjects/e_obj.c8
-rw-r--r--Tests/ObjectLibrary/LinkObjects/main.c24
34 files changed, 476 insertions, 78 deletions
diff --git a/Help/command/target_link_libraries.rst b/Help/command/target_link_libraries.rst
index 872e264e99..ac231bc8f2 100644
--- a/Help/command/target_link_libraries.rst
+++ b/Help/command/target_link_libraries.rst
@@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/Help/release/dev/link-objects-first.rst b/Help/release/dev/link-objects-first.rst
new file mode 100644
index 0000000000..0c622e7ae1
--- /dev/null
+++ b/Help/release/dev/link-objects-first.rst
@@ -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.
diff --git a/Modules/Compiler/OpenWatcom.cmake b/Modules/Compiler/OpenWatcom.cmake
index ec369081af..8cfe6838dd 100644
--- a/Modules/Compiler/OpenWatcom.cmake
+++ b/Modules/Compiler/OpenWatcom.cmake
@@ -7,6 +7,7 @@ include_guard()
set(CMAKE_LIBRARY_PATH_FLAG "libpath ")
set(CMAKE_LINK_LIBRARY_FLAG "library ")
set(CMAKE_LINK_LIBRARY_FILE_FLAG "library ")
+set(CMAKE_LINK_OBJECT_FILE_FLAG "file ")
if(CMAKE_VERBOSE_MAKEFILE)
set(CMAKE_WCL_QUIET)
diff --git a/Source/cmComputeLinkDepends.cxx b/Source/cmComputeLinkDepends.cxx
index 5341e8d8c1..4a6518fde4 100644
--- a/Source/cmComputeLinkDepends.cxx
+++ b/Source/cmComputeLinkDepends.cxx
@@ -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)
{
diff --git a/Source/cmComputeLinkDepends.h b/Source/cmComputeLinkDepends.h
index 902500aaf3..72316f1aac 100644
--- a/Source/cmComputeLinkDepends.h
+++ b/Source/cmComputeLinkDepends.h
@@ -42,6 +42,7 @@ public:
cmGeneratorTarget const* Target = nullptr;
bool IsSharedDep = false;
bool IsFlag = false;
+ bool IsObject = false;
};
using EntryVector = std::vector<LinkEntry>;
@@ -65,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.
@@ -153,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;
diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx
index 5473316daf..2647998ce1 100644
--- a/Source/cmComputeLinkInformation.cxx
+++ b/Source/cmComputeLinkInformation.cxx
@@ -309,6 +309,13 @@ cmComputeLinkInformation::cmComputeLinkInformation(
this->LibLinkSuffix =
this->Makefile->GetSafeDefinition("CMAKE_LINK_LIBRARY_SUFFIX");
}
+ if (cmProp flag = this->Makefile->GetDefinition(
+ "CMAKE_" + this->LinkLanguage + "_LINK_OBJECT_FILE_FLAG")) {
+ this->ObjLinkFileFlag = *flag;
+ } else {
+ this->ObjLinkFileFlag =
+ this->Makefile->GetSafeDefinition("CMAKE_LINK_OBJECT_FILE_FLAG");
+ }
// Get options needed to specify RPATHs.
this->RuntimeUseChrpath = false;
@@ -514,7 +521,8 @@ bool cmComputeLinkInformation::Compute()
if (linkEntry.IsSharedDep) {
this->AddSharedDepItem(linkEntry.Item, linkEntry.Target);
} else {
- this->AddItem(linkEntry.Item, linkEntry.Target);
+ this->AddItem(linkEntry.Item, linkEntry.Target,
+ linkEntry.IsObject ? ItemIsObject::Yes : ItemIsObject::No);
}
}
@@ -634,7 +642,8 @@ void cmComputeLinkInformation::AddImplicitLinkInfo(std::string const& lang)
}
void cmComputeLinkInformation::AddItem(BT<std::string> const& item,
- cmGeneratorTarget const* tgt)
+ cmGeneratorTarget const* tgt,
+ ItemIsObject isObject)
{
// Compute the proper name to use to link this library.
const std::string& config = this->Config;
@@ -659,14 +668,15 @@ void cmComputeLinkInformation::AddItem(BT<std::string> const& item,
std::string exe = tgt->GetFullPath(config, artifact, true);
linkItem += exe;
- this->Items.emplace_back(BT<std::string>(linkItem, item.Backtrace), true,
- tgt);
+ this->Items.emplace_back(BT<std::string>(linkItem, item.Backtrace),
+ ItemIsPath::Yes, ItemIsObject::No, tgt);
this->Depends.push_back(std::move(exe));
} else if (tgt->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
// Add the interface library as an item so it can be considered as part
// of COMPATIBLE_INTERFACE_ enforcement. The generators will ignore
// this for the actual link line.
- this->Items.emplace_back(std::string(), false, tgt);
+ this->Items.emplace_back(std::string(), ItemIsPath::No, ItemIsObject::No,
+ tgt);
// Also add the item the interface specifies to be used in its place.
std::string const& libName = tgt->GetImportedLibName(config);
@@ -719,7 +729,7 @@ void cmComputeLinkInformation::AddItem(BT<std::string> const& item,
} else {
// Use the full path given to the library file.
this->Depends.push_back(item.Value);
- this->AddFullItem(item);
+ this->AddFullItem(item, isObject);
this->AddLibraryRuntimeInfo(item.Value);
}
} else {
@@ -1038,10 +1048,10 @@ void cmComputeLinkInformation::SetCurrentLinkType(LinkType lt)
if (this->LinkTypeEnabled) {
switch (this->CurrentLinkType) {
case LinkStatic:
- this->Items.emplace_back(this->StaticLinkTypeFlag, false);
+ this->Items.emplace_back(this->StaticLinkTypeFlag, ItemIsPath::No);
break;
case LinkShared:
- this->Items.emplace_back(this->SharedLinkTypeFlag, false);
+ this->Items.emplace_back(this->SharedLinkTypeFlag, ItemIsPath::No);
break;
default:
break;
@@ -1084,10 +1094,11 @@ void cmComputeLinkInformation::AddTargetItem(BT<std::string> const& item,
}
// Now add the full path to the library.
- this->Items.emplace_back(item, true, target);
+ this->Items.emplace_back(item, ItemIsPath::Yes, ItemIsObject::No, target);
}
-void cmComputeLinkInformation::AddFullItem(BT<std::string> const& item)
+void cmComputeLinkInformation::AddFullItem(BT<std::string> const& item,
+ ItemIsObject isObject)
{
// Check for the implicit link directory special case.
if (this->CheckImplicitDirItem(item.Value)) {
@@ -1138,7 +1149,7 @@ void cmComputeLinkInformation::AddFullItem(BT<std::string> const& item)
}
// Now add the full path to the library.
- this->Items.emplace_back(item, true);
+ this->Items.emplace_back(item, ItemIsPath::Yes, isObject);
}
bool cmComputeLinkInformation::CheckImplicitDirItem(std::string const& item)
@@ -1226,7 +1237,7 @@ void cmComputeLinkInformation::AddUserItem(BT<std::string> const& item,
this->SetCurrentLinkType(this->StartLinkType);
// Use the item verbatim.
- this->Items.emplace_back(item, false);
+ this->Items.emplace_back(item, ItemIsPath::No);
return;
}
@@ -1296,7 +1307,8 @@ void cmComputeLinkInformation::AddUserItem(BT<std::string> const& item,
// Create an option to ask the linker to search for the library.
std::string out = cmStrCat(this->LibLinkFlag, lib, this->LibLinkSuffix);
- this->Items.emplace_back(BT<std::string>(out, item.Backtrace), false);
+ this->Items.emplace_back(BT<std::string>(out, item.Backtrace),
+ ItemIsPath::No);
// Here we could try to find the library the linker will find and
// add a runtime information entry for it. It would probably not be
@@ -1328,13 +1340,13 @@ void cmComputeLinkInformation::AddFrameworkItem(std::string const& item)
if (this->GlobalGenerator->IsXcode()) {
// Add framework path - it will be handled by Xcode after it's added to
// "Link Binary With Libraries" build phase
- this->Items.emplace_back(item, true);
+ this->Items.emplace_back(item, ItemIsPath::Yes);
} else {
// Add the item using the -framework option.
- this->Items.emplace_back(std::string("-framework"), false);
+ this->Items.emplace_back(std::string("-framework"), ItemIsPath::No);
cmOutputConverter converter(this->Makefile->GetStateSnapshot());
fw = converter.EscapeForShell(fw);
- this->Items.emplace_back(fw, false);
+ this->Items.emplace_back(fw, ItemIsPath::No);
}
}
diff --git a/Source/cmComputeLinkInformation.h b/Source/cmComputeLinkInformation.h
index 4acb99f742..7fe30b3bb6 100644
--- a/Source/cmComputeLinkInformation.h
+++ b/Source/cmComputeLinkInformation.h
@@ -35,17 +35,33 @@ public:
~cmComputeLinkInformation();
bool Compute();
+ enum class ItemIsPath
+ {
+ No,
+ Yes,
+ };
+
+ enum class ItemIsObject
+ {
+ No,
+ Yes,
+ };
+
struct Item
{
Item() = default;
- Item(BT<std::string> v, bool p, cmGeneratorTarget const* target = nullptr)
+ Item(BT<std::string> v, ItemIsPath isPath,
+ ItemIsObject isObject = ItemIsObject::No,
+ cmGeneratorTarget const* target = nullptr)
: Value(std::move(v))
- , IsPath(p)
+ , IsPath(isPath)
+ , IsObject(isObject)
, Target(target)
{
}
BT<std::string> Value;
- bool IsPath = true;
+ ItemIsPath IsPath = ItemIsPath::Yes;
+ ItemIsObject IsObject = ItemIsObject::No;
cmGeneratorTarget const* Target = nullptr;
};
using ItemVector = std::vector<Item>;
@@ -74,6 +90,11 @@ public:
return this->LibLinkFileFlag;
}
+ std::string const& GetObjLinkFileFlag() const
+ {
+ return this->ObjLinkFileFlag;
+ }
+
std::string const& GetRPathLinkFlag() const { return this->RPathLinkFlag; }
std::string GetRPathLinkString() const;
@@ -82,7 +103,8 @@ public:
const cmGeneratorTarget* GetTarget() { return this->Target; }
private:
- void AddItem(BT<std::string> const& item, const cmGeneratorTarget* tgt);
+ void AddItem(BT<std::string> const& item, const cmGeneratorTarget* tgt,
+ ItemIsObject isObject = ItemIsObject::No);
void AddSharedDepItem(BT<std::string> const& item,
cmGeneratorTarget const* tgt);
void AddRuntimeDLL(cmGeneratorTarget const* tgt);
@@ -118,6 +140,7 @@ private:
const char* LoaderFlag;
std::string LibLinkFlag;
std::string LibLinkFileFlag;
+ std::string ObjLinkFileFlag;
std::string LibLinkSuffix;
std::string RuntimeFlag;
std::string RuntimeSep;
@@ -159,7 +182,7 @@ private:
// Handling of link items.
void AddTargetItem(BT<std::string> const& item,
const cmGeneratorTarget* target);
- void AddFullItem(BT<std::string> const& item);
+ void AddFullItem(BT<std::string> const& item, ItemIsObject isObject);
bool CheckImplicitDirItem(std::string const& item);
void AddUserItem(BT<std::string> const& item, bool pathNotKnown);
void AddFrameworkItem(std::string const& item);
diff --git a/Source/cmComputeTargetDepends.cxx b/Source/cmComputeTargetDepends.cxx
index 85a9d9cf12..76712f4eb1 100644
--- a/Source/cmComputeTargetDepends.cxx
+++ b/Source/cmComputeTargetDepends.cxx
@@ -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,32 +228,19 @@ 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.
std::vector<cmSourceFile const*> objectFiles;
depender->GetExternalObjects(objectFiles, it);
for (cmSourceFile const* o : objectFiles) {
- std::string const& objLib = o->GetObjectLibrary();
- if (!objLib.empty()) {
- cmLinkItem const& objItem =
- depender->ResolveLinkItem(objLib, cmListFileBacktrace());
- if (emitted.insert(objItem).second) {
- if (depender->GetType() != cmStateEnums::EXECUTABLE &&
- depender->GetType() != cmStateEnums::STATIC_LIBRARY &&
- depender->GetType() != cmStateEnums::SHARED_LIBRARY &&
- depender->GetType() != cmStateEnums::MODULE_LIBRARY &&
- depender->GetType() != cmStateEnums::OBJECT_LIBRARY) {
- this->GlobalGenerator->GetCMakeInstance()->IssueMessage(
- MessageType::FATAL_ERROR,
- "Only executables and libraries may reference target objects.",
- depender->GetBacktrace());
- return;
- }
- const_cast<cmGeneratorTarget*>(depender)->Target->AddUtility(
- objLib, false);
- }
- }
+ this->AddObjectDepends(depender_index, o, emitted);
}
}
}
@@ -293,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);
+ }
+ }
}
}
@@ -319,6 +313,34 @@ void cmComputeTargetDepends::AddInterfaceDepends(
}
}
+void cmComputeTargetDepends::AddObjectDepends(int depender_index,
+ cmSourceFile const* o,
+ std::set<cmLinkItem>& emitted)
+{
+ std::string const& objLib = o->GetObjectLibrary();
+ if (objLib.empty()) {
+ return;
+ }
+ cmGeneratorTarget const* depender = this->Targets[depender_index];
+ cmLinkItem const& objItem =
+ depender->ResolveLinkItem(objLib, cmListFileBacktrace());
+ if (emitted.insert(objItem).second) {
+ if (depender->GetType() != cmStateEnums::EXECUTABLE &&
+ depender->GetType() != cmStateEnums::STATIC_LIBRARY &&
+ depender->GetType() != cmStateEnums::SHARED_LIBRARY &&
+ depender->GetType() != cmStateEnums::MODULE_LIBRARY &&
+ depender->GetType() != cmStateEnums::OBJECT_LIBRARY) {
+ this->GlobalGenerator->GetCMakeInstance()->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "Only executables and libraries may reference target objects.",
+ depender->GetBacktrace());
+ return;
+ }
+ const_cast<cmGeneratorTarget*>(depender)->Target->AddUtility(objLib,
+ false);
+ }
+}
+
void cmComputeTargetDepends::AddTargetDepend(int depender_index,
cmLinkItem const& dependee_name,
bool linking, bool cross)
diff --git a/Source/cmComputeTargetDepends.h b/Source/cmComputeTargetDepends.h
index 3517844d6b..0eab3683d9 100644
--- a/Source/cmComputeTargetDepends.h
+++ b/Source/cmComputeTargetDepends.h
@@ -16,6 +16,7 @@ class cmComputeComponentGraph;
class cmGeneratorTarget;
class cmGlobalGenerator;
class cmLinkItem;
+class cmSourceFile;
class cmTargetDependSet;
/** \class cmComputeTargetDepends
@@ -71,6 +72,8 @@ private:
cmListFileBacktrace const& dependee_backtrace,
const std::string& config,
std::set<cmLinkItem>& emitted);
+ void AddObjectDepends(int depender_index, cmSourceFile const* o,
+ std::set<cmLinkItem>& emitted);
cmGlobalGenerator* GlobalGenerator;
bool DebugMode;
bool NoCycles;
diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx
index 71251705a8..8030b6466a 100644
--- a/Source/cmGeneratorExpressionNode.cxx
+++ b/Source/cmGeneratorExpressionNode.cxx
@@ -1681,7 +1681,7 @@ static const struct TargetObjectsNode : public cmGeneratorExpressionNode
// Create the cmSourceFile instances in the referencing directory.
cmMakefile* mf = context->LG->GetMakefile();
- for (std::string& o : objects) {
+ for (std::string const& o : objects) {
mf->AddTargetObject(tgtName, o);
}
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index a7d825deb9..5deb2dfe45 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -6255,29 +6255,28 @@ bool cmGeneratorTarget::IsLinkLookupScope(std::string const& n,
return false;
}
-void cmGeneratorTarget::LookupLinkItems(std::vector<std::string> const& names,
- cmListFileBacktrace const& bt,
- std::vector<cmLinkItem>& items) const
+cm::optional<cmLinkItem> cmGeneratorTarget::LookupLinkItem(
+ std::string const& n, cmListFileBacktrace const& bt) const
{
+ cm::optional<cmLinkItem> maybeItem;
cmLocalGenerator const* lg = this->LocalGenerator;
- for (std::string const& n : names) {
- if (this->IsLinkLookupScope(n, lg)) {
- continue;
- }
+ if (this->IsLinkLookupScope(n, lg)) {
+ return maybeItem;
+ }
- std::string name = this->CheckCMP0004(n);
- if (name == this->GetName() || name.empty()) {
- continue;
- }
- items.push_back(this->ResolveLinkItem(name, bt, lg));
+ std::string name = this->CheckCMP0004(n);
+ if (name == this->GetName() || name.empty()) {
+ return maybeItem;
}
+ maybeItem = this->ResolveLinkItem(name, bt, lg);
+ return maybeItem;
}
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.
@@ -6295,7 +6294,25 @@ void cmGeneratorTarget::ExpandLinkItems(
cmExpandList(cge->Evaluate(this->LocalGenerator, config, headTarget,
&dagChecker, this, headTarget->LinkerLanguage),
libs);
- this->LookupLinkItems(libs, cge->GetBacktrace(), items);
+ 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));
+ }
+ }
hadHeadSensitiveCondition = cge->GetHadHeadSensitiveCondition();
hadContextSensitiveCondition = cge->GetHadContextSensitiveCondition();
hadLinkLanguageSensitiveCondition =
@@ -6800,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;
@@ -6824,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;
@@ -6831,7 +6849,7 @@ void cmGeneratorTarget::ComputeLinkInterfaceLibraries(
bool hadLinkLanguageSensitiveConditionDummy = false;
this->ExpandLinkItems(newProp, *newExplicitLibraries, config,
headTarget, usage_requirements_only, ifaceLibs,
- hadHeadSensitiveConditionDummy,
+ ifaceObjects, hadHeadSensitiveConditionDummy,
hadContextSensitiveConditionDummy,
hadLinkLanguageSensitiveConditionDummy);
}
@@ -6899,11 +6917,16 @@ 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);
- this->LookupLinkItems(deps, cmListFileBacktrace(), iface.SharedDeps);
+ for (std::string const& dep : deps) {
+ if (cm::optional<cmLinkItem> maybeItem =
+ this->LookupLinkItem(dep, cmListFileBacktrace())) {
+ iface.SharedDeps.emplace_back(std::move(*maybeItem));
+ }
+ }
}
return &iface;
@@ -7416,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();
@@ -7490,8 +7514,21 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries(
}
// The entry is meant for this configuration.
- impl.Libraries.emplace_back(this->ResolveLinkItem(name, *btIt, lg),
- evaluated != *le);
+ 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);
}
std::set<std::string> const& seenProps = cge->GetSeenTargetProperties();
diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h
index 2935e0bce0..be36f3f206 100644
--- a/Source/cmGeneratorTarget.h
+++ b/Source/cmGeneratorTarget.h
@@ -1036,12 +1036,12 @@ private:
const cmGeneratorTarget* headTarget,
bool usage_requirements_only,
std::vector<cmLinkItem>& items,
+ std::vector<cmLinkItem>& objects,
bool& hadHeadSensitiveCondition,
bool& hadContextSensitiveCondition,
bool& hadLinkLanguageSensitiveCondition) const;
- void LookupLinkItems(std::vector<std::string> const& names,
- cmListFileBacktrace const& bt,
- std::vector<cmLinkItem>& items) const;
+ cm::optional<cmLinkItem> LookupLinkItem(std::string const& n,
+ cmListFileBacktrace const& bt) const;
std::vector<BT<std::string>> GetSourceFilePaths(
std::string const& config) const;
diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx
index 009d133805..693a11c9a1 100644
--- a/Source/cmGlobalXCodeGenerator.cxx
+++ b/Source/cmGlobalXCodeGenerator.cxx
@@ -3454,7 +3454,9 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
libItem.Target->GetType() == cmStateEnums::SHARED_LIBRARY ||
libItem.Target->GetType() == cmStateEnums::MODULE_LIBRARY ||
libItem.Target->GetType() == cmStateEnums::UNKNOWN_LIBRARY)) ||
- (!libItem.Target && libItem.IsPath && forceLinkPhase))) {
+ (!libItem.Target &&
+ libItem.IsPath == cmComputeLinkInformation::ItemIsPath::Yes &&
+ forceLinkPhase))) {
std::string libName;
bool canUseLinkPhase = true;
if (libItem.Target) {
@@ -3565,7 +3567,7 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
auto* libTarget = FindXCodeTarget(libItem->Target);
cmXCodeObject* buildFile;
if (!libTarget) {
- if (libItem->IsPath) {
+ if (libItem->IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
// Get or create a direct file ref in the root project
auto cleanPath = libItem->Value.Value;
if (cmSystemTools::FileIsFullPath(cleanPath)) {
@@ -3724,7 +3726,7 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
BuildObjectListOrString libPaths(this, true);
for (auto const& libItem : configItemMap[configName]) {
auto const& libName = *libItem;
- if (libName.IsPath) {
+ if (libName.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
auto cleanPath = libName.Value.Value;
if (cmSystemTools::FileIsFullPath(cleanPath)) {
cleanPath = cmSystemTools::CollapseFullPath(cleanPath);
diff --git a/Source/cmLinkItem.h b/Source/cmLinkItem.h
index 5a90e7ec65..db44938a37 100644
--- a/Source/cmLinkItem.h
+++ b/Source/cmLinkItem.h
@@ -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;
diff --git a/Source/cmLinkLineComputer.cxx b/Source/cmLinkLineComputer.cxx
index a3f2968b1c..5646368b71 100644
--- a/Source/cmLinkLineComputer.cxx
+++ b/Source/cmLinkLineComputer.cxx
@@ -74,8 +74,12 @@ void cmLinkLineComputer::ComputeLinkLibs(
}
BT<std::string> linkLib;
- if (item.IsPath) {
- linkLib.Value += cli.GetLibLinkFileFlag();
+ if (item.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
+ if (item.IsObject == cmComputeLinkInformation::ItemIsObject::Yes) {
+ linkLib.Value += cli.GetObjLinkFileFlag();
+ } else {
+ linkLib.Value += cli.GetLibLinkFileFlag();
+ }
linkLib.Value += this->ConvertToOutputFormat(
this->ConvertToLinkReference(item.Value.Value));
linkLib.Backtrace = item.Value.Backtrace;
diff --git a/Source/cmLinkLineDeviceComputer.cxx b/Source/cmLinkLineDeviceComputer.cxx
index 9cae926698..2ffff96b9d 100644
--- a/Source/cmLinkLineDeviceComputer.cxx
+++ b/Source/cmLinkLineDeviceComputer.cxx
@@ -111,7 +111,7 @@ void cmLinkLineDeviceComputer::ComputeLinkLibraries(
}
BT<std::string> linkLib;
- if (item.IsPath) {
+ if (item.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
// nvcc understands absolute paths to libraries ending in '.a' or '.lib'.
// These should be passed to nvlink. Other extensions need to be left
// out because nvlink may not understand or need them. Even though it
diff --git a/Source/cmLocalVisualStudio7Generator.cxx b/Source/cmLocalVisualStudio7Generator.cxx
index c50cc5dbff..151470b4e5 100644
--- a/Source/cmLocalVisualStudio7Generator.cxx
+++ b/Source/cmLocalVisualStudio7Generator.cxx
@@ -1254,7 +1254,7 @@ void cmLocalVisualStudio7GeneratorInternals::OutputLibraries(
{
cmLocalVisualStudio7Generator* lg = this->LocalGenerator;
for (auto const& lib : libs) {
- if (lib.IsPath) {
+ if (lib.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
std::string rel = lg->MaybeRelativeToCurBinDir(lib.Value.Value);
fout << lg->ConvertToXMLOutputPath(rel) << " ";
} else if (!lib.Target ||
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index ffe94bad41..908b3f0d25 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -3454,7 +3454,8 @@ void cmMakefile::CreateGeneratedOutputs(
void cmMakefile::AddTargetObject(std::string const& tgtName,
std::string const& objFile)
{
- cmSourceFile* sf = this->GetOrCreateSource(objFile, true);
+ cmSourceFile* sf =
+ this->GetOrCreateSource(objFile, true, cmSourceFileLocationKind::Known);
sf->SetObjectLibrary(tgtName);
sf->SetProperty("EXTERNAL_OBJECT", "1");
#if !defined(CMAKE_BOOTSTRAP)
diff --git a/Source/cmTargetLinkLibrariesCommand.cxx b/Source/cmTargetLinkLibrariesCommand.cxx
index aecc18e3ac..3423b30dcd 100644
--- a/Source/cmTargetLinkLibrariesCommand.cxx
+++ b/Source/cmTargetLinkLibrariesCommand.cxx
@@ -565,7 +565,7 @@ void TLL::AffectsProperty(std::string const& prop)
if (!this->EncodeRemoteReference) {
return;
}
- // Add a wrapper to the expression to tell LookupLinkItems to look up
+ // Add a wrapper to the expression to tell LookupLinkItem to look up
// names in the caller's directory.
if (this->Props.insert(prop).second) {
this->Target->AppendProperty(prop, this->DirectoryId);
diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx
index 1c8b672d22..98d56df32a 100644
--- a/Source/cmVisualStudio10TargetGenerator.cxx
+++ b/Source/cmVisualStudio10TargetGenerator.cxx
@@ -3391,7 +3391,7 @@ bool cmVisualStudio10TargetGenerator::ComputeCudaLinkOptions(
}
}
- if (l.IsPath) {
+ if (l.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
std::string path =
this->LocalGenerator->MaybeRelativeToCurBinDir(l.Value.Value);
ConvertToWindowsSlash(path);
@@ -3945,7 +3945,8 @@ bool cmVisualStudio10TargetGenerator::ComputeLibOptions(
using ItemVector = cmComputeLinkInformation::ItemVector;
const ItemVector& libs = cli.GetItems();
for (cmComputeLinkInformation::Item const& l : libs) {
- if (l.IsPath && cmVS10IsTargetsFile(l.Value.Value)) {
+ if (l.IsPath == cmComputeLinkInformation::ItemIsPath::Yes &&
+ cmVS10IsTargetsFile(l.Value.Value)) {
std::string path =
this->LocalGenerator->MaybeRelativeToCurBinDir(l.Value.Value);
ConvertToWindowsSlash(path);
@@ -4028,7 +4029,7 @@ void cmVisualStudio10TargetGenerator::AddLibraries(
}
}
- if (l.IsPath) {
+ if (l.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
std::string path =
this->LocalGenerator->MaybeRelativeToCurBinDir(l.Value.Value);
ConvertToWindowsSlash(path);
diff --git a/Tests/ObjectLibrary/CMakeLists.txt b/Tests/ObjectLibrary/CMakeLists.txt
index 74f34e4d02..06167a83f4 100644
--- a/Tests/ObjectLibrary/CMakeLists.txt
+++ b/Tests/ObjectLibrary/CMakeLists.txt
@@ -74,4 +74,6 @@ target_link_libraries(UseABstaticObjs ABstatic)
add_subdirectory(ExportLanguages)
+add_subdirectory(LinkObjects)
+
add_subdirectory(Transitive)
diff --git a/Tests/ObjectLibrary/LinkObjects/CMakeLists.txt b/Tests/ObjectLibrary/LinkObjects/CMakeLists.txt
new file mode 100644
index 0000000000..899be45184
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/CMakeLists.txt
@@ -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)
diff --git a/Tests/ObjectLibrary/LinkObjects/a_dep.c b/Tests/ObjectLibrary/LinkObjects/a_dep.c
new file mode 100644
index 0000000000..01287405c8
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/a_dep.c
@@ -0,0 +1,7 @@
+#ifdef OBJA
+# error "OBJA is defined, but should not be"
+#endif
+int a_dep(void)
+{
+ return 0;
+}
diff --git a/Tests/ObjectLibrary/LinkObjects/a_obj.c b/Tests/ObjectLibrary/LinkObjects/a_obj.c
new file mode 100644
index 0000000000..5e79c60c5a
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/a_obj.c
@@ -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();
+}
diff --git a/Tests/ObjectLibrary/LinkObjects/b_dep.c b/Tests/ObjectLibrary/LinkObjects/b_dep.c
new file mode 100644
index 0000000000..ad5d36737e
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/b_dep.c
@@ -0,0 +1,7 @@
+#ifdef OBJB
+# error "OBJB is defined, but should not be"
+#endif
+int b_dep(void)
+{
+ return 0;
+}
diff --git a/Tests/ObjectLibrary/LinkObjects/b_obj.c b/Tests/ObjectLibrary/LinkObjects/b_obj.c
new file mode 100644
index 0000000000..d0f426a241
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/b_obj.c
@@ -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();
+}
diff --git a/Tests/ObjectLibrary/LinkObjects/c_dep.c b/Tests/ObjectLibrary/LinkObjects/c_dep.c
new file mode 100644
index 0000000000..1d99ab874e
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/c_dep.c
@@ -0,0 +1,7 @@
+#ifdef OBJC
+# error "OBJC is defined, but should not be"
+#endif
+int c_dep(void)
+{
+ return 0;
+}
diff --git a/Tests/ObjectLibrary/LinkObjects/c_obj.c b/Tests/ObjectLibrary/LinkObjects/c_obj.c
new file mode 100644
index 0000000000..08fa5f5bdb
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/c_obj.c
@@ -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();
+}
diff --git a/Tests/ObjectLibrary/LinkObjects/d_dep.c b/Tests/ObjectLibrary/LinkObjects/d_dep.c
new file mode 100644
index 0000000000..cf09314115
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/d_dep.c
@@ -0,0 +1,7 @@
+#ifdef OBJD
+# error "OBJD is defined, but should not be"
+#endif
+int d_dep(void)
+{
+ return 0;
+}
diff --git a/Tests/ObjectLibrary/LinkObjects/d_obj.c b/Tests/ObjectLibrary/LinkObjects/d_obj.c
new file mode 100644
index 0000000000..d14ce660eb
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/d_obj.c
@@ -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();
+}
diff --git a/Tests/ObjectLibrary/LinkObjects/e_dep.c b/Tests/ObjectLibrary/LinkObjects/e_dep.c
new file mode 100644
index 0000000000..7fb70c10ad
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/e_dep.c
@@ -0,0 +1,7 @@
+#ifdef OBJE
+# error "OBJE is defined, but should not be"
+#endif
+int e_dep(void)
+{
+ return 0;
+}
diff --git a/Tests/ObjectLibrary/LinkObjects/e_lib.c b/Tests/ObjectLibrary/LinkObjects/e_lib.c
new file mode 100644
index 0000000000..9bb3a44355
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/e_lib.c
@@ -0,0 +1,5 @@
+extern int e_obj(void);
+int e_lib(void)
+{
+ return e_obj();
+}
diff --git a/Tests/ObjectLibrary/LinkObjects/e_obj.c b/Tests/ObjectLibrary/LinkObjects/e_obj.c
new file mode 100644
index 0000000000..02624ebfc2
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/e_obj.c
@@ -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();
+}
diff --git a/Tests/ObjectLibrary/LinkObjects/main.c b/Tests/ObjectLibrary/LinkObjects/main.c
new file mode 100644
index 0000000000..c09c4f11bd
--- /dev/null
+++ b/Tests/ObjectLibrary/LinkObjects/main.c
@@ -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();
+}