mirror of
https://github.com/Kitware/CMake.git
synced 2026-07-01 12:18:01 +00:00
Add and use a couple helper functions for issuing policy warnings. This improves consistency and allows some simplification of many call sites. (One or two instances in particular are greatly simplified.)
353 lines
11 KiB
C++
353 lines
11 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||
file LICENSE.rst or https://cmake.org/licensing for details. */
|
||
#include "cmFindProgramCommand.h"
|
||
|
||
#include <algorithm>
|
||
#include <string>
|
||
|
||
#include <cm/memory>
|
||
#include <cmext/string_view>
|
||
|
||
#include "cmFindCommon.h"
|
||
#include "cmMakefile.h"
|
||
#include "cmPolicies.h"
|
||
#include "cmStateTypes.h"
|
||
#include "cmStringAlgorithms.h"
|
||
#include "cmSystemTools.h"
|
||
#include "cmValue.h"
|
||
#include "cmWindowsRegistry.h"
|
||
|
||
class cmExecutionStatus;
|
||
|
||
#if defined(__APPLE__)
|
||
# include <CoreFoundation/CFBundle.h>
|
||
# include <CoreFoundation/CFString.h>
|
||
# include <CoreFoundation/CFURL.h>
|
||
#endif
|
||
|
||
struct cmFindProgramHelper
|
||
{
|
||
cmFindProgramHelper(cmMakefile* makefile, cmFindBase const* base,
|
||
cmFindCommonDebugState* debugState)
|
||
: DebugState(debugState)
|
||
, Makefile(makefile)
|
||
, FindBase(base)
|
||
, PolicyCMP0109(makefile->GetPolicyStatus(cmPolicies::CMP0109))
|
||
{
|
||
#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
|
||
// Consider platform-specific extensions.
|
||
this->Extensions.push_back(".com");
|
||
this->Extensions.push_back(".exe");
|
||
#endif
|
||
// Consider original name with no extensions.
|
||
this->Extensions.emplace_back();
|
||
}
|
||
|
||
// List of valid extensions.
|
||
std::vector<std::string> Extensions;
|
||
|
||
// Keep track of the best program file found so far.
|
||
std::string BestPath;
|
||
|
||
// Current names under consideration.
|
||
std::vector<std::string> Names;
|
||
|
||
// Debug state
|
||
cmFindCommonDebugState* DebugState;
|
||
cmMakefile* Makefile;
|
||
cmFindBase const* FindBase;
|
||
|
||
cmPolicies::PolicyStatus PolicyCMP0109;
|
||
|
||
void AddName(std::string const& name) { this->Names.push_back(name); }
|
||
void SetName(std::string const& name)
|
||
{
|
||
this->Names.clear();
|
||
this->AddName(name);
|
||
}
|
||
bool CheckCompoundNames()
|
||
{
|
||
return std::any_of(this->Names.begin(), this->Names.end(),
|
||
[this](std::string const& n) -> bool {
|
||
// Only perform search relative to current directory
|
||
// if the file name contains a directory separator.
|
||
return n.find('/') != std::string::npos &&
|
||
this->CheckDirectoryForName("", n);
|
||
});
|
||
}
|
||
bool CheckDirectory(std::string const& path)
|
||
{
|
||
return std::any_of(this->Names.begin(), this->Names.end(),
|
||
[this, &path](std::string const& n) -> bool {
|
||
return this->CheckDirectoryForName(path, n);
|
||
});
|
||
}
|
||
bool CheckDirectoryForName(std::string const& path, std::string const& name)
|
||
{
|
||
return std::any_of(this->Extensions.begin(), this->Extensions.end(),
|
||
[this, &path, &name](std::string const& ext) -> bool {
|
||
if (!ext.empty() && cmHasSuffix(name, ext)) {
|
||
return false;
|
||
}
|
||
std::string testNameExt = cmStrCat(name, ext);
|
||
std::string testPath =
|
||
cmSystemTools::CollapseFullPath(testNameExt, path);
|
||
if (this->FileIsExecutable(testPath)) {
|
||
testPath =
|
||
cmSystemTools::ToNormalizedPathOnDisk(testPath);
|
||
if (this->FindBase->Validate(testPath)) {
|
||
this->BestPath = testPath;
|
||
if (this->DebugState) {
|
||
this->DebugState->FoundAt(testPath);
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
if (this->DebugState) {
|
||
this->DebugState->FailedAt(testPath);
|
||
}
|
||
return false;
|
||
});
|
||
}
|
||
bool FileIsExecutable(std::string const& file) const
|
||
{
|
||
if (!this->FileIsExecutableCMP0109(file)) {
|
||
return false;
|
||
}
|
||
#ifdef _WIN32
|
||
// Pretend the Windows "python" app installer alias does not exist.
|
||
if (cmSystemTools::LowerCase(file).find("/windowsapps/python") !=
|
||
std::string::npos) {
|
||
std::string dest;
|
||
if (cmSystemTools::ReadSymlink(file, dest) &&
|
||
cmHasLiteralSuffix(dest, "\\AppInstallerPythonRedirector.exe")) {
|
||
return false;
|
||
}
|
||
}
|
||
#endif
|
||
return true;
|
||
}
|
||
bool FileIsExecutableCMP0109(std::string const& file) const
|
||
{
|
||
switch (this->PolicyCMP0109) {
|
||
case cmPolicies::OLD:
|
||
return cmSystemTools::FileExists(file, true);
|
||
case cmPolicies::NEW:
|
||
return cmSystemTools::FileIsExecutable(file);
|
||
default:
|
||
break;
|
||
}
|
||
bool const isExeOld = cmSystemTools::FileExists(file, true);
|
||
bool const isExeNew = cmSystemTools::FileIsExecutable(file);
|
||
if (isExeNew == isExeOld) {
|
||
return isExeNew;
|
||
}
|
||
if (isExeNew) {
|
||
this->Makefile->IssuePolicyWarning(
|
||
cmPolicies::CMP0109, {},
|
||
cmStrCat("The file\n "_s, file,
|
||
"\nis executable but not readable. "
|
||
"CMake is ignoring it for compatibility."_s));
|
||
} else {
|
||
this->Makefile->IssuePolicyWarning(
|
||
cmPolicies::CMP0109, {},
|
||
cmStrCat("The file\n "_s, file,
|
||
"\nis readable but not executable. "
|
||
"CMake is using it for compatibility."_s));
|
||
}
|
||
return isExeOld;
|
||
}
|
||
};
|
||
|
||
cmFindProgramCommand::cmFindProgramCommand(cmExecutionStatus& status)
|
||
: cmFindBase("find_program", status)
|
||
{
|
||
this->NamesPerDirAllowed = true;
|
||
this->VariableDocumentation = "Path to a program.";
|
||
this->VariableType = cmStateEnums::FILEPATH;
|
||
// Windows Registry views
|
||
// When policy CMP0134 is not NEW, rely on previous behavior:
|
||
if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0134) !=
|
||
cmPolicies::NEW) {
|
||
if (this->Makefile->GetDefinition("CMAKE_SIZEOF_VOID_P") == "8") {
|
||
this->RegistryView = cmWindowsRegistry::View::Reg64_32;
|
||
} else {
|
||
this->RegistryView = cmWindowsRegistry::View::Reg32_64;
|
||
}
|
||
} else {
|
||
this->RegistryView = cmWindowsRegistry::View::Both;
|
||
}
|
||
}
|
||
|
||
// cmFindProgramCommand
|
||
bool cmFindProgramCommand::InitialPass(std::vector<std::string> const& argsIn)
|
||
{
|
||
|
||
this->CMakePathName = "PROGRAM";
|
||
|
||
// call cmFindBase::ParseArguments
|
||
if (!this->ParseArguments(argsIn)) {
|
||
return false;
|
||
}
|
||
|
||
this->FullDebugMode = this->ComputeIfDebugModeWanted(this->VariableName);
|
||
if (this->FullDebugMode || !this->ComputeIfImplicitDebugModeSuppressed()) {
|
||
this->DebugState = cm::make_unique<cmFindBaseDebugState>(this);
|
||
}
|
||
|
||
if (this->IsFound()) {
|
||
this->NormalizeFindResult();
|
||
return true;
|
||
}
|
||
|
||
std::string const result = this->FindProgram();
|
||
this->StoreFindResult(result);
|
||
return true;
|
||
}
|
||
|
||
std::string cmFindProgramCommand::FindProgram()
|
||
{
|
||
std::string program;
|
||
|
||
if (this->SearchAppBundleFirst || this->SearchAppBundleOnly) {
|
||
program = this->FindAppBundle();
|
||
}
|
||
if (program.empty() && !this->SearchAppBundleOnly) {
|
||
program = this->FindNormalProgram();
|
||
}
|
||
|
||
if (program.empty() && this->SearchAppBundleLast) {
|
||
program = this->FindAppBundle();
|
||
}
|
||
return program;
|
||
}
|
||
|
||
std::string cmFindProgramCommand::FindNormalProgram()
|
||
{
|
||
if (this->NamesPerDir) {
|
||
return this->FindNormalProgramNamesPerDir();
|
||
}
|
||
return this->FindNormalProgramDirsPerName();
|
||
}
|
||
|
||
std::string cmFindProgramCommand::FindNormalProgramNamesPerDir()
|
||
{
|
||
// Search for all names in each directory.
|
||
cmFindProgramHelper helper(this->Makefile, this, this->DebugState.get());
|
||
for (std::string const& n : this->Names) {
|
||
helper.AddName(n);
|
||
}
|
||
|
||
// Check for the names themselves if they contain a directory separator.
|
||
if (helper.CheckCompoundNames()) {
|
||
return helper.BestPath;
|
||
}
|
||
|
||
// Search every directory.
|
||
for (std::string const& sp : this->SearchPaths) {
|
||
if (helper.CheckDirectory(sp)) {
|
||
return helper.BestPath;
|
||
}
|
||
}
|
||
// Couldn't find the program.
|
||
return "";
|
||
}
|
||
|
||
std::string cmFindProgramCommand::FindNormalProgramDirsPerName()
|
||
{
|
||
// Search the entire path for each name.
|
||
cmFindProgramHelper helper(this->Makefile, this, this->DebugState.get());
|
||
for (std::string const& n : this->Names) {
|
||
// Switch to searching for this name.
|
||
helper.SetName(n);
|
||
|
||
// Check for the names themselves if they contain a directory separator.
|
||
if (helper.CheckCompoundNames()) {
|
||
return helper.BestPath;
|
||
}
|
||
|
||
// Search every directory.
|
||
for (std::string const& sp : this->SearchPaths) {
|
||
if (helper.CheckDirectory(sp)) {
|
||
return helper.BestPath;
|
||
}
|
||
}
|
||
}
|
||
// Couldn't find the program.
|
||
return "";
|
||
}
|
||
|
||
std::string cmFindProgramCommand::FindAppBundle()
|
||
{
|
||
for (std::string const& name : this->Names) {
|
||
|
||
std::string appName = name + std::string(".app");
|
||
std::string appPath =
|
||
cmSystemTools::FindDirectory(appName, this->SearchPaths, true);
|
||
|
||
if (!appPath.empty()) {
|
||
std::string executable = this->GetBundleExecutable(appPath);
|
||
if (!executable.empty()) {
|
||
return cmSystemTools::CollapseFullPath(executable);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Couldn't find app bundle
|
||
return "";
|
||
}
|
||
|
||
std::string cmFindProgramCommand::GetBundleExecutable(
|
||
std::string const& bundlePath)
|
||
{
|
||
std::string executable;
|
||
(void)bundlePath;
|
||
#if defined(__APPLE__)
|
||
// Started with an example on developer.apple.com about finding bundles
|
||
// and modified from that.
|
||
|
||
// Get a CFString of the app bundle path
|
||
// XXX - Is it safe to assume everything is in UTF8?
|
||
CFStringRef bundlePathCFS = CFStringCreateWithCString(
|
||
kCFAllocatorDefault, bundlePath.c_str(), kCFStringEncodingUTF8);
|
||
|
||
// Make a CFURLRef from the CFString representation of the
|
||
// bundle’s path.
|
||
CFURLRef bundleURL = CFURLCreateWithFileSystemPath(
|
||
kCFAllocatorDefault, bundlePathCFS, kCFURLPOSIXPathStyle, true);
|
||
|
||
// Make a bundle instance using the URLRef.
|
||
CFBundleRef appBundle = CFBundleCreate(kCFAllocatorDefault, bundleURL);
|
||
|
||
// returned executableURL is relative to <appbundle>/Contents/MacOS/
|
||
CFURLRef executableURL = CFBundleCopyExecutableURL(appBundle);
|
||
|
||
if (executableURL) {
|
||
int const MAX_OSX_PATH_SIZE = 1024;
|
||
UInt8 buffer[MAX_OSX_PATH_SIZE];
|
||
|
||
if (CFURLGetFileSystemRepresentation(executableURL, false, buffer,
|
||
MAX_OSX_PATH_SIZE)) {
|
||
executable = cmStrCat(bundlePath, "/Contents/MacOS/",
|
||
reinterpret_cast<char const*>(buffer));
|
||
}
|
||
// Only release CFURLRef if it's not null
|
||
CFRelease(executableURL);
|
||
}
|
||
|
||
// Any CF objects returned from functions with "create" or
|
||
// "copy" in their names must be released by us!
|
||
CFRelease(bundlePathCFS);
|
||
CFRelease(bundleURL);
|
||
CFRelease(appBundle);
|
||
#endif
|
||
|
||
return executable;
|
||
}
|
||
|
||
bool cmFindProgram(std::vector<std::string> const& args,
|
||
cmExecutionStatus& status)
|
||
{
|
||
return cmFindProgramCommand(status).InitialPass(args);
|
||
}
|