Coverage Report

Created: 2026-07-14 07:09

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
#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
}