resolve cyclic dependency with zstd
[platform/upstream/cmake.git] / Source / cmBinUtilsMacOSMachOLinker.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
4 #include "cmBinUtilsMacOSMachOLinker.h"
5
6 #include <sstream>
7 #include <string>
8 #include <type_traits>
9 #include <utility>
10 #include <vector>
11
12 #include <cm/memory>
13
14 #include "cmBinUtilsMacOSMachOOToolGetRuntimeDependenciesTool.h"
15 #include "cmRuntimeDependencyArchive.h"
16 #include "cmStringAlgorithms.h"
17 #include "cmSystemTools.h"
18
19 namespace {
20 bool IsMissingSystemDylib(std::string const& path)
21 {
22   // Starting on macOS 11, the dynamic loader has a builtin cache of
23   // system-provided dylib files that do not exist on the filesystem.
24   // Tell our caller that these are expected to be missing.
25   return ((cmHasLiteralPrefix(path, "/System/Library/") ||
26            cmHasLiteralPrefix(path, "/usr/lib/")) &&
27           !cmSystemTools::PathExists(path));
28 }
29 }
30
31 cmBinUtilsMacOSMachOLinker::cmBinUtilsMacOSMachOLinker(
32   cmRuntimeDependencyArchive* archive)
33   : cmBinUtilsLinker(archive)
34 {
35 }
36
37 bool cmBinUtilsMacOSMachOLinker::Prepare()
38 {
39   std::string tool = this->Archive->GetGetRuntimeDependenciesTool();
40   if (tool.empty()) {
41     tool = "otool";
42   }
43   if (tool == "otool") {
44     this->Tool =
45       cm::make_unique<cmBinUtilsMacOSMachOOToolGetRuntimeDependenciesTool>(
46         this->Archive);
47   } else {
48     std::ostringstream e;
49     e << "Invalid value for CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL: " << tool;
50     this->SetError(e.str());
51     return false;
52   }
53
54   return true;
55 }
56
57 auto cmBinUtilsMacOSMachOLinker::GetFileInfo(std::string const& file)
58   -> const FileInfo*
59 {
60   // Memoize processed rpaths and library dependencies to reduce the number
61   // of calls to otool, especially in the case of heavily recursive libraries
62   auto iter = ScannedFileInfo.find(file);
63   if (iter != ScannedFileInfo.end()) {
64     return &iter->second;
65   }
66
67   FileInfo file_info;
68   if (!this->Tool->GetFileInfo(file, file_info.libs, file_info.rpaths)) {
69     // Call to otool failed
70     return nullptr;
71   }
72
73   auto iter_inserted = ScannedFileInfo.insert({ file, std::move(file_info) });
74   return &iter_inserted.first->second;
75 }
76
77 bool cmBinUtilsMacOSMachOLinker::ScanDependencies(
78   std::string const& file, cmStateEnums::TargetType type)
79 {
80   std::string executableFile;
81   if (type == cmStateEnums::EXECUTABLE) {
82     executableFile = file;
83   } else {
84     executableFile = this->Archive->GetBundleExecutable();
85   }
86   std::string executablePath;
87   if (!executableFile.empty()) {
88     executablePath = cmSystemTools::GetFilenamePath(executableFile);
89   }
90   const FileInfo* file_info = this->GetFileInfo(file);
91   if (file_info == nullptr) {
92     return false;
93   }
94   return this->ScanDependencies(file, file_info->libs, file_info->rpaths,
95                                 executablePath);
96 }
97
98 bool cmBinUtilsMacOSMachOLinker::ScanDependencies(
99   std::string const& file, std::vector<std::string> const& libs,
100   std::vector<std::string> const& rpaths, std::string const& executablePath)
101 {
102   std::string loaderPath = cmSystemTools::GetFilenamePath(file);
103   return this->GetFileDependencies(libs, executablePath, loaderPath, rpaths);
104 }
105
106 bool cmBinUtilsMacOSMachOLinker::GetFileDependencies(
107   std::vector<std::string> const& names, std::string const& executablePath,
108   std::string const& loaderPath, std::vector<std::string> const& rpaths)
109 {
110   for (std::string const& name : names) {
111     if (!this->Archive->IsPreExcluded(name)) {
112       std::string path;
113       bool resolved;
114       if (!this->ResolveDependency(name, executablePath, loaderPath, rpaths,
115                                    path, resolved)) {
116         return false;
117       }
118       if (resolved) {
119         if (!this->Archive->IsPostExcluded(path) &&
120             !IsMissingSystemDylib(path)) {
121           auto filename = cmSystemTools::GetFilenameName(path);
122           bool unique;
123           const FileInfo* dep_file_info = this->GetFileInfo(path);
124           if (dep_file_info == nullptr) {
125             return false;
126           }
127
128           this->Archive->AddResolvedPath(filename, path, unique,
129                                          dep_file_info->rpaths);
130           if (unique &&
131               !this->ScanDependencies(path, dep_file_info->libs,
132                                       dep_file_info->rpaths, executablePath)) {
133             return false;
134           }
135         }
136       } else {
137         this->Archive->AddUnresolvedPath(name);
138       }
139     }
140   }
141
142   return true;
143 }
144
145 bool cmBinUtilsMacOSMachOLinker::ResolveDependency(
146   std::string const& name, std::string const& executablePath,
147   std::string const& loaderPath, std::vector<std::string> const& rpaths,
148   std::string& path, bool& resolved)
149 {
150   resolved = false;
151   if (cmHasLiteralPrefix(name, "@rpath/")) {
152     if (!this->ResolveRPathDependency(name, executablePath, loaderPath, rpaths,
153                                       path, resolved)) {
154       return false;
155     }
156   } else if (cmHasLiteralPrefix(name, "@loader_path/")) {
157     if (!this->ResolveLoaderPathDependency(name, loaderPath, path, resolved)) {
158       return false;
159     }
160   } else if (cmHasLiteralPrefix(name, "@executable_path/")) {
161     if (!this->ResolveExecutablePathDependency(name, executablePath, path,
162                                                resolved)) {
163       return false;
164     }
165   } else {
166     resolved = true;
167     path = name;
168   }
169
170   if (resolved && !cmSystemTools::FileIsFullPath(path)) {
171     this->SetError("Resolved path is not absolute");
172     return false;
173   }
174
175   return true;
176 }
177
178 bool cmBinUtilsMacOSMachOLinker::ResolveExecutablePathDependency(
179   std::string const& name, std::string const& executablePath,
180   std::string& path, bool& resolved)
181 {
182   if (executablePath.empty()) {
183     resolved = false;
184     return true;
185   }
186
187   // 16 is == "@executable_path".length()
188   path = name;
189   path.replace(0, 16, executablePath);
190
191   if (!cmSystemTools::PathExists(path)) {
192     resolved = false;
193     return true;
194   }
195
196   resolved = true;
197   return true;
198 }
199
200 bool cmBinUtilsMacOSMachOLinker::ResolveLoaderPathDependency(
201   std::string const& name, std::string const& loaderPath, std::string& path,
202   bool& resolved)
203 {
204   if (loaderPath.empty()) {
205     resolved = false;
206     return true;
207   }
208
209   // 12 is "@loader_path".length();
210   path = name;
211   path.replace(0, 12, loaderPath);
212
213   if (!cmSystemTools::PathExists(path)) {
214     resolved = false;
215     return true;
216   }
217
218   resolved = true;
219   return true;
220 }
221
222 bool cmBinUtilsMacOSMachOLinker::ResolveRPathDependency(
223   std::string const& name, std::string const& executablePath,
224   std::string const& loaderPath, std::vector<std::string> const& rpaths,
225   std::string& path, bool& resolved)
226 {
227   for (std::string const& rpath : rpaths) {
228     std::string searchFile = name;
229     searchFile.replace(0, 6, rpath);
230     if (cmHasLiteralPrefix(searchFile, "@loader_path/")) {
231       if (!this->ResolveLoaderPathDependency(searchFile, loaderPath, path,
232                                              resolved)) {
233         return false;
234       }
235       if (resolved) {
236         return true;
237       }
238     } else if (cmHasLiteralPrefix(searchFile, "@executable_path/")) {
239       if (!this->ResolveExecutablePathDependency(searchFile, executablePath,
240                                                  path, resolved)) {
241         return false;
242       }
243       if (resolved) {
244         return true;
245       }
246     } else if (cmSystemTools::PathExists(searchFile)) {
247       /*
248        * paraphrasing @ben.boeckel:
249        *  if /b/libB.dylib is supposed to be used,
250        *  /a/libbB.dylib will be found first if it exists. CMake tries to
251        *  sort rpath directories to avoid this, but sometimes there is no
252        *  right answer.
253        *
254        *  I believe it is possible to resolve this using otools -l
255        *  then checking the LC_LOAD_DYLIB command whose name is
256        *  equal to the value of search_file, UNLESS the build
257        *  specifically sets the RPath to paths that will match
258        *  duplicate libs; at this point can we just point to
259        *  user error, or is there a reason why the advantages
260        *  to this scenario outweigh its disadvantages?
261        *
262        *  Also priority seems to be the order as passed in when compiled
263        *  so as long as this method's resolution guarantees priority
264        *  in that manner further checking should not be necessary?
265        */
266       path = searchFile;
267       resolved = true;
268       return true;
269     }
270   }
271
272   resolved = false;
273   return true;
274 }