diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index fef54e90402d5..6b8b96a6e4639 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -3124,47 +3124,60 @@ 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 { - let os = &sess.target.os; - if sess.target.vendor != "apple" - || !matches!(os.as_ref(), "ios" | "tvos" | "watchos" | "visionos" | "macos") - || !matches!(flavor, LinkerFlavor::Darwin(..)) - { + if !sess.target.is_like_osx { return None; } - - if os == "macos" && !matches!(flavor, LinkerFlavor::Darwin(Cc::No, _)) { + let LinkerFlavor::Darwin(cc, _) = flavor else { return None; - } + }; let sdk_name = rustc_target::spec::apple_sdk_name(&sess.target); - let sdk_root = match get_apple_sdk_root(sdk_name) { + let sdkroot = match get_apple_sdk_root(sdk_name) { Ok(s) => s, Err(e) => { - sess.dcx().emit_err(e); + // If cross compiling from non-macOS, the user might be using something like `zig cc`. + // + // In that case, we shouldn't error when the SDK is missing, though we still warn. + if cfg!(target_os = "macos") { + sess.dcx().emit_err(e); + } else { + sess.dcx().emit_warn(e); + } return None; } }; - match flavor { - LinkerFlavor::Darwin(Cc::Yes, _) => { - // Use `-isysroot` instead of `--sysroot`, as only the former - // makes Clang treat it as a platform SDK. - // - // 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_arg("-isysroot"); - cmd.cc_arg(&sdk_root); - } - LinkerFlavor::Darwin(Cc::No, _) => { - cmd.link_arg("-syslibroot"); - cmd.link_arg(&sdk_root); - } - _ => unreachable!(), + if cc == Cc::Yes { + // To pass the SDK root to `cc`, we have a few options: + // 1. `--sysroot` flag. + // 2. `-isysroot` flag. + // 3. `SDKROOT` environment variable. + // + // `--sysroot` isn't actually enough to get Clang to treat it as a platform SDK, you need to + // specify `-isysroot` - this is admittedly a bit strange, as on most targets `-isysroot` + // only applies to include header files, but on Apple targets it also applies to libraries + // and frameworks. + // + // Now, while the `-isysroot` flag is pretty well supported (both Clang and GCC implements + // the desired behaviour), it may not be understood by any `cc`'s that the user might want + // to use. + // + // So to better support such use-cases, we pass the SDK root in the standard environment + // variable instead. This is also supported by GCC since 2019: + // + // + // This also works better with the trampoline `/usr/bin/cc` which calls `xcrun cc` + // internally, since the presence of `SDKROOT` means it won't have to look up the SDK root + // itself. + cmd.cmd().env("SDKROOT", &sdkroot); + } else { + // For `ld64`, we use the `-syslibroot` parameter (and there are no other options). + cmd.link_arg("-syslibroot"); + cmd.link_arg(&sdkroot); } - Some(sdk_root) + Some(sdkroot) } fn get_apple_sdk_root(sdk_name: &'static str) -> Result { diff --git a/compiler/rustc_target/src/spec/base/apple/mod.rs b/compiler/rustc_target/src/spec/base/apple/mod.rs index 50a16a9078bac..7c79fd6434b93 100644 --- a/compiler/rustc_target/src/spec/base/apple/mod.rs +++ b/compiler/rustc_target/src/spec/base/apple/mod.rs @@ -309,29 +309,10 @@ fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow]> { // that's only applicable to cross-OS compilation. Always leave anything for the // host OS alone though. if os == "macos" { - let mut env_remove = Vec::with_capacity(2); - // Remove the `SDKROOT` environment variable if it's clearly set for the wrong platform, which - // may occur when we're linking a custom build script while targeting iOS for example. - if let Ok(sdkroot) = env::var("SDKROOT") { - if sdkroot.contains("iPhoneOS.platform") - || sdkroot.contains("iPhoneSimulator.platform") - || sdkroot.contains("AppleTVOS.platform") - || sdkroot.contains("AppleTVSimulator.platform") - || sdkroot.contains("WatchOS.platform") - || sdkroot.contains("WatchSimulator.platform") - || sdkroot.contains("XROS.platform") - || sdkroot.contains("XRSimulator.platform") - { - env_remove.push("SDKROOT".into()) - } - } - // Additionally, `IPHONEOS_DEPLOYMENT_TARGET` must not be set when using the Xcode linker at + // `IPHONEOS_DEPLOYMENT_TARGET` must not be set when using the Xcode linker at // "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld", // although this is apparently ignored when using the linker at "/usr/bin/ld". - env_remove.push("IPHONEOS_DEPLOYMENT_TARGET".into()); - env_remove.push("TVOS_DEPLOYMENT_TARGET".into()); - env_remove.push("XROS_DEPLOYMENT_TARGET".into()); - env_remove.into() + cvs!["IPHONEOS_DEPLOYMENT_TARGET", "TVOS_DEPLOYMENT_TARGET", "XROS_DEPLOYMENT_TARGET"] } else { // Otherwise if cross-compiling for a different OS/SDK (including Mac Catalyst), remove any part // of the linking environment that's wrong and reversed. diff --git a/tests/run-make/link-under-xcode/foo.rs b/tests/run-make/link-under-xcode/foo.rs new file mode 100644 index 0000000000000..f328e4d9d04c3 --- /dev/null +++ b/tests/run-make/link-under-xcode/foo.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/tests/run-make/link-under-xcode/rmake.rs b/tests/run-make/link-under-xcode/rmake.rs new file mode 100644 index 0000000000000..f76c983a407a8 --- /dev/null +++ b/tests/run-make/link-under-xcode/rmake.rs @@ -0,0 +1,26 @@ +//! Test that linking works under an environment similar to what Xcode sets up. +//! +//! Regression test for https://github.com/rust-lang/rust/issues/80817. + +//@ only-apple + +use run_make_support::{cmd, rustc, target}; + +fn main() { + // Fetch toolchain `/usr/bin` directory. + // Usually `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin` + let cc_bin = cmd("xcrun").arg("--find").arg("cc").run().stdout_utf8(); + let toolchain_bin = cc_bin.trim().strip_suffix("/cc").unwrap(); + + // Put toolchain directory at the front of PATH. + let path = format!("{}:{}", toolchain_bin, std::env::var("PATH").unwrap()); + + // Check that compiling and linking still works. + // + // Removing `SDKROOT` is necessary for the test to excercise what we want, since bootstrap runs + // under `/usr/bin/python3`, which will set SDKROOT for us. + rustc().target(target()).env_remove("SDKROOT").env("PATH", &path).input("foo.rs").run(); + + // Also check with ld64. + rustc().target(target()).env_remove("SDKROOT").env("PATH", &path).arg("-Clinker-flavor=ld").input("foo.rs").run(); +}