diff --git a/crates/header-translator/src/availability.rs b/crates/header-translator/src/availability.rs index 264909101..ddef2a4b9 100644 --- a/crates/header-translator/src/availability.rs +++ b/crates/header-translator/src/availability.rs @@ -6,14 +6,90 @@ use clang::{Entity, PlatformAvailability, Version}; use crate::context::Context; #[derive(Debug, Clone, PartialEq, Default)] -struct Unavailable { - ios: bool, - ios_app_extension: bool, - macos: bool, - macos_app_extension: bool, - maccatalyst: bool, - watchos: bool, - tvos: bool, +pub struct Unavailable { + pub(crate) ios: bool, + pub(crate) ios_app_extension: bool, + pub(crate) macos: bool, + pub(crate) macos_app_extension: bool, + pub(crate) maccatalyst: bool, + pub(crate) watchos: bool, + pub(crate) tvos: bool, + pub(crate) library_unavailablility: Option>, +} + +impl Unavailable { + fn list_unavailable_oses(&self) -> Vec<&str> { + let mut unavailable_oses = Vec::new(); + if self.ios + && !self + .library_unavailablility + .as_ref() + .map(|u| u.ios) + .unwrap_or_else(|| false) + { + unavailable_oses.push("ios"); + } + if self.macos + && !self + .library_unavailablility + .as_ref() + .map(|u| u.macos) + .unwrap_or_else(|| false) + { + unavailable_oses.push("macos"); + } + if self.tvos + && !self + .library_unavailablility + .as_ref() + .map(|u| u.tvos) + .unwrap_or_else(|| false) + { + unavailable_oses.push("tvos"); + } + if self.watchos + && !self + .library_unavailablility + .as_ref() + .map(|u| u.watchos) + .unwrap_or_else(|| false) + { + unavailable_oses.push("watchos"); + } + unavailable_oses + } + + /// In some cases of enums, we need to know the availability of the parent enum and the enum + /// variant. + pub fn merge(&self, other: &Self) -> Self { + Self { + ios: self.ios || other.ios, + ios_app_extension: self.ios_app_extension || other.ios_app_extension, + macos: self.macos || other.macos, + macos_app_extension: self.macos_app_extension || other.macos_app_extension, + tvos: self.tvos || other.tvos, + watchos: self.watchos || other.watchos, + maccatalyst: self.maccatalyst || other.maccatalyst, + library_unavailablility: self.library_unavailablility.clone(), + } + } +} +impl fmt::Display for Unavailable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let unavailable_oses = self.list_unavailable_oses(); + let unavailable_oses = unavailable_oses + .iter() + .map(|os| format!("target_os = \"{os}\"")) + .collect::>() + .join(","); + if unavailable_oses.len() > 1 { + write!(f, "#[cfg(not(any({unavailable_oses})))]")?; + } + if unavailable_oses.len() == 1 { + write!(f, "#[cfg(not({unavailable_oses}))]")?; + } + Ok(()) + } } #[derive(Debug, Clone, PartialEq, Default)] @@ -29,7 +105,7 @@ struct Versions { #[derive(Debug, Clone, PartialEq)] pub struct Availability { - unavailable: Unavailable, + pub(crate) unavailable: Unavailable, introduced: Versions, deprecated: Versions, message: Option, @@ -37,12 +113,18 @@ pub struct Availability { } impl Availability { - pub fn parse(entity: &Entity<'_>, _context: &Context<'_>) -> Self { + pub fn parse(entity: &Entity<'_>, context: &Context<'_>) -> Self { let availabilities = entity .get_platform_availability() .expect("platform availability"); - let mut unavailable = Unavailable::default(); + let mut unavailable = Unavailable { + library_unavailablility: context + .library_unavailability + .as_ref() + .map(|l| Box::new(l.clone())), + ..Default::default() + }; let mut introduced = Versions::default(); let mut deprecated = Versions::default(); let mut message = None; @@ -157,7 +239,8 @@ impl fmt::Display for Availability { } } } - // TODO: Emit `cfg` attributes based on `self.unavailable` + write!(f, "{}", self.unavailable)?; + // TODO: Emit availability checks based on `self.introduced` Ok(()) } diff --git a/crates/header-translator/src/config.rs b/crates/header-translator/src/config.rs index 8c43db029..1387a2916 100644 --- a/crates/header-translator/src/config.rs +++ b/crates/header-translator/src/config.rs @@ -5,6 +5,7 @@ use std::path::Path; use serde::Deserialize; +use crate::availability::Unavailable; use crate::data; use crate::stmt::{Derives, Mutability}; @@ -80,6 +81,18 @@ pub struct LibraryData { #[serde(default)] pub watchos: Option, } +impl LibraryData { + pub(crate) fn unavailability(&self) -> Unavailable { + Unavailable { + ios: self.ios.is_none(), + macos: self.macos.is_none(), + tvos: self.tvos.is_none(), + watchos: self.watchos.is_none(), + maccatalyst: self.maccatalyst.is_none(), + ..Default::default() + } + } +} #[derive(Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[serde(deny_unknown_fields)] diff --git a/crates/header-translator/src/context.rs b/crates/header-translator/src/context.rs index bbdb06009..a882c3739 100644 --- a/crates/header-translator/src/context.rs +++ b/crates/header-translator/src/context.rs @@ -6,6 +6,7 @@ use apple_sdk::SdkPath; use clang::source::Location; use clang::Entity; +use crate::availability::Unavailable; use crate::config::Config; pub struct Context<'a> { @@ -14,6 +15,7 @@ pub struct Context<'a> { framework_dir: PathBuf, include_dir: PathBuf, system_headers: HashSet<&'static Path>, + pub library_unavailability: Option, } impl<'a> Context<'a> { @@ -29,6 +31,7 @@ impl<'a> Context<'a> { Path::new("objc/NSObject.h"), Path::new("objc/NSObjCRuntime.h"), ]), + library_unavailability: None, } } diff --git a/crates/header-translator/src/library.rs b/crates/header-translator/src/library.rs index c1051d189..4c22900f4 100644 --- a/crates/header-translator/src/library.rs +++ b/crates/header-translator/src/library.rs @@ -4,17 +4,20 @@ use std::fs; use std::io; use std::path::Path; +use crate::availability::Unavailable; use crate::file::{File, FILE_PRELUDE}; #[derive(Debug, PartialEq, Default)] pub struct Library { pub files: BTreeMap, + pub unavailability: Unavailable, } impl Library { - pub fn new() -> Self { + pub(crate) fn new(unavailability: Unavailable) -> Self { Self { files: BTreeMap::new(), + unavailability, } } @@ -60,16 +63,16 @@ impl fmt::Display for Library { // NOTE: some SDK files have '+' in the file name let name = name.replace('+', "_"); for stmt in &file.stmts { - let mut iter = stmt.declared_types(); - if let Some(item) = iter.next() { - // Use a set to deduplicate features, and to have them in - // a consistent order - let mut features = BTreeSet::new(); - stmt.visit_required_types(|item| { - if let Some(feature) = item.feature() { - features.insert(format!("feature = \"{feature}\"")); - } - }); + // Use a set to deduplicate features, and to have them in + // a consistent order + let mut features = BTreeSet::new(); + stmt.visit_required_types(|item| { + if let Some(feature) = item.feature() { + features.insert(format!("feature = \"{feature}\"")); + } + }); + + for (item, unavailability) in stmt.declared_types() { match features.len() { 0 => {} 1 => { @@ -87,12 +90,8 @@ impl fmt::Display for Library { )?; } } - - writeln!(f, "pub use self::__{name}::{{{item}")?; - for item in iter { - writeln!(f, ", {item}")?; - } - writeln!(f, "}};")?; + write!(f, "{unavailability}")?; + writeln!(f, "pub use self::__{name}::{{{item}}};")?; } } } diff --git a/crates/header-translator/src/main.rs b/crates/header-translator/src/main.rs index 0abf71e23..4e7510ced 100644 --- a/crates/header-translator/src/main.rs +++ b/crates/header-translator/src/main.rs @@ -197,7 +197,7 @@ fn parse_sdk(index: &Index<'_>, sdk: &SdkPath, llvm_target: &str, config: &Confi let tu = get_translation_unit(index, sdk, llvm_target); let mut preprocessing = true; - let mut result = Output::from_libraries(config.libraries.keys()); + let mut result = Output::from_libraries(&config.libraries); let mut library_span = None; let mut library_span_name = String::new(); @@ -270,6 +270,7 @@ fn parse_sdk(index: &Index<'_>, sdk: &SdkPath, llvm_target: &str, config: &Confi preprocessing = false; // No more includes / macro expansions after this line let file = library.files.get_mut(&file_name).expect("file"); + context.library_unavailability = Some(library.unavailability.clone()); for stmt in Stmt::parse(&entity, &context) { file.add_stmt(stmt); } diff --git a/crates/header-translator/src/output.rs b/crates/header-translator/src/output.rs index f274a56fc..c783f7391 100644 --- a/crates/header-translator/src/output.rs +++ b/crates/header-translator/src/output.rs @@ -1,7 +1,8 @@ +use std::collections::HashMap; use std::collections::{BTreeMap, BTreeSet}; use std::str::FromStr; -use crate::config::Config; +use crate::config::{Config, LibraryData}; use crate::library::Library; use crate::stmt::Stmt; @@ -11,10 +12,10 @@ pub struct Output { } impl Output { - pub fn from_libraries(libraries: impl IntoIterator>) -> Self { + pub fn from_libraries(libraries: &HashMap) -> Self { let libraries = libraries - .into_iter() - .map(|name| (name.into(), Library::new())) + .iter() + .map(|(name, library_data)| (name.into(), Library::new(library_data.unavailability()))) .collect(); Self { libraries } } diff --git a/crates/header-translator/src/stmt.rs b/crates/header-translator/src/stmt.rs index b98f1a962..ae39e0080 100644 --- a/crates/header-translator/src/stmt.rs +++ b/crates/header-translator/src/stmt.rs @@ -7,7 +7,7 @@ use std::mem; use clang::{Entity, EntityKind, EntityVisitResult}; -use crate::availability::Availability; +use crate::availability::{Availability, Unavailable}; use crate::config::{ClassData, MethodData}; use crate::context::Context; use crate::expr::Expr; @@ -579,7 +579,7 @@ impl Stmt { cls: id.clone(), generics: generics.clone(), category: ItemIdentifier::with_name(None, entity, context), - availability: Availability::parse(entity, context), + availability: availability.clone(), superclasses: superclasses.clone(), methods, description: Some(format!( @@ -1156,30 +1156,66 @@ impl Stmt { } } - pub(crate) fn declared_types(&self) -> impl Iterator { + pub(crate) fn declared_types(&self) -> impl Iterator { match self { - Stmt::ClassDecl { id, skipped, .. } => { + Stmt::ClassDecl { + id, + skipped, + availability, + .. + } => { if *skipped { None } else { - Some(&*id.name) + Some((&*id.name, availability.unavailable.clone())) } } Stmt::Methods { .. } => None, - Stmt::ProtocolDecl { id, .. } => Some(&*id.name), + Stmt::ProtocolDecl { + id, availability, .. + } => Some((&*id.name, availability.unavailable.clone())), Stmt::ProtocolImpl { .. } => None, - Stmt::StructDecl { id, .. } => Some(&*id.name), - Stmt::EnumDecl { id, .. } => id.name.as_deref(), - Stmt::VarDecl { id, .. } => Some(&*id.name), - Stmt::FnDecl { id, body, .. } if body.is_none() => Some(&*id.name), + Stmt::StructDecl { + id, availability, .. + } => Some((&*id.name, availability.unavailable.clone())), + Stmt::EnumDecl { + id, availability, .. + } => id + .name + .as_deref() + .map(|name| (name, availability.unavailable.clone())), + Stmt::VarDecl { + id, availability, .. + } => Some((&*id.name, availability.unavailable.clone())), + Stmt::FnDecl { + id, + body, + availability, + .. + } if body.is_none() => Some((&*id.name, availability.unavailable.clone())), // TODO Stmt::FnDecl { .. } => None, - Stmt::AliasDecl { id, .. } => Some(&*id.name), + Stmt::AliasDecl { + id, availability, .. + } => Some((&*id.name, availability.unavailable.clone())), } .into_iter() .chain({ - if let Stmt::EnumDecl { variants, .. } = self { - variants.iter().map(|(name, _, _)| &**name).collect() + if let Stmt::EnumDecl { + variants, + availability, + .. + } = self + { + variants + .iter() + .map(|(name, variant_availability, _)| { + let unavailable = availability + .unavailable + .merge(&variant_availability.unavailable); + (&**name, unavailable) + }) + .collect() } else { vec![] } @@ -1304,6 +1340,7 @@ impl fmt::Display for Stmt { writeln!(f)?; + write!(f, "{availability}")?; if let Some(feature) = &main_feature_gate { writeln!(f, " #[cfg(feature = \"{feature}\")]")?; } @@ -1361,11 +1398,13 @@ impl fmt::Display for Stmt { generics, category, // TODO: Output `#[deprecated]` only on categories - availability: _, + availability, superclasses, methods, description, } => { + let unavailable = availability.unavailable.clone(); + write!(f, "{unavailable}")?; writeln!(f, "extern_methods!(")?; if let Some(description) = description { writeln!(f, " /// {description}")?; @@ -1379,6 +1418,7 @@ impl fmt::Display for Stmt { if let Some(feature) = cls.feature() { writeln!(f, " #[cfg(feature = \"{feature}\")]")?; } + write!(f, "{unavailable}")?; writeln!( f, " unsafe impl{} {}{} {{", @@ -1434,6 +1474,7 @@ impl fmt::Display for Stmt { // Assume new methods require no extra features writeln!(f, " #[cfg(feature = \"{feature}\")]")?; } + write!(f, "{unavailable}")?; writeln!( f, "impl{} DefaultId for {}{} {{", @@ -1452,7 +1493,7 @@ impl fmt::Display for Stmt { cls, generics, protocol, - availability: _, + availability, } => { let (generic_bound, where_bound) = if !generics.is_empty() { match (&*protocol.library, &*protocol.name) { @@ -1496,6 +1537,8 @@ impl fmt::Display for Stmt { if let Some(feature) = cls.feature() { writeln!(f, "#[cfg(feature = \"{feature}\")]")?; } + let unavailable = availability.unavailable.clone(); + write!(f, "{unavailable}")?; writeln!( f, "unsafe impl{} {} for {}{} {}{{}}", @@ -1512,8 +1555,9 @@ impl fmt::Display for Stmt { protocols, methods, } => { + let unavailable = availability.unavailable.clone(); + write!(f, "{unavailable}")?; writeln!(f, "extern_protocol!(")?; - write!(f, "{availability}")?; write!(f, " pub unsafe trait {}", id.name)?; if !protocols.is_empty() { @@ -1560,6 +1604,7 @@ impl fmt::Display for Stmt { )?; } } + write!(f, "{unavailable}")?; writeln!(f, "{method}")?; } writeln!(f, " }}")?; @@ -1574,11 +1619,12 @@ impl fmt::Display for Stmt { boxable: _, fields, } => { + let unavailable = availability.unavailable.clone(); + write!(f, "{unavailable}")?; writeln!(f, "extern_struct!(")?; if let Some(encoding_name) = encoding_name { writeln!(f, " #[encoding_name({encoding_name:?})]")?; } - write!(f, "{availability}")?; writeln!(f, " pub struct {} {{", id.name)?; for (name, ty) in fields { write!(f, " ")?; @@ -1606,9 +1652,10 @@ impl fmt::Display for Stmt { Some(UnexposedAttr::ErrorEnum) => "ns_error_enum", _ => panic!("invalid enum kind"), }; + let unavailable = availability.unavailable.clone(); + write!(f, "{unavailable}")?; writeln!(f, "{macro_name}!(")?; writeln!(f, " #[underlying({ty})]")?; - write!(f, "{availability}")?; writeln!( f, " pub enum {} {{", @@ -1623,18 +1670,22 @@ impl fmt::Display for Stmt { } Self::VarDecl { id, - availability: _, + availability, ty, value: None, } => { + let unavailable = availability.unavailable.clone(); + write!(f, "{unavailable}")?; writeln!(f, "extern_static!({}: {ty});", id.name)?; } Self::VarDecl { id, - availability: _, + availability, ty, value: Some(expr), } => { + let unavailable = availability.unavailable.clone(); + write!(f, "{unavailable}")?; writeln!(f, "extern_static!({}: {ty} = {expr});", id.name)?; } Self::FnDecl { @@ -1700,20 +1751,26 @@ impl fmt::Display for Stmt { } Self::AliasDecl { id, - availability: _, + availability, ty, kind, } => { match kind { Some(UnexposedAttr::TypedEnum) => { + let unavailable = availability.unavailable.clone(); + write!(f, "{unavailable}")?; writeln!(f, "typed_enum!(pub type {} = {ty};);", id.name)?; } Some(UnexposedAttr::TypedExtensibleEnum) => { + let unavailable = availability.unavailable.clone(); + write!(f, "{unavailable}")?; writeln!(f, "typed_extensible_enum!(pub type {} = {ty};);", id.name)?; } None | Some(UnexposedAttr::BridgedTypedef) => { // "bridged" typedefs should just use a normal type // alias. + let unavailable = availability.unavailable.clone(); + write!(f, "{unavailable}")?; writeln!(f, "pub type {} = {ty};", id.name)?; } kind => panic!("invalid alias kind {kind:?} for {ty:?}"), diff --git a/crates/header-translator/translation-config.toml b/crates/header-translator/translation-config.toml index 97ca20d14..9ae5a55e8 100644 --- a/crates/header-translator/translation-config.toml +++ b/crates/header-translator/translation-config.toml @@ -1532,6 +1532,12 @@ skipped = true [class.MXMetricManager.methods.makeLogHandleWithCategory] skipped = true +# The MPMediaQueryAddition definition does not have API_UNAVAILABLE annotations +# where as the MPMediaItem does. This results in problematic conditional compliation: +# https://github.com/phracker/MacOSX-SDKs/blob/041600eda65c6a668f66cb7d56b7d1da3e8bcc93/MacOSX11.3.sdk/System/Library/Frameworks/MediaPlayer.framework/Versions/A/Headers/MPMediaQuery.h#L103-L118 +[class.MPMediaItem.categories.MPMediaQueryAdditions] +skipped = true + # Custom generics because of auto traits [class.NSArray] definition-skipped = true diff --git a/crates/icrate/src/generated b/crates/icrate/src/generated index 13af2aa6e..d23e6b749 160000 --- a/crates/icrate/src/generated +++ b/crates/icrate/src/generated @@ -1 +1 @@ -Subproject commit 13af2aa6e6ebb964578d928468a46e4036464d52 +Subproject commit d23e6b749470c0dfdf8722ccc6d70d52cca7dc7d