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

Fix bug when with resolver = "1" non-virtual package was allowing unknown features #9437

Merged
merged 12 commits into from
May 24, 2021
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ clap = "2.31.2"
unicode-width = "0.1.5"
openssl = { version = '0.10.11', optional = true }
im-rc = "15.0.0"
itertools = "0.10.0"

# A noop dependency that changes in the Rust repository, it's a bit of a hack.
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
Expand Down
69 changes: 51 additions & 18 deletions src/cargo/core/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::slice;

use anyhow::{bail, Context as _};
use glob::glob;
use itertools::Itertools;
use log::debug;
use url::Url;

Expand Down Expand Up @@ -1071,7 +1072,7 @@ impl<'cfg> Workspace<'cfg> {
if self.allows_new_cli_feature_behavior() {
self.members_with_features_new(specs, cli_features)
} else {
Ok(self.members_with_features_old(specs, cli_features))
self.members_with_features_old(specs, cli_features)
}
}

Expand Down Expand Up @@ -1196,7 +1197,7 @@ impl<'cfg> Workspace<'cfg> {
&self,
specs: &[PackageIdSpec],
cli_features: &CliFeatures,
) -> Vec<(&Package, CliFeatures)> {
) -> CargoResult<Vec<(&Package, CliFeatures)>> {
// Split off any features with the syntax `member-name/feature-name` into a map
// so that those features can be applied directly to those workspace-members.
let mut member_specific_features: HashMap<InternedString, BTreeSet<FeatureValue>> =
Expand All @@ -1215,29 +1216,29 @@ impl<'cfg> Workspace<'cfg> {
} => panic!("unexpected dep: syntax {}", feature),
FeatureValue::DepFeature {
dep_name,
dep_feature,
dep_feature: _,
dep_prefix: _,
weak: _,
} => {
// I think weak can be ignored here.
// * With `--features member?/feat -p member`, the ? doesn't
// really mean anything (either the member is built or it isn't).
// * With `--features nonmember?/feat`, cwd_features will
// handle processing it correctly.
// Check if `dep_name` is member of the workspace or package.
// Weak can be ignored for this moment.
let is_member = self.members().any(|member| member.name() == *dep_name);
if is_member && specs.iter().any(|spec| spec.name() == *dep_name) {
member_specific_features
.entry(*dep_name)
.or_default()
.insert(FeatureValue::Feature(*dep_feature));
.insert(feature.clone());
In-line marked this conversation as resolved.
Show resolved Hide resolved
} else {
// With `--features nonmember?/feat`, cwd_features will
// handle processing it correctly.
cwd_features.insert(feature.clone());
}
}
}
}

let ms = self.members().filter_map(|member| {
let mut result = Vec::new();
for member in self.members() {
let member_id = member.package_id();
match self.current_opt() {
// The features passed on the command-line only apply to
Expand All @@ -1248,13 +1249,29 @@ impl<'cfg> Workspace<'cfg> {
all_features: cli_features.all_features,
uses_default_features: cli_features.uses_default_features,
};
Some((member, feats))

// If any member specific features were passed to non-virtual package, it's error
if !member_specific_features.is_empty() {
In-line marked this conversation as resolved.
Show resolved Hide resolved
let invalid: Vec<_> = member_specific_features
.values()
.map(|set| set.iter())
.flatten()
.map(|feature| feature.to_string())
.sorted()
.collect();

bail!(
"Member specific features with `pkg/feat` syntax are dissalowed outside of workspace with `resolver = \"1\", remove: {}",
invalid.join(", ")
);
}

result.push((member, feats))
}
_ => {
// Ignore members that are not enabled on the command-line.
if specs.iter().any(|spec| spec.matches(member_id)) {
// -p for a workspace member that is not the "current"
// one.
// -p for a workspace member that is not the "current" one.
//
// The odd behavior here is due to backwards
// compatibility. `--features` and
Expand All @@ -1266,20 +1283,36 @@ impl<'cfg> Workspace<'cfg> {
features: Rc::new(
member_specific_features
.remove(member.name().as_str())
.unwrap_or_default(),
.unwrap_or_default()
.into_iter()
.map(|feature| match feature {
// I think weak can be ignored here.
// With `--features member?/feat -p member`, the ? doesn't
// really mean anything (either the member is built or it isn't).
FeatureValue::DepFeature {
dep_name: _,
dep_feature,
dep_prefix: false,
weak: _,
} => FeatureValue::new(dep_feature),
// Member specific features by definition contain only `FeatureValue::DepFeature`
_ => unreachable!(),
})
.collect(),
),
uses_default_features: true,
all_features: cli_features.all_features,
};
Some((member, feats))

result.push((member, feats))
} else {
// This member was not requested on the command-line, skip.
None
}
}
}
});
ms.collect()
}

Ok(result)
}
}

Expand Down
51 changes: 51 additions & 0 deletions tests/testsuite/package_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,51 @@ fn other_member_from_current() {
.run();
}

#[cargo_test]
fn feature_default_resolver() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "a"
version = "0.1.0"

[features]
test = []
"#,
)
.file(
"src/main.rs",
r#"
fn main() {
if cfg!(feature = "test") {
println!("feature set");
}
}
"#,
)
.build();

p.cargo("check --features testt")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr("[ERROR] Package `a[..]` does not have the feature `testt`")
.run();

p.cargo("run --features test")
.masquerade_as_nightly_cargo()
.with_status(0)
.with_stdout("feature set")
.run();

p.cargo("run --features a/test")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr("[ERROR] Member specific features with `pkg/feat` syntax are dissalowed outside of workspace with `resolver = \"1\", remove: a/test")
.run();
}

#[cargo_test]
fn virtual_member_slash() {
// member slash feature syntax
Expand Down Expand Up @@ -458,6 +503,12 @@ fn resolver1_member_features() {
.cwd("member2")
.with_stdout("m1-feature set")
.run();

p.cargo("check -p member1 --features member1/m2-feature")
.cwd("member2")
.with_status(101)
.with_stderr("[ERROR] Package `member1[..]` does not have the feature `m2-feature`")
.run();
}

#[cargo_test]
Expand Down