Coverage Report

Created: 2026-10-28 06:38

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