|
1 |
| -use std::env; |
2 | 1 | use std::fmt::{Display, from_fn};
|
| 2 | +use std::io::ErrorKind; |
3 | 3 | use std::num::ParseIntError;
|
| 4 | +use std::path::{Path, PathBuf}; |
| 5 | +use std::{env, fs}; |
4 | 6 |
|
5 | 7 | use rustc_session::Session;
|
6 | 8 | use rustc_target::spec::Target;
|
7 | 9 |
|
8 |
| -use crate::errors::AppleDeploymentTarget; |
| 10 | +use crate::errors::{AppleDeploymentTarget, AppleSdkError}; |
9 | 11 |
|
10 | 12 | #[cfg(test)]
|
11 | 13 | mod tests;
|
@@ -186,3 +188,132 @@ pub(super) fn add_version_to_llvm_target(
|
186 | 188 | format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}")
|
187 | 189 | }
|
188 | 190 | }
|
| 191 | + |
| 192 | +// TOCTOU is not _really_ an issue with our use of `try_exists` in here, we mostly use it for |
| 193 | +// diagnostics, and these directories are global state that the user can change anytime anyhow in |
| 194 | +// ways that are going to interfere much more with the compilation process. |
| 195 | +fn try_exists(path: &Path) -> Result<bool, AppleSdkError> { |
| 196 | + path.try_exists().map_err(|error| AppleSdkError::FailedReading { path: path.to_owned(), error }) |
| 197 | +} |
| 198 | + |
| 199 | +/// Get the SDK path for an SDK under `/Library/Developer/CommandLineTools`. |
| 200 | +fn sdk_root_in_sdks_dir(sdks_dir: impl Into<PathBuf>, sdk_name: &str) -> PathBuf { |
| 201 | + let mut path = sdks_dir.into(); |
| 202 | + path.push("SDKs"); |
| 203 | + path.push(sdk_name); |
| 204 | + path.set_extension("sdk"); |
| 205 | + path |
| 206 | +} |
| 207 | + |
| 208 | +/// Get the SDK path for an SDK under `/Applications/Xcode.app/Contents/Developer`. |
| 209 | +fn sdk_root_in_developer_dir(developer_dir: impl Into<PathBuf>, sdk_name: &str) -> PathBuf { |
| 210 | + let mut path = developer_dir.into(); |
| 211 | + path.push("Platforms"); |
| 212 | + path.push(sdk_name); |
| 213 | + path.set_extension("platform"); |
| 214 | + path.push("Developer"); |
| 215 | + path.push("SDKs"); |
| 216 | + path.push(sdk_name); |
| 217 | + path.set_extension("sdk"); |
| 218 | + path |
| 219 | +} |
| 220 | + |
| 221 | +/// Find a SDK root from the user's environment for the given SDK name. |
| 222 | +/// |
| 223 | +/// We do this by searching (purely by names in the filesystem, without reading SDKSettings.json) |
| 224 | +/// for a matching SDK in the following places: |
| 225 | +/// - `DEVELOPER_DIR` |
| 226 | +/// - `/var/db/xcode_select_link` |
| 227 | +/// - `/Applications/Xcode.app` |
| 228 | +/// - `/Library/Developer/CommandLineTools` |
| 229 | +/// |
| 230 | +/// This does roughly the same thing as `xcrun -sdk $sdk_name -show-sdk-path` (see `man xcrun` for |
| 231 | +/// a few details on the search algorithm). |
| 232 | +/// |
| 233 | +/// The reason why we implement this logic ourselves is: |
| 234 | +/// - Reading these directly is faster than spawning a new process. |
| 235 | +/// - `xcrun` can be fairly slow to start up after a reboot. |
| 236 | +/// - In the future, we will be able to integrate this better with the compiler's change tracking |
| 237 | +/// mechanisms, allowing rebuilds when the involved env vars and paths here change. See #118204. |
| 238 | +/// - It's easier for us to emit better error messages. |
| 239 | +/// |
| 240 | +/// Though a downside is that `xcrun` might be expanded in the future to check more places, and then |
| 241 | +/// `rustc` would have to be changed to keep up. Furthermore, `xcrun`'s exact algorithm is |
| 242 | +/// undocumented, so it might be doing more things than we do here. |
| 243 | +pub(crate) fn find_sdk_root(sdk_name: &'static str) -> Result<PathBuf, AppleSdkError> { |
| 244 | + // Only try this if the host OS is macOS. |
| 245 | + if !cfg!(target_os = "macos") { |
| 246 | + return Err(AppleSdkError::MissingCrossCompileNonMacOS { sdk_name }); |
| 247 | + } |
| 248 | + |
| 249 | + // NOTE: We could consider walking upwards in `SDKROOT` assuming Xcode directory structure, but |
| 250 | + // that isn't what `xcrun` does, and might still not yield the desired result (e.g. if using an |
| 251 | + // old SDK to compile for an old ARM iOS arch, we don't want `rustc` to pick a macOS SDK from |
| 252 | + // the old Xcode). |
| 253 | + |
| 254 | + // Try reading from `DEVELOPER_DIR`. |
| 255 | + if let Some(dir) = std::env::var_os("DEVELOPER_DIR") { |
| 256 | + let dir = PathBuf::from(dir); |
| 257 | + let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name); |
| 258 | + |
| 259 | + if try_exists(&sdkroot)? { |
| 260 | + return Ok(sdkroot); |
| 261 | + } else { |
| 262 | + let sdkroot_bare = sdk_root_in_sdks_dir(&dir, sdk_name); |
| 263 | + if try_exists(&sdkroot_bare)? { |
| 264 | + return Ok(sdkroot_bare); |
| 265 | + } else { |
| 266 | + return Err(AppleSdkError::MissingDeveloperDir { dir, sdkroot, sdkroot_bare }); |
| 267 | + } |
| 268 | + } |
| 269 | + } |
| 270 | + |
| 271 | + // Next, try to read the link that `xcode-select` sets. |
| 272 | + // |
| 273 | + // FIXME(madsmtm): Support cases where `/var/db/xcode_select_link` contains a relative path? |
| 274 | + let path = Path::new("/var/db/xcode_select_link"); |
| 275 | + match fs::read_link(path) { |
| 276 | + Ok(dir) => { |
| 277 | + let sdkroot = sdk_root_in_developer_dir(&dir, sdk_name); |
| 278 | + if try_exists(&sdkroot)? { |
| 279 | + return Ok(sdkroot); |
| 280 | + } else { |
| 281 | + let sdkroot_bare = sdk_root_in_sdks_dir(&dir, sdk_name); |
| 282 | + if try_exists(&sdkroot_bare)? { |
| 283 | + return Ok(sdkroot_bare); |
| 284 | + } else { |
| 285 | + return Err(AppleSdkError::MissingXcodeSelect { dir, sdkroot, sdkroot_bare }); |
| 286 | + } |
| 287 | + } |
| 288 | + } |
| 289 | + Err(err) if err.kind() == ErrorKind::NotFound => { |
| 290 | + // Intentionally ignore not found errors, if `xcode-select --reset` is called the |
| 291 | + // link will not exist. |
| 292 | + } |
| 293 | + Err(error) => return Err(AppleSdkError::FailedReading { path: path.into(), error }), |
| 294 | + } |
| 295 | + |
| 296 | + // Next, fall back to reading from `/Applications/Xcode.app`. |
| 297 | + let dir = Path::new("/Applications/Xcode.app/Contents/Developer"); |
| 298 | + if try_exists(dir)? { |
| 299 | + let sdkroot = sdk_root_in_developer_dir(dir, sdk_name); |
| 300 | + if try_exists(&sdkroot)? { |
| 301 | + return Ok(sdkroot); |
| 302 | + } else { |
| 303 | + return Err(AppleSdkError::MissingXcode { sdkroot, sdk_name }); |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + // Finally, fall back to reading from `/Library/Developer/CommandLineTools`. |
| 308 | + let dir = Path::new("/Library/Developer/CommandLineTools"); |
| 309 | + if try_exists(dir)? { |
| 310 | + let sdkroot = sdk_root_in_sdks_dir(dir, sdk_name); |
| 311 | + if try_exists(&sdkroot)? { |
| 312 | + return Ok(sdkroot); |
| 313 | + } else { |
| 314 | + return Err(AppleSdkError::MissingCommandlineTools { sdkroot, sdk_name }); |
| 315 | + } |
| 316 | + } |
| 317 | + |
| 318 | + Err(AppleSdkError::Missing { sdk_name }) |
| 319 | +} |
0 commit comments