Coverage Report

Created: 2026-06-15 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmFindProgramCommand.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
#include "cmFindProgramCommand.h"
4
5
#include <algorithm>
6
#include <string>
7
8
#include <cm/memory>
9
#include <cmext/string_view>
10
11
#include "cmFindCommon.h"
12
#include "cmMakefile.h"
13
#include "cmPolicies.h"
14
#include "cmStateTypes.h"
15
#include "cmStringAlgorithms.h"
16
#include "cmSystemTools.h"
17
#include "cmValue.h"
18
#include "cmWindowsRegistry.h"
19
20
class cmExecutionStatus;
21
22
#if defined(__APPLE__)
23
#  include <CoreFoundation/CFBundle.h>
24
#  include <CoreFoundation/CFString.h>
25
#  include <CoreFoundation/CFURL.h>
26
#endif
27
28
struct cmFindProgramHelper
29
{
30
  cmFindProgramHelper(cmMakefile* makefile, cmFindBase const* base,
31
                      cmFindCommonDebugState* debugState)
32
0
    : DebugState(debugState)
33
0
    , Makefile(makefile)
34
0
    , FindBase(base)
35
0
    , PolicyCMP0109(makefile->GetPolicyStatus(cmPolicies::CMP0109))
36
0
  {
37
#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
38
    // Consider platform-specific extensions.
39
    this->Extensions.push_back(".com");
40
    this->Extensions.push_back(".exe");
41
#endif
42
    // Consider original name with no extensions.
43
0
    this->Extensions.emplace_back();
44
0
  }
45
46
  // List of valid extensions.
47
  std::vector<std::string> Extensions;
48
49
  // Keep track of the best program file found so far.
50
  std::string BestPath;
51
52
  // Current names under consideration.
53
  std::vector<std::string> Names;
54
55
  // Debug state
56
  cmFindCommonDebugState* DebugState;
57
  cmMakefile* Makefile;
58
  cmFindBase const* FindBase;
59
60
  cmPolicies::PolicyStatus PolicyCMP0109;
61
62
0
  void AddName(std::string const& name) { this->Names.push_back(name); }
63
  void SetName(std::string const& name)
64
0
  {
65
0
    this->Names.clear();
66
0
    this->AddName(name);
67
0
  }
68
  bool CheckCompoundNames()
69
0
  {
70
0
    return std::any_of(this->Names.begin(), this->Names.end(),
71
0
                       [this](std::string const& n) -> bool {
72
                         // Only perform search relative to current directory
73
                         // if the file name contains a directory separator.
74
0
                         return n.find('/') != std::string::npos &&
75
0
                           this->CheckDirectoryForName("", n);
76
0
                       });
77
0
  }
78
  bool CheckDirectory(std::string const& path)
79
0
  {
80
0
    return std::any_of(this->Names.begin(), this->Names.end(),
81
0
                       [this, &path](std::string const& n) -> bool {
82
0
                         return this->CheckDirectoryForName(path, n);
83
0
                       });
84
0
  }
85
  bool CheckDirectoryForName(std::string const& path, std::string const& name)
86
0
  {
87
0
    return std::any_of(this->Extensions.begin(), this->Extensions.end(),
88
0
                       [this, &path, &name](std::string const& ext) -> bool {
89
0
                         if (!ext.empty() && cmHasSuffix(name, ext)) {
90
0
                           return false;
91
0
                         }
92
0
                         std::string testNameExt = cmStrCat(name, ext);
93
0
                         std::string testPath =
94
0
                           cmSystemTools::CollapseFullPath(testNameExt, path);
95
0
                         if (this->FileIsExecutable(testPath)) {
96
0
                           testPath =
97
0
                             cmSystemTools::ToNormalizedPathOnDisk(testPath);
98
0
                           if (this->FindBase->Validate(testPath)) {
99
0
                             this->BestPath = testPath;
100
0
                             if (this->DebugState) {
101
0
                               this->DebugState->FoundAt(testPath);
102
0
                             }
103
0
                             return true;
104
0
                           }
105
0
                         }
106
0
                         if (this->DebugState) {
107
0
                           this->DebugState->FailedAt(testPath);
108
0
                         }
109
0
                         return false;
110
0
                       });
111
0
  }
112
  bool FileIsExecutable(std::string const& file) const
113
0
  {
114
0
    if (!this->FileIsExecutableCMP0109(file)) {
115
0
      return false;
116
0
    }
117
#ifdef _WIN32
118
    // Pretend the Windows "python" app installer alias does not exist.
119
    if (cmSystemTools::LowerCase(file).find("/windowsapps/python") !=
120
        std::string::npos) {
121
      std::string dest;
122
      if (cmSystemTools::ReadSymlink(file, dest) &&
123
          cmHasLiteralSuffix(dest, "\\AppInstallerPythonRedirector.exe")) {
124
        return false;
125
      }
126
    }
127
#endif
128
0
    return true;
129
0
  }
130
  bool FileIsExecutableCMP0109(std::string const& file) const
131
0
  {
132
0
    switch (this->PolicyCMP0109) {
133
0
      case cmPolicies::OLD:
134
0
        return cmSystemTools::FileExists(file, true);
135
0
      case cmPolicies::NEW:
136
0
        return cmSystemTools::FileIsExecutable(file);
137
0
      default:
138
0
        break;
139
0
    }
140
0
    bool const isExeOld = cmSystemTools::FileExists(file, true);
141
0
    bool const isExeNew = cmSystemTools::FileIsExecutable(file);
142
0
    if (isExeNew == isExeOld) {
143
0
      return isExeNew;
144
0
    }
145
0
    if (isExeNew) {
146
0
      this->Makefile->IssuePolicyWarning(
147
0
        cmPolicies::CMP0109, {},
148
0
        cmStrCat("The file\n  "_s, file,
149
0
                 "\nis executable but not readable.  "
150
0
                 "CMake is ignoring it for compatibility."_s));
151
0
    } else {
152
0
      this->Makefile->IssuePolicyWarning(
153
0
        cmPolicies::CMP0109, {},
154
0
        cmStrCat("The file\n  "_s, file,
155
0
                 "\nis readable but not executable.  "
156
0
                 "CMake is using it for compatibility."_s));
157
0
    }
158
0
    return isExeOld;
159
0
  }
160
};
161
162
cmFindProgramCommand::cmFindProgramCommand(cmExecutionStatus& status)
163
0
  : cmFindBase("find_program", status)
164
0
{
165
0
  this->NamesPerDirAllowed = true;
166
0
  this->VariableDocumentation = "Path to a program.";
167
0
  this->VariableType = cmStateEnums::FILEPATH;
168
  // Windows Registry views
169
  // When policy CMP0134 is not NEW, rely on previous behavior:
170
0
  if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0134) !=
171
0
      cmPolicies::NEW) {
172
0
    if (this->Makefile->GetDefinition("CMAKE_SIZEOF_VOID_P") == "8") {
173
0
      this->RegistryView = cmWindowsRegistry::View::Reg64_32;
174
0
    } else {
175
0
      this->RegistryView = cmWindowsRegistry::View::Reg32_64;
176
0
    }
177
0
  } else {
178
0
    this->RegistryView = cmWindowsRegistry::View::Both;
179
0
  }
180
0
}
181
182
// cmFindProgramCommand
183
bool cmFindProgramCommand::InitialPass(std::vector<std::string> const& argsIn)
184
0
{
185
186
0
  this->CMakePathName = "PROGRAM";
187
188
  // call cmFindBase::ParseArguments
189
0
  if (!this->ParseArguments(argsIn)) {
190
0
    return false;
191
0
  }
192
193
0
  this->FullDebugMode = this->ComputeIfDebugModeWanted(this->VariableName);
194
0
  if (this->FullDebugMode || !this->ComputeIfImplicitDebugModeSuppressed()) {
195
0
    this->DebugState = cm::make_unique<cmFindBaseDebugState>(this);
196
0
  }
197
198
0
  if (this->IsFound()) {
199
0
    this->NormalizeFindResult();
200
0
    return true;
201
0
  }
202
203
0
  std::string const result = this->FindProgram();
204
0
  this->StoreFindResult(result);
205
0
  return true;
206
0
}
207
208
std::string cmFindProgramCommand::FindProgram()
209
0
{
210
0
  std::string program;
211
212
0
  if (this->SearchAppBundleFirst || this->SearchAppBundleOnly) {
213
0
    program = this->FindAppBundle();
214
0
  }
215
0
  if (program.empty() && !this->SearchAppBundleOnly) {
216
0
    program = this->FindNormalProgram();
217
0
  }
218
219
0
  if (program.empty() && this->SearchAppBundleLast) {
220
0
    program = this->FindAppBundle();
221
0
  }
222
0
  return program;
223
0
}
224
225
std::string cmFindProgramCommand::FindNormalProgram()
226
0
{
227
0
  if (this->NamesPerDir) {
228
0
    return this->FindNormalProgramNamesPerDir();
229
0
  }
230
0
  return this->FindNormalProgramDirsPerName();
231
0
}
232
233
std::string cmFindProgramCommand::FindNormalProgramNamesPerDir()
234
0
{
235
  // Search for all names in each directory.
236
0
  cmFindProgramHelper helper(this->Makefile, this, this->DebugState.get());
237
0
  for (std::string const& n : this->Names) {
238
0
    helper.AddName(n);
239
0
  }
240
241
  // Check for the names themselves if they contain a directory separator.
242
0
  if (helper.CheckCompoundNames()) {
243
0
    return helper.BestPath;
244
0
  }
245
246
  // Search every directory.
247
0
  for (std::string const& sp : this->SearchPaths) {
248
0
    if (helper.CheckDirectory(sp)) {
249
0
      return helper.BestPath;
250
0
    }
251
0
  }
252
  // Couldn't find the program.
253
0
  return "";
254
0
}
255
256
std::string cmFindProgramCommand::FindNormalProgramDirsPerName()
257
0
{
258
  // Search the entire path for each name.
259
0
  cmFindProgramHelper helper(this->Makefile, this, this->DebugState.get());
260
0
  for (std::string const& n : this->Names) {
261
    // Switch to searching for this name.
262
0
    helper.SetName(n);
263
264
    // Check for the names themselves if they contain a directory separator.
265
0
    if (helper.CheckCompoundNames()) {
266
0
      return helper.BestPath;
267
0
    }
268
269
    // Search every directory.
270
0
    for (std::string const& sp : this->SearchPaths) {
271
0
      if (helper.CheckDirectory(sp)) {
272
0
        return helper.BestPath;
273
0
      }
274
0
    }
275
0
  }
276
  // Couldn't find the program.
277
0
  return "";
278
0
}
279
280
std::string cmFindProgramCommand::FindAppBundle()
281
0
{
282
0
  for (std::string const& name : this->Names) {
283
284
0
    std::string appName = name + std::string(".app");
285
0
    std::string appPath =
286
0
      cmSystemTools::FindDirectory(appName, this->SearchPaths, true);
287
288
0
    if (!appPath.empty()) {
289
0
      std::string executable = this->GetBundleExecutable(appPath);
290
0
      if (!executable.empty()) {
291
0
        return cmSystemTools::CollapseFullPath(executable);
292
0
      }
293
0
    }
294
0
  }
295
296
  // Couldn't find app bundle
297
0
  return "";
298
0
}
299
300
std::string cmFindProgramCommand::GetBundleExecutable(
301
  std::string const& bundlePath)
302
0
{
303
0
  std::string executable;
304
0
  (void)bundlePath;
305
#if defined(__APPLE__)
306
  // Started with an example on developer.apple.com about finding bundles
307
  // and modified from that.
308
309
  // Get a CFString of the app bundle path
310
  // XXX - Is it safe to assume everything is in UTF8?
311
  CFStringRef bundlePathCFS = CFStringCreateWithCString(
312
    kCFAllocatorDefault, bundlePath.c_str(), kCFStringEncodingUTF8);
313
314
  // Make a CFURLRef from the CFString representation of the
315
  // bundle’s path.
316
  CFURLRef bundleURL = CFURLCreateWithFileSystemPath(
317
    kCFAllocatorDefault, bundlePathCFS, kCFURLPOSIXPathStyle, true);
318
319
  // Make a bundle instance using the URLRef.
320
  CFBundleRef appBundle = CFBundleCreate(kCFAllocatorDefault, bundleURL);
321
322
  // returned executableURL is relative to <appbundle>/Contents/MacOS/
323
  CFURLRef executableURL = CFBundleCopyExecutableURL(appBundle);
324
325
  if (executableURL) {
326
    int const MAX_OSX_PATH_SIZE = 1024;
327
    UInt8 buffer[MAX_OSX_PATH_SIZE];
328
329
    if (CFURLGetFileSystemRepresentation(executableURL, false, buffer,
330
                                         MAX_OSX_PATH_SIZE)) {
331
      executable = cmStrCat(bundlePath, "/Contents/MacOS/",
332
                            reinterpret_cast<char const*>(buffer));
333
    }
334
    // Only release CFURLRef if it's not null
335
    CFRelease(executableURL);
336
  }
337
338
  // Any CF objects returned from functions with "create" or
339
  // "copy" in their names must be released by us!
340
  CFRelease(bundlePathCFS);
341
  CFRelease(bundleURL);
342
  CFRelease(appBundle);
343
#endif
344
345
0
  return executable;
346
0
}
347
348
bool cmFindProgram(std::vector<std::string> const& args,
349
                   cmExecutionStatus& status)
350
0
{
351
0
  return cmFindProgramCommand(status).InitialPass(args);
352
0
}