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

allow enabling features for deps with --features #2876

Merged
merged 1 commit into from
Jul 14, 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
72 changes: 30 additions & 42 deletions src/cargo/core/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -659,53 +659,41 @@ fn build_features(s: &Summary, method: &Method)
visited: &mut HashSet<String>) -> CargoResult<()> {
if feat.is_empty() { return Ok(()) }

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));
// 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));
}
}
None => {
deps.entry(feat.to_string()).or_insert(Vec::new());
}
}
}
None => {
deps.entry(feat.to_string()).or_insert(Vec::new());
visited.remove(&feat.to_string());
}
}

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

Ok(())
}
}
Expand Down
70 changes: 70 additions & 0 deletions tests/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -881,3 +881,73 @@ fn activating_feature_activates_dep() {
assert_that(p.cargo_process("build").arg("--features").arg("a").arg("-v"),
execs().with_status(0));
}

#[test]
fn dep_feature_in_cmd_line() {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []

[dependencies.derived]
path = "derived"
"#)
.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"

[features]
default = []
derived-feat = ["bar/some-feat"]
"#)
.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]
some-feat = []
"#)
.file("bar/src/lib.rs", r#"
#[cfg(feature = "some-feat")]
pub fn test() { print!("test"); }
"#);

// The foo project requires that feature "some-feat" in "bar" is enabled.
// Building without any features enabled should fail:
assert_that(p.cargo_process("build"),
execs().with_status(101));

// We should be able to enable the feature "derived-feat", which enables "some-feat",
// on the command line. The feature is enabled, thus building should be successful:
assert_that(p.cargo_process("build").arg("--features").arg("derived/derived-feat"),
execs().with_status(0));

// Trying to enable features of transitive dependencies is an error
assert_that(p.cargo_process("build").arg("--features").arg("bar/some-feat"),
execs().with_status(101).with_stderr("\
[ERROR] Package `foo v0.0.1 ([..])` does not have these features: `bar`
"));

// Hierarchical feature specification should still be disallowed
assert_that(p.cargo_process("build").arg("--features").arg("derived/bar/some-feat"),
execs().with_status(101).with_stderr("\
[ERROR] feature names may not contain slashes: `bar/some-feat`
"));
}