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