Skip to content

apple-sdk: support reading path set by xcode-select --switch #154

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apple-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
58 changes: 58 additions & 0 deletions apple-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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}"))
}
Expand Down Expand Up @@ -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<Option<Self>, 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
Expand Down
44 changes: 27 additions & 17 deletions apple-sdk/src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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()?,
)),
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -383,6 +400,7 @@ impl Default for SdkSearch {
locations: vec![
SdkSearchLocation::SdkRootEnv,
SdkSearchLocation::DeveloperDirEnv,
SdkSearchLocation::XcodeSelectPaths,
SdkSearchLocation::SystemXcode,
SdkSearchLocation::CommandLineTools,
],
Expand Down Expand Up @@ -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}"),
));
}

Expand All @@ -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}"),
));
}

Expand All @@ -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}"),
));
}

Expand All @@ -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}"),
));
}

Expand Down
Loading