diff options
Diffstat (limited to 'Source/cmComputeLinkInformation.cxx')
-rw-r--r-- | Source/cmComputeLinkInformation.cxx | 1857 |
1 files changed, 1857 insertions, 0 deletions
diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx new file mode 100644 index 0000000..44d8615 --- /dev/null +++ b/Source/cmComputeLinkInformation.cxx @@ -0,0 +1,1857 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmComputeLinkInformation.h" + +#include "cmAlgorithms.h" +#include "cmComputeLinkDepends.h" +#include "cmGeneratorTarget.h" +#include "cmGlobalGenerator.h" +#include "cmLocalGenerator.h" +#include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmOrderDirectories.h" +#include "cmOutputConverter.h" +#include "cmPolicies.h" +#include "cmState.h" +#include "cmStateTypes.h" +#include "cmSystemTools.h" +#include "cmTarget.h" +#include "cmake.h" + +#include <algorithm> +#include <ctype.h> +#include <sstream> +#include <string.h> +#include <utility> + +//#define CM_COMPUTE_LINK_INFO_DEBUG + +/* +Notes about linking on various platforms: + +------------------------------------------------------------------------------ + +Linux, FreeBSD, macOS, Sun, Windows: + +Linking to libraries using the full path works fine. + +------------------------------------------------------------------------------ + +On AIX, more work is needed. + + The "-bnoipath" option is needed. From "man ld": + + Note: If you specify a shared object, or an archive file + containing a shared object, with an absolute or relative path + name, instead of with the -lName flag, the path name is + included in the import file ID string in the loader section of + the output file. You can override this behavior with the + -bnoipath option. + + noipath + + For shared objects listed on the command-line, rather than + specified with the -l flag, use a null path component when + listing the shared object in the loader section of the + output file. A null path component is always used for + shared objects specified with the -l flag. This option + does not affect the specification of a path component by + using a line beginning with #! in an import file. The + default is the ipath option. + + This prevents the full path specified on the compile line from being + compiled directly into the binary. + + By default the linker places -L paths in the embedded runtime path. + In order to implement CMake's RPATH interface correctly, we need the + -blibpath:Path option. From "man ld": + + libpath:Path + + Uses Path as the library path when writing the loader section + of the output file. Path is neither checked for validity nor + used when searching for libraries specified by the -l flag. + Path overrides any library paths generated when the -L flag is + used. + + If you do not specify any -L flags, or if you specify the + nolibpath option, the default library path information is + written in the loader section of the output file. The default + library path information is the value of the LIBPATH + environment variable if it is defined, and /usr/lib:/lib, + otherwise. + + We can pass -Wl,-blibpath:/usr/lib:/lib always to avoid the -L stuff + and not break when the user sets LIBPATH. Then if we want to add an + rpath we insert it into the option before /usr/lib. + +------------------------------------------------------------------------------ + +On HP-UX, more work is needed. There are differences between +versions. + +ld: 92453-07 linker linker ld B.10.33 990520 + + Linking with a full path works okay for static and shared libraries. + The linker seems to always put the full path to where the library + was found in the binary whether using a full path or -lfoo syntax. + Transitive link dependencies work just fine due to the full paths. + + It has the "-l:libfoo.sl" option. The +nodefaultrpath is accepted + but not documented and does not seem to do anything. There is no + +forceload option. + +ld: 92453-07 linker ld HP Itanium(R) B.12.41 IPF/IPF + + Linking with a full path works okay for static libraries. + + Linking with a full path works okay for shared libraries. However + dependent (transitive) libraries of those linked directly must be + either found with an rpath stored in the direct dependencies or + found in -L paths as if they were specified with "-l:libfoo.sl" + (really "-l:<soname>"). The search matches that of the dynamic + loader but only with -L paths. In other words, if we have an + executable that links to shared library bar which links to shared + library foo, the link line for the exe must contain + + /dir/with/bar/libbar.sl -L/dir/with/foo + + It does not matter whether the exe wants to link to foo directly or + whether /dir/with/foo/libfoo.sl is listed. The -L path must still + be present. It should match the runtime path computed for the + executable taking all directly and transitively linked libraries + into account. + + The "+nodefaultrpath" option should be used to avoid getting -L + paths in the rpath unless we add our own rpath with +b. This means + that skip-build-rpath should use this option. + + See documentation in "man ld", "man dld.so", and + http://docs.hp.com/en/B2355-90968/creatingandusinglibraries.htm + + +[no]defaultrpath + +defaultrpath is the default. Include any paths that are + specified with -L in the embedded path, unless you specify the + +b option. If you use +b, only the path list specified by +b is + in the embedded path. + + The +nodefaultrpath option removes all library paths that were + specified with the -L option from the embedded path. The linker + searches the library paths specified by the -L option at link + time. At run time, the only library paths searched are those + specified by the environment variables LD_LIBRARY_PATH and + SHLIB_PATH, library paths specified by the +b linker option, and + finally the default library paths. + + +rpathfirst + This option will cause the paths specified in RPATH (embedded + path) to be used before the paths specified in LD_LIBRARY_PATH + or SHLIB_PATH, in searching for shared libraries. This changes + the default search order of LD_LIBRARY_PATH, SHLIB_PATH, and + RPATH (embedded path). + +------------------------------------------------------------------------------ +Notes about dependent (transitive) shared libraries: + +On non-Windows systems shared libraries may have transitive +dependencies. In order to support LINK_INTERFACE_LIBRARIES we must +support linking to a shared library without listing all the libraries +to which it links. Some linkers want to be able to find the +transitive dependencies (dependent libraries) of shared libraries +listed on the command line. + + - On Windows, DLLs are not directly linked, and the import libraries + have no transitive dependencies. + + - On Mac OS X 10.5 and above transitive dependencies are not needed. + + - On Mac OS X 10.4 and below we need to actually list the dependencies. + Otherwise when using -isysroot for universal binaries it cannot + find the dependent libraries. Listing them on the command line + tells the linker where to find them, but unfortunately also links + the library. + + - On HP-UX, the linker wants to find the transitive dependencies of + shared libraries in the -L paths even if the dependent libraries + are given on the link line. + + - On AIX the transitive dependencies are not needed. + + - On SGI, the linker wants to find the transitive dependencies of + shared libraries in the -L paths if they are not given on the link + line. Transitive linking can be disabled using the options + + -no_transitive_link -Wl,-no_transitive_link + + which disable it. Both options must be given when invoking the + linker through the compiler. + + - On Sun, the linker wants to find the transitive dependencies of + shared libraries in the -L paths if they are not given on the link + line. + + - On Linux, FreeBSD, and QNX: + + The linker wants to find the transitive dependencies of shared + libraries in the "-rpath-link" paths option if they have not been + given on the link line. The option is like rpath but just for + link time: + + -Wl,-rpath-link,"/path1:/path2" + +For -rpath-link, we need a separate runtime path ordering pass +including just the dependent libraries that are not linked. + +For -L paths on non-HP, we can do the same thing as with rpath-link +but put the results in -L paths. The paths should be listed at the +end to avoid conflicting with user search paths (?). + +For -L paths on HP, we should do a runtime path ordering pass with +all libraries, both linked and non-linked. Even dependent +libraries that are also linked need to be listed in -L paths. + +In our implementation we add all dependent libraries to the runtime +path computation. Then the auto-generated RPATH will find everything. + +------------------------------------------------------------------------------ +Notes about shared libraries with not builtin soname: + +Some UNIX shared libraries may be created with no builtin soname. On +some platforms such libraries cannot be linked using the path to their +location because the linker will copy the path into the field used to +find the library at runtime. + + Apple: ../libfoo.dylib ==> libfoo.dylib # ok, uses install_name + SGI: ../libfoo.so ==> libfoo.so # ok + AIX: ../libfoo.so ==> libfoo.so # ok + Linux: ../libfoo.so ==> ../libfoo.so # bad + HP-UX: ../libfoo.so ==> ../libfoo.so # bad + Sun: ../libfoo.so ==> ../libfoo.so # bad + FreeBSD: ../libfoo.so ==> ../libfoo.so # bad + +In order to link these libraries we need to use the old-style split +into -L.. and -lfoo options. This should be fairly safe because most +problems with -lfoo options were related to selecting shared libraries +instead of static but in this case we want the shared lib. Link +directory ordering needs to be done to make sure these shared +libraries are found first. There should be very few restrictions +because this need be done only for shared libraries without soname-s. + +*/ + +cmComputeLinkInformation::cmComputeLinkInformation( + const cmGeneratorTarget* target, const std::string& config) + // Store context information. + : Target(target) + , Makefile(target->Target->GetMakefile()) + , GlobalGenerator(target->GetLocalGenerator()->GetGlobalGenerator()) + , CMakeInstance(this->GlobalGenerator->GetCMakeInstance()) + // The configuration being linked. + , Config(config) +{ + // Check whether to recognize OpenBSD-style library versioned names. + this->OpenBSD = this->Makefile->GetState()->GetGlobalPropertyAsBool( + "FIND_LIBRARY_USE_OPENBSD_VERSIONING"); + + // Allocate internals. + this->OrderLinkerSearchPath = new cmOrderDirectories( + this->GlobalGenerator, target, "linker search path"); + this->OrderRuntimeSearchPath = new cmOrderDirectories( + this->GlobalGenerator, target, "runtime search path"); + this->OrderDependentRPath = nullptr; + + // Get the language used for linking this target. + this->LinkLanguage = this->Target->GetLinkerLanguage(config); + if (this->LinkLanguage.empty()) { + // The Compute method will do nothing, so skip the rest of the + // initialization. + return; + } + + // Check whether we should use an import library for linking a target. + this->UseImportLibrary = + this->Makefile->IsDefinitionSet("CMAKE_IMPORT_LIBRARY_SUFFIX"); + + // Check whether we should skip dependencies on shared library files. + this->LinkDependsNoShared = + this->Target->GetPropertyAsBool("LINK_DEPENDS_NO_SHARED"); + + // On platforms without import libraries there may be a special flag + // to use when creating a plugin (module) that obtains symbols from + // the program that will load it. + this->LoaderFlag = nullptr; + if (!this->UseImportLibrary && + this->Target->GetType() == cmStateEnums::MODULE_LIBRARY) { + std::string loader_flag_var = "CMAKE_SHARED_MODULE_LOADER_"; + loader_flag_var += this->LinkLanguage; + loader_flag_var += "_FLAG"; + this->LoaderFlag = this->Makefile->GetDefinition(loader_flag_var); + } + + // Get options needed to link libraries. + this->LibLinkFlag = + this->Makefile->GetSafeDefinition("CMAKE_LINK_LIBRARY_FLAG"); + this->LibLinkFileFlag = + this->Makefile->GetSafeDefinition("CMAKE_LINK_LIBRARY_FILE_FLAG"); + this->LibLinkSuffix = + this->Makefile->GetSafeDefinition("CMAKE_LINK_LIBRARY_SUFFIX"); + + // Get options needed to specify RPATHs. + this->RuntimeUseChrpath = false; + if (this->Target->GetType() != cmStateEnums::STATIC_LIBRARY) { + const char* tType = ((this->Target->GetType() == cmStateEnums::EXECUTABLE) + ? "EXECUTABLE" + : "SHARED_LIBRARY"); + std::string rtVar = "CMAKE_"; + rtVar += tType; + rtVar += "_RUNTIME_"; + rtVar += this->LinkLanguage; + rtVar += "_FLAG"; + std::string rtSepVar = rtVar + "_SEP"; + this->RuntimeFlag = this->Makefile->GetSafeDefinition(rtVar); + this->RuntimeSep = this->Makefile->GetSafeDefinition(rtSepVar); + this->RuntimeAlways = (this->Makefile->GetSafeDefinition( + "CMAKE_PLATFORM_REQUIRED_RUNTIME_PATH")); + + this->RuntimeUseChrpath = this->Target->IsChrpathUsed(config); + + // Get options needed to help find dependent libraries. + std::string rlVar = "CMAKE_"; + rlVar += tType; + rlVar += "_RPATH_LINK_"; + rlVar += this->LinkLanguage; + rlVar += "_FLAG"; + this->RPathLinkFlag = this->Makefile->GetSafeDefinition(rlVar); + } + + // Check if we need to include the runtime search path at link time. + { + std::string var = "CMAKE_SHARED_LIBRARY_LINK_"; + var += this->LinkLanguage; + var += "_WITH_RUNTIME_PATH"; + this->LinkWithRuntimePath = this->Makefile->IsOn(var); + } + + // Check the platform policy for missing soname case. + this->NoSONameUsesPath = + this->Makefile->IsOn("CMAKE_PLATFORM_USES_PATH_WHEN_NO_SONAME"); + + // Get link type information. + this->ComputeLinkTypeInfo(); + + // Setup the link item parser. + this->ComputeItemParserInfo(); + + // Setup framework support. + this->ComputeFrameworkInfo(); + + // Choose a mode for dealing with shared library dependencies. + this->SharedDependencyMode = SharedDepModeNone; + if (this->Makefile->IsOn("CMAKE_LINK_DEPENDENT_LIBRARY_FILES")) { + this->SharedDependencyMode = SharedDepModeLink; + } else if (this->Makefile->IsOn("CMAKE_LINK_DEPENDENT_LIBRARY_DIRS")) { + this->SharedDependencyMode = SharedDepModeLibDir; + } else if (!this->RPathLinkFlag.empty()) { + this->SharedDependencyMode = SharedDepModeDir; + this->OrderDependentRPath = new cmOrderDirectories( + this->GlobalGenerator, target, "dependent library path"); + } + + // Add the search path entries requested by the user to path ordering. + std::vector<std::string> directories; + this->Target->GetLinkDirectories(directories, config, this->LinkLanguage); + this->OrderLinkerSearchPath->AddUserDirectories(directories); + this->OrderRuntimeSearchPath->AddUserDirectories(directories); + + // Set up the implicit link directories. + this->LoadImplicitLinkInfo(); + this->OrderLinkerSearchPath->SetImplicitDirectories(this->ImplicitLinkDirs); + this->OrderRuntimeSearchPath->SetImplicitDirectories(this->ImplicitLinkDirs); + if (this->OrderDependentRPath) { + this->OrderDependentRPath->SetImplicitDirectories(this->ImplicitLinkDirs); + this->OrderDependentRPath->AddLanguageDirectories(this->RuntimeLinkDirs); + } + + // Decide whether to enable compatible library search path mode. + // There exists code that effectively does + // + // /path/to/libA.so -lB + // + // where -lB is meant to link to /path/to/libB.so. This is broken + // because it specified -lB without specifying a link directory (-L) + // in which to search for B. This worked in CMake 2.4 and below + // because -L/path/to would be added by the -L/-l split for A. In + // order to support such projects we need to add the directories + // containing libraries linked with a full path to the -L path. + this->OldLinkDirMode = + this->Target->GetPolicyStatusCMP0003() != cmPolicies::NEW; + if (this->OldLinkDirMode) { + // Construct a mask to not bother with this behavior for link + // directories already specified by the user. + this->OldLinkDirMask.insert(directories.begin(), directories.end()); + } + + this->CMP0060Warn = this->Makefile->PolicyOptionalWarningEnabled( + "CMAKE_POLICY_WARNING_CMP0060"); +} + +cmComputeLinkInformation::~cmComputeLinkInformation() +{ + delete this->OrderLinkerSearchPath; + delete this->OrderRuntimeSearchPath; + delete this->OrderDependentRPath; +} + +cmComputeLinkInformation::ItemVector const& +cmComputeLinkInformation::GetItems() const +{ + return this->Items; +} + +std::vector<std::string> const& cmComputeLinkInformation::GetDirectories() + const +{ + return this->OrderLinkerSearchPath->GetOrderedDirectories(); +} + +std::string cmComputeLinkInformation::GetRPathLinkString() const +{ + // If there is no separate linker runtime search flag (-rpath-link) + // there is no reason to compute a string. + if (!this->OrderDependentRPath) { + return ""; + } + + // Construct the linker runtime search path. These MUST NOT contain tokens + // such as $ORIGIN, see https://sourceware.org/bugzilla/show_bug.cgi?id=16936 + return cmJoin(this->OrderDependentRPath->GetOrderedDirectories(), ":"); +} + +std::vector<std::string> const& cmComputeLinkInformation::GetDepends() const +{ + return this->Depends; +} + +std::vector<std::string> const& cmComputeLinkInformation::GetFrameworkPaths() + const +{ + return this->FrameworkPaths; +} + +const std::set<const cmGeneratorTarget*>& +cmComputeLinkInformation::GetSharedLibrariesLinked() const +{ + return this->SharedLibrariesLinked; +} + +bool cmComputeLinkInformation::Compute() +{ + // Skip targets that do not link. + if (!(this->Target->GetType() == cmStateEnums::EXECUTABLE || + this->Target->GetType() == cmStateEnums::SHARED_LIBRARY || + this->Target->GetType() == cmStateEnums::MODULE_LIBRARY || + this->Target->GetType() == cmStateEnums::STATIC_LIBRARY)) { + return false; + } + + // We require a link language for the target. + if (this->LinkLanguage.empty()) { + cmSystemTools::Error( + "CMake can not determine linker language for target: " + + this->Target->GetName()); + return false; + } + + // Compute the ordered link line items. + cmComputeLinkDepends cld(this->Target, this->Config); + cld.SetOldLinkDirMode(this->OldLinkDirMode); + cmComputeLinkDepends::EntryVector const& linkEntries = cld.Compute(); + + // Add the link line items. + for (cmComputeLinkDepends::LinkEntry const& linkEntry : linkEntries) { + if (linkEntry.IsSharedDep) { + this->AddSharedDepItem(linkEntry.Item, linkEntry.Target); + } else { + this->AddItem(linkEntry.Item, linkEntry.Target); + } + } + + // Restore the target link type so the correct system runtime + // libraries are found. + const char* lss = this->Target->GetProperty("LINK_SEARCH_END_STATIC"); + if (cmSystemTools::IsOn(lss)) { + this->SetCurrentLinkType(LinkStatic); + } else { + this->SetCurrentLinkType(this->StartLinkType); + } + + // Finish listing compatibility paths. + if (this->OldLinkDirMode) { + // For CMake 2.4 bug-compatibility we need to consider the output + // directories of targets linked in another configuration as link + // directories. + std::set<cmGeneratorTarget const*> const& wrongItems = + cld.GetOldWrongConfigItems(); + for (cmGeneratorTarget const* tgt : wrongItems) { + bool implib = (this->UseImportLibrary && + (tgt->GetType() == cmStateEnums::SHARED_LIBRARY)); + cmStateEnums::ArtifactType artifact = implib + ? cmStateEnums::ImportLibraryArtifact + : cmStateEnums::RuntimeBinaryArtifact; + this->OldLinkDirItems.push_back( + tgt->GetFullPath(this->Config, artifact, true)); + } + } + + // Finish setting up linker search directories. + if (!this->FinishLinkerSearchDirectories()) { + return false; + } + + // Add implicit language runtime libraries and directories. + this->AddImplicitLinkInfo(); + + if (!this->CMP0060WarnItems.empty()) { + std::ostringstream w; + /* clang-format off */ + w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0060) << "\n" + "Some library files are in directories implicitly searched by " + "the linker when invoked for " << this->LinkLanguage << ":\n" + " " << cmJoin(this->CMP0060WarnItems, "\n ") << "\n" + "For compatibility with older versions of CMake, the generated " + "link line will ask the linker to search for these by library " + "name." + ; + /* clang-format on */ + this->CMakeInstance->IssueMessage(MessageType::AUTHOR_WARNING, w.str(), + this->Target->GetBacktrace()); + } + + return true; +} + +void cmComputeLinkInformation::AddImplicitLinkInfo() +{ + // The link closure lists all languages whose implicit info is needed. + cmGeneratorTarget::LinkClosure const* lc = + this->Target->GetLinkClosure(this->Config); + for (std::string const& li : lc->Languages) { + // Skip those of the linker language. They are implicit. + if (li != this->LinkLanguage) { + this->AddImplicitLinkInfo(li); + } + } +} + +void cmComputeLinkInformation::AddImplicitLinkInfo(std::string const& lang) +{ + // Add libraries for this language that are not implied by the + // linker language. + std::string libVar = "CMAKE_"; + libVar += lang; + libVar += "_IMPLICIT_LINK_LIBRARIES"; + if (const char* libs = this->Makefile->GetDefinition(libVar)) { + std::vector<std::string> libsVec; + cmSystemTools::ExpandListArgument(libs, libsVec); + for (std::string const& i : libsVec) { + if (this->ImplicitLinkLibs.find(i) == this->ImplicitLinkLibs.end()) { + this->AddItem(i, nullptr); + } + } + } + + // Add linker search paths for this language that are not + // implied by the linker language. + std::string dirVar = "CMAKE_"; + dirVar += lang; + dirVar += "_IMPLICIT_LINK_DIRECTORIES"; + if (const char* dirs = this->Makefile->GetDefinition(dirVar)) { + std::vector<std::string> dirsVec; + cmSystemTools::ExpandListArgument(dirs, dirsVec); + this->OrderLinkerSearchPath->AddLanguageDirectories(dirsVec); + } +} + +void cmComputeLinkInformation::AddItem(std::string const& item, + cmGeneratorTarget const* tgt) +{ + // Compute the proper name to use to link this library. + const std::string& config = this->Config; + bool impexe = (tgt && tgt->IsExecutableWithExports()); + if (impexe && !this->UseImportLibrary && !this->LoaderFlag) { + // Skip linking to executables on platforms with no import + // libraries or loader flags. + return; + } + + if (tgt && tgt->IsLinkable()) { + // This is a CMake target. Ask the target for its real name. + if (impexe && this->LoaderFlag) { + // This link item is an executable that may provide symbols + // used by this target. A special flag is needed on this + // platform. Add it now. + std::string linkItem; + linkItem = this->LoaderFlag; + cmStateEnums::ArtifactType artifact = this->UseImportLibrary + ? cmStateEnums::ImportLibraryArtifact + : cmStateEnums::RuntimeBinaryArtifact; + + std::string exe = tgt->GetFullPath(config, artifact, true); + linkItem += exe; + this->Items.emplace_back(linkItem, true, 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); + + // Also add the item the interface specifies to be used in its place. + std::string const& libName = tgt->GetImportedLibName(config); + if (!libName.empty()) { + this->AddItem(libName, nullptr); + } + } else if (tgt->GetType() == cmStateEnums::OBJECT_LIBRARY) { + // Ignore object library! + // Its object-files should already have been extracted for linking. + } else { + // Decide whether to use an import library. + bool implib = + (this->UseImportLibrary && + (impexe || tgt->GetType() == cmStateEnums::SHARED_LIBRARY)); + cmStateEnums::ArtifactType artifact = implib + ? cmStateEnums::ImportLibraryArtifact + : cmStateEnums::RuntimeBinaryArtifact; + + // Pass the full path to the target file. + std::string lib = tgt->GetFullPath(config, artifact, true); + if (!this->LinkDependsNoShared || + tgt->GetType() != cmStateEnums::SHARED_LIBRARY) { + this->Depends.push_back(lib); + } + + this->AddTargetItem(lib, tgt); + this->AddLibraryRuntimeInfo(lib, tgt); + } + } else { + // This is not a CMake target. Use the name given. + if (cmSystemTools::FileIsFullPath(item)) { + if (cmSystemTools::FileIsDirectory(item)) { + // This is a directory. + this->AddDirectoryItem(item); + } else { + // Use the full path given to the library file. + this->Depends.push_back(item); + this->AddFullItem(item); + this->AddLibraryRuntimeInfo(item); + } + } else { + // This is a library or option specified by the user. + this->AddUserItem(item, true); + } + } +} + +void cmComputeLinkInformation::AddSharedDepItem(std::string const& item, + const cmGeneratorTarget* tgt) +{ + // If dropping shared library dependencies, ignore them. + if (this->SharedDependencyMode == SharedDepModeNone) { + return; + } + + // The user may have incorrectly named an item. Skip items that are + // not full paths to shared libraries. + if (tgt) { + // The target will provide a full path. Make sure it is a shared + // library. + if (tgt->GetType() != cmStateEnums::SHARED_LIBRARY) { + return; + } + } else { + // Skip items that are not full paths. We will not be able to + // reliably specify them. + if (!cmSystemTools::FileIsFullPath(item)) { + return; + } + + // Get the name of the library from the file name. + std::string file = cmSystemTools::GetFilenameName(item); + if (!this->ExtractSharedLibraryName.find(file)) { + // This is not the name of a shared library. + return; + } + } + + // If in linking mode, just link to the shared library. + if (this->SharedDependencyMode == SharedDepModeLink) { + this->AddItem(item, tgt); + return; + } + + // Get a full path to the dependent shared library. + // Add it to the runtime path computation so that the target being + // linked will be able to find it. + std::string lib; + if (tgt) { + cmStateEnums::ArtifactType artifact = this->UseImportLibrary + ? cmStateEnums::ImportLibraryArtifact + : cmStateEnums::RuntimeBinaryArtifact; + lib = tgt->GetFullPath(this->Config, artifact); + this->AddLibraryRuntimeInfo(lib, tgt); + } else { + lib = item; + this->AddLibraryRuntimeInfo(lib); + } + + // Check if we need to include the dependent shared library in other + // path ordering. + cmOrderDirectories* order = nullptr; + if (this->SharedDependencyMode == SharedDepModeLibDir && + !this->LinkWithRuntimePath /* AddLibraryRuntimeInfo adds it */) { + // Add the item to the linker search path. + order = this->OrderLinkerSearchPath; + } else if (this->SharedDependencyMode == SharedDepModeDir) { + // Add the item to the separate dependent library search path. + order = this->OrderDependentRPath; + } + if (order) { + if (tgt) { + std::string soName = tgt->GetSOName(this->Config); + const char* soname = soName.empty() ? nullptr : soName.c_str(); + order->AddRuntimeLibrary(lib, soname); + } else { + order->AddRuntimeLibrary(lib); + } + } +} + +void cmComputeLinkInformation::ComputeLinkTypeInfo() +{ + // Check whether archives may actually be shared libraries. + this->ArchivesMayBeShared = + this->CMakeInstance->GetState()->GetGlobalPropertyAsBool( + "TARGET_ARCHIVES_MAY_BE_SHARED_LIBS"); + + // First assume we cannot do link type stuff. + this->LinkTypeEnabled = false; + + // Lookup link type selection flags. + const char* static_link_type_flag = nullptr; + const char* shared_link_type_flag = nullptr; + const char* target_type_str = nullptr; + switch (this->Target->GetType()) { + case cmStateEnums::EXECUTABLE: + target_type_str = "EXE"; + break; + case cmStateEnums::SHARED_LIBRARY: + target_type_str = "SHARED_LIBRARY"; + break; + case cmStateEnums::MODULE_LIBRARY: + target_type_str = "SHARED_MODULE"; + break; + default: + break; + } + if (target_type_str) { + std::string static_link_type_flag_var = "CMAKE_"; + static_link_type_flag_var += target_type_str; + static_link_type_flag_var += "_LINK_STATIC_"; + static_link_type_flag_var += this->LinkLanguage; + static_link_type_flag_var += "_FLAGS"; + static_link_type_flag = + this->Makefile->GetDefinition(static_link_type_flag_var); + + std::string shared_link_type_flag_var = "CMAKE_"; + shared_link_type_flag_var += target_type_str; + shared_link_type_flag_var += "_LINK_DYNAMIC_"; + shared_link_type_flag_var += this->LinkLanguage; + shared_link_type_flag_var += "_FLAGS"; + shared_link_type_flag = + this->Makefile->GetDefinition(shared_link_type_flag_var); + } + + // We can support link type switching only if all needed flags are + // known. + if (static_link_type_flag && *static_link_type_flag && + shared_link_type_flag && *shared_link_type_flag) { + this->LinkTypeEnabled = true; + this->StaticLinkTypeFlag = static_link_type_flag; + this->SharedLinkTypeFlag = shared_link_type_flag; + } + + // Lookup the starting link type from the target (linked statically?). + const char* lss = this->Target->GetProperty("LINK_SEARCH_START_STATIC"); + this->StartLinkType = cmSystemTools::IsOn(lss) ? LinkStatic : LinkShared; + this->CurrentLinkType = this->StartLinkType; +} + +void cmComputeLinkInformation::ComputeItemParserInfo() +{ + // Get possible library name prefixes. + cmMakefile* mf = this->Makefile; + this->AddLinkPrefix(mf->GetDefinition("CMAKE_STATIC_LIBRARY_PREFIX")); + this->AddLinkPrefix(mf->GetDefinition("CMAKE_SHARED_LIBRARY_PREFIX")); + + // Import library names should be matched and treated as shared + // libraries for the purposes of linking. + this->AddLinkExtension(mf->GetDefinition("CMAKE_IMPORT_LIBRARY_SUFFIX"), + LinkShared); + this->AddLinkExtension(mf->GetDefinition("CMAKE_STATIC_LIBRARY_SUFFIX"), + LinkStatic); + this->AddLinkExtension(mf->GetDefinition("CMAKE_SHARED_LIBRARY_SUFFIX"), + LinkShared); + this->AddLinkExtension(mf->GetDefinition("CMAKE_LINK_LIBRARY_SUFFIX"), + LinkUnknown); + if (const char* linkSuffixes = + mf->GetDefinition("CMAKE_EXTRA_LINK_EXTENSIONS")) { + std::vector<std::string> linkSuffixVec; + cmSystemTools::ExpandListArgument(linkSuffixes, linkSuffixVec); + for (std::string const& i : linkSuffixVec) { + this->AddLinkExtension(i.c_str(), LinkUnknown); + } + } + if (const char* sharedSuffixes = + mf->GetDefinition("CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES")) { + std::vector<std::string> sharedSuffixVec; + cmSystemTools::ExpandListArgument(sharedSuffixes, sharedSuffixVec); + for (std::string const& i : sharedSuffixVec) { + this->AddLinkExtension(i.c_str(), LinkShared); + } + } + + // Compute a regex to match link extensions. + std::string libext = + this->CreateExtensionRegex(this->LinkExtensions, LinkUnknown); + + // Create regex to remove any library extension. + std::string reg("(.*)"); + reg += libext; + this->OrderLinkerSearchPath->SetLinkExtensionInfo(this->LinkExtensions, reg); + + // Create a regex to match a library name. Match index 1 will be + // the prefix if it exists and empty otherwise. Match index 2 will + // be the library name. Match index 3 will be the library + // extension. + reg = "^("; + for (std::string const& p : this->LinkPrefixes) { + reg += p; + reg += "|"; + } + reg += ")"; + reg += "([^/:]*)"; + + // Create a regex to match any library name. + std::string reg_any = reg; + reg_any += libext; +#ifdef CM_COMPUTE_LINK_INFO_DEBUG + fprintf(stderr, "any regex [%s]\n", reg_any.c_str()); +#endif + this->ExtractAnyLibraryName.compile(reg_any.c_str()); + + // Create a regex to match static library names. + if (!this->StaticLinkExtensions.empty()) { + std::string reg_static = reg; + reg_static += + this->CreateExtensionRegex(this->StaticLinkExtensions, LinkStatic); +#ifdef CM_COMPUTE_LINK_INFO_DEBUG + fprintf(stderr, "static regex [%s]\n", reg_static.c_str()); +#endif + this->ExtractStaticLibraryName.compile(reg_static.c_str()); + } + + // Create a regex to match shared library names. + if (!this->SharedLinkExtensions.empty()) { + std::string reg_shared = reg; + this->SharedRegexString = + this->CreateExtensionRegex(this->SharedLinkExtensions, LinkShared); + reg_shared += this->SharedRegexString; +#ifdef CM_COMPUTE_LINK_INFO_DEBUG + fprintf(stderr, "shared regex [%s]\n", reg_shared.c_str()); +#endif + this->ExtractSharedLibraryName.compile(reg_shared.c_str()); + } +} + +void cmComputeLinkInformation::AddLinkPrefix(const char* p) +{ + if (p && *p) { + this->LinkPrefixes.insert(p); + } +} + +void cmComputeLinkInformation::AddLinkExtension(const char* e, LinkType type) +{ + if (e && *e) { + if (type == LinkStatic) { + this->StaticLinkExtensions.emplace_back(e); + } + if (type == LinkShared) { + this->SharedLinkExtensions.emplace_back(e); + } + this->LinkExtensions.emplace_back(e); + } +} + +std::string cmComputeLinkInformation::CreateExtensionRegex( + std::vector<std::string> const& exts, LinkType type) +{ + // Build a list of extension choices. + std::string libext = "("; + const char* sep = ""; + for (std::string const& i : exts) { + // Separate this choice from the previous one. + libext += sep; + sep = "|"; + + // Store this extension choice with the "." escaped. + libext += "\\"; +#if defined(_WIN32) && !defined(__CYGWIN__) + libext += this->NoCaseExpression(i.c_str()); +#else + libext += i; +#endif + } + + // Finish the list. + libext += ")"; + + // Add an optional OpenBSD-style version or major.minor.version component. + if (this->OpenBSD || type == LinkShared) { + libext += "(\\.[0-9]+)*"; + } + + libext += "$"; + return libext; +} + +std::string cmComputeLinkInformation::NoCaseExpression(const char* str) +{ + std::string ret; + const char* s = str; + while (*s) { + if (*s == '.') { + ret += *s; + } else { + ret += "["; + ret += static_cast<char>(tolower(*s)); + ret += static_cast<char>(toupper(*s)); + ret += "]"; + } + s++; + } + return ret; +} + +void cmComputeLinkInformation::SetCurrentLinkType(LinkType lt) +{ + // If we are changing the current link type add the flag to tell the + // linker about it. + if (this->CurrentLinkType != lt) { + this->CurrentLinkType = lt; + + if (this->LinkTypeEnabled) { + switch (this->CurrentLinkType) { + case LinkStatic: + this->Items.emplace_back(this->StaticLinkTypeFlag, false); + break; + case LinkShared: + this->Items.emplace_back(this->SharedLinkTypeFlag, false); + break; + default: + break; + } + } + } +} + +void cmComputeLinkInformation::AddTargetItem(std::string const& item, + cmGeneratorTarget const* target) +{ + // This is called to handle a link item that is a full path to a target. + // If the target is not a static library make sure the link type is + // shared. This is because dynamic-mode linking can handle both + // shared and static libraries but static-mode can handle only + // static libraries. If a previous user item changed the link type + // to static we need to make sure it is back to shared. + if (target->GetType() != cmStateEnums::STATIC_LIBRARY) { + this->SetCurrentLinkType(LinkShared); + } + + // Keep track of shared library targets linked. + if (target->GetType() == cmStateEnums::SHARED_LIBRARY) { + this->SharedLibrariesLinked.insert(target); + } + + // Handle case of an imported shared library with no soname. + if (this->NoSONameUsesPath && + target->IsImportedSharedLibWithoutSOName(this->Config)) { + this->AddSharedLibNoSOName(item); + return; + } + + // If this platform wants a flag before the full path, add it. + if (!this->LibLinkFileFlag.empty()) { + this->Items.emplace_back(this->LibLinkFileFlag, false); + } + + // For compatibility with CMake 2.4 include the item's directory in + // the linker search path. + if (this->OldLinkDirMode && !target->IsFrameworkOnApple() && + this->OldLinkDirMask.find(cmSystemTools::GetFilenamePath(item)) == + this->OldLinkDirMask.end()) { + this->OldLinkDirItems.push_back(item); + } + + // Now add the full path to the library. + this->Items.emplace_back(item, true, target); +} + +void cmComputeLinkInformation::AddFullItem(std::string const& item) +{ + // Check for the implicit link directory special case. + if (this->CheckImplicitDirItem(item)) { + return; + } + + // Check for case of shared library with no builtin soname. + if (this->NoSONameUsesPath && this->CheckSharedLibNoSOName(item)) { + return; + } + + // Full path libraries should specify a valid library file name. + // See documentation of CMP0008. + std::string generator = this->GlobalGenerator->GetName(); + if (this->Target->GetPolicyStatusCMP0008() != cmPolicies::NEW && + (generator.find("Visual Studio") != std::string::npos || + generator.find("Xcode") != std::string::npos)) { + std::string file = cmSystemTools::GetFilenameName(item); + if (!this->ExtractAnyLibraryName.find(file)) { + this->HandleBadFullItem(item, file); + return; + } + } + + // This is called to handle a link item that is a full path. + // If the target is not a static library make sure the link type is + // shared. This is because dynamic-mode linking can handle both + // shared and static libraries but static-mode can handle only + // static libraries. If a previous user item changed the link type + // to static we need to make sure it is back to shared. + if (this->LinkTypeEnabled) { + std::string name = cmSystemTools::GetFilenameName(item); + if (this->ExtractSharedLibraryName.find(name)) { + this->SetCurrentLinkType(LinkShared); + } else if (!this->ExtractStaticLibraryName.find(item)) { + // We cannot determine the type. Assume it is the target's + // default type. + this->SetCurrentLinkType(this->StartLinkType); + } + } + + // For compatibility with CMake 2.4 include the item's directory in + // the linker search path. + if (this->OldLinkDirMode && + this->OldLinkDirMask.find(cmSystemTools::GetFilenamePath(item)) == + this->OldLinkDirMask.end()) { + this->OldLinkDirItems.push_back(item); + } + + // If this platform wants a flag before the full path, add it. + if (!this->LibLinkFileFlag.empty()) { + this->Items.emplace_back(this->LibLinkFileFlag, false); + } + + // Now add the full path to the library. + this->Items.emplace_back(item, true); +} + +bool cmComputeLinkInformation::CheckImplicitDirItem(std::string const& item) +{ + // We only switch to a pathless item if the link type may be + // enforced. Fortunately only platforms that support link types + // seem to have magic per-architecture implicit link directories. + if (!this->LinkTypeEnabled) { + return false; + } + + // Check if this item is in an implicit link directory. + std::string dir = cmSystemTools::GetFilenamePath(item); + if (this->ImplicitLinkDirs.find(dir) == this->ImplicitLinkDirs.end()) { + // Only libraries in implicit link directories are converted to + // pathless items. + return false; + } + + // Only apply the policy below if the library file is one that can + // be found by the linker. + std::string file = cmSystemTools::GetFilenameName(item); + if (!this->ExtractAnyLibraryName.find(file)) { + return false; + } + + // Check the policy for whether we should use the approach below. + switch (this->Target->GetPolicyStatusCMP0060()) { + case cmPolicies::WARN: + if (this->CMP0060Warn) { + // Print the warning at most once for this item. + std::string const& wid = "CMP0060-WARNING-GIVEN-" + item; + if (!this->CMakeInstance->GetPropertyAsBool(wid)) { + this->CMakeInstance->SetProperty(wid, "1"); + this->CMP0060WarnItems.insert(item); + } + } + case cmPolicies::OLD: + break; + case cmPolicies::REQUIRED_ALWAYS: + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::NEW: + return false; + } + + // Many system linkers support multiple architectures by + // automatically selecting the implicit linker search path for the + // current architecture. If the library appears in an implicit link + // directory then just report the file name without the directory + // portion. This will allow the system linker to locate the proper + // library for the architecture at link time. + this->AddUserItem(file, false); + + // Make sure the link directory ordering will find the library. + this->OrderLinkerSearchPath->AddLinkLibrary(item); + + return true; +} + +void cmComputeLinkInformation::AddUserItem(std::string const& item, + bool pathNotKnown) +{ + // This is called to handle a link item that does not match a CMake + // target and is not a full path. We check here if it looks like a + // library file name to automatically request the proper link type + // from the linker. For example: + // + // foo ==> -lfoo + // libfoo.a ==> -Wl,-Bstatic -lfoo + + // Pass flags through untouched. + if (item[0] == '-' || item[0] == '$' || item[0] == '`') { + // if this is a -l option then we might need to warn about + // CMP0003 so put it in OldUserFlagItems, if it is not a -l + // or -Wl,-l (-framework -pthread), then allow it without a + // CMP0003 as -L will not affect those other linker flags + if (item.find("-l") == 0 || item.find("-Wl,-l") == 0) { + // This is a linker option provided by the user. + this->OldUserFlagItems.push_back(item); + } + + // Restore the target link type since this item does not specify + // one. + this->SetCurrentLinkType(this->StartLinkType); + + // Use the item verbatim. + this->Items.emplace_back(item, false); + return; + } + + // Parse out the prefix, base, and suffix components of the + // library name. If the name matches that of a shared or static + // library then set the link type accordingly. + // + // Search for shared library names first because some platforms + // have shared libraries with names that match the static library + // pattern. For example cygwin and msys use the convention + // libfoo.dll.a for import libraries and libfoo.a for static + // libraries. On AIX a library with the name libfoo.a can be + // shared! + std::string lib; + if (this->ExtractSharedLibraryName.find(item)) { +// This matches a shared library file name. +#ifdef CM_COMPUTE_LINK_INFO_DEBUG + fprintf(stderr, "shared regex matched [%s] [%s] [%s]\n", + this->ExtractSharedLibraryName.match(1).c_str(), + this->ExtractSharedLibraryName.match(2).c_str(), + this->ExtractSharedLibraryName.match(3).c_str()); +#endif + // Set the link type to shared. + this->SetCurrentLinkType(LinkShared); + + // Use just the library name so the linker will search. + lib = this->ExtractSharedLibraryName.match(2); + } else if (this->ExtractStaticLibraryName.find(item)) { +// This matches a static library file name. +#ifdef CM_COMPUTE_LINK_INFO_DEBUG + fprintf(stderr, "static regex matched [%s] [%s] [%s]\n", + this->ExtractStaticLibraryName.match(1).c_str(), + this->ExtractStaticLibraryName.match(2).c_str(), + this->ExtractStaticLibraryName.match(3).c_str()); +#endif + // Set the link type to static. + this->SetCurrentLinkType(LinkStatic); + + // Use just the library name so the linker will search. + lib = this->ExtractStaticLibraryName.match(2); + } else if (this->ExtractAnyLibraryName.find(item)) { +// This matches a library file name. +#ifdef CM_COMPUTE_LINK_INFO_DEBUG + fprintf(stderr, "any regex matched [%s] [%s] [%s]\n", + this->ExtractAnyLibraryName.match(1).c_str(), + this->ExtractAnyLibraryName.match(2).c_str(), + this->ExtractAnyLibraryName.match(3).c_str()); +#endif + // Restore the target link type since this item does not specify + // one. + this->SetCurrentLinkType(this->StartLinkType); + + // Use just the library name so the linker will search. + lib = this->ExtractAnyLibraryName.match(2); + } else { + // This is a name specified by the user. + if (pathNotKnown) { + this->OldUserFlagItems.push_back(item); + } + + // We must ask the linker to search for a library with this name. + // Restore the target link type since this item does not specify + // one. + this->SetCurrentLinkType(this->StartLinkType); + lib = item; + } + + // Create an option to ask the linker to search for the library. + std::string out = this->LibLinkFlag; + out += lib; + out += this->LibLinkSuffix; + this->Items.emplace_back(out, false); + + // 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 + // reliable and we want to encourage use of full paths for library + // specification. +} + +void cmComputeLinkInformation::AddFrameworkItem(std::string const& item) +{ + // Try to separate the framework name and path. + if (!this->SplitFramework.find(item)) { + std::ostringstream e; + e << "Could not parse framework path \"" << item << "\" " + << "linked by target " << this->Target->GetName() << "."; + cmSystemTools::Error(e.str()); + return; + } + + std::string fw_path = this->SplitFramework.match(1); + std::string fw = this->SplitFramework.match(2); + std::string full_fw = fw_path; + full_fw += "/"; + full_fw += fw; + full_fw += ".framework"; + full_fw += "/"; + full_fw += fw; + + // Add the directory portion to the framework search path. + this->AddFrameworkPath(fw_path); + + // add runtime information + this->AddLibraryRuntimeInfo(full_fw); + + // Add the item using the -framework option. + this->Items.emplace_back("-framework", false); + cmOutputConverter converter(this->Makefile->GetStateSnapshot()); + fw = converter.EscapeForShell(fw); + this->Items.emplace_back(fw, false); +} + +void cmComputeLinkInformation::AddDirectoryItem(std::string const& item) +{ + if (this->Makefile->IsOn("APPLE") && + cmSystemTools::IsPathToFramework(item)) { + this->AddFrameworkItem(item); + } else { + this->DropDirectoryItem(item); + } +} + +void cmComputeLinkInformation::DropDirectoryItem(std::string const& item) +{ + // A full path to a directory was found as a link item. Warn the + // user. + std::ostringstream e; + e << "WARNING: Target \"" << this->Target->GetName() + << "\" requests linking to directory \"" << item << "\". " + << "Targets may link only to libraries. " + << "CMake is dropping the item."; + cmSystemTools::Message(e.str()); +} + +void cmComputeLinkInformation::ComputeFrameworkInfo() +{ + // Avoid adding implicit framework paths. + std::vector<std::string> implicitDirVec; + + // Get platform-wide implicit directories. + if (const char* implicitLinks = this->Makefile->GetDefinition( + "CMAKE_PLATFORM_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES")) { + cmSystemTools::ExpandListArgument(implicitLinks, implicitDirVec); + } + + // Get language-specific implicit directories. + std::string implicitDirVar = "CMAKE_"; + implicitDirVar += this->LinkLanguage; + implicitDirVar += "_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES"; + if (const char* implicitDirs = + this->Makefile->GetDefinition(implicitDirVar)) { + cmSystemTools::ExpandListArgument(implicitDirs, implicitDirVec); + } + + this->FrameworkPathsEmmitted.insert(implicitDirVec.begin(), + implicitDirVec.end()); + + // Regular expression to extract a framework path and name. + this->SplitFramework.compile("(.*)/(.*)\\.framework$"); +} + +void cmComputeLinkInformation::AddFrameworkPath(std::string const& p) +{ + if (this->FrameworkPathsEmmitted.insert(p).second) { + this->FrameworkPaths.push_back(p); + } +} + +bool cmComputeLinkInformation::CheckSharedLibNoSOName(std::string const& item) +{ + // This platform will use the path to a library as its soname if the + // library is given via path and was not built with an soname. If + // this is a shared library that might be the case. + std::string file = cmSystemTools::GetFilenameName(item); + if (this->ExtractSharedLibraryName.find(file)) { + // If we can guess the soname fairly reliably then assume the + // library has one. Otherwise assume the library has no builtin + // soname. + std::string soname; + if (!cmSystemTools::GuessLibrarySOName(item, soname)) { + this->AddSharedLibNoSOName(item); + return true; + } + } + return false; +} + +void cmComputeLinkInformation::AddSharedLibNoSOName(std::string const& item) +{ + // We have a full path to a shared library with no soname. We need + // to ask the linker to locate the item because otherwise the path + // we give to it will be embedded in the target linked. Then at + // runtime the dynamic linker will search for the library using the + // path instead of just the name. + std::string file = cmSystemTools::GetFilenameName(item); + this->AddUserItem(file, false); + + // Make sure the link directory ordering will find the library. + this->OrderLinkerSearchPath->AddLinkLibrary(item); +} + +void cmComputeLinkInformation::HandleBadFullItem(std::string const& item, + std::string const& file) +{ + // Do not depend on things that do not exist. + std::vector<std::string>::iterator i = + std::find(this->Depends.begin(), this->Depends.end(), item); + if (i != this->Depends.end()) { + this->Depends.erase(i); + } + + // Tell the linker to search for the item and provide the proper + // path for it. Do not contribute to any CMP0003 warning (do not + // put in OldLinkDirItems or OldUserFlagItems). + this->AddUserItem(file, false); + this->OrderLinkerSearchPath->AddLinkLibrary(item); + + // Produce any needed message. + switch (this->Target->GetPolicyStatusCMP0008()) { + case cmPolicies::WARN: { + // Print the warning at most once for this item. + std::string wid = "CMP0008-WARNING-GIVEN-"; + wid += item; + if (!this->CMakeInstance->GetState()->GetGlobalPropertyAsBool(wid)) { + this->CMakeInstance->GetState()->SetGlobalProperty(wid, "1"); + std::ostringstream w; + /* clang-format off */ + w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0008) << "\n" + << "Target \"" << this->Target->GetName() << "\" links to item\n" + << " " << item << "\n" + << "which is a full-path but not a valid library file name."; + /* clang-format on */ + this->CMakeInstance->IssueMessage(MessageType::AUTHOR_WARNING, w.str(), + this->Target->GetBacktrace()); + } + } + case cmPolicies::OLD: + // OLD behavior does not warn. + break; + case cmPolicies::NEW: + // NEW behavior will not get here. + break; + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::REQUIRED_ALWAYS: { + std::ostringstream e; + /* clang-format off */ + e << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0008) << "\n" + << "Target \"" << this->Target->GetName() << "\" links to item\n" + << " " << item << "\n" + << "which is a full-path but not a valid library file name."; + /* clang-format on */ + this->CMakeInstance->IssueMessage(MessageType::FATAL_ERROR, e.str(), + this->Target->GetBacktrace()); + } break; + } +} + +bool cmComputeLinkInformation::FinishLinkerSearchDirectories() +{ + // Support broken projects if necessary. + if (this->OldLinkDirItems.empty() || this->OldUserFlagItems.empty() || + !this->OldLinkDirMode) { + return true; + } + + // Enforce policy constraints. + switch (this->Target->GetPolicyStatusCMP0003()) { + case cmPolicies::WARN: + if (!this->CMakeInstance->GetState()->GetGlobalPropertyAsBool( + "CMP0003-WARNING-GIVEN")) { + this->CMakeInstance->GetState()->SetGlobalProperty( + "CMP0003-WARNING-GIVEN", "1"); + std::ostringstream w; + this->PrintLinkPolicyDiagnosis(w); + this->CMakeInstance->IssueMessage(MessageType::AUTHOR_WARNING, w.str(), + this->Target->GetBacktrace()); + } + case cmPolicies::OLD: + // OLD behavior is to add the paths containing libraries with + // known full paths as link directories. + break; + case cmPolicies::NEW: + // Should never happen due to assignment of OldLinkDirMode + return true; + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::REQUIRED_ALWAYS: { + std::ostringstream e; + e << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0003) << "\n"; + this->PrintLinkPolicyDiagnosis(e); + this->CMakeInstance->IssueMessage(MessageType::FATAL_ERROR, e.str(), + this->Target->GetBacktrace()); + return false; + } + } + + // Add the link directories for full path items. + for (std::string const& i : this->OldLinkDirItems) { + this->OrderLinkerSearchPath->AddLinkLibrary(i); + } + return true; +} + +void cmComputeLinkInformation::PrintLinkPolicyDiagnosis(std::ostream& os) +{ + // Tell the user what to do. + /* clang-format off */ + os << "Policy CMP0003 should be set before this line. " + << "Add code such as\n" + << " if(COMMAND cmake_policy)\n" + << " cmake_policy(SET CMP0003 NEW)\n" + << " endif(COMMAND cmake_policy)\n" + << "as early as possible but after the most recent call to " + << "cmake_minimum_required or cmake_policy(VERSION). "; + /* clang-format on */ + + // List the items that might need the old-style paths. + os << "This warning appears because target \"" << this->Target->GetName() + << "\" " + << "links to some libraries for which the linker must search:\n"; + { + // Format the list of unknown items to be as short as possible while + // still fitting in the allowed width (a true solution would be the + // bin packing problem if we were allowed to change the order). + std::string::size_type max_size = 76; + std::string line; + const char* sep = " "; + for (std::string const& i : this->OldUserFlagItems) { + // If the addition of another item will exceed the limit then + // output the current line and reset it. Note that the separator + // is either " " or ", " which is always 2 characters. + if (!line.empty() && (line.size() + i.size() + 2) > max_size) { + os << line << "\n"; + sep = " "; + line.clear(); + } + line += sep; + line += i; + // Convert to the other separator. + sep = ", "; + } + if (!line.empty()) { + os << line << "\n"; + } + } + + // List the paths old behavior is adding. + os << "and other libraries with known full path:\n"; + std::set<std::string> emitted; + for (std::string const& i : this->OldLinkDirItems) { + if (emitted.insert(cmSystemTools::GetFilenamePath(i)).second) { + os << " " << i << "\n"; + } + } + + // Explain. + os << "CMake is adding directories in the second list to the linker " + << "search path in case they are needed to find libraries from the " + << "first list (for backwards compatibility with CMake 2.4). " + << "Set policy CMP0003 to OLD or NEW to enable or disable this " + << "behavior explicitly. " + << "Run \"cmake --help-policy CMP0003\" for more information."; +} + +void cmComputeLinkInformation::LoadImplicitLinkInfo() +{ + std::vector<std::string> implicitDirVec; + + // Get platform-wide implicit directories. + if (const char* implicitLinks = (this->Makefile->GetDefinition( + "CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES"))) { + cmSystemTools::ExpandListArgument(implicitLinks, implicitDirVec); + } + + // Append library architecture to all implicit platform directories + // and add them to the set + if (const char* libraryArch = + this->Makefile->GetDefinition("CMAKE_LIBRARY_ARCHITECTURE")) { + for (std::string const& i : implicitDirVec) { + this->ImplicitLinkDirs.insert(i + "/" + libraryArch); + } + } + + // Get language-specific implicit directories. + std::string implicitDirVar = "CMAKE_"; + implicitDirVar += this->LinkLanguage; + implicitDirVar += "_IMPLICIT_LINK_DIRECTORIES"; + if (const char* implicitDirs = + this->Makefile->GetDefinition(implicitDirVar)) { + cmSystemTools::ExpandListArgument(implicitDirs, implicitDirVec); + } + + // Store implicit link directories. + this->ImplicitLinkDirs.insert(implicitDirVec.begin(), implicitDirVec.end()); + + // Get language-specific implicit libraries. + std::vector<std::string> implicitLibVec; + std::string implicitLibVar = "CMAKE_"; + implicitLibVar += this->LinkLanguage; + implicitLibVar += "_IMPLICIT_LINK_LIBRARIES"; + if (const char* implicitLibs = + this->Makefile->GetDefinition(implicitLibVar)) { + cmSystemTools::ExpandListArgument(implicitLibs, implicitLibVec); + } + + // Store implicit link libraries. + for (std::string const& item : implicitLibVec) { + // Items starting in '-' but not '-l' are flags, not libraries, + // and should not be filtered by this implicit list. + if (item[0] != '-' || item[1] == 'l') { + this->ImplicitLinkLibs.insert(item); + } + } + + // Get platform specific rpath link directories + if (const char* rpathDirs = + (this->Makefile->GetDefinition("CMAKE_PLATFORM_RUNTIME_PATH"))) { + cmSystemTools::ExpandListArgument(rpathDirs, this->RuntimeLinkDirs); + } +} + +std::vector<std::string> const& +cmComputeLinkInformation::GetRuntimeSearchPath() const +{ + return this->OrderRuntimeSearchPath->GetOrderedDirectories(); +} + +void cmComputeLinkInformation::AddLibraryRuntimeInfo( + std::string const& fullPath, cmGeneratorTarget const* target) +{ + // Ignore targets on Apple where install_name is not @rpath. + // The dependenty library can be found with other means such as + // @loader_path or full paths. + if (this->Makefile->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) { + if (!target->HasMacOSXRpathInstallNameDir(this->Config)) { + return; + } + } + + // Libraries with unknown type must be handled using just the file + // on disk. + if (target->GetType() == cmStateEnums::UNKNOWN_LIBRARY) { + this->AddLibraryRuntimeInfo(fullPath); + return; + } + + // Skip targets that are not shared libraries (modules cannot be linked). + if (target->GetType() != cmStateEnums::SHARED_LIBRARY) { + return; + } + + // Try to get the soname of the library. Only files with this name + // could possibly conflict. + std::string soName = target->GetSOName(this->Config); + const char* soname = soName.empty() ? nullptr : soName.c_str(); + + // Include this library in the runtime path ordering. + this->OrderRuntimeSearchPath->AddRuntimeLibrary(fullPath, soname); + if (this->LinkWithRuntimePath) { + this->OrderLinkerSearchPath->AddRuntimeLibrary(fullPath, soname); + } +} + +void cmComputeLinkInformation::AddLibraryRuntimeInfo( + std::string const& fullPath) +{ + // Get the name of the library from the file name. + bool is_shared_library = false; + std::string file = cmSystemTools::GetFilenameName(fullPath); + + if (this->Makefile->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) { + // Check that @rpath is part of the install name. + // If it isn't, return. + std::string soname; + if (!cmSystemTools::GuessLibraryInstallName(fullPath, soname)) { + return; + } + + if (soname.find("@rpath") == std::string::npos) { + return; + } + } + + is_shared_library = this->ExtractSharedLibraryName.find(file); + + if (!is_shared_library) { + // On some platforms (AIX) a shared library may look static. + if (this->ArchivesMayBeShared) { + if (this->ExtractStaticLibraryName.find(file)) { + // This is the name of a shared library or archive. + is_shared_library = true; + } + } + } + + // It could be an Apple framework + if (!is_shared_library) { + if (fullPath.find(".framework") != std::string::npos) { + static cmsys::RegularExpression splitFramework( + "^(.*)/(.*).framework/(.*)$"); + if (splitFramework.find(fullPath) && + (std::string::npos != + splitFramework.match(3).find(splitFramework.match(2)))) { + is_shared_library = true; + } + } + } + + if (!is_shared_library) { + return; + } + + // Include this library in the runtime path ordering. + this->OrderRuntimeSearchPath->AddRuntimeLibrary(fullPath); + if (this->LinkWithRuntimePath) { + this->OrderLinkerSearchPath->AddRuntimeLibrary(fullPath); + } +} + +static void cmCLI_ExpandListUnique(const char* str, + std::vector<std::string>& out, + std::set<std::string>& emitted) +{ + std::vector<std::string> tmp; + cmSystemTools::ExpandListArgument(str, tmp); + for (std::string const& i : tmp) { + if (emitted.insert(i).second) { + out.push_back(i); + } + } +} + +void cmComputeLinkInformation::GetRPath(std::vector<std::string>& runtimeDirs, + bool for_install) const +{ + // Select whether to generate runtime search directories. + bool outputRuntime = + !this->Makefile->IsOn("CMAKE_SKIP_RPATH") && !this->RuntimeFlag.empty(); + + // Select whether to generate an rpath for the install tree or the + // build tree. + bool linking_for_install = + (for_install || + this->Target->GetPropertyAsBool("BUILD_WITH_INSTALL_RPATH")); + bool use_install_rpath = + (outputRuntime && this->Target->HaveInstallTreeRPATH() && + linking_for_install); + bool use_build_rpath = + (outputRuntime && this->Target->HaveBuildTreeRPATH(this->Config) && + !linking_for_install); + bool use_link_rpath = outputRuntime && linking_for_install && + !this->Makefile->IsOn("CMAKE_SKIP_INSTALL_RPATH") && + this->Target->GetPropertyAsBool("INSTALL_RPATH_USE_LINK_PATH"); + + // Select whether to use $ORIGIN in RPATHs for artifacts in the build tree. + std::string const& originToken = this->Makefile->GetSafeDefinition( + "CMAKE_SHARED_LIBRARY_RPATH_ORIGIN_TOKEN"); + std::string targetOutputDir = this->Target->GetDirectory(this->Config); + bool use_relative_build_rpath = + this->Target->GetPropertyAsBool("BUILD_RPATH_USE_ORIGIN") && + !originToken.empty() && !targetOutputDir.empty(); + + // Construct the RPATH. + std::set<std::string> emitted; + if (use_install_rpath) { + const char* install_rpath = this->Target->GetProperty("INSTALL_RPATH"); + cmCLI_ExpandListUnique(install_rpath, runtimeDirs, emitted); + } + if (use_build_rpath) { + // Add directories explicitly specified by user + if (const char* build_rpath = this->Target->GetProperty("BUILD_RPATH")) { + // This will not resolve entries to use $ORIGIN, the user is expected to + // do that if necessary. + cmCLI_ExpandListUnique(build_rpath, runtimeDirs, emitted); + } + } + if (use_build_rpath || use_link_rpath) { + std::string rootPath; + if (const char* sysrootLink = + this->Makefile->GetDefinition("CMAKE_SYSROOT_LINK")) { + rootPath = sysrootLink; + } else { + rootPath = this->Makefile->GetSafeDefinition("CMAKE_SYSROOT"); + } + const char* stagePath = + this->Makefile->GetDefinition("CMAKE_STAGING_PREFIX"); + std::string const& installPrefix = + this->Makefile->GetSafeDefinition("CMAKE_INSTALL_PREFIX"); + cmSystemTools::ConvertToUnixSlashes(rootPath); + std::vector<std::string> const& rdirs = this->GetRuntimeSearchPath(); + std::string const& topBinaryDir = + this->CMakeInstance->GetHomeOutputDirectory(); + for (std::string const& ri : rdirs) { + // Put this directory in the rpath if using build-tree rpath + // support or if using the link path as an rpath. + if (use_build_rpath) { + std::string d = ri; + if (!rootPath.empty() && d.find(rootPath) == 0) { + d = d.substr(rootPath.size()); + } else if (stagePath && *stagePath && d.find(stagePath) == 0) { + std::string suffix = d.substr(strlen(stagePath)); + d = installPrefix; + d += "/"; + d += suffix; + cmSystemTools::ConvertToUnixSlashes(d); + } else if (use_relative_build_rpath) { + // If expansion of the $ORIGIN token is supported and permitted per + // policy, use relative paths in the RPATH. + if (cmSystemTools::ComparePath(d, topBinaryDir) || + cmSystemTools::IsSubDirectory(d, topBinaryDir)) { + d = cmSystemTools::RelativePath(targetOutputDir, d); + if (!d.empty()) { + d = originToken + "/" + d; + } else { + d = originToken; + } + } + } + if (emitted.insert(d).second) { + runtimeDirs.push_back(std::move(d)); + } + } else if (use_link_rpath) { + // Do not add any path inside the source or build tree. + std::string const& topSourceDir = + this->CMakeInstance->GetHomeDirectory(); + if (!cmSystemTools::ComparePath(ri, topSourceDir) && + !cmSystemTools::ComparePath(ri, topBinaryDir) && + !cmSystemTools::IsSubDirectory(ri, topSourceDir) && + !cmSystemTools::IsSubDirectory(ri, topBinaryDir)) { + std::string d = ri; + if (!rootPath.empty() && d.find(rootPath) == 0) { + d = d.substr(rootPath.size()); + } else if (stagePath && *stagePath && d.find(stagePath) == 0) { + std::string suffix = d.substr(strlen(stagePath)); + d = installPrefix; + d += "/"; + d += suffix; + cmSystemTools::ConvertToUnixSlashes(d); + } + if (emitted.insert(d).second) { + runtimeDirs.push_back(std::move(d)); + } + } + } + } + } + + // Add runtime paths required by the languages to always be + // present. This is done even when skipping rpath support. + { + cmGeneratorTarget::LinkClosure const* lc = + this->Target->GetLinkClosure(this->Config); + for (std::string const& li : lc->Languages) { + std::string useVar = + "CMAKE_" + li + "_USE_IMPLICIT_LINK_DIRECTORIES_IN_RUNTIME_PATH"; + if (this->Makefile->IsOn(useVar)) { + std::string dirVar = "CMAKE_" + li + "_IMPLICIT_LINK_DIRECTORIES"; + if (const char* dirs = this->Makefile->GetDefinition(dirVar)) { + cmCLI_ExpandListUnique(dirs, runtimeDirs, emitted); + } + } + } + } + + // Add runtime paths required by the platform to always be + // present. This is done even when skipping rpath support. + cmCLI_ExpandListUnique(this->RuntimeAlways.c_str(), runtimeDirs, emitted); +} + +std::string cmComputeLinkInformation::GetRPathString(bool for_install) const +{ + // Get the directories to use. + std::vector<std::string> runtimeDirs; + this->GetRPath(runtimeDirs, for_install); + + // Concatenate the paths. + std::string rpath = cmJoin(runtimeDirs, this->GetRuntimeSep()); + + // If the rpath will be replaced at install time, prepare space. + if (!for_install && this->RuntimeUseChrpath) { + if (!rpath.empty()) { + // Add one trailing separator so the linker does not re-use the + // rpath .dynstr entry for a symbol name that happens to match + // the end of the rpath string. + rpath += this->GetRuntimeSep(); + } + + // Make sure it is long enough to hold the replacement value. + std::string::size_type minLength = this->GetChrpathString().length(); + while (rpath.length() < minLength) { + rpath += this->GetRuntimeSep(); + } + } + + return rpath; +} + +std::string cmComputeLinkInformation::GetChrpathString() const +{ + if (!this->RuntimeUseChrpath) { + return ""; + } + + return this->GetRPathString(true); +} |