Skip to content

Commit 5fb3af6

Browse files
madsmtmindygreg
authored andcommitted
apple-sdk: support reading path set by xcode-select --switch
`xcode-select --switch PATH` creates a symlink at `/var/db/xcode_select_link`, which we can read to get the path of the current globally configured developer directory. Closes #154.
1 parent 9ba97e6 commit 5fb3af6

File tree

3 files changed

+88
-17
lines changed

3 files changed

+88
-17
lines changed

apple-sdk/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ Released on ReleaseDate.
99
* XROS support. `Platform` enumeration added `XrOs` and `XrOsSimulator`
1010
variants. The `aarch64-apple-xros-sim` and `*-apple-xros` triples are
1111
now recognized as XROS.
12+
* The developer directory configured with `xcode-select --switch PATH` can now
13+
be retrieved by using `DeveloperDirectory::from_xcode_select_paths`, and
14+
this is done by default when searching for SDKs. (#154)
1215
* `plist` 1.6 -> 1.7.
1316

1417
## 0.5.2

apple-sdk/src/lib.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ pub const XCODE_APP_RELATIVE_PATH_DEVELOPER: &str = "Contents/Developer";
119119
/// Error type for this crate.
120120
#[derive(Debug)]
121121
pub enum Error {
122+
/// Error occurred when trying to read `xcode-select` paths.
123+
XcodeSelectPathFailedReading(std::io::Error),
122124
/// Error occurred when running `xcode-select`.
123125
XcodeSelectRun(std::io::Error),
124126
/// `xcode-select` did not run successfully.
@@ -162,6 +164,9 @@ pub enum Error {
162164
impl Display for Error {
163165
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
164166
match self {
167+
Self::XcodeSelectPathFailedReading(err) => {
168+
f.write_fmt(format_args!("Error reading xcode-select paths: {err}"))
169+
}
165170
Self::XcodeSelectRun(err) => {
166171
f.write_fmt(format_args!("Error running xcode-select: {err}"))
167172
}
@@ -511,6 +516,59 @@ impl DeveloperDirectory {
511516
}
512517
}
513518

519+
/// Attempt to resolve an instance by checking the paths that
520+
/// `xcode-select --switch` configures. If there is no path configured,
521+
/// this returns `None`.
522+
///
523+
/// This checks, in order:
524+
/// - The path pointed to by `/var/db/xcode_select_link`.
525+
/// - The path pointed to by `/usr/share/xcode-select/xcode_dir_link`
526+
/// (legacy, previously created by `xcode-select`).
527+
/// - The path stored in `/usr/share/xcode-select/xcode_dir_path`
528+
/// (legacy, previously created by `xcode-select`).
529+
///
530+
/// There are no sources available for `xcode-select`, so we do not know
531+
/// if these are the only paths that `xcode-select` uses. We can be fairly
532+
/// sure, though, since the logic has been reverse-engineered
533+
/// [several][darling-xcselect] [times][bouldev-xcselect].
534+
///
535+
/// The exact list of paths that `apple-sdk` searches here is an
536+
/// implementation detail, and may change in the future (e.g. if
537+
/// `xcode-select` is changed to use a different set of paths).
538+
///
539+
/// [darling-xcselect]: https://github.com/darlinghq/darling/blob/773e9874cf38fdeb9518f803e041924e255d0ebe/src/xcselect/xcselect.c#L138-L197
540+
/// [bouldev-xcselect]: https://github.com/bouldev/libxcselect-shim/blob/c5387de92c30ab16cbfc8012e98c74c718ce8eff/src/libxcselect/xcselect_get_developer_dir_path.c#L39-L86
541+
pub fn from_xcode_select_paths() -> Result<Option<Self>, Error> {
542+
match std::fs::read_link("/var/db/xcode_select_link") {
543+
Ok(path) => return Ok(Some(Self { path })),
544+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
545+
// Ignore if the path does not exist
546+
}
547+
Err(err) => return Err(Error::XcodeSelectPathFailedReading(err)),
548+
}
549+
550+
match std::fs::read_link("/usr/share/xcode-select/xcode_dir_link") {
551+
Ok(path) => return Ok(Some(Self { path })),
552+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
553+
// Ignore if the path does not exist
554+
}
555+
Err(err) => return Err(Error::XcodeSelectPathFailedReading(err)),
556+
}
557+
558+
match std::fs::read_to_string("/usr/share/xcode-select/xcode_dir_path") {
559+
Ok(s) => {
560+
let path = PathBuf::from(s.trim_end_matches('\n'));
561+
return Ok(Some(Self { path }));
562+
}
563+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
564+
// Ignore if the path does not exist
565+
}
566+
Err(err) => return Err(Error::XcodeSelectPathFailedReading(err)),
567+
}
568+
569+
Ok(None)
570+
}
571+
514572
/// Attempt to resolve an instance by running `xcode-select`.
515573
///
516574
/// The output from `xcode-select` is implicitly trusted and no validation

apple-sdk/src/search.rs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ pub enum SdkSearchLocation {
8484
/// if available.
8585
CommandLineTools,
8686

87+
/// Check the paths configured by `xcode-select --switch`.
88+
///
89+
/// This effectively controls whether the Developer Directory resolved by
90+
/// [DeveloperDirectory::from_xcode_select_paths()] will be searched, if available.
91+
XcodeSelectPaths,
92+
8793
/// Invoke `xcode-select` to find a *Developer Directory* to search.
8894
///
8995
/// This mechanism is intended as a fallback in case other (pure Rust) mechanisms for locating
@@ -129,6 +135,9 @@ impl Display for SdkSearchLocation {
129135
Self::DeveloperDirEnv => f.write_str("DEVELOPER_DIR environment variable"),
130136
Self::SystemXcode => f.write_str("System-installed Xcode application"),
131137
Self::CommandLineTools => f.write_str("Xcode Command Line Tools installation"),
138+
Self::XcodeSelectPaths => {
139+
f.write_str("Internal xcode-select paths (`/var/db/xcode_select_link`)")
140+
}
132141
Self::XcodeSelect => f.write_str("xcode-select"),
133142
Self::SystemXcodes => f.write_str("All system-installed Xcode applications"),
134143
Self::Developer(dir) => {
@@ -186,6 +195,15 @@ impl SdkSearchLocation {
186195
Ok(SdkSearchResolvedLocation::None)
187196
}
188197
}
198+
Self::XcodeSelectPaths => {
199+
if let Some(dir) = DeveloperDirectory::from_xcode_select_paths()? {
200+
Ok(SdkSearchResolvedLocation::PlatformDirectories(
201+
dir.platforms()?,
202+
))
203+
} else {
204+
Ok(SdkSearchResolvedLocation::None)
205+
}
206+
}
189207
Self::XcodeSelect => Ok(SdkSearchResolvedLocation::PlatformDirectories(
190208
DeveloperDirectory::from_xcode_select()?.platforms()?,
191209
)),
@@ -275,9 +293,7 @@ pub enum SdkSearchEvent {
275293
impl Display for SdkSearchEvent {
276294
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
277295
match self {
278-
Self::SearchingLocation(location) => {
279-
f.write_fmt(format_args!("searching {location}"))
280-
}
296+
Self::SearchingLocation(location) => f.write_fmt(format_args!("searching {location}")),
281297
Self::PlatformDirectoryInclude(path) => f.write_fmt(format_args!(
282298
"searching Platform directory {}",
283299
path.display()
@@ -322,8 +338,9 @@ pub type SdkProgressCallback = fn(SdkSearchEvent);
322338
/// 1. Use path specified by `SDKROOT` environment variable, if defined.
323339
/// 2. Find SDKs within the Developer Directory defined by the `DEVELOPER_DIR` environment
324340
/// variable.
325-
/// 3. Find SDKs within the system installed `Xcode` application.
326-
/// 4. Find SDKs within the system installed Xcode Command Line Tools.
341+
/// 3. Find SDKs within the path configured with `xcode-select --switch`.
342+
/// 4. Find SDKs within the system installed Xcode application.
343+
/// 5. Find SDKs within the system installed Xcode Command Line Tools.
327344
///
328345
/// Simply call [Self::location()] to register a new location. If the default locations
329346
/// are not desirable, construct an empty instance via [Self::empty()] and register your
@@ -383,6 +400,7 @@ impl Default for SdkSearch {
383400
locations: vec![
384401
SdkSearchLocation::SdkRootEnv,
385402
SdkSearchLocation::DeveloperDirEnv,
403+
SdkSearchLocation::XcodeSelectPaths,
386404
SdkSearchLocation::SystemXcode,
387405
SdkSearchLocation::CommandLineTools,
388406
],
@@ -617,9 +635,7 @@ impl SdkSearch {
617635
if let Some(cb) = &self.progress_callback {
618636
cb(SdkSearchEvent::SdkFilterExclude(
619637
sdk_path,
620-
format!(
621-
"SDK version {sdk_version} < minimum version {min_version}"
622-
),
638+
format!("SDK version {sdk_version} < minimum version {min_version}"),
623639
));
624640
}
625641

@@ -630,9 +646,7 @@ impl SdkSearch {
630646
if let Some(cb) = &self.progress_callback {
631647
cb(SdkSearchEvent::SdkFilterExclude(
632648
sdk_path,
633-
format!(
634-
"Unknown SDK version fails to meet minimum version {min_version}"
635-
),
649+
format!("Unknown SDK version fails to meet minimum version {min_version}"),
636650
));
637651
}
638652

@@ -646,9 +660,7 @@ impl SdkSearch {
646660
if let Some(cb) = &self.progress_callback {
647661
cb(SdkSearchEvent::SdkFilterExclude(
648662
sdk_path,
649-
format!(
650-
"SDK version {sdk_version} > maximum version {max_version}"
651-
),
663+
format!("SDK version {sdk_version} > maximum version {max_version}"),
652664
));
653665
}
654666

@@ -660,9 +672,7 @@ impl SdkSearch {
660672
if let Some(cb) = &self.progress_callback {
661673
cb(SdkSearchEvent::SdkFilterExclude(
662674
sdk_path,
663-
format!(
664-
"Unknown SDK version fails to meet maximum version {max_version}"
665-
),
675+
format!("Unknown SDK version fails to meet maximum version {max_version}"),
666676
));
667677
}
668678

0 commit comments

Comments
 (0)