Rollup merge of #139010 - madsmtm:parse-xcrun-better, r=wesleywiser

Improve `xcrun` error handling

The compiler invokes `xcrun` on macOS when linking Apple targets, to find the Xcode SDK which contain all the necessary linker stubs. The error messages that `xcrun` outputs aren't always that great though, so this PR tries to improve that by providing extra context when an error occurs.

Fixes https://github.com/rust-lang/rust/issues/56829.
Fixes https://github.com/rust-lang/rust/issues/84534.
Part of https://github.com/rust-lang/rust/issues/129432.
See also the alternative https://github.com/rust-lang/rust/pull/131433.

Tested on:
- `x86_64-apple-darwin`, MacBook Pro running Mac OS X 10.12.6
    - With no tooling installed
    - With Xcode 9.2
    - With Xcode 9.2 Commandline Tools
- `aarch64-apple-darwin`, MacBook M2 Pro running macOS 14.7.4
    - With Xcode 13.4.1
    - With Xcode 16.2
    - Inside `nix-shell -p xcbuild` (nixpkgs' `xcrun` shim)
- `aarch64-apple-darwin`, VM running macOS 15.3.1
    - With no tooling installed
    - With Xcode 16.2 Commandline Tools

``@rustbot`` label O-apple
r? compiler
CC ``@BlackHoleFox`` ``@thomcc``
This commit is contained in:
Jacob Pratt
2025-03-27 21:41:48 -04:00
committed by GitHub
12 changed files with 293 additions and 88 deletions

View File

@@ -3201,9 +3201,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
}
fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) -> Option<PathBuf> {
let arch = &sess.target.arch;
let os = &sess.target.os;
let llvm_target = &sess.target.llvm_target;
if sess.target.vendor != "apple"
|| !matches!(os.as_ref(), "ios" | "tvos" | "watchos" | "visionos" | "macos")
|| !matches!(flavor, LinkerFlavor::Darwin(..))
@@ -3215,37 +3213,7 @@ fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) ->
return None;
}
let sdk_name = match (arch.as_ref(), os.as_ref()) {
("aarch64", "tvos") if llvm_target.ends_with("-simulator") => "appletvsimulator",
("aarch64", "tvos") => "appletvos",
("x86_64", "tvos") => "appletvsimulator",
("arm", "ios") => "iphoneos",
("aarch64", "ios") if llvm_target.contains("macabi") => "macosx",
("aarch64", "ios") if llvm_target.ends_with("-simulator") => "iphonesimulator",
("aarch64", "ios") => "iphoneos",
("x86", "ios") => "iphonesimulator",
("x86_64", "ios") if llvm_target.contains("macabi") => "macosx",
("x86_64", "ios") => "iphonesimulator",
("x86_64", "watchos") => "watchsimulator",
("arm64_32", "watchos") => "watchos",
("aarch64", "watchos") if llvm_target.ends_with("-simulator") => "watchsimulator",
("aarch64", "watchos") => "watchos",
("aarch64", "visionos") if llvm_target.ends_with("-simulator") => "xrsimulator",
("aarch64", "visionos") => "xros",
("arm", "watchos") => "watchos",
(_, "macos") => "macosx",
_ => {
sess.dcx().emit_err(errors::UnsupportedArch { arch, os });
return None;
}
};
let sdk_root = match get_apple_sdk_root(sdk_name) {
Ok(s) => s,
Err(e) => {
sess.dcx().emit_err(e);
return None;
}
};
let sdk_root = sess.time("get_apple_sdk_root", || get_apple_sdk_root(sess))?;
match flavor {
LinkerFlavor::Darwin(Cc::Yes, _) => {
@@ -3255,28 +3223,32 @@ fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) ->
// This is admittedly a bit strange, as on most targets
// `-isysroot` only applies to include header files, but on Apple
// targets this also applies to libraries and frameworks.
cmd.cc_args(&["-isysroot", &sdk_root]);
cmd.cc_arg("-isysroot");
cmd.cc_arg(&sdk_root);
}
LinkerFlavor::Darwin(Cc::No, _) => {
cmd.link_args(&["-syslibroot", &sdk_root]);
cmd.link_arg("-syslibroot");
cmd.link_arg(&sdk_root);
}
_ => unreachable!(),
}
Some(sdk_root.into())
Some(sdk_root)
}
fn get_apple_sdk_root(sdk_name: &str) -> Result<String, errors::AppleSdkRootError<'_>> {
// Following what clang does
// (https://github.com/llvm/llvm-project/blob/
// 296a80102a9b72c3eda80558fb78a3ed8849b341/clang/lib/Driver/ToolChains/Darwin.cpp#L1661-L1678)
// to allow the SDK path to be set. (For clang, xcrun sets
// SDKROOT; for rustc, the user or build system can set it, or we
// can fall back to checking for xcrun on PATH.)
fn get_apple_sdk_root(sess: &Session) -> Option<PathBuf> {
if let Ok(sdkroot) = env::var("SDKROOT") {
let p = Path::new(&sdkroot);
match sdk_name {
// Ignore `SDKROOT` if it's clearly set for the wrong platform.
let p = PathBuf::from(&sdkroot);
// Ignore invalid SDKs, similar to what clang does:
// https://github.com/llvm/llvm-project/blob/llvmorg-19.1.6/clang/lib/Driver/ToolChains/Darwin.cpp#L2212-L2229
//
// NOTE: Things are complicated here by the fact that `rustc` can be run by Cargo to compile
// build scripts and proc-macros for the host, and thus we need to ignore SDKROOT if it's
// clearly set for the wrong platform.
//
// FIXME(madsmtm): Make this more robust (maybe read `SDKSettings.json` like Clang does?).
match &*apple::sdk_name(&sess.target).to_lowercase() {
"appletvos"
if sdkroot.contains("TVSimulator.platform")
|| sdkroot.contains("MacOSX.platform") => {}
@@ -3303,26 +3275,11 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result<String, errors::AppleSdkRootErro
if sdkroot.contains("XROS.platform") || sdkroot.contains("MacOSX.platform") => {}
// Ignore `SDKROOT` if it's not a valid path.
_ if !p.is_absolute() || p == Path::new("/") || !p.exists() => {}
_ => return Ok(sdkroot),
_ => return Some(p),
}
}
let res =
Command::new("xcrun").arg("--show-sdk-path").arg("-sdk").arg(sdk_name).output().and_then(
|output| {
if output.status.success() {
Ok(String::from_utf8(output.stdout).unwrap())
} else {
let error = String::from_utf8(output.stderr);
let error = format!("process exit with error: {}", error.unwrap());
Err(io::Error::new(io::ErrorKind::Other, &error[..]))
}
},
);
match res {
Ok(output) => Ok(output.trim().to_string()),
Err(error) => Err(errors::AppleSdkRootError::SdkPath { sdk_name, error }),
}
apple::get_sdk_root(sess)
}
/// When using the linker flavors opting in to `lld`, add the necessary paths and arguments to