diff options
author | Brad King <brad.king@kitware.com> | 2008-01-22 14:13:04 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2008-01-22 14:13:04 (GMT) |
commit | 96fd5909d9dd1ffe740230ce652d574cd01c62d5 (patch) | |
tree | e83b3ce2d5066660256a69cacb6caa7d2d95c416 /Source/cmComputeLinkInformation.cxx | |
parent | 0df9e6904cd2416336a85d6d7972ce84dcbab417 (diff) | |
download | CMake-96fd5909d9dd1ffe740230ce652d574cd01c62d5.zip CMake-96fd5909d9dd1ffe740230ce652d574cd01c62d5.tar.gz CMake-96fd5909d9dd1ffe740230ce652d574cd01c62d5.tar.bz2 |
ENH: Implement linking with paths to library files instead of -L and -l separation. See bug #3832
- This is purely an implementation improvement. No interface has changed.
- Create cmComputeLinkInformation class
- Move and re-implement logic from:
cmLocalGenerator::ComputeLinkInformation
cmOrderLinkDirectories
- Link libraries to targets with their full path (if it is known)
- Dirs specified with link_directories command still added with -L
- Make link type specific to library names without paths
(name libfoo.a without path becomes -Wl,-Bstatic -lfoo)
- Make directory ordering specific to a runtime path computation feature
(look for conflicting SONAMEs instead of library names)
- Implement proper rpath support on HP-UX and AIX.
Diffstat (limited to 'Source/cmComputeLinkInformation.cxx')
-rw-r--r-- | Source/cmComputeLinkInformation.cxx | 1157 |
1 files changed, 1157 insertions, 0 deletions
diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx new file mode 100644 index 0000000..50f69a2 --- /dev/null +++ b/Source/cmComputeLinkInformation.cxx @@ -0,0 +1,1157 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. + See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#include "cmComputeLinkInformation.h" + +#include "cmGlobalGenerator.h" +#include "cmLocalGenerator.h" +#include "cmMakefile.h" +#include "cmTarget.h" + +#include <algorithm> + +#include <ctype.h> + +/* +Notes about linking on various platforms: + +------------------------------------------------------------------------------ + +Linux, FreeBSD, Mac OS X, IRIX, 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). +*/ + +//---------------------------------------------------------------------------- +cmComputeLinkInformation +::cmComputeLinkInformation(cmTarget* target, const char* config) +{ + // Store context information. + this->Target = target; + this->Makefile = this->Target->GetMakefile(); + this->LocalGenerator = this->Makefile->GetLocalGenerator(); + this->GlobalGenerator = this->LocalGenerator->GetGlobalGenerator(); + + // The configuration being linked. + this->Config = config; + + // Get the language used for linking this target. + this->LinkLanguage = + this->Target->GetLinkerLanguage(this->GlobalGenerator); + + // Check whether we should use an import library for linking a target. + this->UseImportLibrary = + this->Makefile->GetDefinition("CMAKE_IMPORT_LIBRARY_SUFFIX")?true:false; + + // 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 = 0; + if(!this->UseImportLibrary && + this->Target->GetType() == cmTarget::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.c_str()); + } + + // Get options needed to link libraries. + this->LibLinkFlag = + this->Makefile->GetSafeDefinition("CMAKE_LINK_LIBRARY_FLAG"); + this->LibLinkSuffix = + this->Makefile->GetSafeDefinition("CMAKE_LINK_LIBRARY_SUFFIX"); + + // Get link type information. + this->ComputeLinkTypeInfo(); + + // Setup the link item parser. + this->ComputeItemParserInfo(); + + // Setup framework support. + this->ComputeFrameworkInfo(); + + // Initial state. + this->RuntimeSearchPathComputed = false; +} + +//---------------------------------------------------------------------------- +cmComputeLinkInformation::ItemVector const& +cmComputeLinkInformation::GetItems() +{ + return this->Items; +} + +//---------------------------------------------------------------------------- +std::vector<std::string> const& cmComputeLinkInformation::GetDirectories() +{ + return this->Directories; +} + +//---------------------------------------------------------------------------- +std::vector<std::string> const& cmComputeLinkInformation::GetDepends() +{ + return this->Depends; +} + +//---------------------------------------------------------------------------- +std::vector<std::string> const& cmComputeLinkInformation::GetFrameworkPaths() +{ + return this->FrameworkPaths; +} + +//---------------------------------------------------------------------------- +bool cmComputeLinkInformation::Compute() +{ + // Skip targets that do not link. + if(!(this->Target->GetType() == cmTarget::EXECUTABLE || + this->Target->GetType() == cmTarget::SHARED_LIBRARY || + this->Target->GetType() == cmTarget::MODULE_LIBRARY || + this->Target->GetType() == cmTarget::STATIC_LIBRARY)) + { + return false; + } + + // We require a link language for the target. + if(!this->LinkLanguage) + { + cmSystemTools:: + Error("CMake can not determine linker language for target:", + this->Target->GetName()); + return false; + } + + // Compute which library configuration to link. + cmTarget::LinkLibraryType linkType = cmTarget::OPTIMIZED; + if(this->Config && cmSystemTools::UpperCase(this->Config) == "DEBUG") + { + linkType = cmTarget::DEBUG; + } + + // Get the list of libraries against which this target wants to link. + { + const cmTarget::LinkLibraryVectorType& libs = + this->Target->GetLinkLibraries(); + for(cmTarget::LinkLibraryVectorType::const_iterator li = libs.begin(); + li != libs.end(); ++li) + { + // Link to a library if it is not the same target and is meant for + // this configuration type. + if((this->Target->GetType() == cmTarget::EXECUTABLE || + li->first != this->Target->GetName()) && + (li->second == cmTarget::GENERAL || li->second == linkType)) + { + this->AddItem(li->first); + } + } + } + + // Restore the target link type so the correct system runtime + // libraries are found. + this->SetCurrentLinkType(this->StartLinkType); + + // Compute the linker search path. + this->ComputeLinkerSearchDirectories(); + + return true; +} + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::AddItem(std::string const& item) +{ + // Compute the proper name to use to link this library. + // TODO: Change third argument to support imported libraries. + cmTarget* tgt = this->GlobalGenerator->FindTarget(0, item.c_str(), false); + const char* config = this->Config; + bool implib = this->UseImportLibrary; + bool impexe = (tgt && + tgt->GetType() == cmTarget::EXECUTABLE && + tgt->GetPropertyAsBool("ENABLE_EXPORTS")); + if(impexe && !implib && !this->LoaderFlag) + { + // Skip linking to executables on platforms with no import + // libraries or loader flags. + return; + } + + if(tgt && (tgt->GetType() == cmTarget::STATIC_LIBRARY || + tgt->GetType() == cmTarget::SHARED_LIBRARY || + tgt->GetType() == cmTarget::MODULE_LIBRARY || + impexe)) + { + // 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; + std::string exe = tgt->GetFullPath(config, implib); + linkItem += exe; + this->Items.push_back(Item(linkItem, true)); + this->Depends.push_back(exe); + } + else + { + // Pass the full path to the target file. + std::string lib = tgt->GetFullPath(config, implib); + this->Depends.push_back(lib); +#ifdef __APPLE__ + if(tgt->GetType() == cmTarget::SHARED_LIBRARY && + tgt->GetPropertyAsBool("FRAMEWORK")) + { + // Frameworks on OS X need only the framework directory to + // link. + std::string fw = tgt->GetDirectory(config, implib); + this->AddFrameworkItem(fw); + } + else +#endif + { + this->Items.push_back(Item(lib, true)); + this->AddLibraryRuntimeInfo(lib, tgt); + } + } + } + else + { + // This is not a CMake target. Use the name given. + if(cmSystemTools::FileIsFullPath(item.c_str())) + { + if(cmSystemTools::FileIsDirectory(item.c_str())) + { + // This is a directory. + this->AddDirectoryItem(item); + } + else + { + // Use the full path given to the library file. + this->Items.push_back(Item(item, true)); + this->Depends.push_back(item); + this->AddLibraryRuntimeInfo(item); + } + } + else + { + // This is a library or option specified by the user. + this->AddUserItem(item); + } + } +} + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::ComputeLinkTypeInfo() +{ + // First assume we cannot do link type stuff. + this->LinkTypeEnabled = false; + + // Lookup link type selection flags. + const char* static_link_type_flag = 0; + const char* shared_link_type_flag = 0; + const char* target_type_str = 0; + switch(this->Target->GetType()) + { + case cmTarget::EXECUTABLE: target_type_str = "EXE"; break; + case cmTarget::SHARED_LIBRARY: target_type_str = "SHARED_LIBRARY"; break; + case cmTarget::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.c_str()); + + 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.c_str()); + } + + // 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; + } + + // TODO: Lookup the starting link type from the target (is it being + // linked statically?). + this->StartLinkType = 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::vector<std::string>::iterator i = linkSuffixVec.begin(); + i != linkSuffixVec.end(); ++i) + { + this->AddLinkExtension(i->c_str(), LinkUnknown); + } + } + + // Compute a regex to match link extensions. + std::string libext = this->CreateExtensionRegex(this->LinkExtensions); + + // Create regex to remove any library extension. + std::string reg("(.*)"); + reg += libext; + this->RemoveLibraryExtension.compile(reg.c_str()); + + // 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::set<cmStdString>::iterator p = this->LinkPrefixes.begin(); + p != this->LinkPrefixes.end(); ++p) + { + 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); +#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; + reg_shared += this->CreateExtensionRegex(this->SharedLinkExtensions); +#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) + { + this->LinkPrefixes.insert(p); + } +} + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::AddLinkExtension(const char* e, LinkType type) +{ + if(e && *e) + { + if(type == LinkStatic) + { + this->StaticLinkExtensions.push_back(e); + } + if(type == LinkShared) + { + this->SharedLinkExtensions.push_back(e); + } + this->LinkExtensions.push_back(e); + } +} + +//---------------------------------------------------------------------------- +std::string +cmComputeLinkInformation +::CreateExtensionRegex(std::vector<std::string> const& exts) +{ + // Build a list of extension choices. + std::string libext = "("; + const char* sep = ""; + for(std::vector<std::string>::const_iterator i = exts.begin(); + i != exts.end(); ++i) + { + // 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 += ").*"; + 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 += tolower(*s); + ret += 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.push_back(Item(this->StaticLinkTypeFlag, false)); + break; + case LinkShared: + this->Items.push_back(Item(this->SharedLinkTypeFlag, false)); + break; + default: + break; + } + } + } +} + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::AddUserItem(std::string const& item) +{ + // 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 + std::string lib; + + // 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! + 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 if(item[0] == '-' || item[0] == '$' || item[0] == '`') + { + // This is a linker option provided by the user. Restore the + // target link type since this item does not specify one. + this->SetCurrentLinkType(this->StartLinkType); + + // Use the item verbatim. + this->Items.push_back(Item(item, false)); + return; + } + else + { + // This is a name specified by the user. 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.push_back(Item(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.c_str())) + { + cmOStringStream e; + e << "Could not parse framework path \"" << item << "\" " + << "linked by target " << this->Target->GetName() << "."; + cmSystemTools::Error(e.str().c_str()); + return; + } + + // Add the directory portion to the framework search path. + this->AddFrameworkPath(this->SplitFramework.match(1)); + + // Add the item using the -framework option. + std::string fw = "-framework "; + fw += this->SplitFramework.match(2); + this->Items.push_back(Item(fw, false)); +} + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::AddDirectoryItem(std::string const& item) +{ +#ifdef __APPLE__ + if(cmSystemTools::IsPathToFramework(item.c_str())) + { + this->AddFrameworkItem(item); + } + else +#endif + { + 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. + cmOStringStream 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().c_str()); +} + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::ComputeFrameworkInfo() +{ + // Avoid adding system framework paths. See "man ld" on OS X. + this->FrameworkPathsEmmitted.insert("/Library/Frameworks"); + this->FrameworkPathsEmmitted.insert("/Network/Library/Frameworks"); + this->FrameworkPathsEmmitted.insert("/System/Library/Frameworks"); + + // 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); + } +} + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::ComputeLinkerSearchDirectories() +{ + // Some search paths should never be emitted. + this->DirectoriesEmmitted.insert(""); + if(const char* implicitLinks = + (this->Makefile->GetDefinition + ("CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES"))) + { + std::vector<std::string> implicitLinkVec; + cmSystemTools::ExpandListArgument(implicitLinks, implicitLinkVec); + for(std::vector<std::string>::const_iterator + i = implicitLinkVec.begin(); + i != implicitLinkVec.end(); ++i) + { + this->DirectoriesEmmitted.insert(*i); + } + } + + // 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"; + if(this->Makefile->IsOn(var.c_str())) + { + // This platform requires the runtime library path for shared + // libraries to be specified at link time as -L paths. It needs + // them so that transitive dependencies of the libraries linked + // may be found by the linker. + this->AddLinkerSearchDirectories(this->GetRuntimeSearchPath()); + } + + // Get the search path entries requested by the user. + this->AddLinkerSearchDirectories(this->Target->GetLinkDirectories()); +} + +//---------------------------------------------------------------------------- +void +cmComputeLinkInformation +::AddLinkerSearchDirectories(std::vector<std::string> const& dirs) +{ + for(std::vector<std::string>::const_iterator i = dirs.begin(); + i != dirs.end(); ++i) + { + if(this->DirectoriesEmmitted.insert(*i).second) + { + this->Directories.push_back(*i); + } + } +} + +//---------------------------------------------------------------------------- +std::vector<std::string> const& +cmComputeLinkInformation::GetRuntimeSearchPath() +{ + if(!this->RuntimeSearchPathComputed) + { + this->RuntimeSearchPathComputed = true; + this->CollectRuntimeDirectories(); + this->FindConflictingLibraries(); + this->OrderRuntimeSearchPath(); + } + return this->RuntimeSearchPath; +} + +//============================================================================ +// Directory ordering computation. +// - Useful to compute a safe runtime library path order +// - Need runtime path for supporting INSTALL_RPATH_USE_LINK_PATH +// - Need runtime path at link time to pickup transitive link dependencies +// for shared libraries (in future when we do not always add them). + +//---------------------------------------------------------------------------- +void +cmComputeLinkInformation::AddLibraryRuntimeInfo(std::string const& fullPath, + cmTarget* target) +{ + // Skip targets that are not shared libraries (modules cannot be linked). + if(target->GetType() != cmTarget::SHARED_LIBRARY) + { + return; + } + + // Try to get the soname of the library. Only files with this name + // could possibly conflict. + std::string soName; + const char* soname = 0; + if(!target->IsImported()) + { + std::string name; + std::string realName; + std::string impName; + std::string pdbName; + target->GetLibraryNames(name, soName, realName, impName, pdbName, + this->Config); + soname = soName.c_str(); + } + + // Add the library runtime entry. + this->AddLibraryRuntimeInfo(fullPath, soname); +} + +//---------------------------------------------------------------------------- +void +cmComputeLinkInformation::AddLibraryRuntimeInfo(std::string const& fullPath, + const char* soname) +{ + // Get the name of the library from the file name. + std::string file = cmSystemTools::GetFilenameName(fullPath); + if(!this->ExtractSharedLibraryName.find(file.c_str())) + { + // This is not the name of a shared library. + return; + } + + // Add the runtime information at most once. + if(this->LibraryRuntimeInfoEmmitted.insert(fullPath).second) + { + // Construct the runtime information entry for this library. + LibraryRuntimeEntry entry; + entry.FileName = cmSystemTools::GetFilenameName(fullPath); + entry.SOName = soname? soname : ""; + entry.Directory = cmSystemTools::GetFilenamePath(fullPath); + this->LibraryRuntimeInfo.push_back(entry); + } + else + { + // This can happen if the same library is linked multiple times. + // In that case the runtime information check need be done only + // once anyway. For shared libs we could add a check in AddItem + // to not repeat them. + } +} + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::CollectRuntimeDirectories() +{ + // Get all directories that should be in the runtime search path. + + // Add directories containing libraries linked with full path. + for(std::vector<LibraryRuntimeEntry>::iterator + ei = this->LibraryRuntimeInfo.begin(); + ei != this->LibraryRuntimeInfo.end(); ++ei) + { + ei->DirectoryIndex = this->AddRuntimeDirectory(ei->Directory); + } + + // Add link directories specified for the target. + std::vector<std::string> const& dirs = this->GetDirectories(); + for(std::vector<std::string>::const_iterator di = dirs.begin(); + di != dirs.end(); ++di) + { + this->AddRuntimeDirectory(*di); + } +} + +//---------------------------------------------------------------------------- +int cmComputeLinkInformation::AddRuntimeDirectory(std::string const& dir) +{ + // Add the runtime directory with a unique index. + std::map<cmStdString, int>::iterator i = + this->RuntimeDirectoryIndex.find(dir); + if(i == this->RuntimeDirectoryIndex.end()) + { + std::map<cmStdString, int>::value_type + entry(dir, static_cast<int>(this->RuntimeDirectories.size())); + i = this->RuntimeDirectoryIndex.insert(entry).first; + this->RuntimeDirectories.push_back(dir); + } + + return i->second; +} + +//---------------------------------------------------------------------------- +struct cmCLIRuntimeConflictCompare +{ + typedef std::pair<int, int> RuntimeConflictPair; + + // The conflict pair is unique based on just the directory + // (first). The second element is only used for displaying + // information about why the entry is present. + bool operator()(RuntimeConflictPair const& l, + RuntimeConflictPair const& r) + { + return l.first == r.first; + } +}; + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::FindConflictingLibraries() +{ + // Allocate the conflict graph. + this->RuntimeConflictGraph.resize(this->RuntimeDirectories.size()); + this->RuntimeDirectoryVisited.resize(this->RuntimeDirectories.size(), 0); + + // Find all runtime directories providing each library. + for(unsigned int lri = 0; lri < this->LibraryRuntimeInfo.size(); ++lri) + { + this->FindDirectoriesForLib(lri); + } + + // Clean up the conflict graph representation. + for(std::vector<RuntimeConflictList>::iterator + i = this->RuntimeConflictGraph.begin(); + i != this->RuntimeConflictGraph.end(); ++i) + { + // Sort the outgoing edges for each graph node so that the + // original order will be preserved as much as possible. + std::sort(i->begin(), i->end()); + + // Make the edge list unique so cycle detection will be reliable. + RuntimeConflictList::iterator last = + std::unique(i->begin(), i->end(), cmCLIRuntimeConflictCompare()); + i->erase(last, i->end()); + } +} + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::FindDirectoriesForLib(unsigned int lri) +{ + // Search through the runtime directories to find those providing + // this library. + LibraryRuntimeEntry& re = this->LibraryRuntimeInfo[lri]; + for(unsigned int i = 0; i < this->RuntimeDirectories.size(); ++i) + { + // Skip the directory that is supposed to provide the library. + if(this->RuntimeDirectories[i] == re.Directory) + { + continue; + } + + // Determine which type of check to do. + if(!re.SOName.empty()) + { + // We have the library soname. Check if it will be found. + std::string file = this->RuntimeDirectories[i]; + file += "/"; + file += re.SOName; + std::set<cmStdString> const& files = + (this->GlobalGenerator + ->GetDirectoryContent(this->RuntimeDirectories[i], false)); + if(files.find(re.SOName) != files.end() || + cmSystemTools::FileExists(file.c_str(), true)) + { + // The library will be found in this directory but this is not + // the directory named for it. Add an entry to make sure the + // desired directory comes before this one. + RuntimeConflictPair p(re.DirectoryIndex, lri); + this->RuntimeConflictGraph[i].push_back(p); + } + } + else + { + // We do not have the soname. Look for files in the directory + // that may conflict. + std::set<cmStdString> const& files = + (this->GlobalGenerator + ->GetDirectoryContent(this->RuntimeDirectories[i], true)); + + // Get the extension which should appear in the soname. + std::string ext = + cmSystemTools::GetFilenameLastExtension(re.FileName); + + // Get the set of files that might conflict. + std::string base = + cmSystemTools::GetFilenameWithoutLastExtension(re.FileName); + std::set<cmStdString>::const_iterator first = files.lower_bound(base); + ++base[base.size()-1]; + std::set<cmStdString>::const_iterator last = files.upper_bound(base); + bool found = false; + for(std::set<cmStdString>::const_iterator fi = first; + !found && fi != last; ++fi) + { + // This file name starts with the name of the library file. + // If the name also contains the extension then this is + // possibly an soname of the library. + if(fi->find(ext, base.size()) != fi->npos) + { + found = true; + } + } + + if(found) + { + // The library may be found in this directory but this is not + // the directory named for it. Add an entry to make sure the + // desired directory comes before this one. + RuntimeConflictPair p(re.DirectoryIndex, lri); + this->RuntimeConflictGraph[i].push_back(p); + } + } + } +} + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::OrderRuntimeSearchPath() +{ + // Allow a cycle to be diagnosed once. + this->CycleDiagnosed = false; + + // Iterate through the directories in the original order. + for(unsigned int i=0; i < this->RuntimeDirectories.size(); ++i) + { + this->VisitRuntimeDirectory(i, true); + } +} + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::VisitRuntimeDirectory(unsigned int i, + bool top) +{ + // Skip nodes already visited. + if(this->RuntimeDirectoryVisited[i]) + { + if(!top) + { + // We have reached a previously visited node but were not called + // to start a new section of the graph. There is a cycle. + this->DiagnoseCycle(); + } + return; + } + + // We are not visiting this node so mark it. + this->RuntimeDirectoryVisited[i] = 1; + + // Visit the neighbors of the node first. + RuntimeConflictList const& clist = this->RuntimeConflictGraph[i]; + for(RuntimeConflictList::const_iterator j = clist.begin(); + j != clist.end(); ++j) + { + this->VisitRuntimeDirectory(j->first, false); + } + + // Now that all directories required to come before this one have + // been emmitted, emit this directory. + this->RuntimeSearchPath.push_back(this->RuntimeDirectories[i]); +} + +//---------------------------------------------------------------------------- +void cmComputeLinkInformation::DiagnoseCycle() +{ + // Report the cycle at most once. + if(this->CycleDiagnosed) + { + return; + } + this->CycleDiagnosed = true; + + // Construct the message. + cmOStringStream e; + e << "WARNING: Cannot generate a safe runtime path for target " + << this->Target->GetName() + << " because there is a cycle in the constraint graph:\n"; + + // Clean up the conflict graph representation. + for(unsigned int i=0; i < this->RuntimeConflictGraph.size(); ++i) + { + RuntimeConflictList const& clist = this->RuntimeConflictGraph[i]; + e << "dir " << i << " is [" << this->RuntimeDirectories[i] << "]\n"; + for(RuntimeConflictList::const_iterator j = clist.begin(); + j != clist.end(); ++j) + { + e << " dir " << j->first << " must precede it due to ["; + LibraryRuntimeEntry const& re = this->LibraryRuntimeInfo[j->second]; + if(re.SOName.empty()) + { + e << re.FileName; + } + else + { + e << re.SOName; + } + e << "]\n"; + } + } + cmSystemTools::Message(e.str().c_str()); +} |