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