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