1#[cfg(feature = "parse")]
88mod parsed_sdk;
89mod search;
90mod simple_sdk;
91
92use std::{
93 cmp::Ordering,
94 fmt::{Display, Formatter},
95 ops::Deref,
96 path::{Path, PathBuf},
97 process::{Command, ExitStatus, Stdio},
98 str::FromStr,
99};
100
101pub use crate::{search::*, simple_sdk::SimpleSdk};
102
103#[cfg(feature = "parse")]
104pub use crate::parsed_sdk::{
105 ParsedSdk, SdkSettingsJson, SdkSettingsJsonDefaultProperties, SupportedTarget,
106};
107
108pub const COMMAND_LINE_TOOLS_DEFAULT_PATH: &str = "/Library/Developer/CommandLineTools";
110
111pub const XCODE_APP_DEFAULT_PATH: &str = "/Applications/Xcode.app";
113
114pub const XCODE_APP_RELATIVE_PATH_DEVELOPER: &str = "Contents/Developer";
118
119#[derive(Debug)]
121pub enum Error {
122 XcodeSelectPathFailedReading(std::io::Error),
124 XcodeSelectRun(std::io::Error),
126 XcodeSelectBadStatus(ExitStatus),
128 Io(std::io::Error),
130 DeveloperDirectoryNotFound,
132 PathNotDeveloper(PathBuf),
134 PathNotPlatform(PathBuf),
136 PathNotSdk(PathBuf),
138 VersionParse(String),
140 FunctionalityNotSupported(&'static str),
142 PlistNotDictionary,
144 PlistKeyMissing(String),
148 PlistKeyNotDictionary(String),
152 PlistKeyNotString(String),
156 #[cfg(feature = "parse")]
157 SerdeJson(serde_json::Error),
158 #[cfg(feature = "plist")]
159 Plist(plist::Error),
160 UnknownTarget(String),
162}
163
164impl Display for Error {
165 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
166 match self {
167 Self::XcodeSelectPathFailedReading(err) => {
168 f.write_fmt(format_args!("Error reading xcode-select paths: {err}"))
169 }
170 Self::XcodeSelectRun(err) => {
171 f.write_fmt(format_args!("Error running xcode-select: {err}"))
172 }
173 Self::XcodeSelectBadStatus(v) => {
174 f.write_fmt(format_args!("Error running xcode-select: {v}"))
175 }
176 Self::Io(err) => f.write_fmt(format_args!("I/O error: {err}")),
177 Self::DeveloperDirectoryNotFound => f.write_str("could not find a Developer Directory"),
178 Self::PathNotDeveloper(p) => f.write_fmt(format_args!(
179 "path is not a Developer directory: {}",
180 p.display()
181 )),
182 Self::PathNotPlatform(p) => f.write_fmt(format_args!(
183 "path is not an Apple Platform: {}",
184 p.display()
185 )),
186 Self::PathNotSdk(p) => {
187 f.write_fmt(format_args!("path is not an Apple SDK: {}", p.display()))
188 }
189 Self::VersionParse(s) => f.write_fmt(format_args!("malformed version string: {s}")),
190 Self::FunctionalityNotSupported(s) => f.write_fmt(format_args!("not supported: {s}")),
191 Self::PlistNotDictionary => f.write_str("plist value not a dictionary"),
192 Self::PlistKeyMissing(key) => f.write_fmt(format_args!("plist key missing: {key}")),
193 Self::PlistKeyNotDictionary(key) => {
194 f.write_fmt(format_args!("plist key not a dictionary: {key}"))
195 }
196 Self::PlistKeyNotString(key) => {
197 f.write_fmt(format_args!("plist key not a string: {key}"))
198 }
199 #[cfg(feature = "parse")]
200 Self::SerdeJson(err) => f.write_fmt(format_args!("JSON parsing error: {err}")),
201 #[cfg(feature = "plist")]
202 Self::Plist(err) => f.write_fmt(format_args!("plist error: {err}")),
203 Self::UnknownTarget(target) => f.write_fmt(format_args!("unknown target: {target}")),
204 }
205 }
206}
207
208impl std::error::Error for Error {}
209
210impl From<std::io::Error> for Error {
211 fn from(e: std::io::Error) -> Self {
212 Self::Io(e)
213 }
214}
215
216#[cfg(feature = "parse")]
217impl From<serde_json::Error> for Error {
218 fn from(e: serde_json::Error) -> Self {
219 Self::SerdeJson(e)
220 }
221}
222
223#[cfg(feature = "parse")]
224impl From<plist::Error> for Error {
225 fn from(e: plist::Error) -> Self {
226 Self::Plist(e)
227 }
228}
229
230#[derive(Clone, Debug)]
236pub enum Platform {
237 AppleTvOs,
238 AppleTvSimulator,
239 DriverKit,
240 IPhoneOs,
241 IPhoneSimulator,
242 MacOsX,
243 WatchOs,
244 WatchSimulator,
245 XrOs,
246 XrOsSimulator,
247 Unknown(String),
248}
249
250impl FromStr for Platform {
251 type Err = Error;
252
253 fn from_str(s: &str) -> Result<Self, Self::Err> {
254 match s.to_ascii_lowercase().as_str() {
256 "appletvos" => Ok(Self::AppleTvOs),
257 "appletvsimulator" => Ok(Self::AppleTvSimulator),
258 "driverkit" => Ok(Self::DriverKit),
259 "iphoneos" => Ok(Self::IPhoneOs),
260 "iphonesimulator" => Ok(Self::IPhoneSimulator),
261 "macosx" => Ok(Self::MacOsX),
262 "watchos" => Ok(Self::WatchOs),
263 "watchsimulator" => Ok(Self::WatchSimulator),
264 "xros" => Ok(Self::XrOs),
265 "xrsimulator" => Ok(Self::XrOsSimulator),
266 v => Ok(Self::Unknown(v.to_string())),
267 }
268 }
269}
270
271impl PartialEq for Platform {
272 fn eq(&self, other: &Self) -> bool {
273 self.filesystem_name().eq(other.filesystem_name())
274 }
275}
276
277impl Eq for Platform {}
278
279impl TryFrom<&str> for Platform {
280 type Error = Error;
281
282 fn try_from(s: &str) -> Result<Self, Self::Error> {
283 Self::from_str(s)
284 }
285}
286
287impl Platform {
288 pub fn from_platform_path(p: &Path) -> Result<Self, Error> {
296 let (name, platform) = p
297 .file_name()
298 .ok_or_else(|| Error::PathNotPlatform(p.to_path_buf()))?
299 .to_str()
300 .ok_or_else(|| Error::PathNotPlatform(p.to_path_buf()))?
301 .split_once('.')
302 .ok_or_else(|| Error::PathNotPlatform(p.to_path_buf()))?;
303
304 if platform == "platform" {
305 Self::from_str(name)
306 } else {
307 Err(Error::PathNotPlatform(p.to_path_buf()))
308 }
309 }
310
311 pub fn from_target_triple(target: &str) -> Result<Self, Error> {
319 let platform = match target {
320 target if target.ends_with("-apple-darwin") => Self::MacOsX,
321 "i386-apple-ios" | "x86_64-apple-ios" => Self::IPhoneSimulator,
322 target if target.ends_with("-apple-ios-sim") => Platform::IPhoneSimulator,
323 target if target.ends_with("-apple-ios") => Platform::IPhoneOs,
324 target if target.ends_with("-apple-ios-macabi") => Platform::IPhoneOs,
325 "i386-apple-watchos" => Self::WatchSimulator,
326 target if target.ends_with("-apple-watchos-sim") => Self::WatchSimulator,
327 target if target.ends_with("-apple-watchos") => Platform::WatchOs,
328 "x86_64-apple-tvos" => Self::AppleTvSimulator,
329 target if target.ends_with("-apple-tvos") => Platform::AppleTvOs,
330 "aarch64-apple-xros-sim" => Platform::XrOsSimulator,
331 target if target.ends_with("-apple-xros") => Platform::XrOs,
332 _ => return Err(Error::UnknownTarget(target.to_string())),
333 };
334 Ok(platform)
335 }
336
337 pub fn filesystem_name(&self) -> &str {
344 match self {
345 Self::AppleTvOs => "AppleTVOS",
346 Self::AppleTvSimulator => "AppleTVSimulator",
347 Self::DriverKit => "DriverKit",
348 Self::IPhoneOs => "iPhoneOS",
349 Self::IPhoneSimulator => "iPhoneSimulator",
350 Self::MacOsX => "MacOSX",
351 Self::WatchOs => "WatchOS",
352 Self::WatchSimulator => "WatchSimulator",
353 Self::XrOs => "XROS",
354 Self::XrOsSimulator => "XRSimulator",
355 Self::Unknown(v) => v,
356 }
357 }
358
359 pub fn directory_name(&self) -> String {
363 format!("{}.platform", self.filesystem_name())
364 }
365
366 pub fn path_in_developer_directory(&self, developer_directory: impl AsRef<Path>) -> PathBuf {
368 developer_directory
369 .as_ref()
370 .join("Platforms")
371 .join(self.directory_name())
372 }
373}
374
375pub struct PlatformDirectory {
383 path: PathBuf,
385
386 platform: Platform,
388}
389
390impl PlatformDirectory {
391 pub fn from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
393 let path = path.as_ref().to_path_buf();
394 let platform = Platform::from_platform_path(&path)?;
395
396 Ok(Self { path, platform })
397 }
398
399 pub fn path(&self) -> &Path {
401 &self.path
402 }
403
404 pub fn sdks_path(&self) -> PathBuf {
408 self.path.join("Developer").join("SDKs")
409 }
410
411 pub fn find_sdks<T: AppleSdk>(&self) -> Result<Vec<T>, Error> {
419 T::find_in_directory(&self.sdks_path())
420 }
421}
422
423impl AsRef<Path> for PlatformDirectory {
424 fn as_ref(&self) -> &Path {
425 &self.path
426 }
427}
428
429impl AsRef<Platform> for PlatformDirectory {
430 fn as_ref(&self) -> &Platform {
431 &self.platform
432 }
433}
434
435impl Deref for PlatformDirectory {
436 type Target = Platform;
437
438 fn deref(&self) -> &Self::Target {
439 &self.platform
440 }
441}
442
443impl PartialEq for PlatformDirectory {
444 fn eq(&self, other: &Self) -> bool {
445 self.path.eq(&other.path)
446 }
447}
448
449impl Eq for PlatformDirectory {}
450
451impl PartialOrd for PlatformDirectory {
452 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
453 Some(self.cmp(other))
454 }
455}
456
457impl Ord for PlatformDirectory {
458 fn cmp(&self, other: &Self) -> Ordering {
459 self.path.cmp(&other.path)
460 }
461}
462
463#[derive(Clone, Debug, Eq, PartialEq)]
465pub struct DeveloperDirectory {
466 path: PathBuf,
467}
468
469impl AsRef<Path> for DeveloperDirectory {
470 fn as_ref(&self) -> &Path {
471 &self.path
472 }
473}
474
475impl From<&Path> for DeveloperDirectory {
476 fn from(p: &Path) -> Self {
477 Self {
478 path: p.to_path_buf(),
479 }
480 }
481}
482
483impl From<PathBuf> for DeveloperDirectory {
484 fn from(path: PathBuf) -> Self {
485 Self { path }
486 }
487}
488
489impl From<&PathBuf> for DeveloperDirectory {
490 fn from(path: &PathBuf) -> Self {
491 Self { path: path.clone() }
492 }
493}
494
495impl DeveloperDirectory {
496 pub fn from_env() -> Result<Option<Self>, Error> {
506 if let Some(value) = std::env::var_os("DEVELOPER_DIR") {
507 let path = PathBuf::from(value);
508
509 if path.exists() {
510 Ok(Some(Self { path }))
511 } else {
512 Err(Error::PathNotDeveloper(path))
513 }
514 } else {
515 Ok(None)
516 }
517 }
518
519 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 }
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 }
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 }
566 Err(err) => return Err(Error::XcodeSelectPathFailedReading(err)),
567 }
568
569 Ok(None)
570 }
571
572 pub fn from_xcode_select() -> Result<Self, Error> {
577 let output = Command::new("xcode-select")
578 .args(["--print-path"])
579 .stderr(Stdio::null())
580 .output()
581 .map_err(Error::XcodeSelectRun)?;
582
583 if output.status.success() {
584 let path = String::from_utf8_lossy(&output.stdout);
587 let path = PathBuf::from(path.trim());
588
589 Ok(Self { path })
590 } else {
591 Err(Error::XcodeSelectBadStatus(output.status))
592 }
593 }
594
595 pub fn default_xcode() -> Option<Self> {
600 let path = PathBuf::from(XCODE_APP_DEFAULT_PATH).join(XCODE_APP_RELATIVE_PATH_DEVELOPER);
601
602 if path.exists() {
603 Some(Self { path })
604 } else {
605 None
606 }
607 }
608
609 pub fn find_system_xcodes() -> Result<Vec<Self>, Error> {
617 Ok(find_system_xcode_applications()?
618 .into_iter()
619 .filter_map(|p| {
620 let path = p.join(XCODE_APP_RELATIVE_PATH_DEVELOPER);
621
622 if path.exists() {
623 Some(Self { path })
624 } else {
625 None
626 }
627 })
628 .collect::<Vec<_>>())
629 }
630
631 pub fn find_default() -> Result<Option<Self>, Error> {
648 if let Some(v) = Self::from_env()? {
649 Ok(Some(v))
650 } else if let Some(v) = Self::default_xcode() {
651 Ok(Some(v))
652 } else if let Ok(v) = Self::from_xcode_select() {
653 Ok(Some(v))
654 } else {
655 Ok(None)
656 }
657 }
658
659 pub fn find_default_required() -> Result<Self, Error> {
664 if let Some(v) = Self::find_default()? {
665 Ok(v)
666 } else {
667 Err(Error::DeveloperDirectoryNotFound)
668 }
669 }
670
671 pub fn path(&self) -> &Path {
673 &self.path
674 }
675
676 pub fn platforms_path(&self) -> PathBuf {
678 self.path.join("Platforms")
679 }
680
681 pub fn platforms(&self) -> Result<Vec<PlatformDirectory>, Error> {
691 let platforms_path = self.platforms_path();
692
693 let dir = match std::fs::read_dir(platforms_path) {
694 Ok(v) => Ok(v),
695 Err(e) => {
696 if e.kind() == std::io::ErrorKind::NotFound {
697 return Ok(vec![]);
698 } else {
699 Err(Error::from(e))
700 }
701 }
702 }?;
703
704 let mut res = vec![];
705
706 for entry in dir {
707 let entry = entry?;
708
709 if let Ok(platform) = PlatformDirectory::from_path(entry.path()) {
710 res.push(platform);
711 }
712 }
713
714 res.sort();
716
717 Ok(res)
718 }
719
720 pub fn sdks<SDK: AppleSdk>(&self) -> Result<Vec<SDK>, Error> {
725 Ok(self
726 .platforms()?
727 .into_iter()
728 .map(|platform| Ok(platform.find_sdks()?.into_iter()))
729 .collect::<Result<Vec<_>, Error>>()?
730 .into_iter()
731 .flatten()
732 .collect::<Vec<_>>())
733 }
734}
735
736pub fn command_line_tools_sdks_directory() -> Option<PathBuf> {
740 let sdk_path = PathBuf::from(COMMAND_LINE_TOOLS_DEFAULT_PATH).join("SDKs");
741
742 if sdk_path.exists() {
743 Some(sdk_path)
744 } else {
745 None
746 }
747}
748
749pub fn find_xcode_apps(applications_dir: &Path) -> Result<Vec<PathBuf>, Error> {
760 let dir = match std::fs::read_dir(applications_dir) {
761 Ok(v) => Ok(v),
762 Err(e) => {
763 if e.kind() == std::io::ErrorKind::NotFound {
764 return Ok(vec![]);
765 } else {
766 Err(Error::from(e))
767 }
768 }
769 }?;
770
771 let mut res = dir
772 .into_iter()
773 .map(|entry| {
774 let entry = entry?;
775
776 let name = entry.file_name();
777 let file_name = name.to_string_lossy();
778
779 if file_name.starts_with("Xcode") && file_name.ends_with(".app") {
780 Ok(Some(entry.path()))
781 } else {
782 Ok(None)
783 }
784 })
785 .collect::<Result<Vec<_>, Error>>()?
786 .into_iter()
787 .flatten()
788 .collect::<Vec<_>>();
789
790 res.sort_by(|a, b| match (a.file_name(), b.file_name()) {
792 (Some(x), _) if x == "Xcode.app" => Ordering::Less,
793 (_, Some(x)) if x == "Xcode.app" => Ordering::Greater,
794 (_, _) => a.cmp(b),
795 });
796
797 Ok(res)
798}
799
800pub fn find_system_xcode_applications() -> Result<Vec<PathBuf>, Error> {
805 find_xcode_apps(&PathBuf::from("/Applications"))
806}
807
808#[derive(Clone, Debug, Eq, PartialEq)]
822pub struct SdkVersion {
823 value: String,
824}
825
826impl Display for SdkVersion {
827 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
828 self.value.fmt(f)
829 }
830}
831
832impl AsRef<str> for SdkVersion {
833 fn as_ref(&self) -> &str {
834 &self.value
835 }
836}
837
838impl From<String> for SdkVersion {
839 fn from(value: String) -> Self {
840 Self { value }
841 }
842}
843
844impl From<&str> for SdkVersion {
845 fn from(s: &str) -> Self {
846 Self::from(s.to_string())
847 }
848}
849
850impl From<&String> for SdkVersion {
851 fn from(s: &String) -> Self {
852 Self::from(s.to_string())
853 }
854}
855
856impl SdkVersion {
857 fn normalized_version(&self) -> Result<(u8, u8, u8), Error> {
858 let ints = self
859 .value
860 .split('.')
861 .map(|x| u8::from_str(x).map_err(|_| Error::VersionParse(self.value.to_string())))
862 .collect::<Result<Vec<_>, Error>>()?;
863
864 match ints.len() {
865 1 => Ok((ints[0], 0, 0)),
866 2 => Ok((ints[0], ints[1], 0)),
867 3 => Ok((ints[0], ints[1], ints[2])),
868 _ => Err(Error::VersionParse(self.value.to_string())),
869 }
870 }
871
872 pub fn semantic_version(&self) -> Result<String, Error> {
877 let (x, y, z) = self.normalized_version()?;
878
879 Ok(format!("{x}.{y}.{z}"))
880 }
881}
882
883impl PartialOrd for SdkVersion {
884 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
885 Some(self.cmp(other))
886 }
887}
888
889impl Ord for SdkVersion {
890 fn cmp(&self, other: &Self) -> Ordering {
891 let a = self.normalized_version().unwrap_or((0, 0, 0));
892 let b = other.normalized_version().unwrap_or((0, 0, 0));
893
894 a.cmp(&b)
895 }
896}
897
898#[derive(Clone, Debug)]
900pub struct SdkPath {
901 pub path: PathBuf,
903
904 pub platform: Platform,
906
907 pub version: Option<SdkVersion>,
912}
913
914impl Display for SdkPath {
915 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
916 f.write_fmt(format_args!(
917 "{} (version: {}) SDK at {}",
918 self.platform.filesystem_name(),
919 if let Some(version) = &self.version {
920 version.value.as_str()
921 } else {
922 "unknown"
923 },
924 self.path.display()
925 ))
926 }
927}
928
929impl SdkPath {
930 pub fn from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
931 let path = path.as_ref().to_path_buf();
932
933 let s = path
934 .file_name()
935 .ok_or_else(|| Error::PathNotSdk(path.clone()))?
936 .to_str()
937 .ok_or_else(|| Error::PathNotSdk(path.clone()))?;
938
939 let (prefix, sdk) = s
940 .rsplit_once('.')
941 .ok_or_else(|| Error::PathNotSdk(path.clone()))?;
942
943 if sdk != "sdk" {
944 return Err(Error::PathNotSdk(path));
945 }
946
947 let (platform_name, version) = if let Some(first_digit) = prefix
950 .chars()
951 .enumerate()
952 .find_map(|(i, c)| if c.is_numeric() { Some(i) } else { None })
953 {
954 let (name, version) = prefix.split_at(first_digit);
955
956 (name, Some(version.to_string().into()))
957 } else {
958 (prefix, None)
959 };
960
961 let platform = Platform::from_str(platform_name)?;
962
963 Ok(Self {
964 path,
965 platform,
966 version,
967 })
968 }
969}
970
971pub trait AppleSdk: Sized + AsRef<Path> {
973 fn from_directory(path: &Path) -> Result<Self, Error>;
978
979 fn find_in_directory(root: &Path) -> Result<Vec<Self>, Error> {
986 let dir = match std::fs::read_dir(root) {
987 Ok(v) => Ok(v),
988 Err(e) => {
989 if e.kind() == std::io::ErrorKind::NotFound {
990 return Ok(vec![]);
991 } else {
992 Err(Error::from(e))
993 }
994 }
995 }?;
996
997 let mut res = vec![];
998
999 for entry in dir {
1000 let entry = entry?;
1001
1002 match Self::from_directory(&entry.path()) {
1003 Ok(sdk) => {
1004 res.push(sdk);
1005 }
1006 Err(Error::PathNotSdk(_)) => {}
1007 Err(err) => return Err(err),
1008 }
1009 }
1010
1011 Ok(res)
1012 }
1013
1014 fn find_command_line_tools_sdks() -> Result<Option<Vec<Self>>, Error> {
1022 if let Some(path) = command_line_tools_sdks_directory() {
1023 Ok(Some(Self::find_in_directory(&path)?))
1024 } else {
1025 Ok(None)
1026 }
1027 }
1028
1029 fn sdk_path(&self) -> SdkPath {
1031 SdkPath {
1032 path: self.path().to_path_buf(),
1033 platform: self.platform().clone(),
1034 version: self.version().cloned(),
1035 }
1036 }
1037
1038 #[deprecated(since = "0.1.1", note = "plase use `sdk_path` instead")]
1039 fn as_sdk_path(&self) -> SdkPath {
1040 self.sdk_path()
1041 }
1042
1043 fn path(&self) -> &Path {
1045 self.as_ref()
1046 }
1047
1048 fn is_symlink(&self) -> bool;
1050
1051 fn platform(&self) -> &Platform;
1053
1054 fn version(&self) -> Option<&SdkVersion>;
1060
1061 fn supports_deployment_target(
1063 &self,
1064 target_name: &str,
1065 target_version: &SdkVersion,
1066 ) -> Result<bool, Error>;
1067}
1068
1069#[cfg(test)]
1070mod test {
1071 use super::*;
1072
1073 #[test]
1074 fn find_system_xcode_applications() -> Result<(), Error> {
1075 let res = crate::find_system_xcode_applications()?;
1076
1077 if PathBuf::from(XCODE_APP_DEFAULT_PATH).exists() {
1078 assert!(!res.is_empty());
1079 }
1080
1081 Ok(())
1082 }
1083
1084 #[test]
1085 fn find_system_xcode_developer_directories() -> Result<(), Error> {
1086 let res = DeveloperDirectory::find_system_xcodes()?;
1087
1088 if PathBuf::from(XCODE_APP_DEFAULT_PATH).exists() {
1089 assert!(!res.is_empty());
1090 }
1091
1092 Ok(())
1093 }
1094
1095 #[test]
1096 fn find_all_platform_directories() -> Result<(), Error> {
1097 for dir in DeveloperDirectory::find_system_xcodes()? {
1098 for platform in dir.platforms()? {
1099 assert_eq!(
1101 platform.path,
1102 dir.platforms_path().join(platform.directory_name())
1103 );
1104 assert_eq!(
1105 platform.path,
1106 platform.path_in_developer_directory(dir.path())
1107 );
1108
1109 assert!(!matches!(platform.platform, Platform::Unknown(_)));
1113 }
1114 }
1115
1116 Ok(())
1117 }
1118
1119 #[test]
1120 fn apple_platform() -> Result<(), Error> {
1121 assert_eq!(Platform::from_str("macosx")?, Platform::MacOsX);
1122 assert_eq!(Platform::from_str("MacOSX")?, Platform::MacOsX);
1123
1124 Ok(())
1125 }
1126
1127 #[test]
1128 fn target_platform() -> Result<(), Error> {
1129 use Platform::*;
1130 fn test(target: &str, platform: Platform) {
1131 assert_eq!(Platform::from_target_triple(target).unwrap(), platform);
1132 }
1133 test("aarch64-apple-darwin", MacOsX);
1134 test("aarch64-apple-ios", IPhoneOs);
1135 test("aarch64-apple-ios-macabi", IPhoneOs);
1136 test("aarch64-apple-ios-sim", IPhoneSimulator);
1137 test("aarch64-apple-tvos", AppleTvOs); test("aarch64-apple-watchos-sim", WatchSimulator);
1139 test("arm64_32-apple-watchos", WatchOs);
1140 test("armv7-apple-ios", IPhoneOs);
1141 test("armv7k-apple-watchos", WatchOs);
1142 test("armv7s-apple-ios", IPhoneOs);
1143 test("i386-apple-ios", IPhoneSimulator);
1144 test("i686-apple-darwin", MacOsX);
1145 test("x86_64-apple-darwin", MacOsX);
1146 test("x86_64-apple-ios", IPhoneSimulator);
1147 test("x86_64-apple-ios-macabi", IPhoneOs);
1148 test("x86_64-apple-tvos", AppleTvSimulator);
1149 test("x86_64-apple-watchos-sim", WatchSimulator);
1150 test("aarch64-apple-xros", XrOs);
1151 test("aarch64-apple-xros-sim", XrOsSimulator);
1152
1153 assert!(Platform::from_target_triple("x86_64-unknown-linux-gnu").is_err());
1154
1155 Ok(())
1156 }
1157
1158 #[test]
1159 fn sdk_version() -> Result<(), Error> {
1160 let v = SdkVersion::from("foo");
1161 assert!(v.normalized_version().is_err());
1162 assert!(v.semantic_version().is_err());
1163
1164 let v = SdkVersion::from("12");
1165 assert_eq!(v.normalized_version()?, (12, 0, 0));
1166 assert_eq!(v.semantic_version()?, "12.0.0");
1167
1168 let v = SdkVersion::from("12.3");
1169 assert_eq!(v.normalized_version()?, (12, 3, 0));
1170 assert_eq!(v.semantic_version()?, "12.3.0");
1171
1172 let v = SdkVersion::from("12.3.1");
1173 assert_eq!(v.normalized_version()?, (12, 3, 1));
1174 assert_eq!(v.semantic_version()?, "12.3.1");
1175
1176 let v = SdkVersion::from("12.3.1.2");
1177 assert!(v.normalized_version().is_err());
1178
1179 assert_eq!(
1180 SdkVersion::from("12").cmp(&SdkVersion::from("11")),
1181 Ordering::Greater
1182 );
1183 assert_eq!(
1184 SdkVersion::from("12").cmp(&SdkVersion::from("12")),
1185 Ordering::Equal
1186 );
1187 assert_eq!(
1188 SdkVersion::from("12").cmp(&SdkVersion::from("13")),
1189 Ordering::Less
1190 );
1191
1192 Ok(())
1193 }
1194
1195 #[test]
1196 fn sdk_sorting() {
1197 let sorting = SdkSorting::VersionAscending;
1198
1199 assert_eq!(
1200 sorting.compare_version(Some(&SdkVersion::from("12")), Some(&SdkVersion::from("11"))),
1201 Ordering::Greater
1202 );
1203 assert_eq!(
1204 sorting.compare_version(Some(&SdkVersion::from("11")), Some(&SdkVersion::from("12"))),
1205 Ordering::Less
1206 );
1207
1208 let sorting = SdkSorting::VersionDescending;
1209
1210 assert_eq!(
1211 sorting.compare_version(Some(&SdkVersion::from("12")), Some(&SdkVersion::from("11"))),
1212 Ordering::Less
1213 );
1214 assert_eq!(
1215 sorting.compare_version(Some(&SdkVersion::from("11")), Some(&SdkVersion::from("12"))),
1216 Ordering::Greater
1217 );
1218 }
1219
1220 #[test]
1221 fn parse_sdk_path() -> Result<(), Error> {
1222 assert!(SdkPath::from_path("foo").is_err());
1223 assert!(SdkPath::from_path("foo.bar").is_err());
1224
1225 let sdk = SdkPath::from_path("MacOSX.sdk")?;
1226 assert_eq!(sdk.platform, Platform::MacOsX);
1227 assert_eq!(sdk.version, None);
1228
1229 let sdk = SdkPath::from_path("MacOSX12.3.sdk")?;
1230 assert_eq!(sdk.platform, Platform::MacOsX);
1231 assert_eq!(sdk.version, Some("12.3".to_string().into()));
1232
1233 Ok(())
1234 }
1235
1236 #[test]
1237 fn search_all() -> Result<(), Error> {
1238 let search = SdkSearch::default().location(SdkSearchLocation::SystemXcodes);
1239
1240 search.search::<SimpleSdk>()?;
1241
1242 Ok(())
1243 }
1244
1245 #[cfg(target_os = "macos")]
1249 #[test]
1250 fn github_actions() -> Result<(), Error> {
1251 if std::env::var("GITHUB_ACTIONS").is_err() {
1252 return Ok(());
1253 }
1254
1255 assert_eq!(
1256 DeveloperDirectory::default_xcode(),
1257 Some(DeveloperDirectory {
1258 path: PathBuf::from("/Applications/Xcode.app/Contents/Developer")
1259 })
1260 );
1261 assert!(PathBuf::from(COMMAND_LINE_TOOLS_DEFAULT_PATH).exists());
1262
1263 assert!(crate::find_system_xcode_applications()?.len() > 5);
1265
1266 assert_eq!(
1269 crate::find_system_xcode_applications()?.len(),
1270 DeveloperDirectory::find_system_xcodes()?.len()
1271 );
1272
1273 for platform in [Platform::MacOsX, Platform::IPhoneOs, Platform::WatchOs] {
1275 let sdks = SdkSearch::default()
1276 .platform(platform)
1277 .search::<SimpleSdk>()?;
1278 assert!(!sdks.is_empty());
1279 }
1280
1281 let sdks = SdkSearch::default()
1283 .platform(Platform::MacOsX)
1284 .minimum_version(SdkVersion::from("11.0"))
1285 .search::<SimpleSdk>()?;
1286 assert!(!sdks.is_empty());
1287
1288 Ok(())
1289 }
1290}