1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmFindLibraryCommand.h"
11 #include "cmsys/RegularExpression.hxx"
13 #include "cmGlobalGenerator.h"
14 #include "cmMakefile.h"
16 #include "cmStateTypes.h"
17 #include "cmStringAlgorithms.h"
18 #include "cmSystemTools.h"
21 class cmExecutionStatus;
23 cmFindLibraryCommand::cmFindLibraryCommand(cmExecutionStatus& status)
24 : cmFindBase("find_library", status)
26 this->EnvironmentPath = "LIB";
27 this->NamesPerDirAllowed = true;
28 this->VariableDocumentation = "Path to a library.";
29 this->VariableType = cmStateEnums::FILEPATH;
32 // cmFindLibraryCommand
33 bool cmFindLibraryCommand::InitialPass(std::vector<std::string> const& argsIn)
35 this->CMakePathName = "LIBRARY";
37 if (!this->ParseArguments(argsIn)) {
41 this->DebugMode = this->ComputeIfDebugModeWanted(this->VariableName);
43 if (this->AlreadyDefined) {
44 this->NormalizeFindResult();
48 // add custom lib<qual> paths instead of using fixed lib32, lib64 or
50 if (cmValue customLib = this->Makefile->GetDefinition(
51 "CMAKE_FIND_LIBRARY_CUSTOM_LIB_SUFFIX")) {
52 this->AddArchitecturePaths(customLib->c_str());
54 // add special 32 bit paths if this is a 32 bit compile.
55 else if (this->Makefile->PlatformIs32Bit() &&
56 this->Makefile->GetState()->GetGlobalPropertyAsBool(
57 "FIND_LIBRARY_USE_LIB32_PATHS")) {
58 this->AddArchitecturePaths("32");
60 // add special 64 bit paths if this is a 64 bit compile.
61 else if (this->Makefile->PlatformIs64Bit() &&
62 this->Makefile->GetState()->GetGlobalPropertyAsBool(
63 "FIND_LIBRARY_USE_LIB64_PATHS")) {
64 this->AddArchitecturePaths("64");
66 // add special 32 bit paths if this is an x32 compile.
67 else if (this->Makefile->PlatformIsx32() &&
68 this->Makefile->GetState()->GetGlobalPropertyAsBool(
69 "FIND_LIBRARY_USE_LIBX32_PATHS")) {
70 this->AddArchitecturePaths("x32");
73 std::string const library = this->FindLibrary();
74 this->StoreFindResult(library);
78 void cmFindLibraryCommand::AddArchitecturePaths(const char* suffix)
80 std::vector<std::string> original;
81 original.swap(this->SearchPaths);
82 for (std::string const& o : original) {
83 this->AddArchitecturePath(o, 0, suffix);
84 if (this->DebugMode) {
85 std::string msg = cmStrCat(
86 "find_library(", this->VariableName, ") removed original suffix ", o,
87 " from PATH_SUFFIXES while adding architecture paths for suffix '",
89 this->DebugMessage(msg);
94 static bool cmLibDirsLinked(std::string const& l, std::string const& r)
96 // Compare the real paths of the two directories.
97 // Since our caller only changed the trailing component of each
98 // directory, the real paths can be the same only if at least one of
99 // the trailing components is a symlink. Use this as an optimization
100 // to avoid excessive realpath calls.
101 return (cmSystemTools::FileIsSymlink(l) ||
102 cmSystemTools::FileIsSymlink(r)) &&
103 cmSystemTools::GetRealPath(l) == cmSystemTools::GetRealPath(r);
106 void cmFindLibraryCommand::AddArchitecturePath(
107 std::string const& dir, std::string::size_type start_pos, const char* suffix,
110 std::string::size_type pos = dir.find("lib/", start_pos);
112 if (pos != std::string::npos) {
114 std::string lib = dir.substr(0, pos + 3);
115 bool use_lib = cmSystemTools::FileIsDirectory(lib);
117 // Check for "lib<suffix>" and use it first.
118 std::string libX = lib + suffix;
119 bool use_libX = cmSystemTools::FileIsDirectory(libX);
121 // Avoid copies of the same directory due to symlinks.
122 if (use_libX && use_lib && cmLibDirsLinked(libX, lib)) {
127 libX += dir.substr(pos + 3);
128 std::string::size_type libX_pos = pos + 3 + strlen(suffix) + 1;
129 this->AddArchitecturePath(libX, libX_pos, suffix);
133 this->AddArchitecturePath(dir, pos + 3 + 1, suffix, false);
138 // Check for the original unchanged path.
139 bool use_dir = cmSystemTools::FileIsDirectory(dir);
141 // Check for <dir><suffix>/ and use it first.
142 std::string dirX = dir + suffix;
143 bool use_dirX = cmSystemTools::FileIsDirectory(dirX);
145 // Avoid copies of the same directory due to symlinks.
146 if (use_dirX && use_dir && cmLibDirsLinked(dirX, dir)) {
152 if (this->DebugMode) {
153 std::string msg = cmStrCat(
154 "find_library(", this->VariableName, ") added replacement path ",
155 dirX, " to PATH_SUFFIXES for architecture suffix '", suffix, "'");
156 this->DebugMessage(msg);
158 this->SearchPaths.push_back(std::move(dirX));
162 this->SearchPaths.push_back(dir);
163 if (this->DebugMode) {
164 std::string msg = cmStrCat(
165 "find_library(", this->VariableName, ") added replacement path ",
166 dir, " to PATH_SUFFIXES for architecture suffix '", suffix, "'");
167 this->DebugMessage(msg);
173 std::string cmFindLibraryCommand::FindLibrary()
176 if (this->SearchFrameworkFirst || this->SearchFrameworkOnly) {
177 library = this->FindFrameworkLibrary();
179 if (library.empty() && !this->SearchFrameworkOnly) {
180 library = this->FindNormalLibrary();
182 if (library.empty() && this->SearchFrameworkLast) {
183 library = this->FindFrameworkLibrary();
188 struct cmFindLibraryHelper
190 cmFindLibraryHelper(std::string debugName, cmMakefile* mf,
191 cmFindBase const* findBase);
193 // Context information.
194 cmMakefile* Makefile;
195 cmFindBase const* FindBase;
196 cmGlobalGenerator* GG;
198 // List of valid prefixes and suffixes.
199 std::vector<std::string> Prefixes;
200 std::vector<std::string> Suffixes;
201 std::string PrefixRegexStr;
202 std::string SuffixRegexStr;
204 // Keep track of the best library file found so far.
205 using size_type = std::vector<std::string>::size_type;
206 std::string BestPath;
208 // Support for OpenBSD shared library naming: lib<name>.so.<major>.<minor>
213 // Current names under consideration.
218 cmsys::RegularExpression Regex;
220 std::vector<Name> Names;
222 // Current full path under consideration.
223 std::string TestPath;
225 void RegexFromLiteral(std::string& out, std::string const& in);
226 void RegexFromList(std::string& out, std::vector<std::string> const& in);
227 size_type GetPrefixIndex(std::string const& prefix)
229 return std::find(this->Prefixes.begin(), this->Prefixes.end(), prefix) -
230 this->Prefixes.begin();
232 size_type GetSuffixIndex(std::string const& suffix)
234 return std::find(this->Suffixes.begin(), this->Suffixes.end(), suffix) -
235 this->Suffixes.begin();
237 bool HasValidSuffix(std::string const& name);
238 void AddName(std::string const& name);
239 void SetName(std::string const& name);
240 bool CheckDirectory(std::string const& path);
241 bool CheckDirectoryForName(std::string const& path, Name& name);
243 bool Validate(const std::string& path) const
245 return this->FindBase->Validate(path);
248 cmFindBaseDebugState DebugSearches;
250 void DebugLibraryFailed(std::string const& name, std::string const& path)
252 if (this->DebugMode) {
254 cmStrCat(this->PrefixRegexStr, name, this->SuffixRegexStr);
255 this->DebugSearches.FailedAt(path, regexName);
259 void DebugLibraryFound(std::string const& name, std::string const& path)
261 if (this->DebugMode) {
263 cmStrCat(this->PrefixRegexStr, name, this->SuffixRegexStr);
264 this->DebugSearches.FoundAt(path, regexName);
271 std::string const& get_prefixes(cmMakefile* mf)
274 static std::string defaultPrefix = ";lib";
276 static std::string defaultPrefix = "lib";
278 cmValue prefixProp = mf->GetDefinition("CMAKE_FIND_LIBRARY_PREFIXES");
279 return (prefixProp) ? *prefixProp : defaultPrefix;
282 std::string const& get_suffixes(cmMakefile* mf)
285 static std::string defaultSuffix = ".lib;.dll.a;.a";
286 #elif defined(__APPLE__)
287 static std::string defaultSuffix = ".tbd;.dylib;.so;.a";
288 #elif defined(__hpux)
289 static std::string defaultSuffix = ".sl;.so;.a";
291 static std::string defaultSuffix = ".so;.a";
293 cmValue suffixProp = mf->GetDefinition("CMAKE_FIND_LIBRARY_SUFFIXES");
294 return (suffixProp) ? *suffixProp : defaultSuffix;
297 cmFindLibraryHelper::cmFindLibraryHelper(std::string debugName, cmMakefile* mf,
298 cmFindBase const* base)
301 , DebugMode(base->DebugModeEnabled())
302 , DebugSearches(std::move(debugName), base)
304 this->GG = this->Makefile->GetGlobalGenerator();
306 // Collect the list of library name prefixes/suffixes to try.
307 std::string const& prefixes_list = get_prefixes(this->Makefile);
308 std::string const& suffixes_list = get_suffixes(this->Makefile);
310 cmExpandList(prefixes_list, this->Prefixes, true);
311 cmExpandList(suffixes_list, this->Suffixes, true);
312 this->RegexFromList(this->PrefixRegexStr, this->Prefixes);
313 this->RegexFromList(this->SuffixRegexStr, this->Suffixes);
315 // Check whether to use OpenBSD-style library version comparisons.
316 this->OpenBSD = this->Makefile->GetState()->GetGlobalPropertyAsBool(
317 "FIND_LIBRARY_USE_OPENBSD_VERSIONING");
320 void cmFindLibraryHelper::RegexFromLiteral(std::string& out,
321 std::string const& in)
324 if (ch == '[' || ch == ']' || ch == '(' || ch == ')' || ch == '\\' ||
325 ch == '.' || ch == '*' || ch == '+' || ch == '?' || ch == '-' ||
326 ch == '^' || ch == '$') {
329 #if defined(_WIN32) || defined(__APPLE__)
330 out += static_cast<char>(tolower(ch));
337 void cmFindLibraryHelper::RegexFromList(std::string& out,
338 std::vector<std::string> const& in)
340 // Surround the list in parens so the '|' does not apply to anything
341 // else and the result can be checked after matching.
343 const char* sep = "";
344 for (std::string const& s : in) {
345 // Separate from previous item.
350 this->RegexFromLiteral(out, s);
355 bool cmFindLibraryHelper::HasValidSuffix(std::string const& name)
357 for (std::string suffix : this->Suffixes) {
358 if (name.length() <= suffix.length()) {
361 // Check if the given name ends in a valid library suffix.
362 if (name.substr(name.size() - suffix.length()) == suffix) {
365 // Check if a valid library suffix is somewhere in the name,
366 // this may happen e.g. for versioned shared libraries: libfoo.so.2
368 if (name.find(suffix) != std::string::npos) {
375 void cmFindLibraryHelper::AddName(std::string const& name)
379 // Consider checking the raw name too.
380 entry.TryRaw = this->HasValidSuffix(name);
383 // Build a regular expression to match library names.
384 std::string regex = cmStrCat('^', this->PrefixRegexStr);
385 this->RegexFromLiteral(regex, name);
386 regex += this->SuffixRegexStr;
388 regex += "(\\.[0-9]+\\.[0-9]+)?";
391 entry.Regex.compile(regex);
392 this->Names.push_back(std::move(entry));
395 void cmFindLibraryHelper::SetName(std::string const& name)
401 bool cmFindLibraryHelper::CheckDirectory(std::string const& path)
403 for (Name& i : this->Names) {
404 if (this->CheckDirectoryForName(path, i)) {
411 bool cmFindLibraryHelper::CheckDirectoryForName(std::string const& path,
414 // If the original library name provided by the user matches one of
415 // the suffixes, try it first. This allows users to search
416 // specifically for a static library on some platforms (on MS tools
417 // one cannot tell just from the library name whether it is a static
418 // library or an import library).
420 this->TestPath = cmStrCat(path, name.Raw);
422 const bool exists = cmSystemTools::FileExists(this->TestPath, true);
424 this->DebugLibraryFailed(name.Raw, path);
426 auto testPath = cmSystemTools::CollapseFullPath(this->TestPath);
427 if (this->Validate(testPath)) {
428 this->DebugLibraryFound(name.Raw, path);
429 this->BestPath = testPath;
432 this->DebugLibraryFailed(name.Raw, path);
436 // No library file has yet been found.
437 size_type bestPrefix = this->Prefixes.size();
438 size_type bestSuffix = this->Suffixes.size();
439 unsigned int bestMajor = 0;
440 unsigned int bestMinor = 0;
442 // Search for a file matching the library name regex.
443 std::string dir = path;
444 cmSystemTools::ConvertToUnixSlashes(dir);
445 std::set<std::string> const& files = this->GG->GetDirectoryContent(dir);
446 for (std::string const& origName : files) {
447 #if defined(_WIN32) || defined(__APPLE__)
448 std::string testName = cmSystemTools::LowerCase(origName);
450 std::string const& testName = origName;
452 if (name.Regex.find(testName)) {
453 this->TestPath = cmStrCat(path, origName);
454 // Make sure the path is readable and is not a directory.
455 if (cmSystemTools::FileExists(this->TestPath, true)) {
456 if (!this->Validate(cmSystemTools::CollapseFullPath(this->TestPath))) {
460 this->DebugLibraryFound(name.Raw, dir);
461 // This is a matching file. Check if it is better than the
462 // best name found so far. Earlier prefixes are preferred,
463 // followed by earlier suffixes. For OpenBSD, shared library
464 // version extensions are compared.
465 size_type prefix = this->GetPrefixIndex(name.Regex.match(1));
466 size_type suffix = this->GetSuffixIndex(name.Regex.match(2));
467 unsigned int major = 0;
468 unsigned int minor = 0;
470 sscanf(name.Regex.match(3).c_str(), ".%u.%u", &major, &minor);
472 if (this->BestPath.empty() || prefix < bestPrefix ||
473 (prefix == bestPrefix && suffix < bestSuffix) ||
474 (prefix == bestPrefix && suffix == bestSuffix &&
475 (major > bestMajor ||
476 (major == bestMajor && minor > bestMinor)))) {
477 this->BestPath = this->TestPath;
487 if (this->BestPath.empty()) {
488 this->DebugLibraryFailed(name.Raw, dir);
490 this->DebugLibraryFound(name.Raw, this->BestPath);
493 // Use the best candidate found in this directory, if any.
494 return !this->BestPath.empty();
497 std::string cmFindLibraryCommand::FindNormalLibrary()
499 if (this->NamesPerDir) {
500 return this->FindNormalLibraryNamesPerDir();
502 return this->FindNormalLibraryDirsPerName();
505 std::string cmFindLibraryCommand::FindNormalLibraryNamesPerDir()
507 // Search for all names in each directory.
508 cmFindLibraryHelper helper(this->FindCommandName, this->Makefile, this);
509 for (std::string const& n : this->Names) {
512 // Search every directory.
513 for (std::string const& sp : this->SearchPaths) {
514 if (helper.CheckDirectory(sp)) {
515 return helper.BestPath;
518 // Couldn't find the library.
522 std::string cmFindLibraryCommand::FindNormalLibraryDirsPerName()
524 // Search the entire path for each name.
525 cmFindLibraryHelper helper(this->FindCommandName, this->Makefile, this);
526 for (std::string const& n : this->Names) {
527 // Switch to searching for this name.
530 // Search every directory.
531 for (std::string const& sp : this->SearchPaths) {
532 if (helper.CheckDirectory(sp)) {
533 return helper.BestPath;
537 // Couldn't find the library.
541 std::string cmFindLibraryCommand::FindFrameworkLibrary()
543 if (this->NamesPerDir) {
544 return this->FindFrameworkLibraryNamesPerDir();
546 return this->FindFrameworkLibraryDirsPerName();
549 std::string cmFindLibraryCommand::FindFrameworkLibraryNamesPerDir()
552 // Search for all names in each search path.
553 for (std::string const& d : this->SearchPaths) {
554 for (std::string const& n : this->Names) {
555 fwPath = cmStrCat(d, n, ".framework");
556 if (cmSystemTools::FileIsDirectory(fwPath)) {
557 auto finalPath = cmSystemTools::CollapseFullPath(fwPath);
558 if (this->Validate(finalPath)) {
565 // No framework found.
569 std::string cmFindLibraryCommand::FindFrameworkLibraryDirsPerName()
572 // Search for each name in all search paths.
573 for (std::string const& n : this->Names) {
574 for (std::string const& d : this->SearchPaths) {
575 fwPath = cmStrCat(d, n, ".framework");
576 if (cmSystemTools::FileIsDirectory(fwPath)) {
577 auto finalPath = cmSystemTools::CollapseFullPath(fwPath);
578 if (this->Validate(finalPath)) {
585 // No framework found.
589 bool cmFindLibrary(std::vector<std::string> const& args,
590 cmExecutionStatus& status)
592 return cmFindLibraryCommand(status).InitialPass(args);