Skip to content

Commit 97c6d75

Browse files
committed
Enable Cargo features by default
Enable (almost) all framework crate features by default, to make the user experience when developing binaries (not libraries) much easier. Users that develop a library should use `default-features = false`, but that's also probably something that they only want to do later in the development process (and should be evaluated on a case-by-case basis). Fixes #627 This also makes examples much nicer to run, it's just `cargo run --example $name`, no need to configure all the required features.
1 parent 729925d commit 97c6d75

File tree

143 files changed

+7641
-7566
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

143 files changed

+7641
-7566
lines changed

.github/workflows/ci.yml

+19-23
Large diffs are not rendered by default.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ unsafe_op_in_unsafe_fn = "deny"
3535
[workspace.lints.clippy]
3636
cargo = { level = "warn", priority = -1 } # Because of `lint_groups_priority`
3737
ptr_as_ptr = "warn"
38+
# We can't change the name of `objc2-ad-support`.
39+
redundant_feature_names = "allow"
3840

3941
[profile.assembly-tests]
4042
inherits = "release"
@@ -48,7 +50,6 @@ push-remote = "0origin"
4850
shared-version = true # Framework crates share a version number
4951
tag-prefix = "icrate"
5052
tag-name = "{{prefix}}-{{version}}"
51-
enable-features = ["all"]
5253
owners = ["madsmtm", "simlay"]
5354

5455
# TODO: Check for typos in CI

crates/block2/Cargo.toml

+5-5
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,11 @@ unstable-coerce-pointee = []
5050
[dependencies]
5151
objc2 = { path = "../objc2", version = "0.5.2", default-features = false, features = ["std"] }
5252

53-
[dev-dependencies.objc2-foundation]
54-
path = "../../framework-crates/objc2-foundation"
55-
default-features = false
56-
features = ["std", "NSError"]
53+
[dev-dependencies]
54+
objc2-foundation = { path = "../../framework-crates/objc2-foundation", default-features = false, features = [
55+
"std",
56+
"NSError",
57+
] }
5758

5859
[package.metadata.docs.rs]
5960
default-target = "aarch64-apple-darwin"
@@ -73,4 +74,3 @@ targets = [
7374
[package.metadata.release]
7475
shared-version = false
7576
tag-prefix = "block"
76-
enable-features = []

crates/block2/translation-config.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
framework = "block"
22
crate = "block2"
3-
required-crates = []
3+
required-crates = ["objc2"]
44
link = false
55
skipped = true
66
is-library = true

crates/dispatch2/Cargo.modified.toml

-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@ static_assertions = "1.1.0"
1111
[package.metadata.release]
1212
shared-version = false
1313
tag-prefix = "dispatch"
14-
enable-features = []

crates/dispatch2/Cargo.toml

+6-9
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ objc2 = { path = "../objc2", version = "0.5.2", default-features = false, option
2424

2525
[package.metadata.docs.rs]
2626
default-target = "aarch64-apple-darwin"
27-
features = ["all"]
2827
rustc-args = ["--cfg", "docsrs"] # Fix cross-crate link to objc2::topics
2928
targets = [
3029
"aarch64-apple-darwin",
@@ -39,23 +38,21 @@ targets = [
3938
]
4039

4140
[features]
42-
default = ["std"]
41+
default = [
42+
"std",
43+
"block2",
44+
"libc",
45+
"objc2",
46+
]
4347
std = ["alloc"]
4448
alloc = []
4549
block2 = ["dep:block2"]
4650
libc = ["dep:libc"]
4751
objc2 = ["dep:objc2"]
4852

49-
all = [
50-
"block2",
51-
"libc",
52-
"objc2",
53-
]
54-
5553
[package.metadata.release]
5654
shared-version = false
5755
tag-prefix = "dispatch"
58-
enable-features = []
5956

6057
[dev-dependencies]
6158
static_assertions = "1.1.0"

crates/header-translator/src/bin/check_framework_features.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ fn get_features(cargo_toml: &Path) -> Result<Vec<String>, Box<dyn Error>> {
3535
let cargo_toml = fs::read_to_string(cargo_toml)?;
3636
let CargoToml { features } = basic_toml::from_str(&cargo_toml)?;
3737

38-
// Skip GNUStep-related and "all" features
38+
// Skip GNUStep-related and default/std/alloc features
3939
Ok(features
4040
.into_keys()
41-
.filter(|feature| !feature.contains("gnustep") && feature != "all")
41+
.filter(|feature| {
42+
!feature.contains("gnustep") && !matches!(&**feature, "default" | "std" | "alloc")
43+
})
4244
.collect())
4345
}
4446

@@ -53,6 +55,8 @@ fn test_feature_sets<'a>(
5355
cmd.arg("check");
5456
cmd.arg("--package");
5557
cmd.arg(package);
58+
cmd.arg("--no-default-features");
59+
cmd.arg("--features=std");
5660
cmd.arg("--features");
5761
cmd.arg(features.join(","));
5862

@@ -128,11 +132,13 @@ fn main() -> Result<(), Box<dyn Error>> {
128132
let feature_sets = features.iter().map(|feature| vec![&**feature]);
129133
test_feature_sets(&mut success, workspace_dir, feature_sets, "objc2-metal")?;
130134

131-
println!("Testing building each framework with `--features=all` and only their own features");
135+
println!(
136+
"Testing building each framework with `--features=default` and only their own features"
137+
);
132138
for dir in workspace_dir.join("framework-crates").read_dir().unwrap() {
133139
let dir = dir.unwrap();
134140
if dir.file_type().unwrap().is_dir() {
135-
let feature_sets = [vec!["all"]];
141+
let feature_sets = [vec!["default"]];
136142
// println!("Testing all {dir:?} features");
137143
// let features = get_features(&dir.path().join("Cargo.toml"))?;
138144
// let feature_sets = features.iter().map(|feature| {

crates/header-translator/src/config.rs

+42
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,48 @@ pub struct LibraryConfig {
239239
pub typedef_data: HashMap<String, TypedefData>,
240240
}
241241

242+
impl LibraryConfig {
243+
// TODO: Merge this with `Availability` somehow.
244+
pub(crate) fn can_safely_depend_on(&self, other: &Self) -> bool {
245+
fn inner(
246+
ours: &Option<semver::Version>,
247+
other: &Option<semver::Version>,
248+
rust_min: semver::Version,
249+
) -> bool {
250+
match (ours, other) {
251+
// If both libraries have a platform version, then ensure that
252+
// ours is within the minimum of the other, OR that Rust's
253+
// default min version is high enough that it won't matter.
254+
(Some(ours), Some(other)) => other <= ours || *other <= rust_min,
255+
// If only we have support for a platform, then we will emit a
256+
// cfg-guarded [dependencies] table (done elsewhere), and thus
257+
// it won't affect whether we can safely depend on it.
258+
(Some(_), None) => true,
259+
// If only the other library has support for platform, then
260+
// that's fine.
261+
(None, Some(_)) => true,
262+
// If neither library support the platform, that's also fine.
263+
(None, None) => true,
264+
}
265+
}
266+
267+
inner(&self.macos, &other.macos, semver::Version::new(10, 12, 0))
268+
&& inner(
269+
&self.maccatalyst,
270+
&other.maccatalyst,
271+
semver::Version::new(13, 1, 0),
272+
)
273+
&& inner(&self.ios, &other.ios, semver::Version::new(10, 0, 0))
274+
&& inner(&self.tvos, &other.tvos, semver::Version::new(10, 0, 0))
275+
&& inner(&self.watchos, &other.watchos, semver::Version::new(5, 0, 0))
276+
&& inner(
277+
&self.visionos,
278+
&other.visionos,
279+
semver::Version::new(1, 0, 0),
280+
)
281+
}
282+
}
283+
242284
#[derive(Deserialize, Debug, Default, Clone, PartialEq, Eq)]
243285
#[serde(deny_unknown_fields)]
244286
pub struct Example {

crates/header-translator/src/default_cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ workspace = true
1919

2020
[package.metadata.docs.rs]
2121
default-target = "UNSET"
22-
features = ["all"]
2322
rustc-args = ["--cfg", "docsrs"] # Fix cross-crate link to objc2::topics
2423
targets = [
2524
]

crates/header-translator/src/library.rs

+72-16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::fmt;
55
use std::fs;
66
use std::io::ErrorKind;
77
use std::io::Write;
8+
use std::iter;
89
use std::path::Path;
910

1011
use toml_edit::InlineTable;
@@ -389,33 +390,76 @@ see that for related crates.", self.data.krate)?;
389390
}
390391

391392
let mut emitted_features = self.emitted_features(config);
392-
let _ = emitted_features.insert(
393-
"all".to_string(),
394-
emitted_features.keys().cloned().collect::<BTreeSet<_>>(),
393+
394+
// All features are enabled by default, except for frameworks that
395+
// would bump the minimum version of the library.
396+
//
397+
// The reasoning is that default features are meant for ease of use,
398+
// especially so in end-user binaries and hobby projects. But we also
399+
// don't want to make the user's binary incompatible with older OSes
400+
// if they didn't explicitly opt-in to that.
401+
//
402+
// End result: The "only" cost is compilation time (vs. wasted
403+
// developer time in finding each feature gate, or an unintentionally
404+
// raised minimum OS version).
405+
//
406+
// And yes, libraries that use these crates _will_ want to disable
407+
// default features, but that's the name of the game.
408+
//
409+
// We _could_ technically try to do something fancy to avoid e.g.
410+
// `objc2-app-kit` pulling in `objc2-core-data`, since that is rarely
411+
// needed, but where do we draw the line? And besides, that just masks
412+
// the problem, library developers _should_ also disable the file
413+
// features that they don't use if they really care about compilation
414+
// time.
415+
//
416+
// See also https://github.com/madsmtm/objc2/issues/627.
417+
let is_default_feature = |feature| {
418+
if let Some(lib) = config.try_library_from_crate(feature) {
419+
// Dependency feature
420+
self.data.can_safely_depend_on(lib) || !lib.link
421+
} else {
422+
// File feature
423+
true
424+
}
425+
};
426+
cargo_toml["features"]["default"] = array_with_newlines(
427+
iter::once("std".to_string()).chain(
428+
emitted_features
429+
.keys()
430+
.filter(|feature| is_default_feature(feature))
431+
.cloned(),
432+
),
395433
);
396434

397-
// Emit crates first.
435+
// Enable non-default features when building docs.
436+
let non_default_features: Vec<_> = emitted_features
437+
.keys()
438+
.filter(|feature| !is_default_feature(feature))
439+
.cloned()
440+
.collect();
441+
if !non_default_features.is_empty() {
442+
cargo_toml["package"]["metadata"]["docs"]["rs"]["features"] =
443+
array_with_newlines(non_default_features);
444+
}
445+
446+
// Emit crate features first (the "default" feature overrides in
447+
// `default_cargo.toml`).
398448
for (feature, _) in emitted_features.clone().iter() {
399449
if config.try_library_from_crate(feature).is_none() {
400450
continue;
401451
}
402452
let enabled_features = emitted_features.remove(feature).unwrap();
403-
let array: Array = enabled_features.iter().collect();
404-
cargo_toml["features"][feature] = value(array);
453+
cargo_toml["features"][feature] = array_with_newlines(enabled_features);
405454
}
406-
add_newline_at_end(&mut cargo_toml["features"]);
455+
407456
// And then the rest of the features.
457+
if !emitted_features.is_empty() {
458+
add_newline_at_end(&mut cargo_toml["features"]);
459+
}
408460
for (feature, enabled_features) in emitted_features {
409-
let mut array: Array = enabled_features.into_iter().collect();
410-
if 1 < array.len() {
411-
for item in array.iter_mut() {
412-
item.decor_mut().set_prefix("\n ");
413-
}
414-
array.set_trailing("\n");
415-
array.set_trailing_comma(true);
416-
}
417461
if cargo_toml["features"].get(&feature).is_none() {
418-
cargo_toml["features"][feature] = value(array);
462+
cargo_toml["features"][feature] = array_with_newlines(enabled_features);
419463
}
420464
}
421465

@@ -546,6 +590,18 @@ fn add_newline_at_end(item: &mut Item) {
546590
.set_suffix("\n");
547591
}
548592

593+
fn array_with_newlines(features: impl IntoIterator<Item = String>) -> Item {
594+
let mut array: Array = features.into_iter().collect();
595+
if 1 < array.len() {
596+
for item in array.iter_mut() {
597+
item.decor_mut().set_prefix("\n ");
598+
}
599+
array.set_trailing("\n");
600+
array.set_trailing_comma(true);
601+
}
602+
value(array)
603+
}
604+
549605
pub trait EntryExt<'a> {
550606
fn implicit_table(self) -> &'a mut Table;
551607
}

crates/header-translator/src/main.rs

+25-14
Original file line numberDiff line numberDiff line change
@@ -567,22 +567,32 @@ fn update_ci(workspace_dir: &Path, config: &Config) -> io::Result<()> {
567567
Ok(())
568568
}
569569

570+
// HACK: Linking `objc2-avf-audio` on older systems is not possible
571+
// without an SDK that's new enough.
572+
let uses_avf_audio = |lib: &LibraryConfig| {
573+
matches!(
574+
&*lib.krate,
575+
"objc2-avf-audio"
576+
| "objc2-av-foundation"
577+
| "objc2-av-kit"
578+
| "objc2-media-player"
579+
| "objc2-photos"
580+
| "objc2-photos-ui"
581+
| "objc2-sprite-kit"
582+
| "objc2-scene-kit"
583+
)
584+
};
570585
writer(&mut ci, config, "FRAMEWORKS_MACOS_10_12", |lib| {
571586
lib.macos
572587
.as_ref()
573588
.is_some_and(|v| VersionReq::parse("<=10.12").unwrap().matches(v))
574-
// HACK: These depend on `objc2-uniform-type-identifiers` or
575-
// `objc2-core-ml`, which is not available on macOS 10.12, but
576-
// will be enabled by `"all"`.
577-
&& !["objc2-app-kit", "objc2-file-provider", "objc2-health-kit", "objc2-photos", "objc2-core-image"].contains(&&*lib.krate)
589+
&& !uses_avf_audio(lib)
578590
})?;
579591
writer(&mut ci, config, "FRAMEWORKS_MACOS_10_13", |lib| {
580592
lib.macos
581593
.as_ref()
582594
.is_some_and(|v| VersionReq::parse("<=10.13").unwrap().matches(v))
583-
// HACK: These depend on `objc2-uniform-type-identifiers`, which
584-
// is not available on macOS 10.13, but will be enabled by `"all"`
585-
&& !["objc2-app-kit", "objc2-file-provider", "objc2-health-kit", "objc2-photos"].contains(&&*lib.krate)
595+
&& !uses_avf_audio(lib)
586596
})?;
587597
writer(&mut ci, config, "FRAMEWORKS_MACOS_11", |lib| {
588598
lib.macos
@@ -705,14 +715,15 @@ fn update_test_metadata<'a>(
705715

706716
let mut features = toml_edit::Array::new();
707717
for lib in libraries.clone() {
708-
features.push(format!("dep:{}", lib.krate));
709-
features.push(format!("{}/all", lib.krate));
718+
// Add feature per crate.
719+
//
720+
// This is required for some reason for `cargo run --example` to work
721+
// nicely in our workspace.
722+
cargo_toml["features"][&lib.krate] =
723+
toml_edit::Array::from_iter([format!("dep:{}", lib.krate)]).into();
724+
725+
features.push(lib.krate.to_string());
710726
// Inserting into array removes decor, so set it afterwards
711-
features
712-
.get_mut(features.len() - 2)
713-
.unwrap()
714-
.decor_mut()
715-
.set_prefix("\n ");
716727
features
717728
.get_mut(features.len() - 1)
718729
.unwrap()

crates/objc2-encode/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,3 @@ targets = [
4242
[package.metadata.release]
4343
shared-version = false
4444
tag-prefix = "objc-encode"
45-
enable-features = []

crates/objc2-exception-helper/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,3 @@ targets = [
5858
[package.metadata.release]
5959
shared-version = false
6060
tag-prefix = "objc2-exception-helper"
61-
enable-features = []

crates/objc2-proc-macros/Cargo.toml

-3
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ proc-macro = true
2121
workspace = true
2222

2323
[features]
24-
default = []
25-
2624
# Runtime selection (for future compatibility)
2725
gnustep-1-7 = []
2826
gnustep-1-8 = ["gnustep-1-7"]
@@ -47,4 +45,3 @@ targets = [
4745
[package.metadata.release]
4846
shared-version = false
4947
tag-prefix = "objc-proc-macros"
50-
enable-features = []

crates/objc2/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,3 @@ targets = [
179179
[package.metadata.release]
180180
shared-version = false
181181
tag-prefix = "objc"
182-
enable-features = []

0 commit comments

Comments
 (0)