Skip to content

Commit bbb59cd

Browse files
committed
Re-implement SDK discovery instead of using xcrun
1 parent e889875 commit bbb59cd

File tree

11 files changed

+299
-39
lines changed

11 files changed

+299
-39
lines changed

compiler/rustc_codegen_ssa/messages.ftl

+59-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,65 @@ 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+
{ $sdk_name ->
12+
[MacOSX] You should install Xcode via the App Store, or run `xcode-select --install` to install the Command Line Tools if you only intend on developing for macOS.
13+
*[other] You should install Xcode via the App Store.
14+
}
15+
16+
codegen_ssa_apple_sdk_error_missing_commandline_tools =
17+
failed finding SDK at `{$sdkroot}` in Command Line Tools installation.
18+
19+
{ $sdk_name ->
20+
[MacOSX] Perhaps you need to reinstall it with `xcode-select --install`?
21+
*[other] When compiling for iOS, tvOS, visionOS or watchOS, you will need a full installation of Xcode.
22+
}
23+
24+
codegen_ssa_apple_sdk_error_missing_cross_compile_non_macos =
25+
failed finding Apple SDK with name `{$sdk_name}`.
26+
27+
The SDK is needed by the linker to know where to find symbols in system libraries and for embedding the SDK version in the final object file.
28+
29+
The SDK can be downloaded and extracted from https://developer.apple.com/download/all/?q=xcode (requires an Apple ID).
30+
31+
The full Xcode bundle should contain the SDK in `Xcode.app/Contents/Developer/Platforms/{$sdk_name}.platform/Developer/SDKs/{$sdk_name}.sdk`{ $sdk_name ->
32+
[MacOSX] , but downloading just the Command Line Tools for Xcode should also be sufficient to obtain the macOS SDK.
33+
*[other] .
34+
}
35+
36+
You will then need to tell `rustc` about it using the `SDKROOT` environment variables.
37+
38+
Furthermore, you might need to install a linker capable of linking Mach-O files, or at least ensure that `rustc` is configured to use the bundled `lld`.
39+
40+
{ $sdk_name ->
41+
[MacOSX] {""}
42+
*[other] Beware that cross-compilation to iOS, tvOS, visionOS or watchOS is generally ill supported on non-macOS hosts.
43+
}
44+
45+
codegen_ssa_apple_sdk_error_missing_developer_dir =
46+
failed finding SDK inside active developer directory `{$dir}` set by the DEVELOPER_DIR environment variable. Looked in:
47+
- `{$sdkroot}`
48+
- `{$sdkroot_bare}`
49+
50+
codegen_ssa_apple_sdk_error_missing_xcode =
51+
failed finding SDK at `{$sdkroot}` in Xcode installation.
52+
53+
{ $sdk_name ->
54+
[MacOSX] {""}
55+
*[other] Perhaps you need a newer version of Xcode?
56+
}
57+
58+
codegen_ssa_apple_sdk_error_missing_xcode_select =
59+
failed finding SDK inside active developer directory `{$dir}` set by `xcode-select`. Looked in:
60+
- `{$sdkroot}`
61+
- `{$sdkroot_bare}`
62+
63+
Consider using `sudo xcode-select --switch path/to/Xcode.app` or `sudo xcode-select --reset` to select a valid path.
664
765
codegen_ssa_archive_build_failure = failed to build archive at `{$path}`: {$error}
866

compiler/rustc_codegen_ssa/src/back/apple.rs

+138
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
1+
use std::fs;
2+
use std::io::ErrorKind;
3+
use std::path::{Path, PathBuf};
4+
15
use rustc_target::spec::Target;
26

7+
use crate::errors::AppleSdkError;
8+
9+
#[cfg(test)]
10+
mod tests;
11+
312
pub fn sdk_name(target: &Target) -> &'static str {
413
match (&*target.os, &*target.abi) {
514
("ios", "") => "iPhoneOS",
@@ -16,3 +25,132 @@ pub fn sdk_name(target: &Target) -> &'static str {
1625
(os, abi) => unreachable!("invalid os '{os}' / abi '{abi}' combination for Apple target"),
1726
}
1827
}
28+
29+
// TOCTOU is not _really_ an issue with our use of `try_exists` in here, we mostly use it for
30+
// diagnostics, and these directories are global state that the user can change anytime anyhow in
31+
// ways that are going to interfere much more with the compilation process.
32+
fn try_exists(path: &Path) -> Result<bool, AppleSdkError> {
33+
path.try_exists().map_err(|error| AppleSdkError::FailedReading { path: path.to_owned(), error })
34+
}
35+
36+
/// Get the SDK path for an SDK under `/Library/Developer/CommandLineTools`.
37+
fn sdk_root_in_sdks_dir(sdks_dir: impl Into<PathBuf>, sdk_name: &str) -> PathBuf {
38+
let mut path = sdks_dir.into();
39+
path.push("SDKs");
40+
path.push(sdk_name);
41+
path.set_extension("sdk");
42+
path
43+
}
44+
45+
/// Get the SDK path for an SDK under `/Applications/Xcode.app/Contents/Developer`.
46+
fn sdk_root_in_developer_dir(developer_dir: impl Into<PathBuf>, sdk_name: &str) -> PathBuf {
47+
let mut path = developer_dir.into();
48+
path.push("Platforms");
49+
path.push(sdk_name);
50+
path.set_extension("platform");
51+
path.push("Developer");
52+
path.push("SDKs");
53+
path.push(sdk_name);
54+
path.set_extension("sdk");
55+
path
56+
}
57+
58+
/// Find a SDK root from the user's environment for the given SDK name.
59+
///
60+
/// We do this by searching (purely by names in the filesystem, without reading SDKSettings.json)
61+
/// for a matching SDK in the following places:
62+
/// - `DEVELOPER_DIR`
63+
/// - `/var/db/xcode_select_link`
64+
/// - `/Applications/Xcode.app`
65+
/// - `/Library/Developer/CommandLineTools`
66+
///
67+
/// This does roughly the same thing as `xcrun -sdk $sdk_name -show-sdk-path` (see `man xcrun` for
68+
/// a few details on the search algorithm).
69+
///
70+
/// The reason why we implement this logic ourselves is:
71+
/// - Reading these directly is faster than spawning a new process.
72+
/// - `xcrun` can be fairly slow to start up after a reboot.
73+
/// - In the future, we will be able to integrate this better with the compiler's change tracking
74+
/// mechanisms, allowing rebuilds when the involved env vars and paths here change. See #118204.
75+
/// - It's easier for us to emit better error messages.
76+
///
77+
/// Though a downside is that `xcrun` might be expanded in the future to check more places, and then
78+
/// `rustc` would have to be changed to keep up. Furthermore, `xcrun`'s exact algorithm is
79+
/// undocumented, so it might be doing more things than we do here.
80+
pub(crate) fn find_sdk_root(sdk_name: &'static str) -> Result<PathBuf, AppleSdkError> {
81+
// Only try this if the host OS is macOS.
82+
if !cfg!(target_os = "macos") {
83+
return Err(AppleSdkError::MissingCrossCompileNonMacOS { sdk_name });
84+
}
85+
86+
// NOTE: We could consider walking upwards in `SDKROOT` assuming Xcode directory structure, but
87+
// that isn't what `xcrun` does, and might still not yield the desired result (e.g. if using an
88+
// old SDK to compile for an old ARM iOS arch, we don't want `rustc` to pick a macOS SDK from
89+
// the old Xcode).
90+
91+
// Try reading from `DEVELOPER_DIR`.
92+
if let Some(dir) = std::env::var_os("DEVELOPER_DIR") {
93+
let dir = PathBuf::from(dir);
94+
let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name);
95+
96+
if try_exists(&sdkroot)? {
97+
return Ok(sdkroot);
98+
} else {
99+
let sdkroot_bare = sdk_root_in_sdks_dir(&dir, sdk_name);
100+
if try_exists(&sdkroot_bare)? {
101+
return Ok(sdkroot_bare);
102+
} else {
103+
return Err(AppleSdkError::MissingDeveloperDir { dir, sdkroot, sdkroot_bare });
104+
}
105+
}
106+
}
107+
108+
// Next, try to read the link that `xcode-select` sets.
109+
//
110+
// FIXME(madsmtm): Support cases where `/var/db/xcode_select_link` contains a relative path?
111+
let path = Path::new("/var/db/xcode_select_link");
112+
match fs::read_link(path) {
113+
Ok(dir) => {
114+
let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name);
115+
if try_exists(&sdkroot)? {
116+
return Ok(sdkroot);
117+
} else {
118+
let sdkroot_bare = sdk_root_in_sdks_dir(&dir, sdk_name);
119+
if try_exists(&sdkroot_bare)? {
120+
return Ok(sdkroot_bare);
121+
} else {
122+
return Err(AppleSdkError::MissingXcodeSelect { dir, sdkroot, sdkroot_bare });
123+
}
124+
}
125+
}
126+
Err(err) if err.kind() == ErrorKind::NotFound => {
127+
// Intentionally ignore not found errors, if `xcode-select --reset` is called the
128+
// link will not exist.
129+
}
130+
Err(error) => return Err(AppleSdkError::FailedReading { path: path.into(), error }),
131+
}
132+
133+
// Next, fall back to reading from `/Applications/Xcode.app`.
134+
let dir = Path::new("/Applications/Xcode.app/Contents/Developer");
135+
if try_exists(dir)? {
136+
let sdkroot = sdk_root_in_developer_dir(dir, sdk_name);
137+
if try_exists(&sdkroot)? {
138+
return Ok(sdkroot);
139+
} else {
140+
return Err(AppleSdkError::MissingXcode { sdkroot, sdk_name });
141+
}
142+
}
143+
144+
// Finally, fall back to reading from `/Library/Developer/CommandLineTools`.
145+
let dir = Path::new("/Library/Developer/CommandLineTools");
146+
if try_exists(dir)? {
147+
let sdkroot = sdk_root_in_sdks_dir(dir, sdk_name);
148+
if try_exists(&sdkroot)? {
149+
return Ok(sdkroot);
150+
} else {
151+
return Err(AppleSdkError::MissingCommandlineTools { sdkroot, sdk_name });
152+
}
153+
}
154+
155+
Err(AppleSdkError::Missing { sdk_name })
156+
}
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 = std::str::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

+10-28
Original file line numberDiff line numberDiff line change
@@ -3156,26 +3156,26 @@ fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) ->
31563156
// This is admittedly a bit strange, as on most targets
31573157
// `-isysroot` only applies to include header files, but on Apple
31583158
// targets this also applies to libraries and frameworks.
3159-
cmd.cc_args(&["-isysroot", &sdk_root]);
3159+
cmd.cc_arg("-isysroot");
3160+
cmd.cc_arg(&sdk_root);
31603161
}
31613162
LinkerFlavor::Darwin(Cc::No, _) => {
3162-
cmd.link_args(&["-syslibroot", &sdk_root]);
3163+
cmd.link_arg("-syslibroot");
3164+
cmd.link_arg(&sdk_root);
31633165
}
31643166
_ => unreachable!(),
31653167
}
31663168

3167-
Some(sdk_root.into())
3169+
Some(sdk_root)
31683170
}
31693171

3170-
fn get_apple_sdk_root(sdk_name: &str) -> Result<String, errors::AppleSdkRootError<'_>> {
3172+
fn get_apple_sdk_root(sdk_name: &'static str) -> Result<PathBuf, errors::AppleSdkError> {
31713173
// Following what clang does
31723174
// (https://github.com/llvm/llvm-project/blob/
31733175
// 296a80102a9b72c3eda80558fb78a3ed8849b341/clang/lib/Driver/ToolChains/Darwin.cpp#L1661-L1678)
3174-
// to allow the SDK path to be set. (For clang, xcrun sets
3175-
// SDKROOT; for rustc, the user or build system can set it, or we
3176-
// can fall back to checking for xcrun on PATH.)
3176+
// to allow the SDK path to be set.
31773177
if let Ok(sdkroot) = env::var("SDKROOT") {
3178-
let p = Path::new(&sdkroot);
3178+
let p = PathBuf::from(&sdkroot);
31793179
match &*sdk_name.to_lowercase() {
31803180
// Ignore `SDKROOT` if it's clearly set for the wrong platform.
31813181
"appletvos"
@@ -3204,29 +3204,11 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result<String, errors::AppleSdkRootErro
32043204
if sdkroot.contains("XROS.platform") || sdkroot.contains("MacOSX.platform") => {}
32053205
// Ignore `SDKROOT` if it's not a valid path.
32063206
_ if !p.is_absolute() || p == Path::new("/") || !p.exists() => {}
3207-
_ => return Ok(sdkroot),
3207+
_ => return Ok(p),
32083208
}
32093209
}
32103210

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

32323214
/// 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, sdk_name: &'static str },
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, sdk_name: &'static str },
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)]

src/doc/rustc/src/platform-support/apple-darwin.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,5 @@ Xcode or the macOS SDK (`MacOSX.sdk`) to be available to compile C code and
5353
to link.
5454

5555
The path to the SDK can be passed to `rustc` using the common `SDKROOT`
56-
environment variable.
56+
environment variable, or will be inferred when compiling on host macOS using
57+
roughly the same logic as `xcrun -sdk macosx -show-sdk-path`.

src/doc/rustc/src/platform-support/apple-ios-macabi.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ These targets are cross-compiled, and require the corresponding macOS SDK
2020
iOS-specific headers, as provided by Xcode 11 or higher.
2121

2222
The path to the SDK can be passed to `rustc` using the common `SDKROOT`
23-
environment variable.
23+
environment variable, or will be inferred when compiling on host macOS using
24+
roughly the same logic as `xcrun -sdk macosx -show-sdk-path`.
2425

2526
### OS version
2627

0 commit comments

Comments
 (0)