diff options
34 files changed, 476 insertions, 78 deletions
diff --git a/Help/command/target_link_libraries.rst b/Help/command/target_link_libraries.rst index 872e264..ac231bc 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 0000000..0c622e7 --- /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 ec36908..8cfe683 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 5341e8d..4a6518fd 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 902500a..72316f1 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 5473316..2647998 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 4acb99f..7fe30b3 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 85a9d9c..76712f4 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 3517844..0eab368 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 7125170..8030b64 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 a7d825d..5deb2df 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 2935e0b..be36f3f 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 009d133..693a11c 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 5a90e7e..db44938 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 a3f2968..5646368 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 9cae926..2ffff96 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 c50cc5d..151470b 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 ffe94ba..908b3f0 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 aecc18e..3423b30 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 1c8b672..98d56df 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 74f34e4..06167a8 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 0000000..899be45 --- /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 0000000..0128740 --- /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 0000000..5e79c60 --- /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 0000000..ad5d367 --- /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 0000000..d0f426a --- /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 0000000..1d99ab8 --- /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 0000000..08fa5f5 --- /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 0000000..cf09314 --- /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 0000000..d14ce66 --- /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 0000000..7fb70c1 --- /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 0000000..9bb3a44 --- /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 0000000..02624eb --- /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 0000000..c09c4f1 --- /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(); +} |