Skip to content

Commit

Permalink
Auto merge of #11647 - peterallin:peterallin/11617, r=weihanglo
Browse files Browse the repository at this point in the history
Make cargo install report needed features

### What does this PR try to resolve?

The problem described in issue #11617 where cargo tells the user, that no binaries are available
because of the chosen features, but does not tell which features could be enabled to fix the
situation.

Fixes #11617.

### How should we test and review this PR?

Try to cargo install a crate, where all binaries need some features enabled, without enabling
the features and check the error message.

The test suite already contains tests for this.
  • Loading branch information
bors committed Jan 31, 2023
2 parents bf096ca + f92f42f commit 2c30ebe
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 3 deletions.
52 changes: 49 additions & 3 deletions src/cargo/ops/cargo_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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>,
Expand Down
117 changes: 117 additions & 0 deletions tests/testsuite/required_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
}

0 comments on commit 2c30ebe

Please sign in to comment.