Coverage Report

Created: 2026-02-09 06:05

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
10
#include "cmFindCommon.h"
11
#include "cmMakefile.h"
12
#include "cmMessageType.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->IssueMessage(
147
0
        MessageType::AUTHOR_WARNING,
148
0
        cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0109),
149
0
                 "\n"
150
0
                 "The file\n"
151
0
                 "  ",
152
0
                 file,
153
0
                 "\n"
154
0
                 "is executable but not readable.  "
155
0
                 "CMake is ignoring it for compatibility."));
156
0
    } else {
157
0
      this->Makefile->IssueMessage(
158
0
        MessageType::AUTHOR_WARNING,
159
0
        cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0109),
160
0
                 "\n"
161
0
                 "The file\n"
162
0
                 "  ",
163
0
                 file,
164
0
                 "\n"
165
0
                 "is readable but not executable.  "
166
0
                 "CMake is using it for compatibility."));
167
0
    }
168
0
    return isExeOld;
169
0
  }
170
};
171
172
cmFindProgramCommand::cmFindProgramCommand(cmExecutionStatus& status)
173
0
  : cmFindBase("find_program", status)
174
0
{
175
0
  this->NamesPerDirAllowed = true;
176
0
  this->VariableDocumentation = "Path to a program.";
177
0
  this->VariableType = cmStateEnums::FILEPATH;
178
  // Windows Registry views
179
  // When policy CMP0134 is not NEW, rely on previous behavior:
180
0
  if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0134) !=
181
0
      cmPolicies::NEW) {
182
0
    if (this->Makefile->GetDefinition("CMAKE_SIZEOF_VOID_P") == "8") {
183
0
      this->RegistryView = cmWindowsRegistry::View::Reg64_32;
184
0
    } else {
185
0
      this->RegistryView = cmWindowsRegistry::View::Reg32_64;
186
0
    }
187
0
  } else {
188
0
    this->RegistryView = cmWindowsRegistry::View::Both;
189
0
  }
190
0
}
191
192
// cmFindProgramCommand
193
bool cmFindProgramCommand::InitialPass(std::vector<std::string> const& argsIn)
194
0
{
195
196
0
  this->CMakePathName = "PROGRAM";
197
198
  // call cmFindBase::ParseArguments
199
0
  if (!this->ParseArguments(argsIn)) {
200
0
    return false;
201
0
  }
202
203
0
  this->FullDebugMode = this->ComputeIfDebugModeWanted(this->VariableName);
204
0
  if (this->FullDebugMode || !this->ComputeIfImplicitDebugModeSuppressed()) {
205
0
    this->DebugState = cm::make_unique<cmFindBaseDebugState>(this);
206
0
  }
207
208
0
  if (this->IsFound()) {
209
0
    this->NormalizeFindResult();
210
0
    return true;
211
0
  }
212
213
0
  std::string const result = this->FindProgram();
214
0
  this->StoreFindResult(result);
215
0
  return true;
216
0
}
217
218
std::string cmFindProgramCommand::FindProgram()
219
0
{
220
0
  std::string program;
221
222
0
  if (this->SearchAppBundleFirst || this->SearchAppBundleOnly) {
223
0
    program = this->FindAppBundle();
224
0
  }
225
0
  if (program.empty() && !this->SearchAppBundleOnly) {
226
0
    program = this->FindNormalProgram();
227
0
  }
228
229
0
  if (program.empty() && this->SearchAppBundleLast) {
230
0
    program = this->FindAppBundle();
231
0
  }
232
0
  return program;
233
0
}
234
235
std::string cmFindProgramCommand::FindNormalProgram()
236
0
{
237
0
  if (this->NamesPerDir) {
238
0
    return this->FindNormalProgramNamesPerDir();
239
0
  }
240
0
  return this->FindNormalProgramDirsPerName();
241
0
}
242
243
std::string cmFindProgramCommand::FindNormalProgramNamesPerDir()
244
0
{
245
  // Search for all names in each directory.
246
0
  cmFindProgramHelper helper(this->Makefile, this, this->DebugState.get());
247
0
  for (std::string const& n : this->Names) {
248
0
    helper.AddName(n);
249
0
  }
250
251
  // Check for the names themselves if they contain a directory separator.
252
0
  if (helper.CheckCompoundNames()) {
253
0
    return helper.BestPath;
254
0
  }
255
256
  // Search every directory.
257
0
  for (std::string const& sp : this->SearchPaths) {
258
0
    if (helper.CheckDirectory(sp)) {
259
0
      return helper.BestPath;
260
0
    }
261
0
  }
262
  // Couldn't find the program.
263
0
  return "";
264
0
}
265
266
std::string cmFindProgramCommand::FindNormalProgramDirsPerName()
267
0
{
268
  // Search the entire path for each name.
269
0
  cmFindProgramHelper helper(this->Makefile, this, this->DebugState.get());
270
0
  for (std::string const& n : this->Names) {
271
    // Switch to searching for this name.
272
0
    helper.SetName(n);
273
274
    // Check for the names themselves if they contain a directory separator.
275
0
    if (helper.CheckCompoundNames()) {
276
0
      return helper.BestPath;
277
0
    }
278
279
    // Search every directory.
280
0
    for (std::string const& sp : this->SearchPaths) {
281
0
      if (helper.CheckDirectory(sp)) {
282
0
        return helper.BestPath;
283
0
      }
284
0
    }
285
0
  }
286
  // Couldn't find the program.
287
0
  return "";
288
0
}
289
290
std::string cmFindProgramCommand::FindAppBundle()
291
0
{
292
0
  for (std::string const& name : this->Names) {
293
294
0
    std::string appName = name + std::string(".app");
295
0
    std::string appPath =
296
0
      cmSystemTools::FindDirectory(appName, this->SearchPaths, true);
297
298
0
    if (!appPath.empty()) {
299
0
      std::string executable = this->GetBundleExecutable(appPath);
300
0
      if (!executable.empty()) {
301
0
        return cmSystemTools::CollapseFullPath(executable);
302
0
      }
303
0
    }
304
0
  }
305
306
  // Couldn't find app bundle
307
0
  return "";
308
0
}
309
310
std::string cmFindProgramCommand::GetBundleExecutable(
311
  std::string const& bundlePath)
312
0
{
313
0
  std::string executable;
314
0
  (void)bundlePath;
315
#if defined(__APPLE__)
316
  // Started with an example on developer.apple.com about finding bundles
317
  // and modified from that.
318
319
  // Get a CFString of the app bundle path
320
  // XXX - Is it safe to assume everything is in UTF8?
321
  CFStringRef bundlePathCFS = CFStringCreateWithCString(
322
    kCFAllocatorDefault, bundlePath.c_str(), kCFStringEncodingUTF8);
323
324
  // Make a CFURLRef from the CFString representation of the
325
  // bundle’s path.
326
  CFURLRef bundleURL = CFURLCreateWithFileSystemPath(
327
    kCFAllocatorDefault, bundlePathCFS, kCFURLPOSIXPathStyle, true);
328
329
  // Make a bundle instance using the URLRef.
330
  CFBundleRef appBundle = CFBundleCreate(kCFAllocatorDefault, bundleURL);
331
332
  // returned executableURL is relative to <appbundle>/Contents/MacOS/
333
  CFURLRef executableURL = CFBundleCopyExecutableURL(appBundle);
334
335
  if (executableURL) {
336
    int const MAX_OSX_PATH_SIZE = 1024;
337
    UInt8 buffer[MAX_OSX_PATH_SIZE];
338
339
    if (CFURLGetFileSystemRepresentation(executableURL, false, buffer,
340
                                         MAX_OSX_PATH_SIZE)) {
341
      executable = cmStrCat(bundlePath, "/Contents/MacOS/",
342
                            reinterpret_cast<char const*>(buffer));
343
    }
344
    // Only release CFURLRef if it's not null
345
    CFRelease(executableURL);
346
  }
347
348
  // Any CF objects returned from functions with "create" or
349
  // "copy" in their names must be released by us!
350
  CFRelease(bundlePathCFS);
351
  CFRelease(bundleURL);
352
  CFRelease(appBundle);
353
#endif
354
355
0
  return executable;
356
0
}
357
358
bool cmFindProgram(std::vector<std::string> const& args,
359
                   cmExecutionStatus& status)
360
0
{
361
0
  return cmFindProgramCommand(status).InitialPass(args);
362
0
}