diff --git a/src/cargo/ops/cargo_install.rs b/src/cargo/ops/cargo_install.rs index 4380d3f48c1..13e3c2e1cda 100644 --- a/src/cargo/ops/cargo_install.rs +++ b/src/cargo/ops/cargo_install.rs @@ -5,7 +5,8 @@ use std::{env, fs}; use crate::core::compiler::{CompileKind, DefaultExecutor, Executor, Freshness, UnitOutput}; use crate::core::{Dependency, Edition, Package, PackageId, Source, SourceId, Workspace}; -use crate::ops::common_for_install_and_uninstall::*; +use crate::ops::CompileFilter; +use crate::ops::{common_for_install_and_uninstall::*, FilterRule}; use crate::sources::{GitSource, PathSource, SourceConfigMap}; use crate::util::errors::CargoResult; use crate::util::{Config, Filesystem, Rustc, ToSemver, VersionReqExt}; @@ -272,7 +273,7 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> { Ok(duplicates) } - fn install_one(mut self) -> CargoResult<()> { + fn install_one(mut self) -> CargoResult { self.config.shell().status("Installing", &self.pkg)?; let dst = self.root.join("bin").into_path_unlocked(); @@ -322,7 +323,43 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> { }) .collect::>()?; if binaries.is_empty() { - bail!("no binaries are available for install using the selected features"); + // Cargo already warns the user if they use a target specifier that matches nothing, + // but we want to error if the user asked for a _particular_ binary to be installed, + // and we didn't end up installing it. + // + // NOTE: This _should_ be impossible to hit since --bin=does_not_exist will fail on + // target selection, and --bin=requires_a without --features=a will fail with "target + // .. requires the features ..". But rather than assume that's the case, we define the + // behavior for this fallback case as well. + if let CompileFilter::Only { bins, examples, .. } = &self.opts.filter { + let mut any_specific = false; + if let FilterRule::Just(ref v) = bins { + if !v.is_empty() { + any_specific = true; + } + } + if let FilterRule::Just(ref v) = examples { + if !v.is_empty() { + any_specific = true; + } + } + if any_specific { + bail!("no binaries are available for install using the selected features"); + } + } + + // If there _are_ binaries available, but none were selected given the current set of + // features, let the user know. + // + // 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()) { + self.config + .shell() + .warn("none of the package's binaries are available for install using the selected features")?; + } + + return Ok(false); } // This is primarily to make testing easier. binaries.sort_unstable(); @@ -455,7 +492,7 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> { executables(successful_bins.iter()) ), )?; - Ok(()) + Ok(true) } else { if !to_install.is_empty() { self.config.shell().status( @@ -481,7 +518,7 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> { ), )?; } - Ok(()) + Ok(true) } } @@ -545,10 +582,11 @@ pub fn install( no_track, true, )?; + let mut installed_anything = true; if let Some(installable_pkg) = installable_pkg { - installable_pkg.install_one()?; + installed_anything = installable_pkg.install_one()?; } - (true, false) + (installed_anything, false) } else { let mut succeeded = vec![]; let mut failed = vec![]; @@ -601,8 +639,10 @@ pub fn install( for (krate, result) in install_results { match result { - Ok(()) => { - succeeded.push(krate); + Ok(installed) => { + if installed { + succeeded.push(krate); + } } Err(e) => { crate::display_error(&e, &mut config.shell()); diff --git a/tests/testsuite/required_features.rs b/tests/testsuite/required_features.rs index 04d9aa646ce..78418a422fe 100644 --- a/tests/testsuite/required_features.rs +++ b/tests/testsuite/required_features.rs @@ -650,12 +650,11 @@ fn install_default_features() { p.cargo("uninstall foo").run(); p.cargo("install --path . --no-default-features") - .with_status(101) .with_stderr( "\ [INSTALLING] foo v0.0.1 ([..]) [FINISHED] release [optimized] target(s) in [..] -[ERROR] no binaries are available for install using the selected features +[WARNING] none of the package's binaries are available for install using the selected features ", ) .run(); @@ -755,34 +754,96 @@ fn install_multiple_required_features() { name = "foo_2" path = "src/foo_2.rs" required-features = ["a"] + + [[example]] + name = "foo_3" + path = "src/foo_3.rs" + required-features = ["b", "c"] + + [[example]] + name = "foo_4" + path = "src/foo_4.rs" + required-features = ["a"] "#, ) .file("src/foo_1.rs", "fn main() {}") .file("src/foo_2.rs", "fn main() {}") + .file("src/foo_3.rs", "fn main() {}") + .file("src/foo_4.rs", "fn main() {}") .build(); p.cargo("install --path .").run(); assert_has_not_installed_exe(cargo_home(), "foo_1"); assert_has_installed_exe(cargo_home(), "foo_2"); + assert_has_not_installed_exe(cargo_home(), "foo_3"); + assert_has_not_installed_exe(cargo_home(), "foo_4"); + p.cargo("uninstall foo").run(); + + p.cargo("install --path . --bins --examples").run(); + assert_has_not_installed_exe(cargo_home(), "foo_1"); + assert_has_installed_exe(cargo_home(), "foo_2"); + assert_has_not_installed_exe(cargo_home(), "foo_3"); + assert_has_installed_exe(cargo_home(), "foo_4"); p.cargo("uninstall foo").run(); p.cargo("install --path . --features c").run(); assert_has_installed_exe(cargo_home(), "foo_1"); assert_has_installed_exe(cargo_home(), "foo_2"); + assert_has_not_installed_exe(cargo_home(), "foo_3"); + assert_has_not_installed_exe(cargo_home(), "foo_4"); + p.cargo("uninstall foo").run(); + + p.cargo("install --path . --features c --bins --examples") + .run(); + assert_has_installed_exe(cargo_home(), "foo_1"); + assert_has_installed_exe(cargo_home(), "foo_2"); + assert_has_installed_exe(cargo_home(), "foo_3"); + assert_has_installed_exe(cargo_home(), "foo_4"); p.cargo("uninstall foo").run(); p.cargo("install --path . --no-default-features") - .with_status(101) .with_stderr( "\ [INSTALLING] foo v0.0.1 ([..]) [FINISHED] release [optimized] target(s) in [..] -[ERROR] no binaries are available for install using the selected features +[WARNING] none of the package's binaries are available for install using the selected features +", + ) + .run(); + p.cargo("install --path . --no-default-features --bins") + .with_stderr( + "\ +[INSTALLING] foo v0.0.1 ([..]) +[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 +", + ) + .run(); + p.cargo("install --path . --no-default-features --examples") + .with_stderr( + "\ +[INSTALLING] foo v0.0.1 ([..]) +[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 +", + ) + .run(); + p.cargo("install --path . --no-default-features --bins --examples") + .with_stderr( + "\ +[INSTALLING] foo v0.0.1 ([..]) +[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 ", ) .run(); assert_has_not_installed_exe(cargo_home(), "foo_1"); assert_has_not_installed_exe(cargo_home(), "foo_2"); + assert_has_not_installed_exe(cargo_home(), "foo_3"); + assert_has_not_installed_exe(cargo_home(), "foo_4"); } #[cargo_test] @@ -1029,12 +1090,11 @@ Consider enabling them by passing, e.g., `--features=\"bar/a\"` // install p.cargo("install --path .") - .with_status(101) .with_stderr( "\ [INSTALLING] foo v0.0.1 ([..]) [FINISHED] release [optimized] target(s) in [..] -[ERROR] no binaries are available for install using the selected features +[WARNING] none of the package's binaries are available for install using the selected features ", ) .run();