Skip to content

Commit 696e615

Browse files
authored
Auto merge of #2876 - bennofs:fix-2851, r=alexcrichton
allow enabling features for deps with --features Fixes #2851
2 parents 1c329c4 + 6f28ef2 commit 696e615

File tree

2 files changed

+100
-42
lines changed

2 files changed

+100
-42
lines changed

src/cargo/core/resolver/mod.rs

Lines changed: 30 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -659,53 +659,41 @@ fn build_features(s: &Summary, method: &Method)
659659
visited: &mut HashSet<String>) -> CargoResult<()> {
660660
if feat.is_empty() { return Ok(()) }
661661

662-
if !visited.insert(feat.to_string()) {
663-
bail!("Cyclic feature dependency: feature `{}` depends \
664-
on itself", feat)
665-
}
666-
667-
used.insert(feat.to_string());
668-
669-
// The lookup of here may fail if the feature corresponds to an optional
670-
// dependency. If that is the case, we simply enable the optional dependency.
671-
//
672-
// Otherwise, we check what other features the feature wants and recursively
673-
// add them.
674-
match s.features().get(feat) {
675-
Some(wanted_features) => {
676-
for entry in wanted_features {
677-
// If the entry is of the form `foo/bar`, then we just lookup package
678-
// `foo` and enable its feature `bar`. We also add `foo` to the used
679-
// set because `foo` might have been an optional dependency.
680-
//
681-
// Otherwise the entry refers to another feature of our current package,
682-
// so we recurse by calling add_feature again, which may end up enabling
683-
// more features or just enabling a dependency (remember, optional
684-
// dependencies create an implicit feature with the same name).
685-
let mut parts = entry.splitn(2, '/');
686-
let feat_or_package = parts.next().unwrap();
687-
match parts.next() {
688-
Some(feat) => {
689-
let package = feat_or_package;
690-
used.insert(package.to_string());
691-
deps.entry(package.to_string())
692-
.or_insert(Vec::new())
693-
.push(feat.to_string());
694-
}
695-
None => {
696-
let feat = feat_or_package;
697-
try!(add_feature(s, feat, deps, used, visited));
662+
// If this feature is of the form `foo/bar`, then we just lookup package
663+
// `foo` and enable its feature `bar`. Otherwise this feature is of the
664+
// form `foo` and we need to recurse to enable the feature `foo` for our
665+
// own package, which may end up enabling more features or just enabling
666+
// a dependency.
667+
let mut parts = feat.splitn(2, '/');
668+
let feat_or_package = parts.next().unwrap();
669+
match parts.next() {
670+
Some(feat) => {
671+
let package = feat_or_package;
672+
used.insert(package.to_string());
673+
deps.entry(package.to_string())
674+
.or_insert(Vec::new())
675+
.push(feat.to_string());
676+
}
677+
None => {
678+
let feat = feat_or_package;
679+
if !visited.insert(feat.to_string()) {
680+
bail!("Cyclic feature dependency: feature `{}` depends \
681+
on itself", feat)
682+
}
683+
used.insert(feat.to_string());
684+
match s.features().get(feat) {
685+
Some(recursive) => {
686+
for f in recursive {
687+
try!(add_feature(s, f, deps, used, visited));
698688
}
699689
}
690+
None => {
691+
deps.entry(feat.to_string()).or_insert(Vec::new());
692+
}
700693
}
701-
}
702-
None => {
703-
deps.entry(feat.to_string()).or_insert(Vec::new());
694+
visited.remove(&feat.to_string());
704695
}
705696
}
706-
707-
visited.remove(&feat.to_string());
708-
709697
Ok(())
710698
}
711699
}

tests/features.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,3 +881,73 @@ fn activating_feature_activates_dep() {
881881
assert_that(p.cargo_process("build").arg("--features").arg("a").arg("-v"),
882882
execs().with_status(0));
883883
}
884+
885+
#[test]
886+
fn dep_feature_in_cmd_line() {
887+
let p = project("foo")
888+
.file("Cargo.toml", r#"
889+
[project]
890+
name = "foo"
891+
version = "0.0.1"
892+
authors = []
893+
894+
[dependencies.derived]
895+
path = "derived"
896+
"#)
897+
.file("src/main.rs", r#"
898+
extern crate derived;
899+
fn main() { derived::test(); }
900+
"#)
901+
.file("derived/Cargo.toml", r#"
902+
[package]
903+
name = "derived"
904+
version = "0.0.1"
905+
authors = []
906+
907+
[dependencies.bar]
908+
path = "../bar"
909+
910+
[features]
911+
default = []
912+
derived-feat = ["bar/some-feat"]
913+
"#)
914+
.file("derived/src/lib.rs", r#"
915+
extern crate bar;
916+
pub use bar::test;
917+
"#)
918+
.file("bar/Cargo.toml", r#"
919+
[package]
920+
name = "bar"
921+
version = "0.0.1"
922+
authors = []
923+
924+
[features]
925+
some-feat = []
926+
"#)
927+
.file("bar/src/lib.rs", r#"
928+
#[cfg(feature = "some-feat")]
929+
pub fn test() { print!("test"); }
930+
"#);
931+
932+
// The foo project requires that feature "some-feat" in "bar" is enabled.
933+
// Building without any features enabled should fail:
934+
assert_that(p.cargo_process("build"),
935+
execs().with_status(101));
936+
937+
// We should be able to enable the feature "derived-feat", which enables "some-feat",
938+
// on the command line. The feature is enabled, thus building should be successful:
939+
assert_that(p.cargo_process("build").arg("--features").arg("derived/derived-feat"),
940+
execs().with_status(0));
941+
942+
// Trying to enable features of transitive dependencies is an error
943+
assert_that(p.cargo_process("build").arg("--features").arg("bar/some-feat"),
944+
execs().with_status(101).with_stderr("\
945+
[ERROR] Package `foo v0.0.1 ([..])` does not have these features: `bar`
946+
"));
947+
948+
// Hierarchical feature specification should still be disallowed
949+
assert_that(p.cargo_process("build").arg("--features").arg("derived/bar/some-feat"),
950+
execs().with_status(101).with_stderr("\
951+
[ERROR] feature names may not contain slashes: `bar/some-feat`
952+
"));
953+
}

0 commit comments

Comments
 (0)