diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 357381cf822..e8ec8ac7e43 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -1548,7 +1548,7 @@ impl<'gctx> Workspace<'gctx> { found_features: &mut BTreeSet, ) -> CliFeatures { if cli_features.features.is_empty() { - return cli_features.clone(); + return Workspace::with_default_bin_required_features(member, cli_features.clone()); } // Only include features this member defines. @@ -1613,11 +1613,41 @@ impl<'gctx> Workspace<'gctx> { } } } - CliFeatures { - features: Rc::new(features), - all_features: cli_features.all_features, - uses_default_features: cli_features.uses_default_features, + Workspace::with_default_bin_required_features( + member, + CliFeatures { + features: Rc::new(features), + all_features: cli_features.all_features, + uses_default_features: cli_features.uses_default_features, + }, + ) + } + + fn with_default_bin_required_features( + member: &Package, + mut cli_features: CliFeatures, + ) -> CliFeatures { + if !cli_features.uses_default_features || cli_features.all_features { + return cli_features; + } + + let mut features = (*cli_features.features).clone(); + let original_len = features.len(); + features.extend( + member + .targets() + .iter() + .filter(|target| target.is_bin()) + .filter_map(|target| target.required_features()) + .flatten() + .cloned() + .map(Into::into) + .map(FeatureValue::new), + ); + if features.len() != original_len { + cli_features.features = Rc::new(features); } + cli_features } fn missing_feature_spelling_suggestions( @@ -1959,11 +1989,14 @@ impl<'gctx> Workspace<'gctx> { // The features passed on the command-line only apply to // the "current" package (determined by the cwd). Some(current) if member_id == current.package_id() => { - let feats = CliFeatures { - features: Rc::new(cwd_features.clone()), - all_features: cli_features.all_features, - uses_default_features: cli_features.uses_default_features, - }; + let feats = Workspace::with_default_bin_required_features( + member, + CliFeatures { + features: Rc::new(cwd_features.clone()), + all_features: cli_features.all_features, + uses_default_features: cli_features.uses_default_features, + }, + ); Some((member, feats)) } _ => { @@ -1978,15 +2011,18 @@ impl<'gctx> Workspace<'gctx> { // "current" package. As an extension, this allows // member-name/feature-name to set member-specific // features, which should be backwards-compatible. - let feats = CliFeatures { - features: Rc::new( - member_specific_features - .remove(member.name().as_str()) - .unwrap_or_default(), - ), - uses_default_features: true, - all_features: cli_features.all_features, - }; + let feats = Workspace::with_default_bin_required_features( + member, + CliFeatures { + features: Rc::new( + member_specific_features + .remove(member.name().as_str()) + .unwrap_or_default(), + ), + uses_default_features: true, + all_features: cli_features.all_features, + }, + ); Some((member, feats)) } else { // This member was not requested on the command-line, skip. diff --git a/tests/testsuite/required_features.rs b/tests/testsuite/required_features.rs index ab6b59fc24b..972ece35268 100644 --- a/tests/testsuite/required_features.rs +++ b/tests/testsuite/required_features.rs @@ -85,6 +85,9 @@ fn build_bin_arg_features() { .file("src/main.rs", "fn main() {}") .build(); + p.cargo("build").run(); + assert!(p.bin("foo").is_file()); + p.cargo("build --features a").run(); assert!(p.bin("foo").is_file()); } @@ -124,7 +127,7 @@ fn build_bin_multiple_required_features() { p.cargo("build").run(); - assert!(!p.bin("foo_1").is_file()); + assert!(p.bin("foo_1").is_file()); assert!(p.bin("foo_2").is_file()); p.cargo("build --features c").run(); @@ -132,6 +135,15 @@ fn build_bin_multiple_required_features() { assert!(p.bin("foo_1").is_file()); assert!(p.bin("foo_2").is_file()); + p.cargo("build --bin=foo_1 --no-default-features") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] target `foo_1` in package `foo` requires the features: `b`, `c` +Consider enabling them by passing, e.g., `--features="b c"` + +"#]]) + .run(); + p.cargo("build --no-default-features").run(); } @@ -819,6 +831,10 @@ fn install_arg_features() { .file("src/main.rs", "fn main() {}") .build(); + p.cargo("install --path .").run(); + assert_has_installed_exe(paths::cargo_home(), "foo"); + p.cargo("uninstall foo").run(); + p.cargo("install --features a").run(); assert_has_installed_exe(paths::cargo_home(), "foo"); p.cargo("uninstall foo").run(); @@ -870,16 +886,16 @@ fn install_multiple_required_features() { .build(); p.cargo("install --path .").run(); - assert_has_not_installed_exe(paths::cargo_home(), "foo_1"); + assert_has_installed_exe(paths::cargo_home(), "foo_1"); assert_has_installed_exe(paths::cargo_home(), "foo_2"); assert_has_not_installed_exe(paths::cargo_home(), "foo_3"); assert_has_not_installed_exe(paths::cargo_home(), "foo_4"); p.cargo("uninstall foo").run(); p.cargo("install --path . --bins --examples").run(); - assert_has_not_installed_exe(paths::cargo_home(), "foo_1"); + assert_has_installed_exe(paths::cargo_home(), "foo_1"); assert_has_installed_exe(paths::cargo_home(), "foo_2"); - assert_has_not_installed_exe(paths::cargo_home(), "foo_3"); + assert_has_installed_exe(paths::cargo_home(), "foo_3"); assert_has_installed_exe(paths::cargo_home(), "foo_4"); p.cargo("uninstall foo").run(); @@ -890,6 +906,22 @@ fn install_multiple_required_features() { assert_has_not_installed_exe(paths::cargo_home(), "foo_4"); p.cargo("uninstall foo").run(); + p.cargo("install --path . --bin foo_1 --no-default-features") + .with_status(101) + .with_stderr_data(str![[r#" +[INSTALLING] foo v0.0.1 ([ROOT]/foo) +[ERROR] failed to compile `foo v0.0.1 ([ROOT]/foo)`, intermediate artifacts can be found at `[ROOT]/foo/target`. +To reuse those artifacts with a future compilation, set the environment variable `CARGO_BUILD_BUILD_DIR` to that path. + +Caused by: + target `foo_1` in package `foo` requires the features: `b`, `c` + Consider enabling them by passing, e.g., `--features="b c"` + +"#]]) + .run(); + assert_has_not_installed_exe(paths::cargo_home(), "foo_1"); + assert_has_not_installed_exe(paths::cargo_home(), "foo_2"); + p.cargo("install --path . --features c --bins --examples") .run(); assert_has_installed_exe(paths::cargo_home(), "foo_1");