diff --git a/src/cargo/ops/cargo_install.rs b/src/cargo/ops/cargo_install.rs index 21de0997b..c9fcbc405 100644 --- a/src/cargo/ops/cargo_install.rs +++ b/src/cargo/ops/cargo_install.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use std::{env, fs}; use crate::core::compiler::{CompileKind, DefaultExecutor, Executor, UnitOutput}; -use crate::core::{Dependency, Edition, Package, PackageId, Source, SourceId, Workspace}; +use crate::core::{Dependency, Edition, Package, PackageId, Source, SourceId, Target, Workspace}; use crate::ops::{common_for_install_and_uninstall::*, FilterRule}; use crate::ops::{CompileFilter, Packages}; use crate::sources::{GitSource, PathSource, SourceConfigMap}; @@ -14,6 +14,7 @@ use crate::{drop_println, ops}; use anyhow::{bail, format_err, Context as _}; use cargo_util::paths; +use itertools::Itertools; use semver::VersionReq; use tempfile::Builder as TempFileBuilder; @@ -360,10 +361,16 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> { // // Note that we know at this point that _if_ bins or examples is set to `::Just`, // they're `::Just([])`, which is `FilterRule::none()`. - if self.pkg.targets().iter().any(|t| t.is_executable()) { + let binaries: Vec<_> = self + .pkg + .targets() + .iter() + .filter(|t| t.is_executable()) + .collect(); + if !binaries.is_empty() { self.config .shell() - .warn("none of the package's binaries are available for install using the selected features")?; + .warn(make_warning_about_missing_features(&binaries))?; } return Ok(false); @@ -546,6 +553,45 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> { } } +fn make_warning_about_missing_features(binaries: &[&Target]) -> String { + let max_targets_listed = 7; + let target_features_message = binaries + .iter() + .take(max_targets_listed) + .map(|b| { + let name = b.description_named(); + let features = b + .required_features() + .unwrap_or(&Vec::new()) + .iter() + .map(|f| format!("`{f}`")) + .join(", "); + format!(" {name} requires the features: {features}") + }) + .join("\n"); + + let additional_bins_message = if binaries.len() > max_targets_listed { + format!( + "\n{} more targets also requires features not enabled. See them in the Cargo.toml file.", + binaries.len() - max_targets_listed + ) + } else { + "".into() + }; + + let example_features = binaries[0] + .required_features() + .map(|f| f.join(" ")) + .unwrap_or_default(); + + format!( + "\ +none of the package's binaries are available for install using the selected features +{target_features_message}{additional_bins_message} +Consider enabling some of the needed features by passing, e.g., `--features=\"{example_features}\"`" + ) +} + pub fn install( config: &Config, root: Option<&str>, diff --git a/tests/testsuite/required_features.rs b/tests/testsuite/required_features.rs index 340a138ca..ac6c9d233 100644 --- a/tests/testsuite/required_features.rs +++ b/tests/testsuite/required_features.rs @@ -640,6 +640,9 @@ fn install_default_features() { [INSTALLING] foo v0.0.1 ([..]) [FINISHED] release [optimized] target(s) in [..] [WARNING] none of the package's binaries are available for install using the selected features + bin \"foo\" requires the features: `a` + example \"foo\" requires the features: `a` +Consider enabling some of the needed features by passing, e.g., `--features=\"a\"` ", ) .run(); @@ -792,6 +795,11 @@ fn install_multiple_required_features() { [INSTALLING] foo v0.0.1 ([..]) [FINISHED] release [optimized] target(s) in [..] [WARNING] none of the package's binaries are available for install using the selected features + bin \"foo_1\" requires the features: `b`, `c` + bin \"foo_2\" requires the features: `a` + example \"foo_3\" requires the features: `b`, `c` + example \"foo_4\" requires the features: `a` +Consider enabling some of the needed features by passing, e.g., `--features=\"b c\"` ", ) .run(); @@ -802,6 +810,11 @@ fn install_multiple_required_features() { [WARNING] Target filter `bins` specified, but no targets matched. This is a no-op [FINISHED] release [optimized] target(s) in [..] [WARNING] none of the package's binaries are available for install using the selected features + bin \"foo_1\" requires the features: `b`, `c` + bin \"foo_2\" requires the features: `a` + example \"foo_3\" requires the features: `b`, `c` + example \"foo_4\" requires the features: `a` +Consider enabling some of the needed features by passing, e.g., `--features=\"b c\"` ", ) .run(); @@ -812,6 +825,11 @@ fn install_multiple_required_features() { [WARNING] Target filter `examples` specified, but no targets matched. This is a no-op [FINISHED] release [optimized] target(s) in [..] [WARNING] none of the package's binaries are available for install using the selected features + bin \"foo_1\" requires the features: `b`, `c` + bin \"foo_2\" requires the features: `a` + example \"foo_3\" requires the features: `b`, `c` + example \"foo_4\" requires the features: `a` +Consider enabling some of the needed features by passing, e.g., `--features=\"b c\"` ", ) .run(); @@ -822,6 +840,11 @@ fn install_multiple_required_features() { [WARNING] Target filters `bins`, `examples` specified, but no targets matched. This is a no-op [FINISHED] release [optimized] target(s) in [..] [WARNING] none of the package's binaries are available for install using the selected features + bin \"foo_1\" requires the features: `b`, `c` + bin \"foo_2\" requires the features: `a` + example \"foo_3\" requires the features: `b`, `c` + example \"foo_4\" requires the features: `a` +Consider enabling some of the needed features by passing, e.g., `--features=\"b c\"` ", ) .run(); @@ -1080,6 +1103,9 @@ Consider enabling them by passing, e.g., `--features=\"bar/a\"` [INSTALLING] foo v0.0.1 ([..]) [FINISHED] release [optimized] target(s) in [..] [WARNING] none of the package's binaries are available for install using the selected features + bin \"foo\" requires the features: `bar/a` + example \"foo\" requires the features: `bar/a` +Consider enabling some of the needed features by passing, e.g., `--features=\"bar/a\"` ", ) .run(); @@ -1333,3 +1359,94 @@ Consider enabling them by passing, e.g., `--features=\"a1/f1\"` .with_stdout("a1 f1\na2 f2") .run(); } + +#[cargo_test] +fn truncated_install_warning_message() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2021" + + [features] + feature1 = [] + feature2 = [] + feature3 = [] + feature4 = [] + feature5 = [] + + [[bin]] + name = "foo1" + required-features = ["feature1", "feature2", "feature3"] + + [[bin]] + name = "foo2" + required-features = ["feature2"] + + [[bin]] + name = "foo3" + required-features = ["feature3"] + + [[bin]] + name = "foo4" + required-features = ["feature4", "feature1"] + + [[bin]] + name = "foo5" + required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"] + + [[bin]] + name = "foo6" + required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"] + + [[bin]] + name = "foo7" + required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"] + + [[bin]] + name = "foo8" + required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"] + + [[bin]] + name = "foo9" + required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"] + + [[bin]] + name = "foo10" + required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"] + + [[example]] + name = "example1" + required-features = ["feature1", "feature2"] + "#, + ) + .file("src/bin/foo1.rs", "fn main() {}") + .file("src/bin/foo2.rs", "fn main() {}") + .file("src/bin/foo3.rs", "fn main() {}") + .file("src/bin/foo4.rs", "fn main() {}") + .file("src/bin/foo5.rs", "fn main() {}") + .file("src/bin/foo6.rs", "fn main() {}") + .file("src/bin/foo7.rs", "fn main() {}") + .file("src/bin/foo8.rs", "fn main() {}") + .file("src/bin/foo9.rs", "fn main() {}") + .file("src/bin/foo10.rs", "fn main() {}") + .file("examples/example1.rs", "fn main() {}") + .build(); + + p.cargo("install --path .").with_stderr("\ +[INSTALLING] foo v0.1.0 ([..]) +[FINISHED] release [optimized] target(s) in [..] +[WARNING] none of the package's binaries are available for install using the selected features + bin \"foo1\" requires the features: `feature1`, `feature2`, `feature3` + bin \"foo2\" requires the features: `feature2` + bin \"foo3\" requires the features: `feature3` + bin \"foo4\" requires the features: `feature4`, `feature1` + bin \"foo5\" requires the features: `feature1`, `feature2`, `feature3`, `feature4`, `feature5` + bin \"foo6\" requires the features: `feature1`, `feature2`, `feature3`, `feature4`, `feature5` + bin \"foo7\" requires the features: `feature1`, `feature2`, `feature3`, `feature4`, `feature5` +4 more targets also requires features not enabled. See them in the Cargo.toml file. +Consider enabling some of the needed features by passing, e.g., `--features=\"feature1 feature2 feature3\"`").run(); +}