Skip to content

Commit 98cde29

Browse files
committed
Always attempt to get SDK root, and pass it to Clang via env var
The exact reasoning why we do not always pass the SDK root with `-isysroot` to `cc` when linking on macOS eludes me (the git history dead ends in #100286), but I suspect it's because we want to support `cc`s which do not support this option. So instead, we pass the SDK root via the environment variable SDKROOT. This way, compiler drivers that support setting the SDK root (such as Clang and GCC) can use it, while compiler drivers that don't (presumably because they figure out the SDK root in some other way) can just ignore it. This fixes #80817 (by always passing the SDK root, even when linking with cc on macOS).
1 parent bd1888f commit 98cde29

File tree

4 files changed

+87
-50
lines changed

4 files changed

+87
-50
lines changed

Diff for: compiler/rustc_codegen_ssa/src/back/link.rs

+52-28
Original file line numberDiff line numberDiff line change
@@ -3124,47 +3124,65 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
31243124
}
31253125

31263126
fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) -> Option<PathBuf> {
3127-
let os = &sess.target.os;
3128-
if sess.target.vendor != "apple"
3129-
|| !matches!(os.as_ref(), "ios" | "tvos" | "watchos" | "visionos" | "macos")
3130-
|| !matches!(flavor, LinkerFlavor::Darwin(..))
3131-
{
3127+
if !sess.target.is_like_osx {
31323128
return None;
31333129
}
3134-
3135-
if os == "macos" && !matches!(flavor, LinkerFlavor::Darwin(Cc::No, _)) {
3130+
let LinkerFlavor::Darwin(cc, _) = flavor else {
31363131
return None;
3137-
}
3132+
};
31383133

31393134
let sdk_name = apple::sdk_name(&sess.target);
31403135

3141-
let sdk_root = match get_apple_sdk_root(sdk_name) {
3136+
let sdkroot = match get_apple_sdk_root(sdk_name) {
31423137
Ok(s) => s,
31433138
Err(e) => {
3144-
sess.dcx().emit_err(e);
3139+
// If cross compiling from non-macOS, the user might be using something like `zig cc`.
3140+
//
3141+
// In that case, we shouldn't error when the SDK is missing, though we still warn.
3142+
if cfg!(target_os = "macos") {
3143+
sess.dcx().emit_err(e);
3144+
} else {
3145+
sess.dcx().emit_warn(e);
3146+
}
31453147
return None;
31463148
}
31473149
};
31483150

3149-
match flavor {
3150-
LinkerFlavor::Darwin(Cc::Yes, _) => {
3151-
// Use `-isysroot` instead of `--sysroot`, as only the former
3152-
// makes Clang treat it as a platform SDK.
3153-
//
3154-
// This is admittedly a bit strange, as on most targets
3155-
// `-isysroot` only applies to include header files, but on Apple
3156-
// targets this also applies to libraries and frameworks.
3157-
cmd.cc_arg("-isysroot");
3158-
cmd.cc_arg(&sdk_root);
3159-
}
3160-
LinkerFlavor::Darwin(Cc::No, _) => {
3161-
cmd.link_arg("-syslibroot");
3162-
cmd.link_arg(&sdk_root);
3163-
}
3164-
_ => unreachable!(),
3151+
if cc == Cc::Yes {
3152+
// To pass the SDK root to `cc`, we have a few options:
3153+
// 1. `--sysroot` flag.
3154+
// 2. `-isysroot` flag.
3155+
// 3. `SDKROOT` environment variable.
3156+
//
3157+
// `--sysroot` isn't actually enough to get Clang to treat it as a platform SDK, you need to
3158+
// specify `-isysroot` - this is admittedly a bit strange, as on most targets `-isysroot`
3159+
// only applies to include header files, but on Apple targets it also applies to libraries
3160+
// and frameworks.
3161+
//
3162+
// Now, while the `-isysroot` flag is pretty well supported (both Clang and GCC implements
3163+
// the desired behaviour), it may not be understood by any `cc`'s that the user might want
3164+
// to use.
3165+
//
3166+
// So to better support such use-cases, we pass the SDK root in the standard environment
3167+
// variable instead. This is also supported by GCC since 2019:
3168+
// <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87243>
3169+
//
3170+
// This also works better with the trampoline `/usr/bin/cc` which calls `xcrun cc`
3171+
// internally, since the presence of `SDKROOT` means it won't have to look up the SDK root
3172+
// itself.
3173+
//
3174+
// A final note about precedence: Clang seems to treats `SDKROOT` and `-isysroot` the same,
3175+
// with the latter having precedence. But for the purpose of linking, Clang seems to prefer
3176+
// the value from `--sysroot`.
3177+
cmd.cmd().env("SDKROOT", &sdkroot);
3178+
} else {
3179+
// For `ld64`, we use the `-syslibroot` parameter (this is what Clang passes, and `SDKROOT`
3180+
// is not read by `ld64` so it's really the only option).
3181+
cmd.link_arg("-syslibroot");
3182+
cmd.link_arg(&sdkroot);
31653183
}
31663184

3167-
Some(sdk_root)
3185+
Some(sdkroot)
31683186
}
31693187

31703188
fn get_apple_sdk_root(sdk_name: &'static str) -> Result<PathBuf, errors::AppleSdkError> {
@@ -3189,7 +3207,13 @@ fn get_apple_sdk_root(sdk_name: &'static str) -> Result<PathBuf, errors::AppleSd
31893207
}
31903208
"macosx"
31913209
if sdkroot.contains("iPhoneOS.platform")
3192-
|| sdkroot.contains("iPhoneSimulator.platform") => {}
3210+
|| sdkroot.contains("iPhoneSimulator.platform")
3211+
|| sdkroot.contains("AppleTVOS.platform")
3212+
|| sdkroot.contains("AppleTVSimulator.platform")
3213+
|| sdkroot.contains("WatchOS.platform")
3214+
|| sdkroot.contains("WatchSimulator.platform")
3215+
|| sdkroot.contains("XROS.platform")
3216+
|| sdkroot.contains("XRSimulator.platform") => {}
31933217
"watchos"
31943218
if sdkroot.contains("WatchSimulator.platform")
31953219
|| sdkroot.contains("MacOSX.platform") => {}

Diff for: compiler/rustc_target/src/spec/base/apple/mod.rs

+2-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use std::borrow::Cow;
2-
use std::env;
32

43
use crate::spec::{
54
Cc, DebuginfoKind, FramePointer, LinkerFlavor, Lld, SplitDebuginfo, StackProbeType, StaticCow,
@@ -187,29 +186,10 @@ fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow<str>]> {
187186
// that's only applicable to cross-OS compilation. Always leave anything for the
188187
// host OS alone though.
189188
if os == "macos" {
190-
let mut env_remove = Vec::with_capacity(2);
191-
// Remove the `SDKROOT` environment variable if it's clearly set for the wrong platform, which
192-
// may occur when we're linking a custom build script while targeting iOS for example.
193-
if let Ok(sdkroot) = env::var("SDKROOT") {
194-
if sdkroot.contains("iPhoneOS.platform")
195-
|| sdkroot.contains("iPhoneSimulator.platform")
196-
|| sdkroot.contains("AppleTVOS.platform")
197-
|| sdkroot.contains("AppleTVSimulator.platform")
198-
|| sdkroot.contains("WatchOS.platform")
199-
|| sdkroot.contains("WatchSimulator.platform")
200-
|| sdkroot.contains("XROS.platform")
201-
|| sdkroot.contains("XRSimulator.platform")
202-
{
203-
env_remove.push("SDKROOT".into())
204-
}
205-
}
206-
// Additionally, `IPHONEOS_DEPLOYMENT_TARGET` must not be set when using the Xcode linker at
189+
// `IPHONEOS_DEPLOYMENT_TARGET` must not be set when using the Xcode linker at
207190
// "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld",
208191
// although this is apparently ignored when using the linker at "/usr/bin/ld".
209-
env_remove.push("IPHONEOS_DEPLOYMENT_TARGET".into());
210-
env_remove.push("TVOS_DEPLOYMENT_TARGET".into());
211-
env_remove.push("XROS_DEPLOYMENT_TARGET".into());
212-
env_remove.into()
192+
cvs!["IPHONEOS_DEPLOYMENT_TARGET", "TVOS_DEPLOYMENT_TARGET", "XROS_DEPLOYMENT_TARGET"]
213193
} else {
214194
// Otherwise if cross-compiling for a different OS/SDK (including Mac Catalyst), remove any part
215195
// of the linking environment that's wrong and reversed.

Diff for: tests/run-make/link-under-xcode/foo.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn main() {}

Diff for: tests/run-make/link-under-xcode/rmake.rs

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//! Test that linking works under an environment similar to what Xcode sets up.
2+
//!
3+
//! Regression test for https://github.com/rust-lang/rust/issues/80817.
4+
5+
//@ only-apple
6+
7+
use run_make_support::{cmd, rustc, target};
8+
9+
fn main() {
10+
// Fetch toolchain `/usr/bin` directory. Usually:
11+
// /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
12+
let cc_bin = cmd("xcrun").arg("--find").arg("cc").run().stdout_utf8();
13+
let toolchain_bin = cc_bin.trim().strip_suffix("/cc").unwrap();
14+
15+
// Put toolchain directory at the front of PATH.
16+
let path = format!("{}:{}", toolchain_bin, std::env::var("PATH").unwrap());
17+
18+
// Check that compiling and linking still works.
19+
//
20+
// Removing `SDKROOT` is necessary for the test to excercise what we want, since bootstrap runs
21+
// under `/usr/bin/python3`, which will set SDKROOT for us.
22+
rustc().target(target()).env_remove("SDKROOT").env("PATH", &path).input("foo.rs").run();
23+
24+
// Also check with ld64.
25+
rustc()
26+
.target(target())
27+
.env_remove("SDKROOT")
28+
.env("PATH", &path)
29+
.arg("-Clinker-flavor=ld")
30+
.input("foo.rs")
31+
.run();
32+
}

0 commit comments

Comments
 (0)