Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Put methods behind feature flags #328

Merged
merged 30 commits into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ea1b545
Put methods behind feature flags
madsmtm Jan 10, 2023
123bf9f
Fix Class { params } type
madsmtm Jan 10, 2023
c2d36c0
Add feature section for icrates Cargo.toml
silvanshade Jan 10, 2023
5f1f99b
Properly emit cargo features
madsmtm Jan 10, 2023
f1f5c3a
Handle builtin NSObject differently
madsmtm Jan 10, 2023
365d3ea
Add "_all" cargo features to help with testing / for convenience
madsmtm Jan 10, 2023
a489e25
Refactor identifier handling
madsmtm Jan 10, 2023
39fda4f
Access items in a namespaced fasion instead of directly
madsmtm Jan 10, 2023
0274b5f
Properly rename QuartzCore to CoreAnimation
madsmtm Jan 10, 2023
10203a5
Fix required features
madsmtm Jan 10, 2023
e5c59df
Add base cargo features to "_all" cargo features
madsmtm Jan 10, 2023
d2c5c28
Fix NSError paths
madsmtm Jan 10, 2023
4b45ebd
Fix typedef, enum and struct paths
madsmtm Jan 10, 2023
c7db248
Fix the core graphics types
madsmtm Jan 10, 2023
44612ba
Refactor Expr slightly
madsmtm Jan 10, 2023
a7fba3d
Use wildcard imports again until we've resolved some more details
madsmtm Jan 10, 2023
127ebca
Fix the manual code we've written to match the new feature flags
madsmtm Jan 10, 2023
6bc5c92
Fix more feature flags and refactor tests
madsmtm Jan 11, 2023
9bf999c
cfg-gate functions
madsmtm Jan 11, 2023
b2a38fb
cfg-gate class declarations
madsmtm Jan 11, 2023
60d902e
cfg-gate protocol methods
madsmtm Jan 11, 2023
6f131aa
Fix remaining Foundation compilation errors
madsmtm Jan 11, 2023
2d0e590
Create potentially missing directories in header-translator
madsmtm Jan 11, 2023
8717fad
Fix compilation errors in tests and examples
madsmtm Jan 11, 2023
a560f5e
Fix remaining icrate compilation errors
madsmtm Jan 11, 2023
bea5c51
Appease clippy
madsmtm Jan 11, 2023
fd61fe6
Add CI step for checking each icrate feature separately
madsmtm Jan 11, 2023
41399c7
Improve check_icrate_features script
madsmtm Jan 11, 2023
4df93f0
Fix a few more mistakes regarding feature checks
madsmtm Jan 11, 2023
933b09c
Fix Apple CI step
madsmtm Jan 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,33 @@ jobs:
- name: Verify that no files changed
run: git diff --exit-code --submodule=diff

check-icrate-features:
# if: ${{ env.FULL }}
# This will take ~40 minutes
if: ${{ github.ref_name == 'new-versions' || github.ref_name == 'ci-full' }}
name: Check icrate features
runs-on: macos-12
needs:
- fmt
- lint

steps:
- uses: actions/checkout@v3
with:
submodules: true

- name: Use system Rust
run: cargo --version

- name: Cache Cargo
uses: actions/cache@v3
with:
path: ${{ env.CARGO_CACHE_PATH }}
key: cargo-${{ github.job }}-${{ matrix.name }}-${{ hashFiles('**/Cargo.lock') }}

- name: Check `icrate` with each feature enabled separately
run: cargo run --bin=check_icrate_features --features=run-icrate-check

test-macos:
name: Test macOS 12
runs-on: macos-12
Expand Down Expand Up @@ -435,16 +462,17 @@ jobs:
run: cargo test $ARGS

- name: Test Foundation
run: cargo test $ARGS --features=$INTERESTING_FEATURES,Foundation
run: cargo test $ARGS --features=$INTERESTING_FEATURES,Foundation_all

- name: Test all frameworks
run: cargo test $ARGS --features=$INTERESTING_FEATURES,catch-all,unstable-frameworks-${{ matrix.frameworks }}

- name: Test in release mode
run: cargo test $ARGS --features=$INTERESTING_FEATURES,catch-all,Foundation --release
run: cargo test $ARGS --features=$INTERESTING_FEATURES,catch-all,Foundation_all --release

- name: Test with unstable features
run: cargo test $ARGS --features=$INTERESTING_FEATURES,catch-all,Foundation,$UNSTABLE_FEATURES
if: ${{ matrix.nightly }}
run: cargo test $ARGS --features=$INTERESTING_FEATURES,catch-all,Foundation_all,$UNSTABLE_FEATURES

# TODO: Re-enable this on Foundation once we do some form of
# availability checking.
Expand Down
8 changes: 8 additions & 0 deletions crates/header-translator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ run = [
"tracing-subscriber",
"tracing-tree",
]
run-icrate-check = [
"toml",
"serde",
]

[dependencies]
clang = { version = "2.0", features = ["runtime", "clang_10_0"], optional = true }
Expand All @@ -31,3 +35,7 @@ proc-macro2 = "1.0.49"
[[bin]]
name = "header-translator"
required-features = ["run"]

[[bin]]
name = "check_icrate_features"
required-features = ["run-icrate-check"]
101 changes: 101 additions & 0 deletions crates/header-translator/src/bin/check_icrate_features.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! # Utility for testing `icrate`'s feature set
//!
//! Run using:
//! ```sh
//! cargo run --bin=check_icrate_features --features=run
//! ```
use std::collections::BTreeMap;
use std::error::Error;
use std::fs;
use std::path::Path;
use std::process::Command;

use serde::Deserialize;

#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
struct CargoToml {
features: BTreeMap<String, Vec<String>>,
}

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",
];

fn get_pairs<'a>(items: &'a [&'a str]) -> impl Iterator<Item = (&'a str, &'a str)> + 'a {
items
.into_iter()
.enumerate()
.map(|(i, &item1)| items[i..].into_iter().map(move |&item2| (item1, item2)))
.flatten()
}

fn main() -> Result<(), Box<dyn Error>> {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let crates_dir = manifest_dir.parent().unwrap();
let cargo_toml = fs::read_to_string(crates_dir.join("icrate").join("Cargo.toml"))?;

let CargoToml { features } = toml::from_str(&cargo_toml)?;

println!("Testing all Foundation features in `icrate`");

let feature_sets = get_pairs(POPULAR_FEATURES)
.map(|(feature1, feature2)| vec![feature1, feature2])
.chain(features.keys().filter_map(|feature| {
if feature.contains("gnustep") {
// Skip GNUStep-related features
None
} 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
None
} else {
Some(vec![&**feature])
}
}));

let mut success = true;

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

let status = Command::new("cargo")
.args([
"check",
"--quiet",
"--package=icrate",
"--features=Foundation",
"--features",
&features.join(","),
])
.current_dir(crates_dir)
.status()?;

if !status.success() {
success = false;
}
}

if !success {
panic!("one or more checks failed");
}

Ok(())
}
72 changes: 38 additions & 34 deletions crates/header-translator/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ use std::mem;
use crate::availability::Availability;
use crate::config::{ClassData, Config};
use crate::file::File;
use crate::id::ItemIdentifier;
use crate::method::Method;
use crate::output::Output;
use crate::rust_type::Ownership;
use crate::stmt::{ClassDefReference, Stmt};
use crate::stmt::Stmt;

#[derive(Debug, PartialEq, Clone)]
struct MethodCache {
availability: Availability,
methods: Vec<Method>,
category_name: Option<String>,
category: ItemIdentifier<Option<String>>,
}

#[derive(Debug, PartialEq, Clone, Default)]
Expand All @@ -35,7 +36,7 @@ impl ClassCache {
/// A helper struct for doing global analysis on the output.
#[derive(Debug, PartialEq, Clone)]
pub struct Cache<'a> {
classes: BTreeMap<ClassDefReference, ClassCache>,
classes: BTreeMap<ItemIdentifier, ClassCache>,
ownership_map: BTreeMap<String, Ownership>,
config: &'a Config,
}
Expand All @@ -50,13 +51,13 @@ impl<'a> Cache<'a> {
for (name, file) in &library.files {
let _span = debug_span!("file", name).entered();
for stmt in &file.stmts {
if let Some((ty, method_cache)) = Self::cache_stmt(stmt) {
let cache = classes.entry(ty.clone()).or_default();
if let Some((cls, method_cache)) = Self::cache_stmt(stmt) {
let cache = classes.entry(cls.clone()).or_default();
cache.to_emit.push(method_cache);
}
if let Stmt::ClassDecl { ty, ownership, .. } = stmt {
if let Stmt::ClassDecl { id, ownership, .. } = stmt {
if *ownership != Ownership::default() {
ownership_map.insert(ty.name.clone(), ownership.clone());
ownership_map.insert(id.name.clone(), ownership.clone());
}
}
}
Expand All @@ -70,16 +71,18 @@ impl<'a> Cache<'a> {
}
}

fn cache_stmt(stmt: &Stmt) -> Option<(&ClassDefReference, MethodCache)> {
fn cache_stmt(stmt: &Stmt) -> Option<(&ItemIdentifier, MethodCache)> {
if let Stmt::Methods {
ty,
cls,
generics: _,
category,
availability,
superclasses: _,
methods,
category_name,
description,
} = stmt
{
let _span = debug_span!("Stmt::Methods", ?ty).entered();
let _span = debug_span!("Stmt::Methods", ?cls).entered();
let methods: Vec<Method> = methods
.iter()
.filter(|method| method.emit_on_subclasses())
Expand All @@ -91,12 +94,13 @@ impl<'a> Cache<'a> {
if description.is_some() {
warn!(description, "description was set");
}
let category = category.clone().with_new_path(cls);
Some((
ty,
cls,
MethodCache {
availability: availability.clone(),
methods,
category_name: category_name.clone(),
category,
},
))
} else {
Expand All @@ -119,22 +123,25 @@ impl<'a> Cache<'a> {
for stmt in &mut file.stmts {
match stmt {
Stmt::ClassDecl {
ty, superclasses, ..
id,
generics,
superclasses,
..
} => {
let _span = debug_span!("Stmt::ClassDecl", ?ty).entered();
let data = self.config.class_data.get(&ty.name);
let _span = debug_span!("Stmt::ClassDecl", ?id).entered();
let data = self.config.class_data.get(&id.name);

// Used for duplicate checking (sometimes the subclass
// defines the same method that the superclass did).
let mut seen_methods: Vec<_> = self
.classes
.get(ty)
.get(id)
.map(|cache| cache.all_methods_data())
.into_iter()
.flatten()
.collect();

for superclass in superclasses {
for (superclass, _) in &*superclasses {
if let Some(cache) = self.classes.get(superclass) {
new_stmts.extend(cache.to_emit.iter().filter_map(|cache| {
let mut methods: Vec<_> = cache
Expand All @@ -154,13 +161,15 @@ impl<'a> Cache<'a> {
return None;
}

self.update_methods(&mut methods, &ty.name);
self.update_methods(&mut methods, &id.name);

Some(Stmt::Methods {
ty: ty.clone(),
cls: id.clone(),
generics: generics.clone(),
category: cache.category.clone(),
availability: cache.availability.clone(),
superclasses: superclasses.clone(),
methods,
category_name: cache.category_name.clone(),
description: Some(format!(
"Methods declared on superclass `{}`",
superclass.name
Expand All @@ -172,11 +181,11 @@ impl<'a> Cache<'a> {
}
}
}
Stmt::Methods { ty, methods, .. } => {
self.update_methods(methods, &ty.name);
Stmt::Methods { cls, methods, .. } => {
self.update_methods(methods, &cls.name);
}
Stmt::ProtocolDecl { name, methods, .. } => {
self.update_methods(methods, name);
Stmt::ProtocolDecl { id, methods, .. } => {
self.update_methods(methods, &id.name);
}
_ => {}
}
Expand All @@ -186,20 +195,15 @@ impl<'a> Cache<'a> {
// Fix up a few typedef + enum declarations
let mut iter = mem::take(&mut file.stmts).into_iter().peekable();
while let Some(stmt) = iter.next() {
if let Stmt::AliasDecl {
name,
ty,
kind: None,
} = &stmt
{
if let Stmt::AliasDecl { id, ty, kind: None } = &stmt {
if let Some(Stmt::EnumDecl {
name: enum_name,
id: enum_id,
ty: enum_ty,
..
}) = iter.peek_mut()
{
if enum_ty.is_typedef_to(name) {
*enum_name = Some(name.clone());
if enum_ty.is_typedef_to(&id.name) {
*enum_id = id.clone().to_some();
*enum_ty = ty.clone();
// Skip adding the now-redundant alias to the list of statements
continue;
Expand Down
17 changes: 17 additions & 0 deletions crates/header-translator/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,26 @@ pub struct Config {
pub libraries: HashMap<String, LibraryData>,
}

impl Config {
pub fn get_library_alias(&self, library_name: String) -> String {
self.libraries
.iter()
.find_map(|(n, data)| {
if let Some(name) = &data.name {
if n == &library_name {
return Some(name.clone());
}
}
None
})
.unwrap_or(library_name)
}
}

#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct LibraryData {
pub name: Option<String>,
pub imports: Vec<String>,
}

Expand Down
Loading