diff --git a/src/bin/cargo/commands/install.rs b/src/bin/cargo/commands/install.rs index 621558d97c4..f0bbb4d7971 100644 --- a/src/bin/cargo/commands/install.rs +++ b/src/bin/cargo/commands/install.rs @@ -1,9 +1,11 @@ use crate::command_prelude::*; -use cargo::core::{GitReference, SourceId}; +use cargo::core::{GitReference, SourceId, Workspace}; use cargo::ops; use cargo::util::IntoUrl; +use cargo_util::paths; + pub fn cli() -> App { subcommand("install") .about("Install a Rust binary. Default location is $HOME/.cargo/bin") @@ -80,13 +82,20 @@ pub fn cli() -> App { } pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { - if let Some(path) = args.value_of_path("path", config) { + let path = args.value_of_path("path", config); + if let Some(path) = &path { config.reload_rooted_at(path)?; } else { // TODO: Consider calling set_search_stop_path(home). config.reload_rooted_at(config.home().clone().into_path_unlocked())?; } + // In general, we try to avoid normalizing paths in Cargo, + // but in these particular cases we need it to fix rust-lang/cargo#10283. + // (Handle `SourceId::for_path` and `Workspace::new`, + // but not `Config::reload_rooted_at` which is always cwd) + let path = path.map(|p| paths::normalize_path(&p)); + let krates = args .values_of("crate") .unwrap_or_default() @@ -106,7 +115,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { GitReference::DefaultBranch }; SourceId::for_git(&url, gitref)? - } else if let Some(path) = args.value_of_path("path", config) { + } else if let Some(path) = &path { SourceId::for_path(&path)? } else if krates.is_empty() { from_cwd = true; @@ -125,9 +134,14 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { // We only provide workspace information for local crate installation from // one of the following sources: // - From current working directory (only work for edition 2015). - // - From a specific local file path. - let workspace = if from_cwd || args.is_present("path") { + // - From a specific local file path (from `--path` arg). + // + // This workspace information is for emitting helpful messages from + // `ArgMatchesExt::compile_options` and won't affect the actual compilation. + let workspace = if from_cwd { args.workspace(config).ok() + } else if let Some(path) = &path { + Workspace::new(&path.join("Cargo.toml"), config).ok() } else { None }; diff --git a/tests/testsuite/install.rs b/tests/testsuite/install.rs index c4a63edb39f..5442c1884c9 100644 --- a/tests/testsuite/install.rs +++ b/tests/testsuite/install.rs @@ -7,7 +7,7 @@ use cargo_test_support::cross_compile; use cargo_test_support::git; use cargo_test_support::registry::{self, registry_path, registry_url, Package}; use cargo_test_support::{ - basic_manifest, cargo_process, no_such_file_err_msg, project, symlink_supported, t, + basic_manifest, cargo_process, no_such_file_err_msg, project, project_in, symlink_supported, t, }; use cargo_test_support::install::{ @@ -493,6 +493,70 @@ but found cargo.toml please try to rename it to Cargo.toml. --path must point to .run(); } +#[cargo_test] +fn install_relative_path_outside_current_ws() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "bar" + version = "0.1.0" + authors = [] + + [workspace] + members = ["baz"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "baz/Cargo.toml", + r#" + [package] + name = "baz" + version = "0.1.0" + authors = [] + edition = "2021" + + [dependencies] + foo = "1" + "#, + ) + .file("baz/src/lib.rs", "") + .build(); + + let _bin_project = project_in("bar") + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("install --path ../bar/foo") + .with_stderr(&format!( + "\ +[INSTALLING] foo v0.0.1 ([..]/bar/foo) +[COMPILING] foo v0.0.1 ([..]/bar/foo) +[FINISHED] release [..] +[INSTALLING] {home}/bin/foo[EXE] +[INSTALLED] package `foo v0.0.1 ([..]/bar/foo)` (executable `foo[EXE]`) +[WARNING] be sure to add [..] +", + home = cargo_home().display(), + )) + .run(); + + // Validate the workspace error message to display available targets. + p.cargo("install --path ../bar/foo --bin") + .with_status(101) + .with_stderr( + "\ +[ERROR] \"--bin\" takes one argument. +Available binaries: + foo + +", + ) + .run(); +} + #[cargo_test] fn multiple_crates_error() { let p = git::repo(&paths::root().join("foo"))