CMake Nightly Date Stamp
[kiteware-cmake.git] / Source / cmFindProgramCommand.cxx
blob8a2a69e421976be27f4a4eeeb46c628c65e473fa
1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmFindProgramCommand.h"
5 #include <algorithm>
6 #include <string>
7 #include <utility>
9 #include "cmMakefile.h"
10 #include "cmMessageType.h"
11 #include "cmPolicies.h"
12 #include "cmStateTypes.h"
13 #include "cmStringAlgorithms.h"
14 #include "cmSystemTools.h"
15 #include "cmValue.h"
16 #include "cmWindowsRegistry.h"
18 class cmExecutionStatus;
20 #if defined(__APPLE__)
21 # include <CoreFoundation/CoreFoundation.h>
22 #endif
24 struct cmFindProgramHelper
26 cmFindProgramHelper(std::string debugName, cmMakefile* makefile,
27 cmFindBase const* base)
28 : DebugSearches(std::move(debugName), base)
29 , Makefile(makefile)
30 , FindBase(base)
31 , PolicyCMP0109(makefile->GetPolicyStatus(cmPolicies::CMP0109))
33 #if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
34 // Consider platform-specific extensions.
35 this->Extensions.push_back(".com");
36 this->Extensions.push_back(".exe");
37 #endif
38 // Consider original name with no extensions.
39 this->Extensions.emplace_back();
42 // List of valid extensions.
43 std::vector<std::string> Extensions;
45 // Keep track of the best program file found so far.
46 std::string BestPath;
48 // Current names under consideration.
49 std::vector<std::string> Names;
51 // Current name with extension under consideration.
52 std::string TestNameExt;
54 // Current full path under consideration.
55 std::string TestPath;
57 // Debug state
58 cmFindBaseDebugState DebugSearches;
59 cmMakefile* Makefile;
60 cmFindBase const* FindBase;
62 cmPolicies::PolicyStatus PolicyCMP0109;
64 void AddName(std::string const& name) { this->Names.push_back(name); }
65 void SetName(std::string const& name)
67 this->Names.clear();
68 this->AddName(name);
70 bool CheckCompoundNames()
72 return std::any_of(this->Names.begin(), this->Names.end(),
73 [this](std::string const& n) -> bool {
74 // Only perform search relative to current directory
75 // if the file name contains a directory separator.
76 return n.find('/') != std::string::npos &&
77 this->CheckDirectoryForName("", n);
78 });
80 bool CheckDirectory(std::string const& path)
82 return std::any_of(this->Names.begin(), this->Names.end(),
83 [this, &path](std::string const& n) -> bool {
84 // Only perform search relative to current directory
85 // if the file name contains a directory separator.
86 return this->CheckDirectoryForName(path, n);
87 });
89 bool CheckDirectoryForName(std::string const& path, std::string const& name)
91 return std::any_of(this->Extensions.begin(), this->Extensions.end(),
92 [this, &path, &name](std::string const& ext) -> bool {
93 if (!ext.empty() && cmHasSuffix(name, ext)) {
94 return false;
96 this->TestNameExt = cmStrCat(name, ext);
97 this->TestPath = cmSystemTools::CollapseFullPath(
98 this->TestNameExt, path);
99 bool exists = this->FileIsValid(this->TestPath);
100 exists ? this->DebugSearches.FoundAt(this->TestPath)
101 : this->DebugSearches.FailedAt(this->TestPath);
102 if (exists) {
103 this->BestPath = this->TestPath;
104 return true;
106 return false;
109 bool FileIsValid(std::string const& file) const
111 if (!this->FileIsExecutableCMP0109(file)) {
112 return false;
114 #ifdef _WIN32
115 // Pretend the Windows "python" app installer alias does not exist.
116 if (cmSystemTools::LowerCase(file).find("/windowsapps/python") !=
117 std::string::npos) {
118 std::string dest;
119 if (cmSystemTools::ReadSymlink(file, dest) &&
120 cmHasLiteralSuffix(dest, "\\AppInstallerPythonRedirector.exe")) {
121 return false;
124 #endif
125 return this->FindBase->Validate(file);
127 bool FileIsExecutableCMP0109(std::string const& file) const
129 switch (this->PolicyCMP0109) {
130 case cmPolicies::OLD:
131 return cmSystemTools::FileExists(file, true);
132 case cmPolicies::NEW:
133 case cmPolicies::REQUIRED_ALWAYS:
134 case cmPolicies::REQUIRED_IF_USED:
135 return cmSystemTools::FileIsExecutable(file);
136 default:
137 break;
139 bool const isExeOld = cmSystemTools::FileExists(file, true);
140 bool const isExeNew = cmSystemTools::FileIsExecutable(file);
141 if (isExeNew == isExeOld) {
142 return isExeNew;
144 if (isExeNew) {
145 this->Makefile->IssueMessage(
146 MessageType::AUTHOR_WARNING,
147 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0109),
148 "\n"
149 "The file\n"
150 " ",
151 file,
152 "\n"
153 "is executable but not readable. "
154 "CMake is ignoring it for compatibility."));
155 } else {
156 this->Makefile->IssueMessage(
157 MessageType::AUTHOR_WARNING,
158 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0109),
159 "\n"
160 "The file\n"
161 " ",
162 file,
163 "\n"
164 "is readable but not executable. "
165 "CMake is using it for compatibility."));
167 return isExeOld;
171 cmFindProgramCommand::cmFindProgramCommand(cmExecutionStatus& status)
172 : cmFindBase("find_program", status)
174 this->NamesPerDirAllowed = true;
175 this->VariableDocumentation = "Path to a program.";
176 this->VariableType = cmStateEnums::FILEPATH;
177 // Windows Registry views
178 // When policy CMP0134 is not NEW, rely on previous behavior:
179 if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0134) !=
180 cmPolicies::NEW) {
181 if (this->Makefile->GetDefinition("CMAKE_SIZEOF_VOID_P") == "8") {
182 this->RegistryView = cmWindowsRegistry::View::Reg64_32;
183 } else {
184 this->RegistryView = cmWindowsRegistry::View::Reg32_64;
186 } else {
187 this->RegistryView = cmWindowsRegistry::View::Both;
191 // cmFindProgramCommand
192 bool cmFindProgramCommand::InitialPass(std::vector<std::string> const& argsIn)
195 this->CMakePathName = "PROGRAM";
197 // call cmFindBase::ParseArguments
198 if (!this->ParseArguments(argsIn)) {
199 return false;
201 this->DebugMode = this->ComputeIfDebugModeWanted(this->VariableName);
203 if (this->AlreadyDefined) {
204 this->NormalizeFindResult();
205 return true;
208 std::string const result = this->FindProgram();
209 this->StoreFindResult(result);
210 return true;
213 std::string cmFindProgramCommand::FindProgram()
215 std::string program;
217 if (this->SearchAppBundleFirst || this->SearchAppBundleOnly) {
218 program = this->FindAppBundle();
220 if (program.empty() && !this->SearchAppBundleOnly) {
221 program = this->FindNormalProgram();
224 if (program.empty() && this->SearchAppBundleLast) {
225 program = this->FindAppBundle();
227 return program;
230 std::string cmFindProgramCommand::FindNormalProgram()
232 if (this->NamesPerDir) {
233 return this->FindNormalProgramNamesPerDir();
235 return this->FindNormalProgramDirsPerName();
238 std::string cmFindProgramCommand::FindNormalProgramNamesPerDir()
240 // Search for all names in each directory.
241 cmFindProgramHelper helper(this->FindCommandName, this->Makefile, this);
242 for (std::string const& n : this->Names) {
243 helper.AddName(n);
246 // Check for the names themselves if they contain a directory separator.
247 if (helper.CheckCompoundNames()) {
248 return helper.BestPath;
251 // Search every directory.
252 for (std::string const& sp : this->SearchPaths) {
253 if (helper.CheckDirectory(sp)) {
254 return helper.BestPath;
257 // Couldn't find the program.
258 return "";
261 std::string cmFindProgramCommand::FindNormalProgramDirsPerName()
263 // Search the entire path for each name.
264 cmFindProgramHelper helper(this->FindCommandName, this->Makefile, this);
265 for (std::string const& n : this->Names) {
266 // Switch to searching for this name.
267 helper.SetName(n);
269 // Check for the names themselves if they contain a directory separator.
270 if (helper.CheckCompoundNames()) {
271 return helper.BestPath;
274 // Search every directory.
275 for (std::string const& sp : this->SearchPaths) {
276 if (helper.CheckDirectory(sp)) {
277 return helper.BestPath;
281 // Couldn't find the program.
282 return "";
285 std::string cmFindProgramCommand::FindAppBundle()
287 for (std::string const& name : this->Names) {
289 std::string appName = name + std::string(".app");
290 std::string appPath =
291 cmSystemTools::FindDirectory(appName, this->SearchPaths, true);
293 if (!appPath.empty()) {
294 std::string executable = this->GetBundleExecutable(appPath);
295 if (!executable.empty()) {
296 return cmSystemTools::CollapseFullPath(executable);
301 // Couldn't find app bundle
302 return "";
305 std::string cmFindProgramCommand::GetBundleExecutable(
306 std::string const& bundlePath)
308 std::string executable;
309 (void)bundlePath;
310 #if defined(__APPLE__)
311 // Started with an example on developer.apple.com about finding bundles
312 // and modified from that.
314 // Get a CFString of the app bundle path
315 // XXX - Is it safe to assume everything is in UTF8?
316 CFStringRef bundlePathCFS = CFStringCreateWithCString(
317 kCFAllocatorDefault, bundlePath.c_str(), kCFStringEncodingUTF8);
319 // Make a CFURLRef from the CFString representation of the
320 // bundle’s path.
321 CFURLRef bundleURL = CFURLCreateWithFileSystemPath(
322 kCFAllocatorDefault, bundlePathCFS, kCFURLPOSIXPathStyle, true);
324 // Make a bundle instance using the URLRef.
325 CFBundleRef appBundle = CFBundleCreate(kCFAllocatorDefault, bundleURL);
327 // returned executableURL is relative to <appbundle>/Contents/MacOS/
328 CFURLRef executableURL = CFBundleCopyExecutableURL(appBundle);
330 if (executableURL != nullptr) {
331 const int MAX_OSX_PATH_SIZE = 1024;
332 UInt8 buffer[MAX_OSX_PATH_SIZE];
334 if (CFURLGetFileSystemRepresentation(executableURL, false, buffer,
335 MAX_OSX_PATH_SIZE)) {
336 executable = bundlePath + "/Contents/MacOS/" +
337 std::string(reinterpret_cast<char*>(buffer));
339 // Only release CFURLRef if it's not null
340 CFRelease(executableURL);
343 // Any CF objects returned from functions with "create" or
344 // "copy" in their names must be released by us!
345 CFRelease(bundlePathCFS);
346 CFRelease(bundleURL);
347 CFRelease(appBundle);
348 #endif
350 return executable;
353 bool cmFindProgram(std::vector<std::string> const& args,
354 cmExecutionStatus& status)
356 return cmFindProgramCommand(status).InitialPass(args);