diff --git a/apple-sdk/CHANGELOG.md b/apple-sdk/CHANGELOG.md index 1a0fe45f..2c8aebe1 100644 --- a/apple-sdk/CHANGELOG.md +++ b/apple-sdk/CHANGELOG.md @@ -9,6 +9,9 @@ Released on ReleaseDate. * XROS support. `Platform` enumeration added `XrOs` and `XrOsSimulator` variants. The `aarch64-apple-xros-sim` and `*-apple-xros` triples are now recognized as XROS. +* The developer directory configured with `xcode-select --switch PATH` can now + be retrieved by using `DeveloperDirectory::from_xcode_select_paths`, and + this is done by default when searching for SDKs. ## 0.5.2 diff --git a/apple-sdk/src/lib.rs b/apple-sdk/src/lib.rs index 9a909870..17034e9a 100644 --- a/apple-sdk/src/lib.rs +++ b/apple-sdk/src/lib.rs @@ -119,6 +119,8 @@ pub const XCODE_APP_RELATIVE_PATH_DEVELOPER: &str = "Contents/Developer"; /// Error type for this crate. #[derive(Debug)] pub enum Error { + /// Error occurred when trying to read `xcode-select` paths. + XcodeSelectPathFailedReading(std::io::Error), /// Error occurred when running `xcode-select`. XcodeSelectRun(std::io::Error), /// `xcode-select` did not run successfully. @@ -162,6 +164,9 @@ pub enum Error { impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { + Self::XcodeSelectPathFailedReading(err) => { + f.write_fmt(format_args!("Error reading xcode-select paths: {err}")) + } Self::XcodeSelectRun(err) => { f.write_fmt(format_args!("Error running xcode-select: {err}")) } @@ -511,6 +516,59 @@ impl DeveloperDirectory { } } + /// Attempt to resolve an instance by checking the paths that + /// `xcode-select --switch` configures. If there is no path configured, + /// this returns `None`. + /// + /// This checks, in order: + /// - The path pointed to by `/var/db/xcode_select_link`. + /// - The path pointed to by `/usr/share/xcode-select/xcode_dir_link` + /// (legacy, previously created by `xcode-select`). + /// - The path stored in `/usr/share/xcode-select/xcode_dir_path` + /// (legacy, previously created by `xcode-select`). + /// + /// There are no sources available for `xcode-select`, so we do not know + /// if these are the only paths that `xcode-select` uses. We can be fairly + /// sure, though, since the logic has been reverse-engineered + /// [several][darling-xcselect] [times][bouldev-xcselect]. + /// + /// The exact list of paths that `apple-sdk` searches here is an + /// implementation detail, and may change in the future (e.g. if + /// `xcode-select` is changed to use a different set of paths). + /// + /// [darling-xcselect]: https://github.com/darlinghq/darling/blob/773e9874cf38fdeb9518f803e041924e255d0ebe/src/xcselect/xcselect.c#L138-L197 + /// [bouldev-xcselect]: https://github.com/bouldev/libxcselect-shim/blob/c5387de92c30ab16cbfc8012e98c74c718ce8eff/src/libxcselect/xcselect_get_developer_dir_path.c#L39-L86 + pub fn from_xcode_select_paths() -> Result, Error> { + match std::fs::read_link("/var/db/xcode_select_link") { + Ok(path) => return Ok(Some(Self { path })), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + // Ignore if the path does not exist + } + Err(err) => return Err(Error::XcodeSelectPathFailedReading(err)), + } + + match std::fs::read_link("/usr/share/xcode-select/xcode_dir_link") { + Ok(path) => return Ok(Some(Self { path })), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + // Ignore if the path does not exist + } + Err(err) => return Err(Error::XcodeSelectPathFailedReading(err)), + } + + match std::fs::read_to_string("/usr/share/xcode-select/xcode_dir_path") { + Ok(s) => { + let path = PathBuf::from(s.trim_end_matches('\n')); + return Ok(Some(Self { path })); + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + // Ignore if the path does not exist + } + Err(err) => return Err(Error::XcodeSelectPathFailedReading(err)), + } + + Ok(None) + } + /// Attempt to resolve an instance by running `xcode-select`. /// /// The output from `xcode-select` is implicitly trusted and no validation diff --git a/apple-sdk/src/search.rs b/apple-sdk/src/search.rs index b29b1077..a8261a6c 100644 --- a/apple-sdk/src/search.rs +++ b/apple-sdk/src/search.rs @@ -84,6 +84,12 @@ pub enum SdkSearchLocation { /// if available. CommandLineTools, + /// Check the paths configured by `xcode-select --switch`. + /// + /// This effectively controls whether the Developer Directory resolved by + /// [DeveloperDirectory::from_xcode_select_paths()] will be searched, if available. + XcodeSelectPaths, + /// Invoke `xcode-select` to find a *Developer Directory* to search. /// /// This mechanism is intended as a fallback in case other (pure Rust) mechanisms for locating @@ -129,6 +135,9 @@ impl Display for SdkSearchLocation { Self::DeveloperDirEnv => f.write_str("DEVELOPER_DIR environment variable"), Self::SystemXcode => f.write_str("System-installed Xcode application"), Self::CommandLineTools => f.write_str("Xcode Command Line Tools installation"), + Self::XcodeSelectPaths => { + f.write_str("Internal xcode-select paths (`/var/db/xcode_select_link`)") + } Self::XcodeSelect => f.write_str("xcode-select"), Self::SystemXcodes => f.write_str("All system-installed Xcode applications"), Self::Developer(dir) => { @@ -186,6 +195,15 @@ impl SdkSearchLocation { Ok(SdkSearchResolvedLocation::None) } } + Self::XcodeSelectPaths => { + if let Some(dir) = DeveloperDirectory::from_xcode_select_paths()? { + Ok(SdkSearchResolvedLocation::PlatformDirectories( + dir.platforms()?, + )) + } else { + Ok(SdkSearchResolvedLocation::None) + } + } Self::XcodeSelect => Ok(SdkSearchResolvedLocation::PlatformDirectories( DeveloperDirectory::from_xcode_select()?.platforms()?, )), @@ -275,9 +293,7 @@ pub enum SdkSearchEvent { impl Display for SdkSearchEvent { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - Self::SearchingLocation(location) => { - f.write_fmt(format_args!("searching {location}")) - } + Self::SearchingLocation(location) => f.write_fmt(format_args!("searching {location}")), Self::PlatformDirectoryInclude(path) => f.write_fmt(format_args!( "searching Platform directory {}", path.display() @@ -322,8 +338,9 @@ pub type SdkProgressCallback = fn(SdkSearchEvent); /// 1. Use path specified by `SDKROOT` environment variable, if defined. /// 2. Find SDKs within the Developer Directory defined by the `DEVELOPER_DIR` environment /// variable. -/// 3. Find SDKs within the system installed `Xcode` application. -/// 4. Find SDKs within the system installed Xcode Command Line Tools. +/// 3. Find SDKs within the path configured with `xcode-select --switch`. +/// 4. Find SDKs within the system installed Xcode application. +/// 5. Find SDKs within the system installed Xcode Command Line Tools. /// /// Simply call [Self::location()] to register a new location. If the default locations /// are not desirable, construct an empty instance via [Self::empty()] and register your @@ -383,6 +400,7 @@ impl Default for SdkSearch { locations: vec![ SdkSearchLocation::SdkRootEnv, SdkSearchLocation::DeveloperDirEnv, + SdkSearchLocation::XcodeSelectPaths, SdkSearchLocation::SystemXcode, SdkSearchLocation::CommandLineTools, ], @@ -617,9 +635,7 @@ impl SdkSearch { if let Some(cb) = &self.progress_callback { cb(SdkSearchEvent::SdkFilterExclude( sdk_path, - format!( - "SDK version {sdk_version} < minimum version {min_version}" - ), + format!("SDK version {sdk_version} < minimum version {min_version}"), )); } @@ -630,9 +646,7 @@ impl SdkSearch { if let Some(cb) = &self.progress_callback { cb(SdkSearchEvent::SdkFilterExclude( sdk_path, - format!( - "Unknown SDK version fails to meet minimum version {min_version}" - ), + format!("Unknown SDK version fails to meet minimum version {min_version}"), )); } @@ -646,9 +660,7 @@ impl SdkSearch { if let Some(cb) = &self.progress_callback { cb(SdkSearchEvent::SdkFilterExclude( sdk_path, - format!( - "SDK version {sdk_version} > maximum version {max_version}" - ), + format!("SDK version {sdk_version} > maximum version {max_version}"), )); } @@ -660,9 +672,7 @@ impl SdkSearch { if let Some(cb) = &self.progress_callback { cb(SdkSearchEvent::SdkFilterExclude( sdk_path, - format!( - "Unknown SDK version fails to meet maximum version {max_version}" - ), + format!("Unknown SDK version fails to meet maximum version {max_version}"), )); }