Imported Upstream version 3.25.0
[platform/upstream/cmake.git] / Source / cmFindLibraryCommand.cxx
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"
4
5 #include <algorithm>
6 #include <cstdio>
7 #include <cstring>
8 #include <set>
9 #include <utility>
10
11 #include "cmsys/RegularExpression.hxx"
12
13 #include "cmGlobalGenerator.h"
14 #include "cmMakefile.h"
15 #include "cmState.h"
16 #include "cmStateTypes.h"
17 #include "cmStringAlgorithms.h"
18 #include "cmSystemTools.h"
19 #include "cmValue.h"
20
21 class cmExecutionStatus;
22
23 cmFindLibraryCommand::cmFindLibraryCommand(cmExecutionStatus& status)
24   : cmFindBase("find_library", status)
25 {
26   this->EnvironmentPath = "LIB";
27   this->NamesPerDirAllowed = true;
28   this->VariableDocumentation = "Path to a library.";
29   this->VariableType = cmStateEnums::FILEPATH;
30 }
31
32 // cmFindLibraryCommand
33 bool cmFindLibraryCommand::InitialPass(std::vector<std::string> const& argsIn)
34 {
35   this->CMakePathName = "LIBRARY";
36
37   if (!this->ParseArguments(argsIn)) {
38     return false;
39   }
40
41   this->DebugMode = this->ComputeIfDebugModeWanted(this->VariableName);
42
43   if (this->AlreadyDefined) {
44     this->NormalizeFindResult();
45     return true;
46   }
47
48   // add custom lib<qual> paths instead of using fixed lib32, lib64 or
49   // libx32
50   if (cmValue customLib = this->Makefile->GetDefinition(
51         "CMAKE_FIND_LIBRARY_CUSTOM_LIB_SUFFIX")) {
52     this->AddArchitecturePaths(customLib->c_str());
53   }
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");
59   }
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");
65   }
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");
71   }
72
73   std::string const library = this->FindLibrary();
74   this->StoreFindResult(library);
75   return true;
76 }
77
78 void cmFindLibraryCommand::AddArchitecturePaths(const char* suffix)
79 {
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 '",
88         suffix, "'");
89       this->DebugMessage(msg);
90     }
91   }
92 }
93
94 static bool cmLibDirsLinked(std::string const& l, std::string const& r)
95 {
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);
104 }
105
106 void cmFindLibraryCommand::AddArchitecturePath(
107   std::string const& dir, std::string::size_type start_pos, const char* suffix,
108   bool fresh)
109 {
110   std::string::size_type pos = dir.find("lib/", start_pos);
111
112   if (pos != std::string::npos) {
113     // Check for "lib".
114     std::string lib = dir.substr(0, pos + 3);
115     bool use_lib = cmSystemTools::FileIsDirectory(lib);
116
117     // Check for "lib<suffix>" and use it first.
118     std::string libX = lib + suffix;
119     bool use_libX = cmSystemTools::FileIsDirectory(libX);
120
121     // Avoid copies of the same directory due to symlinks.
122     if (use_libX && use_lib && cmLibDirsLinked(libX, lib)) {
123       use_libX = false;
124     }
125
126     if (use_libX) {
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);
130     }
131
132     if (use_lib) {
133       this->AddArchitecturePath(dir, pos + 3 + 1, suffix, false);
134     }
135   }
136
137   if (fresh) {
138     // Check for the original unchanged path.
139     bool use_dir = cmSystemTools::FileIsDirectory(dir);
140
141     // Check for <dir><suffix>/ and use it first.
142     std::string dirX = dir + suffix;
143     bool use_dirX = cmSystemTools::FileIsDirectory(dirX);
144
145     // Avoid copies of the same directory due to symlinks.
146     if (use_dirX && use_dir && cmLibDirsLinked(dirX, dir)) {
147       use_dirX = false;
148     }
149
150     if (use_dirX) {
151       dirX += "/";
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);
157       }
158       this->SearchPaths.push_back(std::move(dirX));
159     }
160
161     if (use_dir) {
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);
168       }
169     }
170   }
171 }
172
173 std::string cmFindLibraryCommand::FindLibrary()
174 {
175   std::string library;
176   if (this->SearchFrameworkFirst || this->SearchFrameworkOnly) {
177     library = this->FindFrameworkLibrary();
178   }
179   if (library.empty() && !this->SearchFrameworkOnly) {
180     library = this->FindNormalLibrary();
181   }
182   if (library.empty() && this->SearchFrameworkLast) {
183     library = this->FindFrameworkLibrary();
184   }
185   return library;
186 }
187
188 struct cmFindLibraryHelper
189 {
190   cmFindLibraryHelper(std::string debugName, cmMakefile* mf,
191                       cmFindBase const* findBase);
192
193   // Context information.
194   cmMakefile* Makefile;
195   cmFindBase const* FindBase;
196   cmGlobalGenerator* GG;
197
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;
203
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;
207
208   // Support for OpenBSD shared library naming: lib<name>.so.<major>.<minor>
209   bool OpenBSD;
210
211   bool DebugMode;
212
213   // Current names under consideration.
214   struct Name
215   {
216     bool TryRaw = false;
217     std::string Raw;
218     cmsys::RegularExpression Regex;
219   };
220   std::vector<Name> Names;
221
222   // Current full path under consideration.
223   std::string TestPath;
224
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)
228   {
229     return std::find(this->Prefixes.begin(), this->Prefixes.end(), prefix) -
230       this->Prefixes.begin();
231   }
232   size_type GetSuffixIndex(std::string const& suffix)
233   {
234     return std::find(this->Suffixes.begin(), this->Suffixes.end(), suffix) -
235       this->Suffixes.begin();
236   }
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);
242
243   bool Validate(const std::string& path) const
244   {
245     return this->FindBase->Validate(path);
246   }
247
248   cmFindBaseDebugState DebugSearches;
249
250   void DebugLibraryFailed(std::string const& name, std::string const& path)
251   {
252     if (this->DebugMode) {
253       auto regexName =
254         cmStrCat(this->PrefixRegexStr, name, this->SuffixRegexStr);
255       this->DebugSearches.FailedAt(path, regexName);
256     }
257   }
258
259   void DebugLibraryFound(std::string const& name, std::string const& path)
260   {
261     if (this->DebugMode) {
262       auto regexName =
263         cmStrCat(this->PrefixRegexStr, name, this->SuffixRegexStr);
264       this->DebugSearches.FoundAt(path, regexName);
265     }
266   }
267 };
268
269 namespace {
270
271 std::string const& get_prefixes(cmMakefile* mf)
272 {
273 #ifdef _WIN32
274   static std::string defaultPrefix = ";lib";
275 #else
276   static std::string defaultPrefix = "lib";
277 #endif
278   cmValue prefixProp = mf->GetDefinition("CMAKE_FIND_LIBRARY_PREFIXES");
279   return (prefixProp) ? *prefixProp : defaultPrefix;
280 }
281
282 std::string const& get_suffixes(cmMakefile* mf)
283 {
284 #ifdef _WIN32
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";
290 #else
291   static std::string defaultSuffix = ".so;.a";
292 #endif
293   cmValue suffixProp = mf->GetDefinition("CMAKE_FIND_LIBRARY_SUFFIXES");
294   return (suffixProp) ? *suffixProp : defaultSuffix;
295 }
296 }
297 cmFindLibraryHelper::cmFindLibraryHelper(std::string debugName, cmMakefile* mf,
298                                          cmFindBase const* base)
299   : Makefile(mf)
300   , FindBase(base)
301   , DebugMode(base->DebugModeEnabled())
302   , DebugSearches(std::move(debugName), base)
303 {
304   this->GG = this->Makefile->GetGlobalGenerator();
305
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);
309
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);
314
315   // Check whether to use OpenBSD-style library version comparisons.
316   this->OpenBSD = this->Makefile->GetState()->GetGlobalPropertyAsBool(
317     "FIND_LIBRARY_USE_OPENBSD_VERSIONING");
318 }
319
320 void cmFindLibraryHelper::RegexFromLiteral(std::string& out,
321                                            std::string const& in)
322 {
323   for (char ch : in) {
324     if (ch == '[' || ch == ']' || ch == '(' || ch == ')' || ch == '\\' ||
325         ch == '.' || ch == '*' || ch == '+' || ch == '?' || ch == '-' ||
326         ch == '^' || ch == '$') {
327       out += "\\";
328     }
329 #if defined(_WIN32) || defined(__APPLE__)
330     out += static_cast<char>(tolower(ch));
331 #else
332     out += ch;
333 #endif
334   }
335 }
336
337 void cmFindLibraryHelper::RegexFromList(std::string& out,
338                                         std::vector<std::string> const& in)
339 {
340   // Surround the list in parens so the '|' does not apply to anything
341   // else and the result can be checked after matching.
342   out += "(";
343   const char* sep = "";
344   for (std::string const& s : in) {
345     // Separate from previous item.
346     out += sep;
347     sep = "|";
348
349     // Append this item.
350     this->RegexFromLiteral(out, s);
351   }
352   out += ")";
353 }
354
355 bool cmFindLibraryHelper::HasValidSuffix(std::string const& name)
356 {
357   for (std::string suffix : this->Suffixes) {
358     if (name.length() <= suffix.length()) {
359       continue;
360     }
361     // Check if the given name ends in a valid library suffix.
362     if (name.substr(name.size() - suffix.length()) == suffix) {
363       return true;
364     }
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
367     suffix += ".";
368     if (name.find(suffix) != std::string::npos) {
369       return true;
370     }
371   }
372   return false;
373 }
374
375 void cmFindLibraryHelper::AddName(std::string const& name)
376 {
377   Name entry;
378
379   // Consider checking the raw name too.
380   entry.TryRaw = this->HasValidSuffix(name);
381   entry.Raw = name;
382
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;
387   if (this->OpenBSD) {
388     regex += "(\\.[0-9]+\\.[0-9]+)?";
389   }
390   regex += "$";
391   entry.Regex.compile(regex);
392   this->Names.push_back(std::move(entry));
393 }
394
395 void cmFindLibraryHelper::SetName(std::string const& name)
396 {
397   this->Names.clear();
398   this->AddName(name);
399 }
400
401 bool cmFindLibraryHelper::CheckDirectory(std::string const& path)
402 {
403   for (Name& i : this->Names) {
404     if (this->CheckDirectoryForName(path, i)) {
405       return true;
406     }
407   }
408   return false;
409 }
410
411 bool cmFindLibraryHelper::CheckDirectoryForName(std::string const& path,
412                                                 Name& name)
413 {
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).
419   if (name.TryRaw) {
420     this->TestPath = cmStrCat(path, name.Raw);
421
422     const bool exists = cmSystemTools::FileExists(this->TestPath, true);
423     if (!exists) {
424       this->DebugLibraryFailed(name.Raw, path);
425     } else {
426       auto testPath = cmSystemTools::CollapseFullPath(this->TestPath);
427       if (this->Validate(testPath)) {
428         this->DebugLibraryFound(name.Raw, path);
429         this->BestPath = testPath;
430         return true;
431       }
432       this->DebugLibraryFailed(name.Raw, path);
433     }
434   }
435
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;
441
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);
449 #else
450     std::string const& testName = origName;
451 #endif
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))) {
457           continue;
458         }
459
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;
469         if (this->OpenBSD) {
470           sscanf(name.Regex.match(3).c_str(), ".%u.%u", &major, &minor);
471         }
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;
478           bestPrefix = prefix;
479           bestSuffix = suffix;
480           bestMajor = major;
481           bestMinor = minor;
482         }
483       }
484     }
485   }
486
487   if (this->BestPath.empty()) {
488     this->DebugLibraryFailed(name.Raw, dir);
489   } else {
490     this->DebugLibraryFound(name.Raw, this->BestPath);
491   }
492
493   // Use the best candidate found in this directory, if any.
494   return !this->BestPath.empty();
495 }
496
497 std::string cmFindLibraryCommand::FindNormalLibrary()
498 {
499   if (this->NamesPerDir) {
500     return this->FindNormalLibraryNamesPerDir();
501   }
502   return this->FindNormalLibraryDirsPerName();
503 }
504
505 std::string cmFindLibraryCommand::FindNormalLibraryNamesPerDir()
506 {
507   // Search for all names in each directory.
508   cmFindLibraryHelper helper(this->FindCommandName, this->Makefile, this);
509   for (std::string const& n : this->Names) {
510     helper.AddName(n);
511   }
512   // Search every directory.
513   for (std::string const& sp : this->SearchPaths) {
514     if (helper.CheckDirectory(sp)) {
515       return helper.BestPath;
516     }
517   }
518   // Couldn't find the library.
519   return "";
520 }
521
522 std::string cmFindLibraryCommand::FindNormalLibraryDirsPerName()
523 {
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.
528     helper.SetName(n);
529
530     // Search every directory.
531     for (std::string const& sp : this->SearchPaths) {
532       if (helper.CheckDirectory(sp)) {
533         return helper.BestPath;
534       }
535     }
536   }
537   // Couldn't find the library.
538   return "";
539 }
540
541 std::string cmFindLibraryCommand::FindFrameworkLibrary()
542 {
543   if (this->NamesPerDir) {
544     return this->FindFrameworkLibraryNamesPerDir();
545   }
546   return this->FindFrameworkLibraryDirsPerName();
547 }
548
549 std::string cmFindLibraryCommand::FindFrameworkLibraryNamesPerDir()
550 {
551   std::string fwPath;
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)) {
559           return finalPath;
560         }
561       }
562     }
563   }
564
565   // No framework found.
566   return "";
567 }
568
569 std::string cmFindLibraryCommand::FindFrameworkLibraryDirsPerName()
570 {
571   std::string fwPath;
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)) {
579           return finalPath;
580         }
581       }
582     }
583   }
584
585   // No framework found.
586   return "";
587 }
588
589 bool cmFindLibrary(std::vector<std::string> const& args,
590                    cmExecutionStatus& status)
591 {
592   return cmFindLibraryCommand(status).InitialPass(args);
593 }