[lldb/PlatformDarwin] Be more robust in computing the SDK path with xcrun

The current implementation isn't very resilient when it comes to the
output of xcrun. Currently it cannot deal with:

 - Trailing newlines.
 - Leading newlines and errors/warnings before the Xcode path.
 - Xcode not being named Xcode.app.

This extract the logic into a helper in PlatformDarwin and fixes those
issues. It's also the first step towards removing code duplication
between the different platforms and downstream Swift.

Differential revision: https://reviews.llvm.org/D76261
This commit is contained in:
Jonas Devlieghere 2020-03-17 14:28:34 -07:00
parent d177c36e5c
commit 7aa28995e8
4 changed files with 264 additions and 159 deletions

View File

@ -1268,76 +1268,6 @@ void PlatformDarwin::CalculateTrapHandlerSymbolNames() {
m_trap_handlers.push_back(ConstString("_sigtramp"));
}
static const char *const sdk_strings[] = {
"MacOSX", "iPhoneSimulator", "iPhoneOS",
};
static FileSpec CheckPathForXcode(const FileSpec &fspec) {
if (FileSystem::Instance().Exists(fspec)) {
const char substr[] = ".app/Contents";
std::string path_to_shlib = fspec.GetPath();
size_t pos = path_to_shlib.rfind(substr);
if (pos != std::string::npos) {
path_to_shlib.erase(pos + strlen(substr));
FileSpec ret(path_to_shlib);
FileSpec xcode_binary_path = ret;
xcode_binary_path.AppendPathComponent("MacOS");
xcode_binary_path.AppendPathComponent("Xcode");
if (FileSystem::Instance().Exists(xcode_binary_path)) {
return ret;
}
}
}
return FileSpec();
}
static FileSpec GetXcodeContentsPath() {
static FileSpec g_xcode_filespec;
static llvm::once_flag g_once_flag;
llvm::call_once(g_once_flag, []() {
FileSpec fspec;
// First get the program file spec. If lldb.so or LLDB.framework is running
// in a program and that program is Xcode, the path returned with be the
// path to Xcode.app/Contents/MacOS/Xcode, so this will be the correct
// Xcode to use.
fspec = HostInfo::GetProgramFileSpec();
if (fspec) {
// Ignore the current binary if it is python.
std::string basename_lower = fspec.GetFilename().GetCString();
std::transform(basename_lower.begin(), basename_lower.end(),
basename_lower.begin(), tolower);
if (basename_lower != "python") {
g_xcode_filespec = CheckPathForXcode(fspec);
}
}
// Next check DEVELOPER_DIR environment variable
if (!g_xcode_filespec) {
const char *developer_dir_env_var = getenv("DEVELOPER_DIR");
if (developer_dir_env_var && developer_dir_env_var[0]) {
FileSpec developer_dir_spec = FileSpec(developer_dir_env_var);
FileSystem::Instance().Resolve(developer_dir_spec);
g_xcode_filespec = CheckPathForXcode(developer_dir_spec);
}
// Fall back to using "xcode-select" to find the selected Xcode
if (!g_xcode_filespec) {
FileSpec xcode_select_path(GetXcodeSelectPath());
xcode_select_path.RemoveLastPathComponent();
g_xcode_filespec = CheckPathForXcode(xcode_select_path);
}
}
});
return g_xcode_filespec;
}
static FileSpec GetCommandLineToolsLibraryPath() {
static FileSpec g_command_line_tools_filespec;
@ -1359,7 +1289,14 @@ bool PlatformDarwin::SDKSupportsModules(SDKType sdk_type,
return version >= llvm::VersionTuple(10, 10);
case SDKType::iPhoneOS:
case SDKType::iPhoneSimulator:
case SDKType::AppleTVOS:
case SDKType::AppleTVSimulator:
return version >= llvm::VersionTuple(8);
case SDKType::watchOS:
case SDKType::WatchSimulator:
return version >= llvm::VersionTuple(6);
default:
return false;
}
return false;
@ -1372,10 +1309,12 @@ bool PlatformDarwin::SDKSupportsModules(SDKType desired_type,
if (last_path_component) {
const llvm::StringRef sdk_name = last_path_component.GetStringRef();
if (!sdk_name.startswith(sdk_strings[desired_type]))
const std::string sdk_name_lower = sdk_name.lower();
const llvm::StringRef sdk_string = GetSDKNameForType(desired_type);
if (!llvm::StringRef(sdk_name_lower).startswith(sdk_string))
return false;
auto version_part =
sdk_name.drop_front(strlen(sdk_strings[desired_type]));
auto version_part = sdk_name.drop_front(sdk_string.size());
version_part.consume_back(".sdk");
llvm::VersionTuple version;
@ -1427,14 +1366,7 @@ FileSpec PlatformDarwin::FindSDKInXcodeForModules(SDKType sdk_type,
}
FileSpec PlatformDarwin::GetSDKDirectoryForModules(SDKType sdk_type) {
switch (sdk_type) {
case SDKType::MacOSX:
case SDKType::iPhoneSimulator:
case SDKType::iPhoneOS:
break;
}
FileSpec sdks_spec = GetXcodeContentsPath();
FileSpec sdks_spec = GetXcodeContentsDirectory();
sdks_spec.AppendPathComponent("Developer");
sdks_spec.AppendPathComponent("Platforms");
@ -1448,6 +1380,8 @@ FileSpec PlatformDarwin::GetSDKDirectoryForModules(SDKType sdk_type) {
case SDKType::iPhoneOS:
sdks_spec.AppendPathComponent("iPhoneOS.platform");
break;
default:
llvm_unreachable("unsupported sdk");
}
sdks_spec.AppendPathComponent("Developer");
@ -1656,6 +1590,8 @@ void PlatformDarwin::AddClangModuleCompilationOptionsForSDKType(
use_current_os_version = false;
#endif
break;
default:
break;
}
llvm::VersionTuple version;
@ -1685,6 +1621,9 @@ void PlatformDarwin::AddClangModuleCompilationOptionsForSDKType(
case SDKType::MacOSX:
minimum_version_option.PutCString("-mmacosx-version-min=");
minimum_version_option.PutCString(version.getAsString());
break;
default:
llvm_unreachable("unsupported sdk");
}
options.push_back(std::string(minimum_version_option.GetString()));
}
@ -1744,8 +1683,7 @@ llvm::VersionTuple PlatformDarwin::GetOSVersion(Process *process) {
lldb_private::FileSpec PlatformDarwin::LocateExecutable(const char *basename) {
// A collection of SBFileSpec whose SBFileSpec.m_directory members are filled
// in with
// any executable directories that should be searched.
// in with any executable directories that should be searched.
static std::vector<FileSpec> g_executable_dirs;
// Find the global list of directories that we will search for executables
@ -1754,7 +1692,7 @@ lldb_private::FileSpec PlatformDarwin::LocateExecutable(const char *basename) {
llvm::call_once(g_once_flag, []() {
// When locating executables, trust the DEVELOPER_DIR first if it is set
FileSpec xcode_contents_dir = GetXcodeContentsPath();
FileSpec xcode_contents_dir = GetXcodeContentsDirectory();
if (xcode_contents_dir) {
FileSpec xcode_lldb_resources = xcode_contents_dir;
xcode_lldb_resources.AppendPathComponent("SharedFrameworks");
@ -1816,12 +1754,10 @@ PlatformDarwin::LaunchProcess(lldb_private::ProcessLaunchInfo &launch_info) {
return PlatformPOSIX::LaunchProcess(launch_info);
}
lldb_private::Status
PlatformDarwin::FindBundleBinaryInExecSearchPaths (const ModuleSpec &module_spec, Process *process,
ModuleSP &module_sp,
const FileSpecList *module_search_paths_ptr,
ModuleSP *old_module_sp_ptr, bool *did_create_ptr)
{
lldb_private::Status PlatformDarwin::FindBundleBinaryInExecSearchPaths(
const ModuleSpec &module_spec, Process *process, ModuleSP &module_sp,
const FileSpecList *module_search_paths_ptr, ModuleSP *old_module_sp_ptr,
bool *did_create_ptr) {
const FileSpec &platform_file = module_spec.GetFileSpec();
// See if the file is present in any of the module_search_paths_ptr
// directories.
@ -1892,3 +1828,133 @@ PlatformDarwin::FindBundleBinaryInExecSearchPaths (const ModuleSpec &module_spec
}
return Status();
}
std::string
PlatformDarwin::FindXcodeContentsDirectoryInPath(llvm::StringRef path) {
auto begin = llvm::sys::path::begin(path);
auto end = llvm::sys::path::end(path);
// Iterate over the path components until we find something that ends with
// .app. If the next component is Contents then we've found the Contents
// directory.
for (auto it = begin; it != end; ++it) {
if (it->endswith(".app")) {
auto next = it;
if (++next != end && *next == "Contents") {
llvm::SmallString<128> buffer;
llvm::sys::path::append(buffer, begin, ++next);
return buffer.str().str();
}
}
}
return {};
}
llvm::StringRef PlatformDarwin::GetSDKNameForType(SDKType type) {
switch (type) {
case MacOSX:
return "macosx";
case iPhoneSimulator:
return "iphonesimulator";
case iPhoneOS:
return "iphoneos";
case AppleTVSimulator:
return "appletvsimulator";
case AppleTVOS:
return "appletvos";
case WatchSimulator:
return "watchsimulator";
case watchOS:
return "watchos";
case bridgeOS:
return "bridgeos";
case Linux:
return "linux";
case numSDKTypes:
case unknown:
return "";
}
llvm_unreachable("unhandled switch case");
}
FileSpec PlatformDarwin::GetXcodeSDK(SDKType type) {
std::string xcrun_cmd =
"xcrun --show-sdk-path --sdk " + GetSDKNameForType(type).str();
int status = 0;
int signo = 0;
std::string output_str;
lldb_private::Status error =
Host::RunShellCommand(xcrun_cmd.c_str(), FileSpec(), &status, &signo,
&output_str, std::chrono::seconds(15));
// Check that xcrun return something useful.
if (status != 0 || output_str.empty())
return {};
// Convert to a StringRef so we can manipulate the string without modifying
// the underlying data.
llvm::StringRef output(output_str);
// Remove any trailing newline characters.
output = output.rtrim();
// Strip any leading newline characters and everything before them.
const size_t last_newline = output.rfind('\n');
if (last_newline != llvm::StringRef::npos)
output = output.substr(last_newline + 1);
// Whatever is left in output should be a valid path.
if (!FileSystem::Instance().Exists(output))
return {};
// Find the contents dir in the xcrun provided path.
std::string xcode_contents_dir = FindXcodeContentsDirectoryInPath(output);
if (xcode_contents_dir.empty())
return {};
return FileSpec(xcode_contents_dir);
}
FileSpec PlatformDarwin::GetXcodeContentsDirectory() {
static FileSpec g_xcode_contents_path;
static std::once_flag g_once_flag;
std::call_once(g_once_flag, [&]() {
// Try the shlib dir first.
if (FileSpec fspec = HostInfo::GetShlibDir()) {
if (FileSystem::Instance().Exists(fspec)) {
std::string xcode_contents_dir =
FindXcodeContentsDirectoryInPath(fspec.GetPath());
if (!xcode_contents_dir.empty()) {
g_xcode_contents_path = FileSpec(xcode_contents_dir);
return;
}
}
}
if (const char *developer_dir_env_var = getenv("DEVELOPER_DIR")) {
FileSpec fspec(developer_dir_env_var);
if (FileSystem::Instance().Exists(fspec)) {
std::string xcode_contents_dir =
FindXcodeContentsDirectoryInPath(fspec.GetPath());
if (!xcode_contents_dir.empty()) {
g_xcode_contents_path = FileSpec(xcode_contents_dir);
return;
}
}
}
if (FileSpec fspec = GetXcodeSDK(SDKType::MacOSX)) {
if (FileSystem::Instance().Exists(fspec)) {
std::string xcode_contents_dir =
FindXcodeContentsDirectoryInPath(fspec.GetPath());
if (!xcode_contents_dir.empty()) {
g_xcode_contents_path = FileSpec(xcode_contents_dir);
return;
}
}
}
});
return g_xcode_contents_path;
}

View File

@ -79,15 +79,27 @@ public:
static std::tuple<llvm::VersionTuple, llvm::StringRef>
ParseVersionBuildDir(llvm::StringRef str);
enum SDKType : unsigned {
enum SDKType : int {
MacOSX = 0,
iPhoneSimulator,
iPhoneOS,
AppleTVSimulator,
AppleTVOS,
WatchSimulator,
watchOS,
bridgeOS,
Linux,
numSDKTypes,
unknown = -1
};
llvm::Expected<lldb_private::StructuredData::DictionarySP>
FetchExtendedCrashInformation(lldb_private::Process &process) override;
static llvm::StringRef GetSDKNameForType(SDKType type);
static lldb_private::FileSpec GetXcodeSDK(SDKType type);
static lldb_private::FileSpec GetXcodeContentsDirectory();
protected:
struct CrashInfoAnnotations {
uint64_t version; // unsigned long
@ -154,14 +166,16 @@ protected:
const char *GetDeveloperDirectory();
lldb_private::Status
FindBundleBinaryInExecSearchPaths (const lldb_private::ModuleSpec &module_spec, lldb_private::Process *process,
lldb::ModuleSP &module_sp, const lldb_private::FileSpecList *module_search_paths_ptr,
lldb::ModuleSP *old_module_sp_ptr, bool *did_create_ptr);
lldb_private::Status FindBundleBinaryInExecSearchPaths(
const lldb_private::ModuleSpec &module_spec,
lldb_private::Process *process, lldb::ModuleSP &module_sp,
const lldb_private::FileSpecList *module_search_paths_ptr,
lldb::ModuleSP *old_module_sp_ptr, bool *did_create_ptr);
static std::string FindXcodeContentsDirectoryInPath(llvm::StringRef path);
std::string m_developer_directory;
private:
DISALLOW_COPY_AND_ASSIGN(PlatformDarwin);
};

View File

@ -185,73 +185,35 @@ PlatformMacOSX::~PlatformMacOSX() {}
ConstString PlatformMacOSX::GetSDKDirectory(lldb_private::Target &target) {
ModuleSP exe_module_sp(target.GetExecutableModule());
if (exe_module_sp) {
ObjectFile *objfile = exe_module_sp->GetObjectFile();
if (objfile) {
std::string xcode_contents_path;
std::string default_xcode_sdk;
FileSpec fspec;
llvm::VersionTuple version = objfile->GetSDKVersion();
if (!version.empty()) {
fspec = HostInfo::GetShlibDir();
if (fspec) {
std::string path;
xcode_contents_path = fspec.GetPath();
size_t pos = xcode_contents_path.find("/Xcode.app/Contents/");
if (pos != std::string::npos) {
// LLDB.framework is inside an Xcode app bundle, we can locate the
// SDK from here
xcode_contents_path.erase(pos + strlen("/Xcode.app/Contents/"));
} else {
xcode_contents_path.clear();
// Use the selected Xcode
int status = 0;
int signo = 0;
std::string output;
const char *command = "xcrun -sdk macosx --show-sdk-path";
lldb_private::Status error = RunShellCommand(
command, // shell command to run
FileSpec(), // current working directory
&status, // Put the exit status of the process in here
&signo, // Put the signal that caused the process to exit in
// here
&output, // Get the output from the command and place it in this
// string
std::chrono::seconds(3));
if (status == 0 && !output.empty()) {
size_t first_non_newline = output.find_last_not_of("\r\n");
if (first_non_newline != std::string::npos)
output.erase(first_non_newline + 1);
default_xcode_sdk = output;
if (!exe_module_sp)
return {};
pos = default_xcode_sdk.find("/Xcode.app/Contents/");
if (pos != std::string::npos)
xcode_contents_path = default_xcode_sdk.substr(
0, pos + strlen("/Xcode.app/Contents/"));
}
}
}
ObjectFile *objfile = exe_module_sp->GetObjectFile();
if (!objfile)
return {};
if (!xcode_contents_path.empty()) {
StreamString sdk_path;
sdk_path.Printf("%sDeveloper/Platforms/MacOSX.platform/Developer/"
"SDKs/MacOSX%u.%u.sdk",
xcode_contents_path.c_str(), version.getMajor(),
version.getMinor().getValue());
fspec.SetFile(sdk_path.GetString(), FileSpec::Style::native);
if (FileSystem::Instance().Exists(fspec))
return ConstString(sdk_path.GetString());
}
llvm::VersionTuple version = objfile->GetSDKVersion();
if (version.empty())
return {};
if (!default_xcode_sdk.empty()) {
fspec.SetFile(default_xcode_sdk, FileSpec::Style::native);
if (FileSystem::Instance().Exists(fspec))
return ConstString(default_xcode_sdk);
}
}
}
// First try to find an SDK that matches the given SDK version.
if (FileSpec fspec = GetXcodeContentsDirectory()) {
StreamString sdk_path;
sdk_path.Printf("%s/Developer/Platforms/MacOSX.platform/Developer/"
"SDKs/MacOSX%u.%u.sdk",
fspec.GetPath().c_str(), version.getMajor(),
version.getMinor().getValue());
if (FileSystem::Instance().Exists(fspec))
return ConstString(sdk_path.GetString());
}
return ConstString();
// Use the default SDK as a fallback.
if (FileSpec fspec = GetXcodeSDK(SDKType::MacOSX)) {
if (FileSystem::Instance().Exists(fspec))
return ConstString(fspec.GetPath());
}
return {};
}
Status PlatformMacOSX::GetSymbolFile(const FileSpec &platform_file,

View File

@ -18,6 +18,8 @@ using namespace lldb;
using namespace lldb_private;
struct PlatformDarwinTester : public PlatformDarwin {
public:
using PlatformDarwin::FindXcodeContentsDirectoryInPath;
static bool SDKSupportsModules(SDKType desired_type,
const lldb_private::FileSpec &sdk_path) {
return PlatformDarwin::SDKSupportsModules(desired_type, sdk_path);
@ -69,3 +71,64 @@ TEST(PlatformDarwinTest, TestParseVersionBuildDir) {
PlatformDarwin::SDKType::MacOSX,
FileSpec(base + "MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk")));
}
TEST(PlatformDarwinTest, FindXcodeContentsDirectoryInPath) {
std::string standard =
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/"
"Developer/SDKs/MacOSX.sdk";
EXPECT_EQ("/Applications/Xcode.app/Contents",
PlatformDarwinTester::FindXcodeContentsDirectoryInPath(standard));
std::string standard_version =
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/"
"Developer/SDKs/MacOSX10.15.sdk";
EXPECT_EQ(
"/Applications/Xcode.app/Contents",
PlatformDarwinTester::FindXcodeContentsDirectoryInPath(standard_version));
std::string beta = "/Applications/Xcode-beta.app/Contents/Developer/"
"Platforms/MacOSX.platform/"
"Developer/SDKs/MacOSX10.15.sdk";
EXPECT_EQ("/Applications/Xcode-beta.app/Contents",
PlatformDarwinTester::FindXcodeContentsDirectoryInPath(beta));
std::string no_app =
"/Applications/Xcode/Contents/Developer/Platforms/MacOSX.platform/"
"Developer/SDKs/MacOSX10.15.sdk";
EXPECT_EQ("", PlatformDarwinTester::FindXcodeContentsDirectoryInPath(no_app));
std::string no_contents =
"/Applications/Xcode.app/Developer/Platforms/MacOSX.platform/"
"Developer/SDKs/MacOSX10.15.sdk";
EXPECT_EQ(
"", PlatformDarwinTester::FindXcodeContentsDirectoryInPath(no_contents));
std::string no_capitalization =
"/Applications/Xcode.app/contents/Developer/Platforms/MacOSX.platform/"
"Developer/SDKs/MacOSX10.15.sdk";
EXPECT_EQ("", PlatformDarwinTester::FindXcodeContentsDirectoryInPath(
no_capitalization));
}
TEST(PlatformDarwinTest, GetSDKNameForType) {
EXPECT_EQ("macosx",
PlatformDarwin::GetSDKNameForType(PlatformDarwin::SDKType::MacOSX));
EXPECT_EQ("iphonesimulator", PlatformDarwin::GetSDKNameForType(
PlatformDarwin::SDKType::iPhoneSimulator));
EXPECT_EQ("iphoneos", PlatformDarwin::GetSDKNameForType(
PlatformDarwin::SDKType::iPhoneOS));
EXPECT_EQ("appletvsimulator", PlatformDarwin::GetSDKNameForType(
PlatformDarwin::SDKType::AppleTVSimulator));
EXPECT_EQ("appletvos", PlatformDarwin::GetSDKNameForType(
PlatformDarwin::SDKType::AppleTVOS));
EXPECT_EQ("watchsimulator", PlatformDarwin::GetSDKNameForType(
PlatformDarwin::SDKType::WatchSimulator));
EXPECT_EQ("watchos", PlatformDarwin::GetSDKNameForType(
PlatformDarwin::SDKType::watchOS));
EXPECT_EQ("linux",
PlatformDarwin::GetSDKNameForType(PlatformDarwin::SDKType::Linux));
EXPECT_EQ("", PlatformDarwin::GetSDKNameForType(
PlatformDarwin::SDKType::numSDKTypes));
EXPECT_EQ(
"", PlatformDarwin::GetSDKNameForType(PlatformDarwin::SDKType::unknown));
}