From 168951cbbe1d002b23f204d3b9c3cc2c9badaa9e Mon Sep 17 00:00:00 2001
From: Mads Marquart <mads@marquart.dk>
Date: Sun, 3 Nov 2024 23:08:37 +0100
Subject: [PATCH 1/2] Move knowledge of SDK names to
 rustc_codegen_ssa::back::apple

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/apple.rs | 17 ++++++
 compiler/rustc_codegen_ssa/src/back/link.rs  | 57 +++++++-------------
 compiler/rustc_codegen_ssa/src/errors.rs     |  7 ---
 4 files changed, 35 insertions(+), 48 deletions(-)

diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index 3b34eb063ec04..9751d043c9867 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -348,8 +348,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/apple.rs b/compiler/rustc_codegen_ssa/src/back/apple.rs
index 93d90cd16b24a..48f5b7e662047 100644
--- a/compiler/rustc_codegen_ssa/src/back/apple.rs
+++ b/compiler/rustc_codegen_ssa/src/back/apple.rs
@@ -10,6 +10,23 @@ use crate::errors::AppleDeploymentTarget;
 #[cfg(test)]
 mod tests;
 
+pub(super) 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(super) fn macho_platform(target: &Target) -> u32 {
     match (&*target.os, &*target.abi) {
         ("macos", _) => object::macho::PLATFORM_MACOS,
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index 20920d16f3cea..e644a864a4854 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -3124,9 +3124,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(..))
@@ -3138,30 +3136,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 = apple::sdk_name(&sess.target);
+
     let sdk_root = match get_apple_sdk_root(sdk_name) {
         Ok(s) => s,
         Err(e) => {
@@ -3198,7 +3174,7 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result<String, errors::AppleSdkRootErro
     // can fall back to checking for xcrun on PATH.)
     if let Ok(sdkroot) = env::var("SDKROOT") {
         let p = Path::new(&sdkroot);
-        match sdk_name {
+        match &*sdk_name.to_lowercase() {
             // Ignore `SDKROOT` if it's clearly set for the wrong platform.
             "appletvos"
                 if sdkroot.contains("TVSimulator.platform")
@@ -3229,18 +3205,21 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result<String, errors::AppleSdkRootErro
             _ => 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 cf8d1cfa0d10e..44c4fab573beb 100644
--- a/compiler/rustc_codegen_ssa/src/errors.rs
+++ b/compiler/rustc_codegen_ssa/src/errors.rs
@@ -533,13 +533,6 @@ pub enum ExtractBundledLibsError<'a> {
     ExtractSection { rlib: &'a Path, error: Box<dyn std::error::Error> },
 }
 
-#[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 AppleDeploymentTarget {
     #[diag(codegen_ssa_apple_deployment_target_invalid)]

From bd1888f7c36dc86d888094394d319d7a54242af0 Mon Sep 17 00:00:00 2001
From: Mads Marquart <mads@marquart.dk>
Date: Wed, 9 Oct 2024 04:57:56 +0200
Subject: [PATCH 2/2] Re-implement SDK discovery instead of using xcrun

---
 compiler/rustc_codegen_ssa/messages.ftl       |  60 +++++++-
 compiler/rustc_codegen_ssa/src/back/apple.rs  | 135 +++++++++++++++++-
 .../rustc_codegen_ssa/src/back/apple/tests.rs |  59 +++++++-
 compiler/rustc_codegen_ssa/src/back/link.rs   |  38 ++---
 compiler/rustc_codegen_ssa/src/errors.rs      |  26 +++-
 .../src/platform-support/apple-darwin.md      |   3 +-
 .../src/platform-support/apple-ios-macabi.md  |   3 +-
 .../rustc/src/platform-support/apple-ios.md   |   3 +-
 .../rustc/src/platform-support/apple-tvos.md  |   3 +-
 .../src/platform-support/apple-visionos.md    |   3 +-
 .../src/platform-support/apple-watchos.md     |   3 +-
 11 files changed, 294 insertions(+), 42 deletions(-)

diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index 9751d043c9867..4924b41242fcd 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -8,7 +8,65 @@ codegen_ssa_apple_deployment_target_invalid =
 codegen_ssa_apple_deployment_target_too_low =
     deployment target in {$env_var} was set to {$version}, but the minimum supported by `rustc` is {$os_min}
 
-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` to install the Command Line Tools 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 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 and for embedding the SDK version in the final object file.
+
+    The SDK can be downloaded and extracted from https://developer.apple.com/download/all/?q=xcode (requires an Apple ID).
+
+    The full Xcode bundle should contain the SDK in `Xcode.app/Contents/Developer/Platforms/{$sdk_name}.platform/Developer/SDKs/{$sdk_name}.sdk`{ $sdk_name ->
+        [MacOSX] , but downloading just the Command Line Tools for Xcode should also be sufficient to obtain the macOS SDK.
+       *[other] .
+    }
+
+    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 `rustc` is configured to use the bundled `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 directory `{$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 directory `{$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/back/apple.rs b/compiler/rustc_codegen_ssa/src/back/apple.rs
index 48f5b7e662047..451ff6874e1ef 100644
--- a/compiler/rustc_codegen_ssa/src/back/apple.rs
+++ b/compiler/rustc_codegen_ssa/src/back/apple.rs
@@ -1,11 +1,13 @@
-use std::env;
 use std::fmt::{Display, from_fn};
+use std::io::ErrorKind;
 use std::num::ParseIntError;
+use std::path::{Path, PathBuf};
+use std::{env, fs};
 
 use rustc_session::Session;
 use rustc_target::spec::Target;
 
-use crate::errors::AppleDeploymentTarget;
+use crate::errors::{AppleDeploymentTarget, AppleSdkError};
 
 #[cfg(test)]
 mod tests;
@@ -186,3 +188,132 @@ pub(super) fn add_version_to_llvm_target(
         format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}")
     }
 }
+
+// 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 can change anytime anyhow in
+// ways that are going to interfere much more with the compilation process.
+fn try_exists(path: &Path) -> Result<bool, AppleSdkError> {
+    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<PathBuf>, 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<PathBuf>, 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<PathBuf, AppleSdkError> {
+    // Only try this if the 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`.
+    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 = Path::new("/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: path.into(), error }),
+    }
+
+    // Next, fall back to reading from `/Applications/Xcode.app`.
+    let dir = Path::new("/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 = Path::new("/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/back/apple/tests.rs b/compiler/rustc_codegen_ssa/src/back/apple/tests.rs
index 7ccda5a8190c7..6ebc01d376830 100644
--- a/compiler/rustc_codegen_ssa/src/back/apple/tests.rs
+++ b/compiler/rustc_codegen_ssa/src/back/apple/tests.rs
@@ -1,4 +1,8 @@
-use super::{add_version_to_llvm_target, parse_version};
+use std::io;
+use std::path::PathBuf;
+use std::process::Command;
+
+use super::{add_version_to_llvm_target, find_sdk_root, parse_version};
 
 #[test]
 fn test_add_version_to_llvm_target() {
@@ -19,3 +23,56 @@ fn test_parse_version() {
     assert_eq!(parse_version("10.12.6"), Ok((10, 12, 6)));
     assert_eq!(parse_version("9999.99.99"), Ok((9999, 99, 99)));
 }
+
+fn find_sdk_root_xcrun(sdk_name: &str) -> io::Result<PathBuf> {
+    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 = std::str::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 e644a864a4854..9cf0c7e392d3c 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -3154,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<String, errors::AppleSdkRootError<'_>> {
+fn get_apple_sdk_root(sdk_name: &'static str) -> Result<PathBuf, errors::AppleSdkError> {
     // 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"
@@ -3202,29 +3202,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 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 }),
-    }
+    apple::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 44c4fab573beb..6c3eedbec91d4 100644
--- a/compiler/rustc_codegen_ssa/src/errors.rs
+++ b/compiler/rustc_codegen_ssa/src/errors.rs
@@ -541,10 +541,28 @@ pub(crate) enum AppleDeploymentTarget {
     TooLow { env_var: &'static str, version: String, os_min: String },
 }
 
-#[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/src/doc/rustc/src/platform-support/apple-darwin.md b/src/doc/rustc/src/platform-support/apple-darwin.md
index 17ea225805b5c..31ae642fc93fd 100644
--- a/src/doc/rustc/src/platform-support/apple-darwin.md
+++ b/src/doc/rustc/src/platform-support/apple-darwin.md
@@ -58,4 +58,5 @@ the `-mmacosx-version-min=...`, `-miphoneos-version-min=...` or similar flags
 to disambiguate.
 
 The path to the SDK can be passed to `rustc` using the common `SDKROOT`
-environment variable.
+environment variable, or will be inferred when compiling on host macOS using
+roughly the same logic as `xcrun -sdk macosx -show-sdk-path`.
diff --git a/src/doc/rustc/src/platform-support/apple-ios-macabi.md b/src/doc/rustc/src/platform-support/apple-ios-macabi.md
index a54656190d1f3..2d54e15f16314 100644
--- a/src/doc/rustc/src/platform-support/apple-ios-macabi.md
+++ b/src/doc/rustc/src/platform-support/apple-ios-macabi.md
@@ -20,7 +20,8 @@ These targets are cross-compiled, and require the corresponding macOS SDK
 iOS-specific headers, as provided by Xcode 11 or higher.
 
 The path to the SDK can be passed to `rustc` using the common `SDKROOT`
-environment variable.
+environment variable, or will be inferred when compiling on host macOS using
+roughly the same logic as `xcrun -sdk macosx -show-sdk-path`.
 
 ### OS version
 
diff --git a/src/doc/rustc/src/platform-support/apple-ios.md b/src/doc/rustc/src/platform-support/apple-ios.md
index 5045f810400f5..c1c34118ad986 100644
--- a/src/doc/rustc/src/platform-support/apple-ios.md
+++ b/src/doc/rustc/src/platform-support/apple-ios.md
@@ -26,7 +26,8 @@ These targets are cross-compiled, and require the corresponding iOS SDK
 ARM64 targets, Xcode 12 or higher is required.
 
 The path to the SDK can be passed to `rustc` using the common `SDKROOT`
-environment variable.
+environment variable, or will be inferred when compiling on host macOS using
+roughly the same logic as `xcrun -sdk iphoneos -show-sdk-path`.
 
 ### OS version
 
diff --git a/src/doc/rustc/src/platform-support/apple-tvos.md b/src/doc/rustc/src/platform-support/apple-tvos.md
index 7a3b601579a01..761f7a7ffb903 100644
--- a/src/doc/rustc/src/platform-support/apple-tvos.md
+++ b/src/doc/rustc/src/platform-support/apple-tvos.md
@@ -20,7 +20,8 @@ These targets are cross-compiled, and require the corresponding tvOS SDK
 ARM64 targets, Xcode 12 or higher is required.
 
 The path to the SDK can be passed to `rustc` using the common `SDKROOT`
-environment variable.
+environment variable, or will be inferred when compiling on host macOS using
+roughly the same logic as `xcrun -sdk appletvos -show-sdk-path`.
 
 ### OS version
 
diff --git a/src/doc/rustc/src/platform-support/apple-visionos.md b/src/doc/rustc/src/platform-support/apple-visionos.md
index 56224d7e20d4e..df9a173b8b414 100644
--- a/src/doc/rustc/src/platform-support/apple-visionos.md
+++ b/src/doc/rustc/src/platform-support/apple-visionos.md
@@ -18,7 +18,8 @@ These targets are cross-compiled, and require the corresponding visionOS SDK
 (`XROS.sdk` or `XRSimulator.sdk`), as provided by Xcode 15 or newer.
 
 The path to the SDK can be passed to `rustc` using the common `SDKROOT`
-environment variable.
+environment variable, or will be inferred when compiling on host macOS using
+roughly the same logic as `xcrun -sdk xros -show-sdk-path`.
 
 ### OS version
 
diff --git a/src/doc/rustc/src/platform-support/apple-watchos.md b/src/doc/rustc/src/platform-support/apple-watchos.md
index 8ba35f70b8578..f6f9bc5cac337 100644
--- a/src/doc/rustc/src/platform-support/apple-watchos.md
+++ b/src/doc/rustc/src/platform-support/apple-watchos.md
@@ -24,7 +24,8 @@ These targets are cross-compiled, and require the corresponding watchOS SDK
 ARM64 targets, Xcode 12 or higher is required.
 
 The path to the SDK can be passed to `rustc` using the common `SDKROOT`
-environment variable.
+environment variable, or will be inferred when compiling on host macOS using
+roughly the same logic as `xcrun -sdk watchos -show-sdk-path`.
 
 ### OS version