Skip to content

Commit f91a3b8

Browse files
committed
Re-implement SDK discovery instead of using xcrun
1 parent 8055191 commit f91a3b8

File tree

6 files changed

+268
-33
lines changed

6 files changed

+268
-33
lines changed

compiler/rustc_codegen_ssa/messages.ftl

+40-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,46 @@ codegen_ssa_L4Bender_exporting_symbols_unimplemented = exporting symbols not imp
22
33
codegen_ssa_add_native_library = failed to add native library {$library_path}: {$error}
44
5-
codegen_ssa_apple_sdk_error_sdk_path = failed to get {$sdk_name} SDK path: {$error}
5+
codegen_ssa_apple_sdk_error_failed_reading =
6+
failed reading `{$path}` while looking for SDK root: {$error}
7+
8+
codegen_ssa_apple_sdk_error_missing =
9+
failed finding SDK for platform `{$sdk_name}`. It looks like you have not installed Xcode?
10+
11+
You should install Xcode via the App Store, or run `xcode-select --install` if you only intend on developing for macOS.
12+
13+
codegen_ssa_apple_sdk_error_missing_commandline_tools =
14+
failed finding SDK at `{$sdkroot}` from Command Line Tools installation.
15+
16+
If cross-compiling for iOS, tvOS, visionOS or watchOS, you will likely need a full installation of Xcode.
17+
18+
codegen_ssa_apple_sdk_error_missing_cross_compile_non_macos =
19+
failed finding Apple SDK with name `{$sdk_name}`.
20+
21+
The SDK is needed by the linker to know where to find symbols in system libraries.
22+
23+
The SDK can be downloaded and extracted from https://developer.apple.com/download/all/?q=xcode (requires an Apple ID).
24+
25+
You will then need to tell `rustc` about it using the `SDKROOT` environment variables.
26+
27+
Furthermore, you might need to install a linker capable of linking Mach-O files, or at least ensure that your linker is configured as `lld`.
28+
29+
codegen_ssa_apple_sdk_error_missing_developer_dir =
30+
failed finding SDK inside active developer path `{$dir}` set by the DEVELOPER_DIR environment variable. Looked in:
31+
- `{$sdkroot}`
32+
- `{$sdkroot_bare}`
33+
34+
codegen_ssa_apple_sdk_error_missing_xcode =
35+
failed finding SDK at `{$sdkroot}` in Xcode installation.
36+
37+
Perhaps you need a newer version of Xcode?
38+
39+
codegen_ssa_apple_sdk_error_missing_xcode_select =
40+
failed finding SDK inside active developer path `{$dir}` set by `xcode-select`. Looked in:
41+
- `{$sdkroot}`
42+
- `{$sdkroot_bare}`
43+
44+
Consider using `sudo xcode-select --switch path/to/Xcode.app` or `sudo xcode-select --reset` to select a valid path.
645
746
codegen_ssa_archive_build_failure = failed to build archive at `{$path}`: {$error}
847
+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
use std::fs;
2+
use std::io::ErrorKind;
3+
use std::path::{Path, PathBuf};
4+
5+
use crate::errors::AppleSdkError;
6+
7+
#[cfg(test)]
8+
mod tests;
9+
10+
// TOCTOU is not _really_ an issue with our use of `try_exists` in here, we mostly use it for
11+
// diagnostics, and these directories are global state that the user may change anytime anyhow.
12+
fn try_exists(path: &Path) -> Result<bool, AppleSdkError> {
13+
path.try_exists().map_err(|error| AppleSdkError::FailedReading { path: path.to_owned(), error })
14+
}
15+
16+
/// Get the SDK path for an SDK under `/Library/Developer/CommandLineTools`.
17+
fn sdk_root_in_sdks_dir(sdks_dir: impl Into<PathBuf>, sdk_name: &str) -> PathBuf {
18+
let mut path = sdks_dir.into();
19+
path.push("SDKs");
20+
path.push(sdk_name);
21+
path.set_extension("sdk");
22+
path
23+
}
24+
25+
/// Get the SDK path for an SDK under `/Applications/Xcode.app/Contents/Developer`.
26+
fn sdk_root_in_developer_dir(developer_dir: impl Into<PathBuf>, sdk_name: &str) -> PathBuf {
27+
let mut path = developer_dir.into();
28+
path.push("Platforms");
29+
path.push(sdk_name);
30+
path.set_extension("platform");
31+
path.push("Developer");
32+
path.push("SDKs");
33+
path.push(sdk_name);
34+
path.set_extension("sdk");
35+
path
36+
}
37+
38+
/// Find a SDK root from the user's environment for the given SDK name.
39+
///
40+
/// We do this by searching (purely by names in the filesystem, without reading SDKSettings.json)
41+
/// for a matching SDK in the following places:
42+
/// - `DEVELOPER_DIR`
43+
/// - `/var/db/xcode_select_link`
44+
/// - `/Applications/Xcode.app`
45+
/// - `/Library/Developer/CommandLineTools`
46+
///
47+
/// This does roughly the same thing as `xcrun -sdk $sdk_name -show-sdk-path` (see `man xcrun` for
48+
/// a few details on the search algorithm).
49+
///
50+
/// The reason why we implement this logic ourselves is:
51+
/// - Reading these directly is faster than spawning a new process.
52+
/// - `xcrun` can be fairly slow to start up after a reboot.
53+
/// - In the future, we will be able to integrate this better with the compiler's change tracking
54+
/// mechanisms, allowing rebuilds when the involved env vars and paths here change. See #118204.
55+
/// - It's easier for us to emit better error messages.
56+
///
57+
/// Though a downside is that `xcrun` might be expanded in the future to check more places, and then
58+
/// `rustc` would have to be changed to keep up. Furthermore, `xcrun`'s exact algorithm is
59+
/// undocumented, so it might be doing more things than we do here.
60+
pub(crate) fn find_sdk_root(sdk_name: &'static str) -> Result<PathBuf, AppleSdkError> {
61+
// Only try this if host OS is macOS.
62+
if !cfg!(target_os = "macos") {
63+
return Err(AppleSdkError::MissingCrossCompileNonMacOS { sdk_name });
64+
}
65+
66+
// NOTE: We could consider walking upwards in `SDKROOT` assuming Xcode directory structure, but
67+
// that isn't what `xcrun` does, and might still not yield the desired result (e.g. if using an
68+
// old SDK to compile for an old ARM iOS arch, we don't want `rustc` to pick a macOS SDK from
69+
// the old Xcode).
70+
71+
// Try reading from `DEVELOPER_DIR` on all hosts.
72+
if let Some(dir) = std::env::var_os("DEVELOPER_DIR") {
73+
let dir = PathBuf::from(dir);
74+
let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name);
75+
76+
if try_exists(&sdkroot)? {
77+
return Ok(sdkroot);
78+
} else {
79+
let sdkroot_bare = sdk_root_in_sdks_dir(&dir, sdk_name);
80+
if try_exists(&sdkroot_bare)? {
81+
return Ok(sdkroot_bare);
82+
} else {
83+
return Err(AppleSdkError::MissingDeveloperDir { dir, sdkroot, sdkroot_bare });
84+
}
85+
}
86+
}
87+
88+
// Next, try to read the link that `xcode-select` sets.
89+
//
90+
// FIXME(madsmtm): Support cases where `/var/db/xcode_select_link` contains a relative path?
91+
let path = PathBuf::from("/var/db/xcode_select_link");
92+
match fs::read_link(&path) {
93+
Ok(dir) => {
94+
let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name);
95+
if try_exists(&sdkroot)? {
96+
return Ok(sdkroot);
97+
} else {
98+
let sdkroot_bare = sdk_root_in_sdks_dir(&dir, sdk_name);
99+
if try_exists(&sdkroot_bare)? {
100+
return Ok(sdkroot_bare);
101+
} else {
102+
return Err(AppleSdkError::MissingXcodeSelect { dir, sdkroot, sdkroot_bare });
103+
}
104+
}
105+
}
106+
Err(err) if err.kind() == ErrorKind::NotFound => {
107+
// Intentionally ignore not found errors, if `xcode-select --reset` is called the
108+
// link will not exist.
109+
}
110+
Err(error) => return Err(AppleSdkError::FailedReading { path, error }),
111+
}
112+
113+
// Next, fall back to reading from `/Applications/Xcode.app`.
114+
let dir = PathBuf::from("/Applications/Xcode.app/Contents/Developer");
115+
if try_exists(&dir)? {
116+
let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name);
117+
if try_exists(&sdkroot)? {
118+
return Ok(sdkroot);
119+
} else {
120+
return Err(AppleSdkError::MissingXcode { sdkroot });
121+
}
122+
}
123+
124+
// Finally, fall back to reading from `/Library/Developer/CommandLineTools`.
125+
let dir = PathBuf::from("/Library/Developer/CommandLineTools");
126+
if try_exists(&dir)? {
127+
let sdkroot = sdk_root_in_sdks_dir(&dir, sdk_name);
128+
if try_exists(&sdkroot)? {
129+
return Ok(sdkroot);
130+
} else {
131+
return Err(AppleSdkError::MissingCommandlineTools { sdkroot });
132+
}
133+
}
134+
135+
Err(AppleSdkError::Missing { sdk_name })
136+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use std::io;
2+
use std::path::PathBuf;
3+
use std::process::Command;
4+
5+
use super::find_sdk_root;
6+
7+
fn find_sdk_root_xcrun(sdk_name: &str) -> io::Result<PathBuf> {
8+
let output = Command::new("xcrun")
9+
.arg("-sdk")
10+
.arg(sdk_name.to_lowercase())
11+
.arg("-show-sdk-path")
12+
.output()?;
13+
if output.status.success() {
14+
// FIXME(madsmtm): If using this for real, we should not error on non-UTF-8 paths.
15+
let output = String::from_utf8(output.stdout).unwrap();
16+
Ok(PathBuf::from(output.trim()))
17+
} else {
18+
let error = String::from_utf8(output.stderr);
19+
let error = format!("process exit with error: {}", error.unwrap());
20+
Err(io::Error::new(io::ErrorKind::Other, &error[..]))
21+
}
22+
}
23+
24+
/// Ensure that our `find_sdk_root` matches `xcrun`'s behaviour.
25+
///
26+
/// `xcrun` is quite slow the first time it's run after a reboot, so this test may take some time.
27+
#[test]
28+
#[cfg_attr(not(target_os = "macos"), ignore = "xcrun is only available on macOS")]
29+
fn test_find_sdk_root() {
30+
let sdks = [
31+
"MacOSX",
32+
"AppleTVOS",
33+
"AppleTVSimulator",
34+
"iPhoneOS",
35+
"iPhoneSimulator",
36+
"WatchOS",
37+
"WatchSimulator",
38+
"XROS",
39+
"XRSimulator",
40+
];
41+
for sdk_name in sdks {
42+
if let Ok(expected) = find_sdk_root_xcrun(sdk_name) {
43+
// `xcrun` prefers `MacOSX14.0.sdk` over `MacOSX.sdk`, so let's compare canonical paths.
44+
let expected = std::fs::canonicalize(expected).unwrap();
45+
let actual = find_sdk_root(sdk_name).unwrap();
46+
let actual = std::fs::canonicalize(actual).unwrap();
47+
assert_eq!(expected, actual);
48+
} else {
49+
// The macOS SDK must always be findable in Rust's CI.
50+
//
51+
// The other SDKs are allowed to not be found in the current developer directory when
52+
// running this test.
53+
if sdk_name == "MacOSX" {
54+
panic!("Could not find macOS SDK with `xcrun -sdk macosx -show-sdk-path`");
55+
}
56+
}
57+
}
58+
}

compiler/rustc_codegen_ssa/src/back/link.rs

+11-28
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ use super::command::Command;
5050
use super::linker::{self, Linker};
5151
use super::metadata::{MetadataPosition, create_wrapper_file};
5252
use super::rpath::{self, RPathConfig};
53+
use crate::apple::find_sdk_root;
5354
use crate::{
5455
CodegenResults, CompiledModule, CrateInfo, NativeLib, common, errors,
5556
looks_like_rust_object_file,
@@ -3153,26 +3154,26 @@ fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) ->
31533154
// This is admittedly a bit strange, as on most targets
31543155
// `-isysroot` only applies to include header files, but on Apple
31553156
// targets this also applies to libraries and frameworks.
3156-
cmd.cc_args(&["-isysroot", &sdk_root]);
3157+
cmd.cc_arg("-isysroot");
3158+
cmd.cc_arg(&sdk_root);
31573159
}
31583160
LinkerFlavor::Darwin(Cc::No, _) => {
3159-
cmd.link_args(&["-syslibroot", &sdk_root]);
3161+
cmd.link_arg("-syslibroot");
3162+
cmd.link_arg(&sdk_root);
31603163
}
31613164
_ => unreachable!(),
31623165
}
31633166

3164-
Some(sdk_root.into())
3167+
Some(sdk_root)
31653168
}
31663169

3167-
fn get_apple_sdk_root(sdk_name: &str) -> Result<String, errors::AppleSdkRootError<'_>> {
3170+
fn get_apple_sdk_root(sdk_name: &'static str) -> Result<PathBuf, errors::AppleSdkError> {
31683171
// Following what clang does
31693172
// (https://github.com/llvm/llvm-project/blob/
31703173
// 296a80102a9b72c3eda80558fb78a3ed8849b341/clang/lib/Driver/ToolChains/Darwin.cpp#L1661-L1678)
3171-
// to allow the SDK path to be set. (For clang, xcrun sets
3172-
// SDKROOT; for rustc, the user or build system can set it, or we
3173-
// can fall back to checking for xcrun on PATH.)
3174+
// to allow the SDK path to be set.
31743175
if let Ok(sdkroot) = env::var("SDKROOT") {
3175-
let p = Path::new(&sdkroot);
3176+
let p = PathBuf::from(&sdkroot);
31763177
match &*sdk_name.to_lowercase() {
31773178
// Ignore `SDKROOT` if it's clearly set for the wrong platform.
31783179
"appletvos"
@@ -3201,29 +3202,11 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result<String, errors::AppleSdkRootErro
32013202
if sdkroot.contains("XROS.platform") || sdkroot.contains("MacOSX.platform") => {}
32023203
// Ignore `SDKROOT` if it's not a valid path.
32033204
_ if !p.is_absolute() || p == Path::new("/") || !p.exists() => {}
3204-
_ => return Ok(sdkroot),
3205+
_ => return Ok(p),
32053206
}
32063207
}
32073208

3208-
let res = Command::new("xcrun")
3209-
.arg("--show-sdk-path")
3210-
.arg("-sdk")
3211-
.arg(sdk_name.to_lowercase())
3212-
.output()
3213-
.and_then(|output| {
3214-
if output.status.success() {
3215-
Ok(String::from_utf8(output.stdout).unwrap())
3216-
} else {
3217-
let error = String::from_utf8(output.stderr);
3218-
let error = format!("process exit with error: {}", error.unwrap());
3219-
Err(io::Error::new(io::ErrorKind::Other, &error[..]))
3220-
}
3221-
});
3222-
3223-
match res {
3224-
Ok(output) => Ok(output.trim().to_string()),
3225-
Err(error) => Err(errors::AppleSdkRootError::SdkPath { sdk_name, error }),
3226-
}
3209+
find_sdk_root(sdk_name)
32273210
}
32283211

32293212
/// When using the linker flavors opting in to `lld`, add the necessary paths and arguments to

compiler/rustc_codegen_ssa/src/errors.rs

+22-4
Original file line numberDiff line numberDiff line change
@@ -532,10 +532,28 @@ pub enum ExtractBundledLibsError<'a> {
532532
ExtractSection { rlib: &'a Path, error: Box<dyn std::error::Error> },
533533
}
534534

535-
#[derive(Diagnostic)]
536-
pub(crate) enum AppleSdkRootError<'a> {
537-
#[diag(codegen_ssa_apple_sdk_error_sdk_path)]
538-
SdkPath { sdk_name: &'a str, error: Error },
535+
#[derive(Diagnostic, Debug)]
536+
pub(crate) enum AppleSdkError {
537+
#[diag(codegen_ssa_apple_sdk_error_failed_reading)]
538+
FailedReading { path: PathBuf, error: std::io::Error },
539+
540+
#[diag(codegen_ssa_apple_sdk_error_missing)]
541+
Missing { sdk_name: &'static str },
542+
543+
#[diag(codegen_ssa_apple_sdk_error_missing_commandline_tools)]
544+
MissingCommandlineTools { sdkroot: PathBuf },
545+
546+
#[diag(codegen_ssa_apple_sdk_error_missing_cross_compile_non_macos)]
547+
MissingCrossCompileNonMacOS { sdk_name: &'static str },
548+
549+
#[diag(codegen_ssa_apple_sdk_error_missing_developer_dir)]
550+
MissingDeveloperDir { dir: PathBuf, sdkroot: PathBuf, sdkroot_bare: PathBuf },
551+
552+
#[diag(codegen_ssa_apple_sdk_error_missing_xcode)]
553+
MissingXcode { sdkroot: PathBuf },
554+
555+
#[diag(codegen_ssa_apple_sdk_error_missing_xcode_select)]
556+
MissingXcodeSelect { dir: PathBuf, sdkroot: PathBuf, sdkroot_bare: PathBuf },
539557
}
540558

541559
#[derive(Diagnostic)]

compiler/rustc_codegen_ssa/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use rustc_session::cstore::{self, CrateSource};
4444
use rustc_session::utils::NativeLibKind;
4545
use rustc_span::symbol::Symbol;
4646

47+
pub mod apple;
4748
pub mod assert_module_sources;
4849
pub mod back;
4950
pub mod base;

0 commit comments

Comments
 (0)