Skip to content

Commit

Permalink
Make Cargo feature flags be file-based instead of class based
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed Mar 14, 2024
1 parent f121b09 commit 3d26587
Show file tree
Hide file tree
Showing 74 changed files with 2,365 additions and 3,776 deletions.
16 changes: 8 additions & 8 deletions crates/header-translator/src/bin/check_icrate_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,12 @@ struct CargoToml {

const POPULAR_FEATURES: &[&str] = &[
"Foundation_NSString",
"Foundation_NSMutableString",
"Foundation_NSArray",
"Foundation_NSMutableArray",
"Foundation_NSDictionary",
"Foundation_NSMutableDictionary",
"Foundation_NSSet",
"Foundation_NSMutableSet",
"Foundation_NSEnumerator",
"Foundation_NSError",
"Foundation_NSException",
"Foundation_NSNumber",
"Foundation_NSValue",
"Foundation_NSThread",
];
Expand Down Expand Up @@ -55,8 +50,11 @@ fn main() -> Result<(), Box<dyn Error>> {
} else if feature.contains("_all") || feature.contains("unstable-frameworks-") {
// Skip "_all" features for now
None
} else if !feature.contains("Foundation") {
// Skip all other than "Foundation" features for now
} else if !feature.contains("Foundation")
&& !feature.contains("AppKit")
&& !feature.contains("Metal")
{
// Skip all other than "Foundation", "AppKit" and "Metal" features for now
None
} else {
Some(vec![&**feature])
Expand All @@ -67,7 +65,7 @@ fn main() -> Result<(), Box<dyn Error>> {

for features in feature_sets {
println!(
"running: cargo check --features=Foundation,{}",
"running: cargo check --features=Foundation,AppKit,Metal,{}",
features.join(",")
);

Expand All @@ -77,6 +75,8 @@ fn main() -> Result<(), Box<dyn Error>> {
"--quiet",
"--package=icrate",
"--features=Foundation",
"--features=AppKit",
"--features=Metal",
"--features",
&features.join(","),
])
Expand Down
12 changes: 6 additions & 6 deletions crates/header-translator/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,14 @@ impl Expr {
// features.add_item(&id);
}
}
Self::Enum { .. } => {
// features.add_item(&id);
Self::Enum { id, .. } => {
features.add_item(id);
}
Self::Const(_) => {
// features.add_item(&id);
Self::Const(id) => {
features.add_item(id);
}
Self::Var { ty, .. } => {
// features.add_item(&id);
Self::Var { id, ty, .. } => {
features.add_item(id);
features.merge(ty.required_features(&Features::new()));
}
Self::Tokens(tokens) => {
Expand Down
51 changes: 33 additions & 18 deletions crates/header-translator/src/feature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,27 @@ use std::fmt;

use clang::{Entity, EntityKind};

use crate::file::clean_name;
use crate::id::ToOptionString;
use crate::stmt::parse_direct_protocols;
use crate::Mutability;
use crate::{stmt::parse_superclasses, Context, ItemIdentifier};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Feature<'a>(&'a ItemIdentifier);
pub struct Feature<'a, N>(&'a ItemIdentifier<N>);

impl<'a> Feature<'a> {
pub fn new(item: &'a ItemIdentifier) -> Self {
impl<'a, N: ToOptionString> Feature<'a, N> {
pub fn new(item: &'a ItemIdentifier<N>) -> Self {
Self(item)
}

pub fn name(&self) -> Option<String> {
if self.0.is_system() {
None
} else if let Some(file_name) = &self.0.file_name {
Some(format!("{}_{}", self.0.library, clean_name(file_name)))
} else {
Some(format!("{}_{}", self.0.library, self.0.name))
Some(format!("Unknown-{}_{:?}", self.0.library, self.0.name))
}
}
}
Expand All @@ -38,7 +44,7 @@ impl Features {
}
}

pub fn remove_item(&mut self, item: &ItemIdentifier) {
pub fn remove_item<N: ToOptionString>(&mut self, item: &ItemIdentifier<N>) {
if let Some(name) = Feature::new(item).name() {
self.0.remove(&name);
}
Expand Down Expand Up @@ -91,28 +97,37 @@ impl Features {
pub(crate) fn required_by_decl(entity: &Entity<'_>, context: &Context<'_>) -> Self {
match entity.get_kind() {
EntityKind::ObjCInterfaceDecl => {
let id = ItemIdentifier::new(entity, context);
let data = context.class_data.get(&id.name);

let mut features = Self::new();
features.add_item(&id);
for (id, _, _) in parse_superclasses(entity, context) {
features.add_item(&id);
}
if let Some(Mutability::ImmutableWithMutableSubclass(subclass)) =
data.map(|data| &data.mutability)
{
features.add_item(subclass);
}

features
}
EntityKind::ObjCProtocolDecl => {
let mut features = Self::new();
features.add_item(&ItemIdentifier::new(entity, context));
for entity in parse_direct_protocols(entity, context) {
features.merge(Self::required_by_decl(&entity, context));
}
features
}
EntityKind::ObjCProtocolDecl => Self::new(),
_ => panic!("invalid required_by_decl kind {entity:?}"),
}
}

/// Get the features implied enabled by having a given declaration.
pub(crate) fn implied_by_decl(entity: &Entity<'_>, context: &Context<'_>) -> Self {
let mut features = Self::required_by_decl(entity, context);
match entity.get_kind() {
EntityKind::ObjCInterfaceDecl => {
// The feature is enabled if superclass' features are.
for (id, _, _) in parse_superclasses(entity, context) {
features.add_item(&id);
}
features
}
EntityKind::ObjCProtocolDecl => features,
_ => panic!("invalid implied_by_decl kind {entity:?}"),
}
// No extra features are implied
Self::required_by_decl(entity, context)
}
}
11 changes: 11 additions & 0 deletions crates/header-translator/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::fmt;

use crate::context::Context;
use crate::stmt::Stmt;
use crate::ItemIdentifier;

#[derive(Debug, PartialEq)]
pub struct File {
Expand All @@ -10,6 +11,11 @@ pub struct File {
pub(crate) stmts: Vec<Stmt>,
}

/// Some SDK files have '+' in the file name, so we change those to `_`.
pub(crate) fn clean_name(name: &str) -> String {
name.replace('+', "_")
}

impl File {
pub fn new(library_name: &str, context: &Context<'_>) -> Self {
Self {
Expand All @@ -27,6 +33,11 @@ impl File {
pub fn add_stmt(&mut self, stmt: Stmt) {
self.stmts.push(stmt);
}

pub(crate) fn required_imports(&self) -> impl Iterator<Item = ItemIdentifier> + '_ {
// TODO
vec![].into_iter()
}
}

impl fmt::Display for File {
Expand Down
39 changes: 28 additions & 11 deletions crates/header-translator/src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use clang::Entity;

use crate::context::Context;

pub trait ToOptionString {
pub trait ToOptionString: fmt::Debug {
fn to_option(&self) -> Option<&str>;
}

Expand All @@ -22,6 +22,12 @@ impl ToOptionString for Option<String> {
}
}

impl ToOptionString for () {
fn to_option(&self) -> Option<&str> {
None
}
}

#[derive(Debug, Clone)]
pub struct ItemIdentifier<N = String> {
/// Names in Objective-C are global, so this is always enough to uniquely
Expand Down Expand Up @@ -76,12 +82,14 @@ impl<N: ToOptionString> ItemIdentifier<N> {
("__Unknown__".to_string(), None)
});

// TODO: Get rid of this hack
if library_name == "CoreGraphics" {
if let Some("CGFloat" | "CGPoint" | "CGRect" | "CGSize") = name.to_option() {
library_name = "Foundation".to_string();
file_name = Some("NSGeometry".to_string());
}
// TODO: Get rid of these hacks
if let Some("CGFloat" | "CGPoint" | "CGRect" | "CGSize") = name.to_option() {
library_name = "Foundation".to_string();
file_name = Some("NSGeometry".to_string());
}
if let Some("CFTimeInterval") = name.to_option() {
library_name = "System".to_string();
file_name = None;
}

Self {
Expand All @@ -104,13 +112,24 @@ impl<N: ToOptionString> ItemIdentifier<N> {
}
}

pub fn location(&self) -> Location
where
N: Clone,
{
self.clone().map_name(|_| ())
}

pub fn with_new_path<R: ToOptionString>(self, other: &ItemIdentifier<R>) -> Self {
Self {
name: self.name,
library: other.library.clone(),
file_name: other.file_name.clone(),
}
}

pub fn is_system(&self) -> bool {
self.library == "System"
}
}

impl ItemIdentifier {
Expand All @@ -123,10 +142,6 @@ impl ItemIdentifier {
self.map_name(Some)
}

pub fn is_system(&self) -> bool {
self.library == "System"
}

pub fn is_nsobject(&self) -> bool {
self.library == "System" && (self.name == "NSObject" || self.name == "NSObjectProtocol")
}
Expand Down Expand Up @@ -193,3 +208,5 @@ impl ItemIdentifier<Option<String>> {
Self::with_name(entity.get_name(), entity, context)
}
}

pub type Location = ItemIdentifier<()>;
9 changes: 5 additions & 4 deletions crates/header-translator/src/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::io;
use std::path::Path;

use crate::config::LibraryData;
use crate::file::clean_name;
use crate::file::File;

/// Some SDK files have '+' in the file name, so we change those to `_`.
Expand Down Expand Up @@ -100,7 +101,8 @@ impl fmt::Display for Library {
writeln!(f)?;

for name in self.files.keys() {
let name = clean_file_name(name);
let name = clean_name(name);
write!(f, "#[cfg(feature = \"{}_{}\")]", self.link_name, name)?;
writeln!(f, "#[path = \"{name}.rs\"]")?;
writeln!(f, "mod __{name};")?;
}
Expand All @@ -109,19 +111,18 @@ impl fmt::Display for Library {

for (file_name, file) in &self.files {
for stmt in &file.stmts {
let features = stmt.required_features();

if let Some(item) = stmt.provided_item() {
assert_eq!(item.file_name.as_ref(), Some(file_name));

let mut features = stmt.required_features();
features.add_item(&item);
write!(f, "{}", features.cfg_gate_ln())?;

let visibility = if item.name.starts_with('_') {
"pub(crate)"
} else {
"pub"
};

write!(
f,
"{visibility} use self::__{}::{{{}}};",
Expand Down
43 changes: 14 additions & 29 deletions crates/header-translator/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use std::path::Path;

use crate::config::{Config, LibraryData};
use crate::feature::Feature;
use crate::file::clean_name;
use crate::library::Library;
use crate::stmt::Stmt;

use semver::VersionReq;

Expand Down Expand Up @@ -125,34 +125,19 @@ impl Output {
for (library_name, library) in &self.libraries {
let mut library_features = BTreeSet::from([library_name.clone()]);

for file in library.files.values() {
for stmt in &file.stmts {
#[allow(clippy::single_match)] // There will be others
match stmt {
Stmt::ClassDecl {
id, superclasses, ..
} => {
if let Some(feature) = Feature::new(id).name() {
// Only require the first superclass as feature,
// since the rest will be enabled transitively.
if let Some((superclass, _)) = superclasses.first() {
let superclass_features: Vec<_> =
Feature::new(superclass).name().into_iter().collect();
if let Some(existing) =
features.insert(feature.clone(), superclass_features)
{
error!(?existing, "duplicate feature");
}
}

if !library_features.insert(feature) {
error!("duplicate feature");
}
}
}
_ => {}
}
}
for (file_name, file) in &library.files {
let feature_name = format!("{}_{}", library_name, clean_name(file_name));
let mut required_features: BTreeSet<_> = file
.required_imports() // TODO
.filter_map(|id| Feature::new(&id).name())
.collect();
required_features.remove(&feature_name); // Don't depend on itself
required_features.clear(); // TODO
features.insert(
feature_name.clone(),
required_features.into_iter().collect(),
);
library_features.insert(feature_name);
}

let _ = features.insert(
Expand Down
Loading

0 comments on commit 3d26587

Please sign in to comment.