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"
9 #include "cmMakefile.h"
10 #include "cmMessageType.h"
11 #include "cmPolicies.h"
12 #include "cmStateTypes.h"
13 #include "cmStringAlgorithms.h"
14 #include "cmSystemTools.h"
16 #include "cmWindowsRegistry.h"
18 class cmExecutionStatus
;
20 #if defined(__APPLE__)
21 # include <CoreFoundation/CoreFoundation.h>
24 struct cmFindProgramHelper
26 cmFindProgramHelper(std::string debugName
, cmMakefile
* makefile
,
27 cmFindBase
const* base
)
28 : DebugSearches(std::move(debugName
), 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");
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.
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.
58 cmFindBaseDebugState DebugSearches
;
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
)
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
);
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
);
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
)) {
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
);
103 this->BestPath
= this->TestPath
;
109 bool FileIsValid(std::string
const& file
) const
111 if (!this->FileIsExecutableCMP0109(file
)) {
115 // Pretend the Windows "python" app installer alias does not exist.
116 if (cmSystemTools::LowerCase(file
).find("/windowsapps/python") !=
119 if (cmSystemTools::ReadSymlink(file
, dest
) &&
120 cmHasLiteralSuffix(dest
, "\\AppInstallerPythonRedirector.exe")) {
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
);
139 bool const isExeOld
= cmSystemTools::FileExists(file
, true);
140 bool const isExeNew
= cmSystemTools::FileIsExecutable(file
);
141 if (isExeNew
== isExeOld
) {
145 this->Makefile
->IssueMessage(
146 MessageType::AUTHOR_WARNING
,
147 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0109
),
153 "is executable but not readable. "
154 "CMake is ignoring it for compatibility."));
156 this->Makefile
->IssueMessage(
157 MessageType::AUTHOR_WARNING
,
158 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0109
),
164 "is readable but not executable. "
165 "CMake is using it for compatibility."));
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
) !=
181 if (this->Makefile
->GetDefinition("CMAKE_SIZEOF_VOID_P") == "8") {
182 this->RegistryView
= cmWindowsRegistry::View::Reg64_32
;
184 this->RegistryView
= cmWindowsRegistry::View::Reg32_64
;
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
)) {
201 this->DebugMode
= this->ComputeIfDebugModeWanted(this->VariableName
);
203 if (this->AlreadyDefined
) {
204 this->NormalizeFindResult();
208 std::string
const result
= this->FindProgram();
209 this->StoreFindResult(result
);
213 std::string
cmFindProgramCommand::FindProgram()
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();
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
) {
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.
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.
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.
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
305 std::string
cmFindProgramCommand::GetBundleExecutable(
306 std::string
const& bundlePath
)
308 std::string executable
;
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
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
);
353 bool cmFindProgram(std::vector
<std::string
> const& args
,
354 cmExecutionStatus
& status
)
356 return cmFindProgramCommand(status
).InitialPass(args
);