Coverage Report

Created: 2026-03-12 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}