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

Disallow specifying features of transitive deps #2821

Merged
merged 1 commit into from
Jul 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
79 changes: 45 additions & 34 deletions src/cargo/core/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -658,41 +658,53 @@ fn build_features(s: &Summary, method: &Method)
visited: &mut HashSet<String>) -> CargoResult<()> {
if feat.is_empty() { return Ok(()) }

// If this feature is of the form `foo/bar`, then we just lookup package
// `foo` and enable its feature `bar`. Otherwise this feature is of the
// form `foo` and we need to recurse to enable the feature `foo` for our
// own package, which may end up enabling more features or just enabling
// a dependency.
let mut parts = feat.splitn(2, '/');
let feat_or_package = parts.next().unwrap();
match parts.next() {
Some(feat) => {
let package = feat_or_package;
used.insert(package.to_string());
deps.entry(package.to_string())
.or_insert(Vec::new())
.push(feat.to_string());
}
None => {
let feat = feat_or_package;
if !visited.insert(feat.to_string()) {
bail!("Cyclic feature dependency: feature `{}` depends \
on itself", feat)
}
used.insert(feat.to_string());
match s.features().get(feat) {
Some(recursive) => {
for f in recursive {
try!(add_feature(s, f, deps, used, visited));
if !visited.insert(feat.to_string()) {
bail!("Cyclic feature dependency: feature `{}` depends \
on itself", feat)
}

used.insert(feat.to_string());

// The lookup of here may fail if the feature corresponds to an optional
// dependency. If that is the case, we simply enable the optional dependency.
//
// Otherwise, we check what other features the feature wants and recursively
// add them.
match s.features().get(feat) {
Some(wanted_features) => {
for entry in wanted_features {
// If the entry is of the form `foo/bar`, then we just lookup package
// `foo` and enable its feature `bar`. We also add `foo` to the used
// set because `foo` might have been an optional dependency.
//
// Otherwise the entry refers to another feature of our current package,
// so we recurse by calling add_feature again, which may end up enabling
// more features or just enabling a dependency (remember, optional
// dependencies create an implicit feature with the same name).
let mut parts = entry.splitn(2, '/');
let feat_or_package = parts.next().unwrap();
match parts.next() {
Some(feat) => {
let package = feat_or_package;
used.insert(package.to_string());
deps.entry(package.to_string())
.or_insert(Vec::new())
.push(feat.to_string());
}
None => {
let feat = feat_or_package;
try!(add_feature(s, feat, deps, used, visited));
}
}
None => {
deps.entry(feat.to_string()).or_insert(Vec::new());
}
}
visited.remove(&feat.to_string());
}
None => {
deps.entry(feat.to_string()).or_insert(Vec::new());
}
}

visited.remove(&feat.to_string());

Ok(())
}
}
Expand Down Expand Up @@ -843,11 +855,10 @@ impl<'a> Context<'a> {
continue
}
let mut base = feature_deps.remove(dep.name()).unwrap_or(vec![]);
for feature in dep.features().iter() {
base.push(feature.clone());
base.extend(dep.features().iter().map(|x| x.clone()));
for feature in base.iter() {
if feature.contains("/") {
bail!("features in dependencies cannot enable features in \
other dependencies: `{}`", feature)
bail!("feature names may not contain slashes: `{}`", feature);
}
}
ret.push((dep.clone(), base));
Expand Down
53 changes: 52 additions & 1 deletion tests/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,58 @@ fn invalid8() {

assert_that(p.cargo_process("build").arg("--features").arg("foo"),
execs().with_status(101).with_stderr("\
[ERROR] features in dependencies cannot enable features in other dependencies: `foo/bar`
[ERROR] feature names may not contain slashes: `foo/bar`
"));
}

#[test]
fn no_transitive_dep_feature_requirement() {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
[dependencies.derived]
path = "derived"
[features]
default = ["derived/bar/qux"]
"#)
.file("src/main.rs", r#"
extern crate derived;
fn main() { derived::test(); }
"#)
.file("derived/Cargo.toml", r#"
[package]
name = "derived"
version = "0.0.1"
authors = []
[dependencies.bar]
path = "../bar"
"#)
.file("derived/src/lib.rs", r#"
extern crate bar;
pub use bar::test;
"#)
.file("bar/Cargo.toml", r#"
[package]
name = "bar"
version = "0.0.1"
authors = []
[features]
qux = []
"#)
.file("bar/src/lib.rs", r#"
#[cfg(feature = "qux")]
pub fn test() { print!("test"); }
"#);
assert_that(p.cargo_process("build"),
execs().with_status(101).with_stderr("\
[ERROR] feature names may not contain slashes: `bar/qux`
"));
}

Expand Down