|
| 1 | +use std::fs; |
| 2 | +use std::io::ErrorKind; |
| 3 | +use std::path::{Path, PathBuf}; |
| 4 | + |
| 5 | +use crate::errors::AppleSdkError; |
| 6 | + |
| 7 | +#[cfg(test)] |
| 8 | +mod tests; |
| 9 | + |
| 10 | +// TOCTOU is not _really_ an issue with our use of `try_exists` in here, we mostly use it for |
| 11 | +// diagnostics, and these directories are global state that the user may change anytime anyhow. |
| 12 | +fn try_exists(path: &Path) -> Result<bool, AppleSdkError> { |
| 13 | + path.try_exists().map_err(|error| AppleSdkError::FailedReading { path: path.to_owned(), error }) |
| 14 | +} |
| 15 | + |
| 16 | +/// Get the SDK path for an SDK under `/Library/Developer/CommandLineTools`. |
| 17 | +fn sdk_root_in_sdks_dir(sdks_dir: impl Into<PathBuf>, sdk_name: &str) -> PathBuf { |
| 18 | + let mut path = sdks_dir.into(); |
| 19 | + path.push("SDKs"); |
| 20 | + path.push(sdk_name); |
| 21 | + path.set_extension("sdk"); |
| 22 | + path |
| 23 | +} |
| 24 | + |
| 25 | +/// Get the SDK path for an SDK under `/Applications/Xcode.app/Contents/Developer`. |
| 26 | +fn sdk_root_in_developer_dir(developer_dir: impl Into<PathBuf>, sdk_name: &str) -> PathBuf { |
| 27 | + let mut path = developer_dir.into(); |
| 28 | + path.push("Platforms"); |
| 29 | + path.push(sdk_name); |
| 30 | + path.set_extension("platform"); |
| 31 | + path.push("Developer"); |
| 32 | + path.push("SDKs"); |
| 33 | + path.push(sdk_name); |
| 34 | + path.set_extension("sdk"); |
| 35 | + path |
| 36 | +} |
| 37 | + |
| 38 | +/// Find a SDK root from the user's environment for the given SDK name. |
| 39 | +/// |
| 40 | +/// We do this by searching (purely by names in the filesystem, without reading SDKSettings.json) |
| 41 | +/// for a matching SDK in the following places: |
| 42 | +/// - `DEVELOPER_DIR` |
| 43 | +/// - `/var/db/xcode_select_link` |
| 44 | +/// - `/Applications/Xcode.app` |
| 45 | +/// - `/Library/Developer/CommandLineTools` |
| 46 | +/// |
| 47 | +/// This does roughly the same thing as `xcrun -sdk $sdk_name -show-sdk-path` (see `man xcrun` for |
| 48 | +/// a few details on the search algorithm). |
| 49 | +/// |
| 50 | +/// The reason why we implement this logic ourselves is: |
| 51 | +/// - Reading these directly is faster than spawning a new process. |
| 52 | +/// - `xcrun` can be fairly slow to start up after a reboot. |
| 53 | +/// - In the future, we will be able to integrate this better with the compiler's change tracking |
| 54 | +/// mechanisms, allowing rebuilds when the involved env vars and paths here change. See #118204. |
| 55 | +/// - It's easier for us to emit better error messages. |
| 56 | +/// |
| 57 | +/// Though a downside is that `xcrun` might be expanded in the future to check more places, and then |
| 58 | +/// `rustc` would have to be changed to keep up. Furthermore, `xcrun`'s exact algorithm is |
| 59 | +/// undocumented, so it might be doing more things than we do here. |
| 60 | +pub(crate) fn find_sdk_root(sdk_name: &'static str) -> Result<PathBuf, AppleSdkError> { |
| 61 | + // Only try this if host OS is macOS. |
| 62 | + if !cfg!(target_os = "macos") { |
| 63 | + return Err(AppleSdkError::MissingCrossCompileNonMacOS { sdk_name }); |
| 64 | + } |
| 65 | + |
| 66 | + // NOTE: We could consider walking upwards in `SDKROOT` assuming Xcode directory structure, but |
| 67 | + // that isn't what `xcrun` does, and might still not yield the desired result (e.g. if using an |
| 68 | + // old SDK to compile for an old ARM iOS arch, we don't want `rustc` to pick a macOS SDK from |
| 69 | + // the old Xcode). |
| 70 | + |
| 71 | + // Try reading from `DEVELOPER_DIR` on all hosts. |
| 72 | + if let Some(dir) = std::env::var_os("DEVELOPER_DIR") { |
| 73 | + let dir = PathBuf::from(dir); |
| 74 | + let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name); |
| 75 | + |
| 76 | + if try_exists(&sdkroot)? { |
| 77 | + return Ok(sdkroot); |
| 78 | + } else { |
| 79 | + let sdkroot_bare = sdk_root_in_sdks_dir(&dir, sdk_name); |
| 80 | + if try_exists(&sdkroot_bare)? { |
| 81 | + return Ok(sdkroot_bare); |
| 82 | + } else { |
| 83 | + return Err(AppleSdkError::MissingDeveloperDir { dir, sdkroot, sdkroot_bare }); |
| 84 | + } |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + // Next, try to read the link that `xcode-select` sets. |
| 89 | + // |
| 90 | + // FIXME(madsmtm): Support cases where `/var/db/xcode_select_link` contains a relative path? |
| 91 | + let path = PathBuf::from("/var/db/xcode_select_link"); |
| 92 | + match fs::read_link(&path) { |
| 93 | + Ok(dir) => { |
| 94 | + let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name); |
| 95 | + if try_exists(&sdkroot)? { |
| 96 | + return Ok(sdkroot); |
| 97 | + } else { |
| 98 | + let sdkroot_bare = sdk_root_in_sdks_dir(&dir, sdk_name); |
| 99 | + if try_exists(&sdkroot_bare)? { |
| 100 | + return Ok(sdkroot_bare); |
| 101 | + } else { |
| 102 | + return Err(AppleSdkError::MissingXcodeSelect { dir, sdkroot, sdkroot_bare }); |
| 103 | + } |
| 104 | + } |
| 105 | + } |
| 106 | + Err(err) if err.kind() == ErrorKind::NotFound => { |
| 107 | + // Intentionally ignore not found errors, if `xcode-select --reset` is called the |
| 108 | + // link will not exist. |
| 109 | + } |
| 110 | + Err(error) => return Err(AppleSdkError::FailedReading { path, error }), |
| 111 | + } |
| 112 | + |
| 113 | + // Next, fall back to reading from `/Applications/Xcode.app`. |
| 114 | + let dir = PathBuf::from("/Applications/Xcode.app/Contents/Developer"); |
| 115 | + if try_exists(&dir)? { |
| 116 | + let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name); |
| 117 | + if try_exists(&sdkroot)? { |
| 118 | + return Ok(sdkroot); |
| 119 | + } else { |
| 120 | + return Err(AppleSdkError::MissingXcode { sdkroot }); |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + // Finally, fall back to reading from `/Library/Developer/CommandLineTools`. |
| 125 | + let dir = PathBuf::from("/Library/Developer/CommandLineTools"); |
| 126 | + if try_exists(&dir)? { |
| 127 | + let sdkroot = sdk_root_in_sdks_dir(&dir, sdk_name); |
| 128 | + if try_exists(&sdkroot)? { |
| 129 | + return Ok(sdkroot); |
| 130 | + } else { |
| 131 | + return Err(AppleSdkError::MissingCommandlineTools { sdkroot }); |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + Err(AppleSdkError::Missing { sdk_name }) |
| 136 | +} |
0 commit comments