1use {
12 crate::{AppleSdk, Error, Platform, SdkPath, SdkVersion, SimpleSdk},
13 serde::Deserialize,
14 std::{
15 collections::HashMap,
16 path::{Path, PathBuf},
17 },
18};
19
20#[derive(Debug, Deserialize)]
22#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
23pub struct SdkSettingsJsonDefaultProperties {
24 pub platform_name: String,
25}
26
27#[derive(Clone, Debug, Deserialize)]
29#[serde(rename_all = "PascalCase")]
30pub struct SupportedTarget {
31 pub archs: Vec<String>,
35
36 pub default_deployment_target: String,
41
42 pub default_variant: Option<String>,
44
45 pub deployment_target_setting_name: Option<String>,
51
52 pub minimum_deployment_target: String,
58
59 pub platform_family_name: Option<String>,
63
64 pub valid_deployment_targets: Vec<String>,
69}
70
71impl SupportedTarget {
72 pub fn deployment_targets_versions(&self) -> Vec<SdkVersion> {
74 self.valid_deployment_targets
75 .iter()
76 .map(SdkVersion::from)
77 .collect::<Vec<_>>()
78 }
79}
80
81#[derive(Debug, Deserialize)]
83#[serde(rename_all = "PascalCase")]
84pub struct SdkSettingsJson {
85 pub canonical_name: String,
86 pub default_deployment_target: String,
87 pub default_properties: SdkSettingsJsonDefaultProperties,
88 pub default_variant: Option<String>,
89 pub display_name: String,
90 pub maximum_deployment_target: String,
91 pub minimal_display_name: String,
92 pub supported_targets: HashMap<String, SupportedTarget>,
93 pub version: String,
94}
95
96#[derive(Clone, Debug)]
101pub struct ParsedSdk {
102 path: PathBuf,
104
105 is_symlink: bool,
107
108 platform: Platform,
110
111 version: SdkVersion,
112
113 pub platform_name: String,
118
119 pub name: String,
121
122 pub default_deployment_target: String,
126
127 pub default_variant: Option<String>,
135
136 pub display_name: String,
140
141 pub maximum_deployment_target: String,
146
147 pub minimal_display_name: String,
151
152 pub supported_targets: HashMap<String, SupportedTarget>,
160}
161
162impl AsRef<Path> for ParsedSdk {
163 fn as_ref(&self) -> &Path {
164 &self.path
165 }
166}
167
168impl AppleSdk for ParsedSdk {
169 fn from_directory(path: &Path) -> Result<Self, Error> {
170 let sdk = SdkPath::from_path(path)?;
171
172 let metadata = std::fs::symlink_metadata(path)?;
174
175 let is_symlink = metadata.file_type().is_symlink();
176
177 let json_path = path.join("SDKSettings.json");
178 let plist_path = path.join("SDKSettings.plist");
179
180 if json_path.exists() {
181 let fh = std::fs::File::open(&json_path)?;
182 let value: SdkSettingsJson = serde_json::from_reader(fh)?;
183
184 Self::from_json(path.to_path_buf(), is_symlink, sdk.platform, value)
185 } else if plist_path.exists() {
186 let value = plist::Value::from_file(&plist_path)?;
187
188 Self::from_plist(path.to_path_buf(), is_symlink, sdk.platform, value)
189 } else {
190 Err(Error::PathNotSdk(path.to_path_buf()))
191 }
192 }
193
194 fn is_symlink(&self) -> bool {
195 self.is_symlink
196 }
197
198 fn platform(&self) -> &Platform {
199 &self.platform
200 }
201
202 fn version(&self) -> Option<&SdkVersion> {
203 Some(&self.version)
204 }
205
206 fn supports_deployment_target(
211 &self,
212 target_name: &str,
213 target_version: &SdkVersion,
214 ) -> Result<bool, Error> {
215 Ok(
216 if let Some(target) = self.supported_targets.get(target_name) {
217 target
218 .deployment_targets_versions()
219 .contains(target_version)
220 } else {
221 false
222 },
223 )
224 }
225}
226
227impl ParsedSdk {
228 pub fn from_json(
233 path: PathBuf,
234 is_symlink: bool,
235 platform: Platform,
236 value: SdkSettingsJson,
237 ) -> Result<Self, Error> {
238 Ok(Self {
239 path,
240 is_symlink,
241 platform,
242 version: value.version.into(),
243 platform_name: value.default_properties.platform_name,
244 name: value.canonical_name,
245 default_deployment_target: value.default_deployment_target,
246 default_variant: value.default_variant,
247 display_name: value.display_name,
248 maximum_deployment_target: value.maximum_deployment_target,
249 minimal_display_name: value.minimal_display_name,
250 supported_targets: value.supported_targets,
251 })
252 }
253
254 pub fn from_plist(
260 path: PathBuf,
261 is_symlink: bool,
262 platform: Platform,
263 value: plist::Value,
264 ) -> Result<Self, Error> {
265 let value = value.into_dictionary().ok_or(Error::PlistNotDictionary)?;
266
267 let get_string = |dict: &plist::Dictionary, key: &str| -> Result<String, Error> {
268 Ok(dict
269 .get(key)
270 .ok_or_else(|| Error::PlistKeyMissing(key.to_string()))?
271 .as_string()
272 .ok_or_else(|| Error::PlistKeyNotString(key.to_string()))?
273 .to_string())
274 };
275
276 let name = get_string(&value, "CanonicalName")?;
277 let display_name = get_string(&value, "DisplayName")?;
278 let maximum_deployment_target = get_string(&value, "MaximumDeploymentTarget")?;
279 let minimal_display_name = get_string(&value, "MinimalDisplayName")?;
280 let version = get_string(&value, "Version")?;
281
282 let props = value
283 .get("DefaultProperties")
284 .ok_or_else(|| Error::PlistKeyMissing("DefaultProperties".to_string()))?
285 .as_dictionary()
286 .ok_or_else(|| Error::PlistKeyNotDictionary("DefaultProperties".to_string()))?;
287
288 let platform_name = get_string(props, "PLATFORM_NAME")?;
289
290 let default_deployment_target =
300 if let Ok(setting_name) = get_string(props, "DEPLOYMENT_TARGET_SETTING_NAME") {
301 get_string(props, &setting_name)?
302 } else if let Ok(value) = get_string(
303 props,
304 &format!("{}_DEPLOYMENT_TARGET", platform_name.to_ascii_uppercase()),
305 ) {
306 value
307 } else {
308 let supported_targets = value
309 .get("SupportedTargets")
310 .ok_or_else(|| Error::PlistKeyMissing("SupportedTargets".to_string()))?
311 .as_dictionary()
312 .ok_or_else(|| Error::PlistKeyNotDictionary("SupportedTargets".to_string()))?;
313
314 let default_target = supported_targets
315 .get(&platform_name)
316 .ok_or_else(|| Error::PlistKeyMissing(platform_name.clone()))?
317 .as_dictionary()
318 .ok_or_else(|| Error::PlistKeyNotDictionary(platform_name.clone()))?;
319
320 let llvm_target_triple = get_string(default_target, "LLVMTargetTripleSys")?;
321
322 get_string(
323 props,
324 &format!(
325 "{}_DEPLOYMENT_TARGET",
326 llvm_target_triple.to_ascii_uppercase()
327 ),
328 )?
329 };
330
331 Ok(Self {
332 path,
333 is_symlink,
334 platform,
335 version: version.into(),
336 platform_name,
337 name,
338 default_deployment_target,
339 default_variant: None,
340 display_name,
341 maximum_deployment_target,
342 minimal_display_name,
343 supported_targets: HashMap::new(),
344 })
345 }
346}
347
348impl TryFrom<SimpleSdk> for ParsedSdk {
349 type Error = Error;
350
351 fn try_from(v: SimpleSdk) -> Result<Self, Self::Error> {
352 Self::from_directory(v.path())
353 }
354}
355
356#[cfg(test)]
357mod test {
358 use {
359 super::*,
360 crate::{
361 DeveloperDirectory, SdkSearch, SdkSearchLocation, COMMAND_LINE_TOOLS_DEFAULT_PATH,
362 },
363 };
364
365 const MACOSX_10_9_SETTINGS_PLIST: &[u8] = include_bytes!("testfiles/macosx10.9-settings.plist");
366 const MACOSX_10_10_SETTINGS_PLIST: &[u8] =
367 include_bytes!("testfiles/macosx10.10-settings.plist");
368 const MACOSX_10_15_SETTINGS_JSON: &[u8] = include_bytes!("testfiles/macosx10.15-settings.json");
369 const MACOSX_11_3_SETTINGS_JSON: &[u8] = include_bytes!("testfiles/macosx11.3-settings.json");
370
371 fn macosx_10_9() -> Result<ParsedSdk, Error> {
372 let value = plist::Value::from_reader(std::io::Cursor::new(MACOSX_10_9_SETTINGS_PLIST))?;
373
374 ParsedSdk::from_plist(
375 PathBuf::from("MacOSX10.9.sdk"),
376 false,
377 Platform::MacOsX,
378 value,
379 )
380 }
381
382 fn macosx_10_10() -> Result<ParsedSdk, Error> {
383 let value = plist::Value::from_reader(std::io::Cursor::new(MACOSX_10_10_SETTINGS_PLIST))?;
384
385 ParsedSdk::from_plist(
386 PathBuf::from("MacOSX10.10.sdk"),
387 false,
388 Platform::MacOsX,
389 value,
390 )
391 }
392
393 fn macosx_10_15() -> Result<ParsedSdk, Error> {
394 let value = serde_json::from_slice::<SdkSettingsJson>(MACOSX_10_15_SETTINGS_JSON)?;
395
396 ParsedSdk::from_json(
397 PathBuf::from("MacOSX10.15.sdk"),
398 false,
399 Platform::MacOsX,
400 value,
401 )
402 }
403
404 fn macosx_11_3() -> Result<ParsedSdk, Error> {
405 let value = serde_json::from_slice::<SdkSettingsJson>(MACOSX_11_3_SETTINGS_JSON)?;
406
407 ParsedSdk::from_json(
408 PathBuf::from("MacOSX11.3.sdk"),
409 false,
410 Platform::MacOsX,
411 value,
412 )
413 }
414
415 fn all_test_sdks() -> Result<Vec<ParsedSdk>, Error> {
416 Ok(vec![
417 macosx_10_9()?,
418 macosx_10_10()?,
419 macosx_10_15()?,
420 macosx_11_3()?,
421 ])
422 }
423
424 #[test]
425 fn find_default_sdks() -> Result<(), Error> {
426 if let Ok(developer_dir) = DeveloperDirectory::find_default_required() {
427 assert!(!developer_dir.sdks::<ParsedSdk>()?.is_empty());
428 }
429
430 Ok(())
431 }
432
433 #[test]
434 fn find_command_line_tools_sdks() -> Result<(), Error> {
435 let sdk_path = PathBuf::from(COMMAND_LINE_TOOLS_DEFAULT_PATH).join("SDKs");
436
437 let res = ParsedSdk::find_command_line_tools_sdks()?;
438
439 if sdk_path.exists() {
440 assert!(res.is_some());
441 assert!(!res.unwrap().is_empty());
442 } else {
443 assert!(res.is_none());
444 }
445
446 Ok(())
447 }
448
449 #[test]
450 fn find_all_sdks() -> Result<(), Error> {
451 for dir in DeveloperDirectory::find_system_xcodes()? {
452 for sdk in dir.sdks::<ParsedSdk>()? {
453 assert!(!matches!(sdk.platform(), Platform::Unknown(_)));
454 assert!(sdk.version().is_some());
455 }
456 }
457
458 SdkSearch::default()
459 .location(SdkSearchLocation::SystemXcodes)
460 .search::<ParsedSdk>()?;
461
462 Ok(())
463 }
464
465 #[test]
466 fn parse_test_sdks() -> Result<(), Error> {
467 all_test_sdks()?;
468
469 Ok(())
470 }
471
472 #[test]
473 fn supports_deployment_target() -> Result<(), Error> {
474 let sdk = macosx_10_15()?;
475
476 assert!(!sdk.supports_deployment_target("ios", &SdkVersion::from("55.0"))?);
477 assert!(!sdk.supports_deployment_target("macosx", &SdkVersion::from("10.5"))?);
478 assert!(!sdk.supports_deployment_target("macosx", &SdkVersion::from("10.16"))?);
479 assert!(!sdk.supports_deployment_target("macosx", &SdkVersion::from("11.0"))?);
480
481 let mut versions = vec!["10.9", "10.10", "10.11", "10.12", "10.13", "10.14", "10.15"];
482
483 for version in &versions {
484 assert!(sdk.supports_deployment_target("macosx", &SdkVersion::from(*version))?);
485 }
486
487 let sdk = macosx_11_3()?;
488 versions.extend(["11.0", "11.1", "11.2", "11.3"]);
489
490 for version in &versions {
491 assert!(sdk.supports_deployment_target("macosx", &SdkVersion::from(*version))?);
492 }
493
494 assert!(!macosx_10_9()?.supports_deployment_target("macosx", &SdkVersion::from("10.9"))?);
496 assert!(!macosx_10_10()?.supports_deployment_target("macosx", &SdkVersion::from("10.9"))?);
497
498 Ok(())
499 }
500}