From 80551911e3755e6669aade75253c374f26ec8458 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 9 Oct 2024 05:40:55 +0200 Subject: [PATCH 1/3] Move knowledge of SDK names to rustc_target Also make the SDK name be the same casing as used in the file system. --- compiler/rustc_codegen_ssa/messages.ftl | 2 - compiler/rustc_codegen_ssa/src/back/link.rs | 57 ++++++------------- compiler/rustc_codegen_ssa/src/errors.rs | 7 --- .../rustc_target/src/spec/base/apple/mod.rs | 17 ++++++ compiler/rustc_target/src/spec/mod.rs | 2 +- 5 files changed, 36 insertions(+), 49 deletions(-) diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index f02b0f7267430..5e83cab6b2636 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -337,8 +337,6 @@ codegen_ssa_unknown_atomic_ordering = unknown ordering in atomic intrinsic codegen_ssa_unknown_reuse_kind = unknown cgu-reuse-kind `{$kind}` specified -codegen_ssa_unsupported_arch = unsupported arch `{$arch}` for os `{$os}` - codegen_ssa_unsupported_link_self_contained = option `-C link-self-contained` is not supported on this target codegen_ssa_use_cargo_directive = use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index e7b1c63a82251..566fe7e6ea080 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -3123,9 +3123,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 { - 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(..)) @@ -3137,30 +3135,8 @@ 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_name = rustc_target::spec::apple_sdk_name(&sess.target); + let sdk_root = match get_apple_sdk_root(sdk_name) { Ok(s) => s, Err(e) => { @@ -3197,7 +3173,7 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result Result return Ok(sdkroot), } } - 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[..])) - } - }, - ); + + let res = Command::new("xcrun") + .arg("--show-sdk-path") + .arg("-sdk") + .arg(sdk_name.to_lowercase()) + .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()), diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index ab909abcead8e..4ec95d11c6848 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -532,13 +532,6 @@ pub enum ExtractBundledLibsError<'a> { ExtractSection { rlib: &'a Path, error: Box }, } -#[derive(Diagnostic)] -#[diag(codegen_ssa_unsupported_arch)] -pub(crate) struct UnsupportedArch<'a> { - pub arch: &'a str, - pub os: &'a str, -} - #[derive(Diagnostic)] pub(crate) enum AppleSdkRootError<'a> { #[diag(codegen_ssa_apple_sdk_error_sdk_path)] diff --git a/compiler/rustc_target/src/spec/base/apple/mod.rs b/compiler/rustc_target/src/spec/base/apple/mod.rs index 73763cf034ca0..50a16a9078bac 100644 --- a/compiler/rustc_target/src/spec/base/apple/mod.rs +++ b/compiler/rustc_target/src/spec/base/apple/mod.rs @@ -158,6 +158,23 @@ pub(crate) fn base( (opts, llvm_target(os, arch, abi), arch.target_arch()) } +pub fn sdk_name(target: &Target) -> &'static str { + match (&*target.os, &*target.abi) { + ("ios", "") => "iPhoneOS", + ("ios", "sim") => "iPhoneSimulator", + // Mac Catalyst uses the macOS SDK + ("ios", "macabi") => "MacOSX", + ("macos", "") => "MacOSX", + ("tvos", "") => "AppleTVOS", + ("tvos", "sim") => "AppleTVSimulator", + ("visionos", "") => "XROS", + ("visionos", "sim") => "XRSimulator", + ("watchos", "") => "WatchOS", + ("watchos", "sim") => "WatchSimulator", + (os, abi) => unreachable!("invalid os '{os}' / abi '{abi}' combination for Apple target"), + } +} + pub fn platform(target: &Target) -> Option { Some(match (&*target.os, &*target.abi) { ("macos", _) => object::macho::PLATFORM_MACOS, diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 82e11a3afce32..a8cfc3ff80fff 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -61,7 +61,7 @@ pub mod crt_objects; mod base; pub use base::apple::{ deployment_target_for_target as current_apple_deployment_target, - platform as current_apple_platform, + platform as current_apple_platform, sdk_name as apple_sdk_name, }; pub use base::avr_gnu::ef_avr_arch; From edf397bacc9bedcfb3a5c4e4b4d2ddd323bbe933 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 9 Oct 2024 04:57:56 +0200 Subject: [PATCH 2/3] Re-implement SDK discovery instead of using xcrun --- compiler/rustc_codegen_ssa/messages.ftl | 55 ++++++- compiler/rustc_codegen_ssa/src/apple.rs | 136 ++++++++++++++++++ compiler/rustc_codegen_ssa/src/apple/tests.rs | 58 ++++++++ compiler/rustc_codegen_ssa/src/back/link.rs | 39 ++--- compiler/rustc_codegen_ssa/src/errors.rs | 26 +++- compiler/rustc_codegen_ssa/src/lib.rs | 1 + 6 files changed, 282 insertions(+), 33 deletions(-) create mode 100644 compiler/rustc_codegen_ssa/src/apple.rs create mode 100644 compiler/rustc_codegen_ssa/src/apple/tests.rs diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index 5e83cab6b2636..eb5793661b981 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -2,7 +2,60 @@ codegen_ssa_L4Bender_exporting_symbols_unimplemented = exporting symbols not imp codegen_ssa_add_native_library = failed to add native library {$library_path}: {$error} -codegen_ssa_apple_sdk_error_sdk_path = failed to get {$sdk_name} SDK path: {$error} +codegen_ssa_apple_sdk_error_failed_reading = + failed reading `{$path}` while looking for SDK root: {$error} + +codegen_ssa_apple_sdk_error_missing = + failed finding SDK for platform `{$sdk_name}`. It looks like you have not installed Xcode? + + { $sdk_name -> + [MacOSX] You should install Xcode via the App Store, or run `xcode-select --install` if you only intend on developing for macOS. + *[other] You should install Xcode via the App Store. + } + +codegen_ssa_apple_sdk_error_missing_commandline_tools = + failed finding SDK at `{$sdkroot}` in Command Line Tools installation. + + { $sdk_name -> + [MacOSX] Perhaps you need to reinstall it with `xcode-select --install`? + *[other] When cross-compiling for iOS, tvOS, visionOS or watchOS, you will need a full installation of Xcode. + } + +codegen_ssa_apple_sdk_error_missing_cross_compile_non_macos = + failed finding Apple SDK with name `{$sdk_name}`. + + The SDK is needed by the linker to know where to find symbols in system libraries. + + The SDK can be downloaded and extracted from https://developer.apple.com/download/all/?q=xcode (requires an Apple ID). + + You will then need to tell `rustc` about it using the `SDKROOT` environment variables. + + 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`. + + { $sdk_name -> + [MacOSX] {""} + *[other] Beware that cross-compilation to iOS, tvOS, visionOS or watchOS is generally ill supported on non-macOS hosts. + } + +codegen_ssa_apple_sdk_error_missing_developer_dir = + failed finding SDK inside active developer path `{$dir}` set by the DEVELOPER_DIR environment variable. Looked in: + - `{$sdkroot}` + - `{$sdkroot_bare}` + +codegen_ssa_apple_sdk_error_missing_xcode = + failed finding SDK at `{$sdkroot}` in Xcode installation. + + { $sdk_name -> + [MacOSX] {""} + *[other] Perhaps you need a newer version of Xcode? + } + +codegen_ssa_apple_sdk_error_missing_xcode_select = + failed finding SDK inside active developer path `{$dir}` set by `xcode-select`. Looked in: + - `{$sdkroot}` + - `{$sdkroot_bare}` + + Consider using `sudo xcode-select --switch path/to/Xcode.app` or `sudo xcode-select --reset` to select a valid path. codegen_ssa_archive_build_failure = failed to build archive at `{$path}`: {$error} diff --git a/compiler/rustc_codegen_ssa/src/apple.rs b/compiler/rustc_codegen_ssa/src/apple.rs new file mode 100644 index 0000000000000..5ee8273a0b40b --- /dev/null +++ b/compiler/rustc_codegen_ssa/src/apple.rs @@ -0,0 +1,136 @@ +use std::fs; +use std::io::ErrorKind; +use std::path::{Path, PathBuf}; + +use crate::errors::AppleSdkError; + +#[cfg(test)] +mod tests; + +// TOCTOU is not _really_ an issue with our use of `try_exists` in here, we mostly use it for +// diagnostics, and these directories are global state that the user may change anytime anyhow. +fn try_exists(path: &Path) -> Result { + path.try_exists().map_err(|error| AppleSdkError::FailedReading { path: path.to_owned(), error }) +} + +/// Get the SDK path for an SDK under `/Library/Developer/CommandLineTools`. +fn sdk_root_in_sdks_dir(sdks_dir: impl Into, sdk_name: &str) -> PathBuf { + let mut path = sdks_dir.into(); + path.push("SDKs"); + path.push(sdk_name); + path.set_extension("sdk"); + path +} + +/// Get the SDK path for an SDK under `/Applications/Xcode.app/Contents/Developer`. +fn sdk_root_in_developer_dir(developer_dir: impl Into, sdk_name: &str) -> PathBuf { + let mut path = developer_dir.into(); + path.push("Platforms"); + path.push(sdk_name); + path.set_extension("platform"); + path.push("Developer"); + path.push("SDKs"); + path.push(sdk_name); + path.set_extension("sdk"); + path +} + +/// Find a SDK root from the user's environment for the given SDK name. +/// +/// We do this by searching (purely by names in the filesystem, without reading SDKSettings.json) +/// for a matching SDK in the following places: +/// - `DEVELOPER_DIR` +/// - `/var/db/xcode_select_link` +/// - `/Applications/Xcode.app` +/// - `/Library/Developer/CommandLineTools` +/// +/// This does roughly the same thing as `xcrun -sdk $sdk_name -show-sdk-path` (see `man xcrun` for +/// a few details on the search algorithm). +/// +/// The reason why we implement this logic ourselves is: +/// - Reading these directly is faster than spawning a new process. +/// - `xcrun` can be fairly slow to start up after a reboot. +/// - In the future, we will be able to integrate this better with the compiler's change tracking +/// mechanisms, allowing rebuilds when the involved env vars and paths here change. See #118204. +/// - It's easier for us to emit better error messages. +/// +/// Though a downside is that `xcrun` might be expanded in the future to check more places, and then +/// `rustc` would have to be changed to keep up. Furthermore, `xcrun`'s exact algorithm is +/// undocumented, so it might be doing more things than we do here. +pub(crate) fn find_sdk_root(sdk_name: &'static str) -> Result { + // Only try this if host OS is macOS. + if !cfg!(target_os = "macos") { + return Err(AppleSdkError::MissingCrossCompileNonMacOS { sdk_name }); + } + + // NOTE: We could consider walking upwards in `SDKROOT` assuming Xcode directory structure, but + // that isn't what `xcrun` does, and might still not yield the desired result (e.g. if using an + // old SDK to compile for an old ARM iOS arch, we don't want `rustc` to pick a macOS SDK from + // the old Xcode). + + // Try reading from `DEVELOPER_DIR` on all hosts. + if let Some(dir) = std::env::var_os("DEVELOPER_DIR") { + let dir = PathBuf::from(dir); + let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name); + + if try_exists(&sdkroot)? { + return Ok(sdkroot); + } else { + let sdkroot_bare = sdk_root_in_sdks_dir(&dir, sdk_name); + if try_exists(&sdkroot_bare)? { + return Ok(sdkroot_bare); + } else { + return Err(AppleSdkError::MissingDeveloperDir { dir, sdkroot, sdkroot_bare }); + } + } + } + + // Next, try to read the link that `xcode-select` sets. + // + // FIXME(madsmtm): Support cases where `/var/db/xcode_select_link` contains a relative path? + let path = PathBuf::from("/var/db/xcode_select_link"); + match fs::read_link(&path) { + Ok(dir) => { + let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name); + if try_exists(&sdkroot)? { + return Ok(sdkroot); + } else { + let sdkroot_bare = sdk_root_in_sdks_dir(&dir, sdk_name); + if try_exists(&sdkroot_bare)? { + return Ok(sdkroot_bare); + } else { + return Err(AppleSdkError::MissingXcodeSelect { dir, sdkroot, sdkroot_bare }); + } + } + } + Err(err) if err.kind() == ErrorKind::NotFound => { + // Intentionally ignore not found errors, if `xcode-select --reset` is called the + // link will not exist. + } + Err(error) => return Err(AppleSdkError::FailedReading { path, error }), + } + + // Next, fall back to reading from `/Applications/Xcode.app`. + let dir = PathBuf::from("/Applications/Xcode.app/Contents/Developer"); + if try_exists(&dir)? { + let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name); + if try_exists(&sdkroot)? { + return Ok(sdkroot); + } else { + return Err(AppleSdkError::MissingXcode { sdkroot, sdk_name }); + } + } + + // Finally, fall back to reading from `/Library/Developer/CommandLineTools`. + let dir = PathBuf::from("/Library/Developer/CommandLineTools"); + if try_exists(&dir)? { + let sdkroot = sdk_root_in_sdks_dir(&dir, sdk_name); + if try_exists(&sdkroot)? { + return Ok(sdkroot); + } else { + return Err(AppleSdkError::MissingCommandlineTools { sdkroot, sdk_name }); + } + } + + Err(AppleSdkError::Missing { sdk_name }) +} diff --git a/compiler/rustc_codegen_ssa/src/apple/tests.rs b/compiler/rustc_codegen_ssa/src/apple/tests.rs new file mode 100644 index 0000000000000..fed942866b9f2 --- /dev/null +++ b/compiler/rustc_codegen_ssa/src/apple/tests.rs @@ -0,0 +1,58 @@ +use std::io; +use std::path::PathBuf; +use std::process::Command; + +use super::find_sdk_root; + +fn find_sdk_root_xcrun(sdk_name: &str) -> io::Result { + let output = Command::new("xcrun") + .arg("-sdk") + .arg(sdk_name.to_lowercase()) + .arg("-show-sdk-path") + .output()?; + if output.status.success() { + // FIXME(madsmtm): If using this for real, we should not error on non-UTF-8 paths. + let output = String::from_utf8(output.stdout).unwrap(); + Ok(PathBuf::from(output.trim())) + } 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[..])) + } +} + +/// Ensure that our `find_sdk_root` matches `xcrun`'s behaviour. +/// +/// `xcrun` is quite slow the first time it's run after a reboot, so this test may take some time. +#[test] +#[cfg_attr(not(target_os = "macos"), ignore = "xcrun is only available on macOS")] +fn test_find_sdk_root() { + let sdks = [ + "MacOSX", + "AppleTVOS", + "AppleTVSimulator", + "iPhoneOS", + "iPhoneSimulator", + "WatchOS", + "WatchSimulator", + "XROS", + "XRSimulator", + ]; + for sdk_name in sdks { + if let Ok(expected) = find_sdk_root_xcrun(sdk_name) { + // `xcrun` prefers `MacOSX14.0.sdk` over `MacOSX.sdk`, so let's compare canonical paths. + let expected = std::fs::canonicalize(expected).unwrap(); + let actual = find_sdk_root(sdk_name).unwrap(); + let actual = std::fs::canonicalize(actual).unwrap(); + assert_eq!(expected, actual); + } else { + // The macOS SDK must always be findable in Rust's CI. + // + // The other SDKs are allowed to not be found in the current developer directory when + // running this test. + if sdk_name == "MacOSX" { + panic!("Could not find macOS SDK with `xcrun -sdk macosx -show-sdk-path`"); + } + } + } +} diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 566fe7e6ea080..fef54e90402d5 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -50,6 +50,7 @@ use super::command::Command; use super::linker::{self, Linker}; use super::metadata::{MetadataPosition, create_wrapper_file}; use super::rpath::{self, RPathConfig}; +use crate::apple::find_sdk_root; use crate::{ CodegenResults, CompiledModule, CrateInfo, NativeLib, common, errors, looks_like_rust_object_file, @@ -3153,26 +3154,26 @@ 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> { +fn get_apple_sdk_root(sdk_name: &'static str) -> Result { // 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.) + // to allow the SDK path to be set. if let Ok(sdkroot) = env::var("SDKROOT") { - let p = Path::new(&sdkroot); + let p = PathBuf::from(&sdkroot); match &*sdk_name.to_lowercase() { // Ignore `SDKROOT` if it's clearly set for the wrong platform. "appletvos" @@ -3201,29 +3202,11 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result {} // Ignore `SDKROOT` if it's not a valid path. _ if !p.is_absolute() || p == Path::new("/") || !p.exists() => {} - _ => return Ok(sdkroot), + _ => return Ok(p), } } - let res = Command::new("xcrun") - .arg("--show-sdk-path") - .arg("-sdk") - .arg(sdk_name.to_lowercase()) - .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 }), - } + find_sdk_root(sdk_name) } /// When using the linker flavors opting in to `lld`, add the necessary paths and arguments to diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index 4ec95d11c6848..db8b966632a87 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -532,10 +532,28 @@ pub enum ExtractBundledLibsError<'a> { ExtractSection { rlib: &'a Path, error: Box }, } -#[derive(Diagnostic)] -pub(crate) enum AppleSdkRootError<'a> { - #[diag(codegen_ssa_apple_sdk_error_sdk_path)] - SdkPath { sdk_name: &'a str, error: Error }, +#[derive(Diagnostic, Debug)] +pub(crate) enum AppleSdkError { + #[diag(codegen_ssa_apple_sdk_error_failed_reading)] + FailedReading { path: PathBuf, error: std::io::Error }, + + #[diag(codegen_ssa_apple_sdk_error_missing)] + Missing { sdk_name: &'static str }, + + #[diag(codegen_ssa_apple_sdk_error_missing_commandline_tools)] + MissingCommandlineTools { sdkroot: PathBuf, sdk_name: &'static str }, + + #[diag(codegen_ssa_apple_sdk_error_missing_cross_compile_non_macos)] + MissingCrossCompileNonMacOS { sdk_name: &'static str }, + + #[diag(codegen_ssa_apple_sdk_error_missing_developer_dir)] + MissingDeveloperDir { dir: PathBuf, sdkroot: PathBuf, sdkroot_bare: PathBuf }, + + #[diag(codegen_ssa_apple_sdk_error_missing_xcode)] + MissingXcode { sdkroot: PathBuf, sdk_name: &'static str }, + + #[diag(codegen_ssa_apple_sdk_error_missing_xcode_select)] + MissingXcodeSelect { dir: PathBuf, sdkroot: PathBuf, sdkroot_bare: PathBuf }, } #[derive(Diagnostic)] diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index cbd95146294f5..aeae825f30508 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -44,6 +44,7 @@ use rustc_session::cstore::{self, CrateSource}; use rustc_session::utils::NativeLibKind; use rustc_span::symbol::Symbol; +pub mod apple; pub mod assert_module_sources; pub mod back; pub mod base; From 25e7f0f117766ae561acef4a6353ee34a49cbec1 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 10 Oct 2024 00:31:48 +0200 Subject: [PATCH 3/3] 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 https://github.com/rust-lang/rust/issues/80817 (by always passing the SDK root, even when linking with cc on macOS). --- compiler/rustc_codegen_ssa/src/back/link.rs | 67 +++++++++++-------- .../rustc_target/src/spec/base/apple/mod.rs | 23 +------ tests/run-make/link-under-xcode/foo.rs | 1 + tests/run-make/link-under-xcode/rmake.rs | 32 +++++++++ 4 files changed, 75 insertions(+), 48 deletions(-) create mode 100644 tests/run-make/link-under-xcode/foo.rs create mode 100644 tests/run-make/link-under-xcode/rmake.rs 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..4f79f96532f8b --- /dev/null +++ b/tests/run-make/link-under-xcode/rmake.rs @@ -0,0 +1,32 @@ +//! 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(); +}