use {
crate::{AppleSdk, Error, Platform, SdkPath, SdkVersion, SimpleSdk},
serde::Deserialize,
std::{
collections::HashMap,
path::{Path, PathBuf},
},
};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub struct SdkSettingsJsonDefaultProperties {
pub platform_name: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SupportedTarget {
pub archs: Vec<String>,
pub default_deployment_target: String,
pub default_variant: Option<String>,
pub deployment_target_setting_name: Option<String>,
pub minimum_deployment_target: String,
pub platform_family_name: Option<String>,
pub valid_deployment_targets: Vec<String>,
}
impl SupportedTarget {
pub fn deployment_targets_versions(&self) -> Vec<SdkVersion> {
self.valid_deployment_targets
.iter()
.map(SdkVersion::from)
.collect::<Vec<_>>()
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SdkSettingsJson {
pub canonical_name: String,
pub default_deployment_target: String,
pub default_properties: SdkSettingsJsonDefaultProperties,
pub default_variant: Option<String>,
pub display_name: String,
pub maximum_deployment_target: String,
pub minimal_display_name: String,
pub supported_targets: HashMap<String, SupportedTarget>,
pub version: String,
}
#[derive(Clone, Debug)]
pub struct ParsedSdk {
path: PathBuf,
is_symlink: bool,
platform: Platform,
version: SdkVersion,
pub platform_name: String,
pub name: String,
pub default_deployment_target: String,
pub default_variant: Option<String>,
pub display_name: String,
pub maximum_deployment_target: String,
pub minimal_display_name: String,
pub supported_targets: HashMap<String, SupportedTarget>,
}
impl AsRef<Path> for ParsedSdk {
fn as_ref(&self) -> &Path {
&self.path
}
}
impl AppleSdk for ParsedSdk {
fn from_directory(path: &Path) -> Result<Self, Error> {
let sdk = SdkPath::from_path(path)?;
let metadata = std::fs::symlink_metadata(path)?;
let is_symlink = metadata.file_type().is_symlink();
let json_path = path.join("SDKSettings.json");
let plist_path = path.join("SDKSettings.plist");
if json_path.exists() {
let fh = std::fs::File::open(&json_path)?;
let value: SdkSettingsJson = serde_json::from_reader(fh)?;
Self::from_json(path.to_path_buf(), is_symlink, sdk.platform, value)
} else if plist_path.exists() {
let value = plist::Value::from_file(&plist_path)?;
Self::from_plist(path.to_path_buf(), is_symlink, sdk.platform, value)
} else {
Err(Error::PathNotSdk(path.to_path_buf()))
}
}
fn is_symlink(&self) -> bool {
self.is_symlink
}
fn platform(&self) -> &Platform {
&self.platform
}
fn version(&self) -> Option<&SdkVersion> {
Some(&self.version)
}
fn supports_deployment_target(
&self,
target_name: &str,
target_version: &SdkVersion,
) -> Result<bool, Error> {
Ok(
if let Some(target) = self.supported_targets.get(target_name) {
target
.deployment_targets_versions()
.contains(target_version)
} else {
false
},
)
}
}
impl ParsedSdk {
pub fn from_json(
path: PathBuf,
is_symlink: bool,
platform: Platform,
value: SdkSettingsJson,
) -> Result<Self, Error> {
Ok(Self {
path,
is_symlink,
platform,
version: value.version.into(),
platform_name: value.default_properties.platform_name,
name: value.canonical_name,
default_deployment_target: value.default_deployment_target,
default_variant: value.default_variant,
display_name: value.display_name,
maximum_deployment_target: value.maximum_deployment_target,
minimal_display_name: value.minimal_display_name,
supported_targets: value.supported_targets,
})
}
pub fn from_plist(
path: PathBuf,
is_symlink: bool,
platform: Platform,
value: plist::Value,
) -> Result<Self, Error> {
let value = value.into_dictionary().ok_or(Error::PlistNotDictionary)?;
let get_string = |dict: &plist::Dictionary, key: &str| -> Result<String, Error> {
Ok(dict
.get(key)
.ok_or_else(|| Error::PlistKeyMissing(key.to_string()))?
.as_string()
.ok_or_else(|| Error::PlistKeyNotString(key.to_string()))?
.to_string())
};
let name = get_string(&value, "CanonicalName")?;
let display_name = get_string(&value, "DisplayName")?;
let maximum_deployment_target = get_string(&value, "MaximumDeploymentTarget")?;
let minimal_display_name = get_string(&value, "MinimalDisplayName")?;
let version = get_string(&value, "Version")?;
let props = value
.get("DefaultProperties")
.ok_or_else(|| Error::PlistKeyMissing("DefaultProperties".to_string()))?
.as_dictionary()
.ok_or_else(|| Error::PlistKeyNotDictionary("DefaultProperties".to_string()))?;
let platform_name = get_string(props, "PLATFORM_NAME")?;
let default_deployment_target =
if let Ok(setting_name) = get_string(props, "DEPLOYMENT_TARGET_SETTING_NAME") {
get_string(props, &setting_name)?
} else if let Ok(value) = get_string(
props,
&format!("{}_DEPLOYMENT_TARGET", platform_name.to_ascii_uppercase()),
) {
value
} else {
let supported_targets = value
.get("SupportedTargets")
.ok_or_else(|| Error::PlistKeyMissing("SupportedTargets".to_string()))?
.as_dictionary()
.ok_or_else(|| Error::PlistKeyNotDictionary("SupportedTargets".to_string()))?;
let default_target = supported_targets
.get(&platform_name)
.ok_or_else(|| Error::PlistKeyMissing(platform_name.clone()))?
.as_dictionary()
.ok_or_else(|| Error::PlistKeyNotDictionary(platform_name.clone()))?;
let llvm_target_triple = get_string(default_target, "LLVMTargetTripleSys")?;
get_string(
props,
&format!(
"{}_DEPLOYMENT_TARGET",
llvm_target_triple.to_ascii_uppercase()
),
)?
};
Ok(Self {
path,
is_symlink,
platform,
version: version.into(),
platform_name,
name,
default_deployment_target,
default_variant: None,
display_name,
maximum_deployment_target,
minimal_display_name,
supported_targets: HashMap::new(),
})
}
}
impl TryFrom<SimpleSdk> for ParsedSdk {
type Error = Error;
fn try_from(v: SimpleSdk) -> Result<Self, Self::Error> {
Self::from_directory(v.path())
}
}
#[cfg(test)]
mod test {
use {
super::*,
crate::{
DeveloperDirectory, SdkSearch, SdkSearchLocation, COMMAND_LINE_TOOLS_DEFAULT_PATH,
},
};
const MACOSX_10_9_SETTINGS_PLIST: &[u8] = include_bytes!("testfiles/macosx10.9-settings.plist");
const MACOSX_10_10_SETTINGS_PLIST: &[u8] =
include_bytes!("testfiles/macosx10.10-settings.plist");
const MACOSX_10_15_SETTINGS_JSON: &[u8] = include_bytes!("testfiles/macosx10.15-settings.json");
const MACOSX_11_3_SETTINGS_JSON: &[u8] = include_bytes!("testfiles/macosx11.3-settings.json");
fn macosx_10_9() -> Result<ParsedSdk, Error> {
let value = plist::Value::from_reader(std::io::Cursor::new(MACOSX_10_9_SETTINGS_PLIST))?;
ParsedSdk::from_plist(
PathBuf::from("MacOSX10.9.sdk"),
false,
Platform::MacOsX,
value,
)
}
fn macosx_10_10() -> Result<ParsedSdk, Error> {
let value = plist::Value::from_reader(std::io::Cursor::new(MACOSX_10_10_SETTINGS_PLIST))?;
ParsedSdk::from_plist(
PathBuf::from("MacOSX10.10.sdk"),
false,
Platform::MacOsX,
value,
)
}
fn macosx_10_15() -> Result<ParsedSdk, Error> {
let value = serde_json::from_slice::<SdkSettingsJson>(MACOSX_10_15_SETTINGS_JSON)?;
ParsedSdk::from_json(
PathBuf::from("MacOSX10.15.sdk"),
false,
Platform::MacOsX,
value,
)
}
fn macosx_11_3() -> Result<ParsedSdk, Error> {
let value = serde_json::from_slice::<SdkSettingsJson>(MACOSX_11_3_SETTINGS_JSON)?;
ParsedSdk::from_json(
PathBuf::from("MacOSX11.3.sdk"),
false,
Platform::MacOsX,
value,
)
}
fn all_test_sdks() -> Result<Vec<ParsedSdk>, Error> {
Ok(vec![
macosx_10_9()?,
macosx_10_10()?,
macosx_10_15()?,
macosx_11_3()?,
])
}
#[test]
fn find_default_sdks() -> Result<(), Error> {
if let Ok(developer_dir) = DeveloperDirectory::find_default_required() {
assert!(!developer_dir.sdks::<ParsedSdk>()?.is_empty());
}
Ok(())
}
#[test]
fn find_command_line_tools_sdks() -> Result<(), Error> {
let sdk_path = PathBuf::from(COMMAND_LINE_TOOLS_DEFAULT_PATH).join("SDKs");
let res = ParsedSdk::find_command_line_tools_sdks()?;
if sdk_path.exists() {
assert!(res.is_some());
assert!(!res.unwrap().is_empty());
} else {
assert!(res.is_none());
}
Ok(())
}
#[test]
fn find_all_sdks() -> Result<(), Error> {
for dir in DeveloperDirectory::find_system_xcodes()? {
for sdk in dir.sdks::<ParsedSdk>()? {
assert!(!matches!(sdk.platform(), Platform::Unknown(_)));
assert!(sdk.version().is_some());
}
}
SdkSearch::default()
.location(SdkSearchLocation::SystemXcodes)
.search::<ParsedSdk>()?;
Ok(())
}
#[test]
fn parse_test_sdks() -> Result<(), Error> {
all_test_sdks()?;
Ok(())
}
#[test]
fn supports_deployment_target() -> Result<(), Error> {
let sdk = macosx_10_15()?;
assert!(!sdk.supports_deployment_target("ios", &SdkVersion::from("55.0"))?);
assert!(!sdk.supports_deployment_target("macosx", &SdkVersion::from("10.5"))?);
assert!(!sdk.supports_deployment_target("macosx", &SdkVersion::from("10.16"))?);
assert!(!sdk.supports_deployment_target("macosx", &SdkVersion::from("11.0"))?);
let mut versions = vec!["10.9", "10.10", "10.11", "10.12", "10.13", "10.14", "10.15"];
for version in &versions {
assert!(sdk.supports_deployment_target("macosx", &SdkVersion::from(*version))?);
}
let sdk = macosx_11_3()?;
versions.extend(["11.0", "11.1", "11.2", "11.3"]);
for version in &versions {
assert!(sdk.supports_deployment_target("macosx", &SdkVersion::from(*version))?);
}
assert!(!macosx_10_9()?.supports_deployment_target("macosx", &SdkVersion::from("10.9"))?);
assert!(!macosx_10_10()?.supports_deployment_target("macosx", &SdkVersion::from("10.9"))?);
Ok(())
}
}