#[cfg(feature = "parse")]
mod parsed_sdk;
mod search;
mod simple_sdk;
use std::{
cmp::Ordering,
fmt::{Display, Formatter},
ops::Deref,
path::{Path, PathBuf},
process::{Command, ExitStatus, Stdio},
str::FromStr,
};
pub use crate::{search::*, simple_sdk::SimpleSdk};
#[cfg(feature = "parse")]
pub use crate::parsed_sdk::{
ParsedSdk, SdkSettingsJson, SdkSettingsJsonDefaultProperties, SupportedTarget,
};
pub const COMMAND_LINE_TOOLS_DEFAULT_PATH: &str = "/Library/Developer/CommandLineTools";
pub const XCODE_APP_DEFAULT_PATH: &str = "/Applications/Xcode.app";
pub const XCODE_APP_RELATIVE_PATH_DEVELOPER: &str = "Contents/Developer";
#[derive(Debug)]
pub enum Error {
XcodeSelectPathFailedReading(std::io::Error),
XcodeSelectRun(std::io::Error),
XcodeSelectBadStatus(ExitStatus),
Io(std::io::Error),
DeveloperDirectoryNotFound,
PathNotDeveloper(PathBuf),
PathNotPlatform(PathBuf),
PathNotSdk(PathBuf),
VersionParse(String),
FunctionalityNotSupported(&'static str),
PlistNotDictionary,
PlistKeyMissing(String),
PlistKeyNotDictionary(String),
PlistKeyNotString(String),
#[cfg(feature = "parse")]
SerdeJson(serde_json::Error),
#[cfg(feature = "plist")]
Plist(plist::Error),
UnknownTarget(String),
}
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}"))
}
Self::XcodeSelectBadStatus(v) => {
f.write_fmt(format_args!("Error running xcode-select: {v}"))
}
Self::Io(err) => f.write_fmt(format_args!("I/O error: {err}")),
Self::DeveloperDirectoryNotFound => f.write_str("could not find a Developer Directory"),
Self::PathNotDeveloper(p) => f.write_fmt(format_args!(
"path is not a Developer directory: {}",
p.display()
)),
Self::PathNotPlatform(p) => f.write_fmt(format_args!(
"path is not an Apple Platform: {}",
p.display()
)),
Self::PathNotSdk(p) => {
f.write_fmt(format_args!("path is not an Apple SDK: {}", p.display()))
}
Self::VersionParse(s) => f.write_fmt(format_args!("malformed version string: {s}")),
Self::FunctionalityNotSupported(s) => f.write_fmt(format_args!("not supported: {s}")),
Self::PlistNotDictionary => f.write_str("plist value not a dictionary"),
Self::PlistKeyMissing(key) => f.write_fmt(format_args!("plist key missing: {key}")),
Self::PlistKeyNotDictionary(key) => {
f.write_fmt(format_args!("plist key not a dictionary: {key}"))
}
Self::PlistKeyNotString(key) => {
f.write_fmt(format_args!("plist key not a string: {key}"))
}
#[cfg(feature = "parse")]
Self::SerdeJson(err) => f.write_fmt(format_args!("JSON parsing error: {err}")),
#[cfg(feature = "plist")]
Self::Plist(err) => f.write_fmt(format_args!("plist error: {err}")),
Self::UnknownTarget(target) => f.write_fmt(format_args!("unknown target: {target}")),
}
}
}
impl std::error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
#[cfg(feature = "parse")]
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Self::SerdeJson(e)
}
}
#[cfg(feature = "parse")]
impl From<plist::Error> for Error {
fn from(e: plist::Error) -> Self {
Self::Plist(e)
}
}
#[derive(Clone, Debug)]
pub enum Platform {
AppleTvOs,
AppleTvSimulator,
DriverKit,
IPhoneOs,
IPhoneSimulator,
MacOsX,
WatchOs,
WatchSimulator,
XrOs,
XrOsSimulator,
Unknown(String),
}
impl FromStr for Platform {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"appletvos" => Ok(Self::AppleTvOs),
"appletvsimulator" => Ok(Self::AppleTvSimulator),
"driverkit" => Ok(Self::DriverKit),
"iphoneos" => Ok(Self::IPhoneOs),
"iphonesimulator" => Ok(Self::IPhoneSimulator),
"macosx" => Ok(Self::MacOsX),
"watchos" => Ok(Self::WatchOs),
"watchsimulator" => Ok(Self::WatchSimulator),
"xros" => Ok(Self::XrOs),
"xrsimulator" => Ok(Self::XrOsSimulator),
v => Ok(Self::Unknown(v.to_string())),
}
}
}
impl PartialEq for Platform {
fn eq(&self, other: &Self) -> bool {
self.filesystem_name().eq(other.filesystem_name())
}
}
impl Eq for Platform {}
impl TryFrom<&str> for Platform {
type Error = Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Self::from_str(s)
}
}
impl Platform {
pub fn from_platform_path(p: &Path) -> Result<Self, Error> {
let (name, platform) = p
.file_name()
.ok_or_else(|| Error::PathNotPlatform(p.to_path_buf()))?
.to_str()
.ok_or_else(|| Error::PathNotPlatform(p.to_path_buf()))?
.split_once('.')
.ok_or_else(|| Error::PathNotPlatform(p.to_path_buf()))?;
if platform == "platform" {
Self::from_str(name)
} else {
Err(Error::PathNotPlatform(p.to_path_buf()))
}
}
pub fn from_target_triple(target: &str) -> Result<Self, Error> {
let platform = match target {
target if target.ends_with("-apple-darwin") => Self::MacOsX,
"i386-apple-ios" | "x86_64-apple-ios" => Self::IPhoneSimulator,
target if target.ends_with("-apple-ios-sim") => Platform::IPhoneSimulator,
target if target.ends_with("-apple-ios") => Platform::IPhoneOs,
target if target.ends_with("-apple-ios-macabi") => Platform::IPhoneOs,
"i386-apple-watchos" => Self::WatchSimulator,
target if target.ends_with("-apple-watchos-sim") => Self::WatchSimulator,
target if target.ends_with("-apple-watchos") => Platform::WatchOs,
"x86_64-apple-tvos" => Self::AppleTvSimulator,
target if target.ends_with("-apple-tvos") => Platform::AppleTvOs,
"aarch64-apple-xros-sim" => Platform::XrOsSimulator,
target if target.ends_with("-apple-xros") => Platform::XrOs,
_ => return Err(Error::UnknownTarget(target.to_string())),
};
Ok(platform)
}
pub fn filesystem_name(&self) -> &str {
match self {
Self::AppleTvOs => "AppleTVOS",
Self::AppleTvSimulator => "AppleTVSimulator",
Self::DriverKit => "DriverKit",
Self::IPhoneOs => "iPhoneOS",
Self::IPhoneSimulator => "iPhoneSimulator",
Self::MacOsX => "MacOSX",
Self::WatchOs => "WatchOS",
Self::WatchSimulator => "WatchSimulator",
Self::XrOs => "XROS",
Self::XrOsSimulator => "XRSimulator",
Self::Unknown(v) => v,
}
}
pub fn directory_name(&self) -> String {
format!("{}.platform", self.filesystem_name())
}
pub fn path_in_developer_directory(&self, developer_directory: impl AsRef<Path>) -> PathBuf {
developer_directory
.as_ref()
.join("Platforms")
.join(self.directory_name())
}
}
pub struct PlatformDirectory {
path: PathBuf,
platform: Platform,
}
impl PlatformDirectory {
pub fn from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
let path = path.as_ref().to_path_buf();
let platform = Platform::from_platform_path(&path)?;
Ok(Self { path, platform })
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn sdks_path(&self) -> PathBuf {
self.path.join("Developer").join("SDKs")
}
pub fn find_sdks<T: AppleSdk>(&self) -> Result<Vec<T>, Error> {
T::find_in_directory(&self.sdks_path())
}
}
impl AsRef<Path> for PlatformDirectory {
fn as_ref(&self) -> &Path {
&self.path
}
}
impl AsRef<Platform> for PlatformDirectory {
fn as_ref(&self) -> &Platform {
&self.platform
}
}
impl Deref for PlatformDirectory {
type Target = Platform;
fn deref(&self) -> &Self::Target {
&self.platform
}
}
impl PartialEq for PlatformDirectory {
fn eq(&self, other: &Self) -> bool {
self.path.eq(&other.path)
}
}
impl Eq for PlatformDirectory {}
impl PartialOrd for PlatformDirectory {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PlatformDirectory {
fn cmp(&self, other: &Self) -> Ordering {
self.path.cmp(&other.path)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DeveloperDirectory {
path: PathBuf,
}
impl AsRef<Path> for DeveloperDirectory {
fn as_ref(&self) -> &Path {
&self.path
}
}
impl From<&Path> for DeveloperDirectory {
fn from(p: &Path) -> Self {
Self {
path: p.to_path_buf(),
}
}
}
impl From<PathBuf> for DeveloperDirectory {
fn from(path: PathBuf) -> Self {
Self { path }
}
}
impl From<&PathBuf> for DeveloperDirectory {
fn from(path: &PathBuf) -> Self {
Self { path: path.clone() }
}
}
impl DeveloperDirectory {
pub fn from_env() -> Result<Option<Self>, Error> {
if let Some(value) = std::env::var_os("DEVELOPER_DIR") {
let path = PathBuf::from(value);
if path.exists() {
Ok(Some(Self { path }))
} else {
Err(Error::PathNotDeveloper(path))
}
} else {
Ok(None)
}
}
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 => {
}
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 => {
}
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 => {
}
Err(err) => return Err(Error::XcodeSelectPathFailedReading(err)),
}
Ok(None)
}
pub fn from_xcode_select() -> Result<Self, Error> {
let output = Command::new("xcode-select")
.args(["--print-path"])
.stderr(Stdio::null())
.output()
.map_err(Error::XcodeSelectRun)?;
if output.status.success() {
let path = String::from_utf8_lossy(&output.stdout);
let path = PathBuf::from(path.trim());
Ok(Self { path })
} else {
Err(Error::XcodeSelectBadStatus(output.status))
}
}
pub fn default_xcode() -> Option<Self> {
let path = PathBuf::from(XCODE_APP_DEFAULT_PATH).join(XCODE_APP_RELATIVE_PATH_DEVELOPER);
if path.exists() {
Some(Self { path })
} else {
None
}
}
pub fn find_system_xcodes() -> Result<Vec<Self>, Error> {
Ok(find_system_xcode_applications()?
.into_iter()
.filter_map(|p| {
let path = p.join(XCODE_APP_RELATIVE_PATH_DEVELOPER);
if path.exists() {
Some(Self { path })
} else {
None
}
})
.collect::<Vec<_>>())
}
pub fn find_default() -> Result<Option<Self>, Error> {
if let Some(v) = Self::from_env()? {
Ok(Some(v))
} else if let Some(v) = Self::default_xcode() {
Ok(Some(v))
} else if let Ok(v) = Self::from_xcode_select() {
Ok(Some(v))
} else {
Ok(None)
}
}
pub fn find_default_required() -> Result<Self, Error> {
if let Some(v) = Self::find_default()? {
Ok(v)
} else {
Err(Error::DeveloperDirectoryNotFound)
}
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn platforms_path(&self) -> PathBuf {
self.path.join("Platforms")
}
pub fn platforms(&self) -> Result<Vec<PlatformDirectory>, Error> {
let platforms_path = self.platforms_path();
let dir = match std::fs::read_dir(platforms_path) {
Ok(v) => Ok(v),
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
return Ok(vec![]);
} else {
Err(Error::from(e))
}
}
}?;
let mut res = vec![];
for entry in dir {
let entry = entry?;
if let Ok(platform) = PlatformDirectory::from_path(entry.path()) {
res.push(platform);
}
}
res.sort();
Ok(res)
}
pub fn sdks<SDK: AppleSdk>(&self) -> Result<Vec<SDK>, Error> {
Ok(self
.platforms()?
.into_iter()
.map(|platform| Ok(platform.find_sdks()?.into_iter()))
.collect::<Result<Vec<_>, Error>>()?
.into_iter()
.flatten()
.collect::<Vec<_>>())
}
}
pub fn command_line_tools_sdks_directory() -> Option<PathBuf> {
let sdk_path = PathBuf::from(COMMAND_LINE_TOOLS_DEFAULT_PATH).join("SDKs");
if sdk_path.exists() {
Some(sdk_path)
} else {
None
}
}
pub fn find_xcode_apps(applications_dir: &Path) -> Result<Vec<PathBuf>, Error> {
let dir = match std::fs::read_dir(applications_dir) {
Ok(v) => Ok(v),
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
return Ok(vec![]);
} else {
Err(Error::from(e))
}
}
}?;
let mut res = dir
.into_iter()
.map(|entry| {
let entry = entry?;
let name = entry.file_name();
let file_name = name.to_string_lossy();
if file_name.starts_with("Xcode") && file_name.ends_with(".app") {
Ok(Some(entry.path()))
} else {
Ok(None)
}
})
.collect::<Result<Vec<_>, Error>>()?
.into_iter()
.flatten()
.collect::<Vec<_>>();
res.sort_by(|a, b| match (a.file_name(), b.file_name()) {
(Some(x), _) if x == "Xcode.app" => Ordering::Less,
(_, Some(x)) if x == "Xcode.app" => Ordering::Greater,
(_, _) => a.cmp(b),
});
Ok(res)
}
pub fn find_system_xcode_applications() -> Result<Vec<PathBuf>, Error> {
find_xcode_apps(&PathBuf::from("/Applications"))
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SdkVersion {
value: String,
}
impl Display for SdkVersion {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
impl AsRef<str> for SdkVersion {
fn as_ref(&self) -> &str {
&self.value
}
}
impl From<String> for SdkVersion {
fn from(value: String) -> Self {
Self { value }
}
}
impl From<&str> for SdkVersion {
fn from(s: &str) -> Self {
Self::from(s.to_string())
}
}
impl From<&String> for SdkVersion {
fn from(s: &String) -> Self {
Self::from(s.to_string())
}
}
impl SdkVersion {
fn normalized_version(&self) -> Result<(u8, u8, u8), Error> {
let ints = self
.value
.split('.')
.map(|x| u8::from_str(x).map_err(|_| Error::VersionParse(self.value.to_string())))
.collect::<Result<Vec<_>, Error>>()?;
match ints.len() {
1 => Ok((ints[0], 0, 0)),
2 => Ok((ints[0], ints[1], 0)),
3 => Ok((ints[0], ints[1], ints[2])),
_ => Err(Error::VersionParse(self.value.to_string())),
}
}
pub fn semantic_version(&self) -> Result<String, Error> {
let (x, y, z) = self.normalized_version()?;
Ok(format!("{x}.{y}.{z}"))
}
}
impl PartialOrd for SdkVersion {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SdkVersion {
fn cmp(&self, other: &Self) -> Ordering {
let a = self.normalized_version().unwrap_or((0, 0, 0));
let b = other.normalized_version().unwrap_or((0, 0, 0));
a.cmp(&b)
}
}
#[derive(Clone, Debug)]
pub struct SdkPath {
pub path: PathBuf,
pub platform: Platform,
pub version: Option<SdkVersion>,
}
impl Display for SdkPath {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{} (version: {}) SDK at {}",
self.platform.filesystem_name(),
if let Some(version) = &self.version {
version.value.as_str()
} else {
"unknown"
},
self.path.display()
))
}
}
impl SdkPath {
pub fn from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
let path = path.as_ref().to_path_buf();
let s = path
.file_name()
.ok_or_else(|| Error::PathNotSdk(path.clone()))?
.to_str()
.ok_or_else(|| Error::PathNotSdk(path.clone()))?;
let (prefix, sdk) = s
.rsplit_once('.')
.ok_or_else(|| Error::PathNotSdk(path.clone()))?;
if sdk != "sdk" {
return Err(Error::PathNotSdk(path));
}
let (platform_name, version) = if let Some(first_digit) = prefix
.chars()
.enumerate()
.find_map(|(i, c)| if c.is_numeric() { Some(i) } else { None })
{
let (name, version) = prefix.split_at(first_digit);
(name, Some(version.to_string().into()))
} else {
(prefix, None)
};
let platform = Platform::from_str(platform_name)?;
Ok(Self {
path,
platform,
version,
})
}
}
pub trait AppleSdk: Sized + AsRef<Path> {
fn from_directory(path: &Path) -> Result<Self, Error>;
fn find_in_directory(root: &Path) -> Result<Vec<Self>, Error> {
let dir = match std::fs::read_dir(root) {
Ok(v) => Ok(v),
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
return Ok(vec![]);
} else {
Err(Error::from(e))
}
}
}?;
let mut res = vec![];
for entry in dir {
let entry = entry?;
match Self::from_directory(&entry.path()) {
Ok(sdk) => {
res.push(sdk);
}
Err(Error::PathNotSdk(_)) => {}
Err(err) => return Err(err),
}
}
Ok(res)
}
fn find_command_line_tools_sdks() -> Result<Option<Vec<Self>>, Error> {
if let Some(path) = command_line_tools_sdks_directory() {
Ok(Some(Self::find_in_directory(&path)?))
} else {
Ok(None)
}
}
fn sdk_path(&self) -> SdkPath {
SdkPath {
path: self.path().to_path_buf(),
platform: self.platform().clone(),
version: self.version().cloned(),
}
}
#[deprecated(since = "0.1.1", note = "plase use `sdk_path` instead")]
fn as_sdk_path(&self) -> SdkPath {
self.sdk_path()
}
fn path(&self) -> &Path {
self.as_ref()
}
fn is_symlink(&self) -> bool;
fn platform(&self) -> &Platform;
fn version(&self) -> Option<&SdkVersion>;
fn supports_deployment_target(
&self,
target_name: &str,
target_version: &SdkVersion,
) -> Result<bool, Error>;
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn find_system_xcode_applications() -> Result<(), Error> {
let res = crate::find_system_xcode_applications()?;
if PathBuf::from(XCODE_APP_DEFAULT_PATH).exists() {
assert!(!res.is_empty());
}
Ok(())
}
#[test]
fn find_system_xcode_developer_directories() -> Result<(), Error> {
let res = DeveloperDirectory::find_system_xcodes()?;
if PathBuf::from(XCODE_APP_DEFAULT_PATH).exists() {
assert!(!res.is_empty());
}
Ok(())
}
#[test]
fn find_all_platform_directories() -> Result<(), Error> {
for dir in DeveloperDirectory::find_system_xcodes()? {
for platform in dir.platforms()? {
assert_eq!(
platform.path,
dir.platforms_path().join(platform.directory_name())
);
assert_eq!(
platform.path,
platform.path_in_developer_directory(dir.path())
);
assert!(!matches!(platform.platform, Platform::Unknown(_)));
}
}
Ok(())
}
#[test]
fn apple_platform() -> Result<(), Error> {
assert_eq!(Platform::from_str("macosx")?, Platform::MacOsX);
assert_eq!(Platform::from_str("MacOSX")?, Platform::MacOsX);
Ok(())
}
#[test]
fn target_platform() -> Result<(), Error> {
use Platform::*;
fn test(target: &str, platform: Platform) {
assert_eq!(Platform::from_target_triple(target).unwrap(), platform);
}
test("aarch64-apple-darwin", MacOsX);
test("aarch64-apple-ios", IPhoneOs);
test("aarch64-apple-ios-macabi", IPhoneOs);
test("aarch64-apple-ios-sim", IPhoneSimulator);
test("aarch64-apple-tvos", AppleTvOs); test("aarch64-apple-watchos-sim", WatchSimulator);
test("arm64_32-apple-watchos", WatchOs);
test("armv7-apple-ios", IPhoneOs);
test("armv7k-apple-watchos", WatchOs);
test("armv7s-apple-ios", IPhoneOs);
test("i386-apple-ios", IPhoneSimulator);
test("i686-apple-darwin", MacOsX);
test("x86_64-apple-darwin", MacOsX);
test("x86_64-apple-ios", IPhoneSimulator);
test("x86_64-apple-ios-macabi", IPhoneOs);
test("x86_64-apple-tvos", AppleTvSimulator);
test("x86_64-apple-watchos-sim", WatchSimulator);
test("aarch64-apple-xros", XrOs);
test("aarch64-apple-xros-sim", XrOsSimulator);
assert!(Platform::from_target_triple("x86_64-unknown-linux-gnu").is_err());
Ok(())
}
#[test]
fn sdk_version() -> Result<(), Error> {
let v = SdkVersion::from("foo");
assert!(v.normalized_version().is_err());
assert!(v.semantic_version().is_err());
let v = SdkVersion::from("12");
assert_eq!(v.normalized_version()?, (12, 0, 0));
assert_eq!(v.semantic_version()?, "12.0.0");
let v = SdkVersion::from("12.3");
assert_eq!(v.normalized_version()?, (12, 3, 0));
assert_eq!(v.semantic_version()?, "12.3.0");
let v = SdkVersion::from("12.3.1");
assert_eq!(v.normalized_version()?, (12, 3, 1));
assert_eq!(v.semantic_version()?, "12.3.1");
let v = SdkVersion::from("12.3.1.2");
assert!(v.normalized_version().is_err());
assert_eq!(
SdkVersion::from("12").cmp(&SdkVersion::from("11")),
Ordering::Greater
);
assert_eq!(
SdkVersion::from("12").cmp(&SdkVersion::from("12")),
Ordering::Equal
);
assert_eq!(
SdkVersion::from("12").cmp(&SdkVersion::from("13")),
Ordering::Less
);
Ok(())
}
#[test]
fn sdk_sorting() {
let sorting = SdkSorting::VersionAscending;
assert_eq!(
sorting.compare_version(Some(&SdkVersion::from("12")), Some(&SdkVersion::from("11"))),
Ordering::Greater
);
assert_eq!(
sorting.compare_version(Some(&SdkVersion::from("11")), Some(&SdkVersion::from("12"))),
Ordering::Less
);
let sorting = SdkSorting::VersionDescending;
assert_eq!(
sorting.compare_version(Some(&SdkVersion::from("12")), Some(&SdkVersion::from("11"))),
Ordering::Less
);
assert_eq!(
sorting.compare_version(Some(&SdkVersion::from("11")), Some(&SdkVersion::from("12"))),
Ordering::Greater
);
}
#[test]
fn parse_sdk_path() -> Result<(), Error> {
assert!(SdkPath::from_path("foo").is_err());
assert!(SdkPath::from_path("foo.bar").is_err());
let sdk = SdkPath::from_path("MacOSX.sdk")?;
assert_eq!(sdk.platform, Platform::MacOsX);
assert_eq!(sdk.version, None);
let sdk = SdkPath::from_path("MacOSX12.3.sdk")?;
assert_eq!(sdk.platform, Platform::MacOsX);
assert_eq!(sdk.version, Some("12.3".to_string().into()));
Ok(())
}
#[test]
fn search_all() -> Result<(), Error> {
let search = SdkSearch::default().location(SdkSearchLocation::SystemXcodes);
search.search::<SimpleSdk>()?;
Ok(())
}
#[cfg(target_os = "macos")]
#[test]
fn github_actions() -> Result<(), Error> {
if std::env::var("GITHUB_ACTIONS").is_err() {
return Ok(());
}
assert_eq!(
DeveloperDirectory::default_xcode(),
Some(DeveloperDirectory {
path: PathBuf::from("/Applications/Xcode.app/Contents/Developer")
})
);
assert!(PathBuf::from(COMMAND_LINE_TOOLS_DEFAULT_PATH).exists());
assert!(crate::find_system_xcode_applications()?.len() > 5);
assert_eq!(
crate::find_system_xcode_applications()?.len(),
DeveloperDirectory::find_system_xcodes()?.len()
);
for platform in [Platform::MacOsX, Platform::IPhoneOs, Platform::WatchOs] {
let sdks = SdkSearch::default()
.platform(platform)
.search::<SimpleSdk>()?;
assert!(!sdks.is_empty());
}
let sdks = SdkSearch::default()
.platform(Platform::MacOsX)
.minimum_version(SdkVersion::from("11.0"))
.search::<SimpleSdk>()?;
assert!(!sdks.is_empty());
Ok(())
}
}