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