/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmFindLibraryCommand.h" #include #include #include #include #include #include "cmsys/RegularExpression.hxx" #include "cmGlobalGenerator.h" #include "cmList.h" #include "cmMakefile.h" #include "cmState.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmValue.h" class cmExecutionStatus; cmFindLibraryCommand::cmFindLibraryCommand(cmExecutionStatus& status) : cmFindBase("find_library", status) { this->EnvironmentPath = "LIB"; this->NamesPerDirAllowed = true; this->VariableDocumentation = "Path to a library."; this->VariableType = cmStateEnums::FILEPATH; } // cmFindLibraryCommand bool cmFindLibraryCommand::InitialPass(std::vector const& argsIn) { this->CMakePathName = "LIBRARY"; if (!this->ParseArguments(argsIn)) { return false; } this->DebugMode = this->ComputeIfDebugModeWanted(this->VariableName); if (this->AlreadyDefined) { this->NormalizeFindResult(); return true; } // add custom lib paths instead of using fixed lib32, lib64 or // libx32 if (cmValue customLib = this->Makefile->GetDefinition( "CMAKE_FIND_LIBRARY_CUSTOM_LIB_SUFFIX")) { this->AddArchitecturePaths(customLib->c_str()); } // add special 32 bit paths if this is a 32 bit compile. else if (this->Makefile->PlatformIs32Bit() && this->Makefile->GetState()->GetGlobalPropertyAsBool( "FIND_LIBRARY_USE_LIB32_PATHS")) { this->AddArchitecturePaths("32"); } // add special 64 bit paths if this is a 64 bit compile. else if (this->Makefile->PlatformIs64Bit() && this->Makefile->GetState()->GetGlobalPropertyAsBool( "FIND_LIBRARY_USE_LIB64_PATHS")) { this->AddArchitecturePaths("64"); } // add special 32 bit paths if this is an x32 compile. else if (this->Makefile->PlatformIsx32() && this->Makefile->GetState()->GetGlobalPropertyAsBool( "FIND_LIBRARY_USE_LIBX32_PATHS")) { this->AddArchitecturePaths("x32"); } std::string const library = this->FindLibrary(); this->StoreFindResult(library); return true; } void cmFindLibraryCommand::AddArchitecturePaths(const char* suffix) { std::vector original; original.swap(this->SearchPaths); for (std::string const& o : original) { this->AddArchitecturePath(o, 0, suffix); if (this->DebugMode) { std::string msg = cmStrCat( "find_library(", this->VariableName, ") removed original suffix ", o, " from PATH_SUFFIXES while adding architecture paths for suffix '", suffix, "'"); this->DebugMessage(msg); } } } static bool cmLibDirsLinked(std::string const& l, std::string const& r) { // Compare the real paths of the two directories. // Since our caller only changed the trailing component of each // directory, the real paths can be the same only if at least one of // the trailing components is a symlink. Use this as an optimization // to avoid excessive realpath calls. return (cmSystemTools::FileIsSymlink(l) || cmSystemTools::FileIsSymlink(r)) && cmSystemTools::GetRealPath(l) == cmSystemTools::GetRealPath(r); } void cmFindLibraryCommand::AddArchitecturePath( std::string const& dir, std::string::size_type start_pos, const char* suffix, bool fresh) { std::string::size_type pos = dir.find("lib/", start_pos); if (pos != std::string::npos) { // Check for "lib". std::string lib = dir.substr(0, pos + 3); bool use_lib = cmSystemTools::FileIsDirectory(lib); // Check for "lib" and use it first. std::string libX = lib + suffix; bool use_libX = cmSystemTools::FileIsDirectory(libX); // Avoid copies of the same directory due to symlinks. if (use_libX && use_lib && cmLibDirsLinked(libX, lib)) { use_libX = false; } if (use_libX) { libX += dir.substr(pos + 3); std::string::size_type libX_pos = pos + 3 + strlen(suffix) + 1; this->AddArchitecturePath(libX, libX_pos, suffix); } if (use_lib) { this->AddArchitecturePath(dir, pos + 3 + 1, suffix, false); } } if (fresh) { // Check for the original unchanged path. bool use_dir = cmSystemTools::FileIsDirectory(dir); // Check for / and use it first. std::string dirX = dir + suffix; bool use_dirX = cmSystemTools::FileIsDirectory(dirX); // Avoid copies of the same directory due to symlinks. if (use_dirX && use_dir && cmLibDirsLinked(dirX, dir)) { use_dirX = false; } if (use_dirX) { dirX += "/"; if (this->DebugMode) { std::string msg = cmStrCat( "find_library(", this->VariableName, ") added replacement path ", dirX, " to PATH_SUFFIXES for architecture suffix '", suffix, "'"); this->DebugMessage(msg); } this->SearchPaths.push_back(std::move(dirX)); } if (use_dir) { this->SearchPaths.push_back(dir); if (this->DebugMode) { std::string msg = cmStrCat( "find_library(", this->VariableName, ") added replacement path ", dir, " to PATH_SUFFIXES for architecture suffix '", suffix, "'"); this->DebugMessage(msg); } } } } std::string cmFindLibraryCommand::FindLibrary() { std::string library; if (this->SearchFrameworkFirst || this->SearchFrameworkOnly) { library = this->FindFrameworkLibrary(); } if (library.empty() && !this->SearchFrameworkOnly) { library = this->FindNormalLibrary(); } if (library.empty() && this->SearchFrameworkLast) { library = this->FindFrameworkLibrary(); } return library; } struct cmFindLibraryHelper { cmFindLibraryHelper(std::string debugName, cmMakefile* mf, cmFindBase const* findBase); // Context information. cmMakefile* Makefile; cmFindBase const* FindBase; cmGlobalGenerator* GG; // List of valid prefixes and suffixes. cmList Prefixes; cmList Suffixes; std::string PrefixRegexStr; std::string SuffixRegexStr; // Keep track of the best library file found so far. using size_type = std::vector::size_type; std::string BestPath; // Support for OpenBSD shared library naming: lib.so.. bool IsOpenBSD; bool DebugMode; // Current names under consideration. struct Name { bool TryRaw = false; std::string Raw; cmsys::RegularExpression Regex; }; std::vector Names; // Current full path under consideration. std::string TestPath; void RegexFromLiteral(std::string& out, std::string const& in); void RegexFromList(std::string& out, cmList const& in); size_type GetPrefixIndex(std::string const& prefix) { return std::find(this->Prefixes.begin(), this->Prefixes.end(), prefix) - this->Prefixes.begin(); } size_type GetSuffixIndex(std::string const& suffix) { return std::find(this->Suffixes.begin(), this->Suffixes.end(), suffix) - this->Suffixes.begin(); } bool HasValidSuffix(std::string const& name); void AddName(std::string const& name); void SetName(std::string const& name); bool CheckDirectory(std::string const& path); bool CheckDirectoryForName(std::string const& path, Name& name); bool Validate(const std::string& path) const { return this->FindBase->Validate(path); } cmFindBaseDebugState DebugSearches; void DebugLibraryFailed(std::string const& name, std::string const& path) { if (this->DebugMode) { // To improve readability of the debug output, if there is only one // prefix/suffix, use the plain prefix/suffix instead of the regex. const auto& prefix = (this->Prefixes.size() == 1) ? this->Prefixes[0] : this->PrefixRegexStr; const auto& suffix = (this->Suffixes.size() == 1) ? this->Suffixes[0] : this->SuffixRegexStr; auto regexName = cmStrCat(prefix, name, suffix); this->DebugSearches.FailedAt(path, regexName); } } void DebugLibraryFound(std::string const& name, std::string const& path) { if (this->DebugMode) { auto regexName = cmStrCat(this->PrefixRegexStr, name, this->SuffixRegexStr); this->DebugSearches.FoundAt(path, regexName); } } }; namespace { std::string const& get_prefixes(cmMakefile* mf) { #ifdef _WIN32 static std::string defaultPrefix = ";lib"; #else static std::string defaultPrefix = "lib"; #endif cmValue prefixProp = mf->GetDefinition("CMAKE_FIND_LIBRARY_PREFIXES"); return (prefixProp) ? *prefixProp : defaultPrefix; } std::string const& get_suffixes(cmMakefile* mf) { #ifdef _WIN32 static std::string defaultSuffix = ".lib;.dll.a;.a"; #elif defined(__APPLE__) static std::string defaultSuffix = ".tbd;.dylib;.so;.a"; #elif defined(__hpux) static std::string defaultSuffix = ".sl;.so;.a"; #else static std::string defaultSuffix = ".so;.a"; #endif cmValue suffixProp = mf->GetDefinition("CMAKE_FIND_LIBRARY_SUFFIXES"); return (suffixProp) ? *suffixProp : defaultSuffix; } } cmFindLibraryHelper::cmFindLibraryHelper(std::string debugName, cmMakefile* mf, cmFindBase const* base) : Makefile(mf) , FindBase(base) , DebugMode(base->DebugModeEnabled()) , DebugSearches(std::move(debugName), base) { this->GG = this->Makefile->GetGlobalGenerator(); // Collect the list of library name prefixes/suffixes to try. std::string const& prefixes_list = get_prefixes(this->Makefile); std::string const& suffixes_list = get_suffixes(this->Makefile); this->Prefixes.assign(prefixes_list, cmList::EmptyElements::Yes); this->Suffixes.assign(suffixes_list, cmList::EmptyElements::Yes); this->RegexFromList(this->PrefixRegexStr, this->Prefixes); this->RegexFromList(this->SuffixRegexStr, this->Suffixes); // Check whether to use OpenBSD-style library version comparisons. this->IsOpenBSD = this->Makefile->GetState()->GetGlobalPropertyAsBool( "FIND_LIBRARY_USE_OPENBSD_VERSIONING"); } void cmFindLibraryHelper::RegexFromLiteral(std::string& out, std::string const& in) { for (char ch : in) { if (ch == '[' || ch == ']' || ch == '(' || ch == ')' || ch == '\\' || ch == '.' || ch == '*' || ch == '+' || ch == '?' || ch == '-' || ch == '^' || ch == '$') { out += "\\"; } #if defined(_WIN32) || defined(__APPLE__) out += static_cast(tolower(ch)); #else out += ch; #endif } } void cmFindLibraryHelper::RegexFromList(std::string& out, cmList const& in) { // Surround the list in parens so the '|' does not apply to anything // else and the result can be checked after matching. out += "("; const char* sep = ""; for (auto const& s : in) { // Separate from previous item. out += sep; sep = "|"; // Append this item. this->RegexFromLiteral(out, s); } out += ")"; } bool cmFindLibraryHelper::HasValidSuffix(std::string const& name) { for (std::string suffix : this->Suffixes) { if (name.length() <= suffix.length()) { continue; } // Check if the given name ends in a valid library suffix. if (name.substr(name.size() - suffix.length()) == suffix) { return true; } // Check if a valid library suffix is somewhere in the name, // this may happen e.g. for versioned shared libraries: libfoo.so.2 suffix += "."; if (name.find(suffix) != std::string::npos) { return true; } } return false; } void cmFindLibraryHelper::AddName(std::string const& name) { Name entry; // Consider checking the raw name too. entry.TryRaw = this->HasValidSuffix(name); entry.Raw = name; // Build a regular expression to match library names. std::string regex = cmStrCat('^', this->PrefixRegexStr); this->RegexFromLiteral(regex, name); regex += this->SuffixRegexStr; if (this->IsOpenBSD) { regex += "(\\.[0-9]+\\.[0-9]+)?"; } regex += "$"; entry.Regex.compile(regex); this->Names.push_back(std::move(entry)); } void cmFindLibraryHelper::SetName(std::string const& name) { this->Names.clear(); this->AddName(name); } bool cmFindLibraryHelper::CheckDirectory(std::string const& path) { for (Name& i : this->Names) { if (this->CheckDirectoryForName(path, i)) { return true; } } return false; } bool cmFindLibraryHelper::CheckDirectoryForName(std::string const& path, Name& name) { // If the original library name provided by the user matches one of // the suffixes, try it first. This allows users to search // specifically for a static library on some platforms (on MS tools // one cannot tell just from the library name whether it is a static // library or an import library). if (name.TryRaw) { this->TestPath = cmStrCat(path, name.Raw); const bool exists = cmSystemTools::FileExists(this->TestPath, true); if (!exists) { this->DebugLibraryFailed(name.Raw, path); } else { auto testPath = cmSystemTools::CollapseFullPath(this->TestPath); if (this->Validate(testPath)) { this->DebugLibraryFound(name.Raw, path); this->BestPath = testPath; return true; } this->DebugLibraryFailed(name.Raw, path); } } // No library file has yet been found. size_type bestPrefix = this->Prefixes.size(); size_type bestSuffix = this->Suffixes.size(); unsigned int bestMajor = 0; unsigned int bestMinor = 0; // Search for a file matching the library name regex. std::string dir = path; cmSystemTools::ConvertToUnixSlashes(dir); std::set const& files = this->GG->GetDirectoryContent(dir); for (std::string const& origName : files) { #if defined(_WIN32) || defined(__APPLE__) std::string testName = cmSystemTools::LowerCase(origName); #else std::string const& testName = origName; #endif if (name.Regex.find(testName)) { this->TestPath = cmStrCat(path, origName); // Make sure the path is readable and is not a directory. if (cmSystemTools::FileExists(this->TestPath, true)) { if (!this->Validate(cmSystemTools::CollapseFullPath(this->TestPath))) { continue; } this->DebugLibraryFound(name.Raw, dir); // This is a matching file. Check if it is better than the // best name found so far. Earlier prefixes are preferred, // followed by earlier suffixes. For OpenBSD, shared library // version extensions are compared. size_type prefix = this->GetPrefixIndex(name.Regex.match(1)); size_type suffix = this->GetSuffixIndex(name.Regex.match(2)); unsigned int major = 0; unsigned int minor = 0; if (this->IsOpenBSD) { sscanf(name.Regex.match(3).c_str(), ".%u.%u", &major, &minor); } if (this->BestPath.empty() || prefix < bestPrefix || (prefix == bestPrefix && suffix < bestSuffix) || (prefix == bestPrefix && suffix == bestSuffix && (major > bestMajor || (major == bestMajor && minor > bestMinor)))) { this->BestPath = this->TestPath; bestPrefix = prefix; bestSuffix = suffix; bestMajor = major; bestMinor = minor; } } } } if (this->BestPath.empty()) { this->DebugLibraryFailed(name.Raw, dir); } else { this->DebugLibraryFound(name.Raw, this->BestPath); } // Use the best candidate found in this directory, if any. return !this->BestPath.empty(); } std::string cmFindLibraryCommand::FindNormalLibrary() { if (this->NamesPerDir) { return this->FindNormalLibraryNamesPerDir(); } return this->FindNormalLibraryDirsPerName(); } std::string cmFindLibraryCommand::FindNormalLibraryNamesPerDir() { // Search for all names in each directory. cmFindLibraryHelper helper(this->FindCommandName, this->Makefile, this); for (std::string const& n : this->Names) { helper.AddName(n); } // Search every directory. for (std::string const& sp : this->SearchPaths) { if (helper.CheckDirectory(sp)) { return helper.BestPath; } } // Couldn't find the library. return ""; } std::string cmFindLibraryCommand::FindNormalLibraryDirsPerName() { // Search the entire path for each name. cmFindLibraryHelper helper(this->FindCommandName, this->Makefile, this); for (std::string const& n : this->Names) { // Switch to searching for this name. helper.SetName(n); // Search every directory. for (std::string const& sp : this->SearchPaths) { if (helper.CheckDirectory(sp)) { return helper.BestPath; } } } // Couldn't find the library. return ""; } std::string cmFindLibraryCommand::FindFrameworkLibrary() { if (this->NamesPerDir) { return this->FindFrameworkLibraryNamesPerDir(); } return this->FindFrameworkLibraryDirsPerName(); } std::string cmFindLibraryCommand::FindFrameworkLibraryNamesPerDir() { std::string fwPath; // Search for all names in each search path. for (std::string const& d : this->SearchPaths) { for (std::string const& n : this->Names) { fwPath = cmStrCat(d, n, ".xcframework"); if (cmSystemTools::FileIsDirectory(fwPath)) { auto finalPath = cmSystemTools::CollapseFullPath(fwPath); if (this->Validate(finalPath)) { return finalPath; } } fwPath = cmStrCat(d, n, ".framework"); if (cmSystemTools::FileIsDirectory(fwPath)) { auto finalPath = cmSystemTools::CollapseFullPath(fwPath); if (this->Validate(finalPath)) { return finalPath; } } } } // No framework found. return ""; } std::string cmFindLibraryCommand::FindFrameworkLibraryDirsPerName() { std::string fwPath; // Search for each name in all search paths. for (std::string const& n : this->Names) { for (std::string const& d : this->SearchPaths) { fwPath = cmStrCat(d, n, ".xcframework"); if (cmSystemTools::FileIsDirectory(fwPath)) { auto finalPath = cmSystemTools::CollapseFullPath(fwPath); if (this->Validate(finalPath)) { return finalPath; } } fwPath = cmStrCat(d, n, ".framework"); if (cmSystemTools::FileIsDirectory(fwPath)) { auto finalPath = cmSystemTools::CollapseFullPath(fwPath); if (this->Validate(finalPath)) { return finalPath; } } } } // No framework found. return ""; } bool cmFindLibrary(std::vector const& args, cmExecutionStatus& status) { return cmFindLibraryCommand(status).InitialPass(args); }