-
Notifications
You must be signed in to change notification settings - Fork 2.6k
implement package feature unification #15684
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
base: master
Are you sure you want to change the base?
Conversation
r? @weihanglo rustbot has assigned @weihanglo. Use |
ad48f18
to
e94564d
Compare
e94564d
to
c6090d5
Compare
Thanks for moving this forward! |
c6090d5
to
e5a3134
Compare
Feel free to edit the commits as needed |
b10f5d9
to
2457810
Compare
I've also just added a test with weak dependencies, which works. I tried to also add namespaced |
2457810
to
d28a30f
Compare
d28a30f
to
a8b46d7
Compare
a8b46d7
to
52725c0
Compare
52725c0
to
6c7ca34
Compare
I had to also narrow down However, simply narrowing it down does not work for resolver v1 because there are tests like I've added a test for this but added it just to the commit that's supposed to fix the tests. I think I've done three different things regarding splitting tests into commits by now since this is adding a feature so behavior of tests before and after the addition may be either behavior with |
Per usual, would prefer tests and test cases to be added in that initial commit. Whatever it does (nothing, change behavior, start passing, start erroring), seeing that change of state through the diffs is very helpful for reviewing and demonstrating what the behavior is. |
6c7ca34
to
06c0d91
Compare
Ok, I've added all the tests with |
src/cargo/ops/resolve.rs
Outdated
// We want to narrow the features to the current specs so that stuff like `cargo check -p a | ||
// -p b -F a/a,b/b` works and the resolver does not contain that `a` does not have feature | ||
// `b` and vice-versa. However, resolver v1 needs to see even features of unselected | ||
// packages turned on if it was because of working directory being inside the unselected | ||
// package, because they might turn on a feature of a selected package. | ||
let narrowed_features = match feature_unification { | ||
FeatureUnification::Package => { | ||
let mut narrowed_features = cli_features.clone(); | ||
let enabled_features = members_with_features | ||
.iter() | ||
.filter_map(|(package, cli_features)| { | ||
specs | ||
.iter() | ||
.any(|spec| spec.matches(package.package_id())) | ||
.then_some(cli_features.features.iter()) | ||
}) | ||
.flatten() | ||
.cloned() | ||
.collect(); | ||
narrowed_features.features = Rc::new(enabled_features); | ||
Cow::Owned(narrowed_features) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the --features
docs
Space or comma separated list of features to activate. Features of workspace members may be enabled with package-name/feature-name syntax. This flag may be specified multiple times, which enables all specified features.
The way this is worded, it makes it sound like you can specify a feature of a dependency so long as its a workspace member. Whether its intended or not, you can even specify features for transitive dependencies so long as all of the activated deps are already in your Cargo.lock
@ehuss wanted to double check my reading of those docs and if you had any additional ideas about the problem of CARGO_RESOLVER_FEATURE_UNIFICATION=package cargo check -p a -F a/a -p b -F b/b
trying to activate the b/b
feature when b
isn't in the resolve.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does work that way, I've added a test case just now to the cli features tests having cargo check -p a -F a/a,common/b
because members_with_features
already contains the a
feature and also a DepFeature
for common/b
, both for package a
. If that's what you mean.
Arguably that reading could even allow cargo check -p a -F b/b
even if b
is not a dependency of a
(the quoted docs don't mention dependencies at all) but that's rejected regardless of feature unification settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if I'm understanding correctly, the narrowed features are preserving that existing behavior
Didn't help that I read through this multiple times before it clicked what you are doing with this code block. You had already divided up features by workspace member, so now you are getting the ones only relevant for the selected workspace members to apply to resolution.
What would this then do with CARGO_RESOLVER_FEATURE_UNIFICATION=package cargo check -p a -F transitive/feature
? I'm assuming it silently ignores transitive/feature
as it wasn't in members_with_features
and so doesn't get forwarded on to the feature resolver.
That case isn't officially supported but it would at least be good to call out if the behavior deviates from regular cargo behavior in the tracking issue. We can then decide how we want to handle that case more generally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean something like this? It fails for both package
and selected
so I assume not, but then I don't understand what case do you mean.
Test case
#[cargo_test]
fn transitive_feature() {
Package::new("child", "1.0.0")
.add_dep(&Dependency::new("grand_child", "1.0"))
.publish();
Package::new("grand_child", "1.0.0")
.feature("a", &[])
.file(
"lib.rs",
r#"
#[cfg(not(feature = "a"))]
compile_error!("missing feature");
"#,
)
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[dependencies]
chile = "1.0"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -F grand_child/a")
.arg("-Zfeature-unification")
.masquerade_as_nightly_cargo(&["feature-unification"])
.env("CARGO_RESOLVER_FEATURE_UNIFICATION", "selected")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] the package 'foo' does not contain this feature: grand_child/a
"#]])
.run();
p.cargo("check -F grand_child/a")
.arg("-Zfeature-unification")
.masquerade_as_nightly_cargo(&["feature-unification"])
.env("CARGO_RESOLVER_FEATURE_UNIFICATION", "package")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] the package 'foo' does not contain this feature: grand_child/a
"#]])
.run();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like I over-estimated what is supported. The case I'm talking about is limited to direct dependencies:
#[cargo_test]
fn non_member_features() {
Package::new("child", "1.0.0")
.add_dep(&Dependency::new("grand_child", "1.0"))
.feature("a", &[])
.file(
"src/lib.rs",
r#"
#[cfg(feature = "a")]
compile_error!("`a` is active");
"#,
)
.publish();
Package::new("grand_child", "1.0.0")
.feature("a", &[])
.file(
"src/lib.rs",
r#"
#[cfg(feature = "a")]
compile_error!("`a` is active");
"#,
)
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[dependencies]
child = "1.0"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -F child/a")
.with_status(101)
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest Rust 1.87.0 compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] grand_child v1.0.0 (registry `dummy-registry`)
[DOWNLOADED] child v1.0.0 (registry `dummy-registry`)
[CHECKING] grand_child v1.0.0
[CHECKING] child v1.0.0
[ERROR] `a` is active
--> [ROOT]/home/.cargo/registry/src/-[HASH]/child-1.0.0/src/lib.rs:3:13
|
3 | compile_error!("`a` is active");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[ERROR] could not compile `child` (lib) due to 1 previous error
"#]])
.run();
p.cargo("check -F grand_child/a")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] the package 'foo' does not contain this feature: grand_child/a
"#]])
.run();
}
Just need to double check on one question (#15684 (comment)) but otherwise, this looks good. Thanks for your work on this and patience through the review feedback! |
06c0d91
to
cd1cff4
Compare
What does this PR try to resolve?
Implements another part of feature unification (#14774, rfc). The
workspace
option was implemented in #15157, this adds thepackage
option.How to test and review this PR?
The important change is changing
WorkspaceResolve
so it can contain multipleResolvedFeature
s. Along with that, it also needs to know which specs those features are resolved for. This was used in several other places:cargo fix --edition
(from 2018 to 2021) - I think it should be ok to disallow usingcargo fix --edition
when someone already uses this feature.cargo tree
- I just use the first feature set. This is definitely not ideal, but I'm not entirely sure what's the correct solution here. Printing multiple trees? Disallowing this, forcing users to select only one package?Based on comments in #15157 I've added tests first with
selected
feature unification and then changed that after implementation. I'm not sure if that's how you expect the tests to be added first, if not, I can change the history.I've expanded the test checking that this is ignored for
cargo install
although it should work the same way even if it is not ignored (selected
andpackage
are the same thing when just one package is selected).