Skip to content

Commit 5854d39

Browse files
authored
fix: display absolute path in the missing in PATH warning (#16125)
### What does this PR try to resolve? close #16023 Improve the warning message for realtive install location. 1. Did a small refactoring to inline GlobalContext::get_path to prevent potential misuse in the future. 2. Made Cargo always display the absolute install path in warnings. 3. Added a warning indicating that `install.root` paths without a trailing backslash will be deprecated in the future. ### How to test and review this PR? Please check the unit test. r?@ghost
2 parents d58031b + 35fb192 commit 5854d39

File tree

4 files changed

+199
-16
lines changed

4 files changed

+199
-16
lines changed

src/cargo/ops/cargo_install.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,15 @@ impl<'gctx> InstallablePackage<'gctx> {
316316
fn install_one(mut self, dry_run: bool) -> CargoResult<bool> {
317317
self.gctx.shell().status("Installing", &self.pkg)?;
318318

319+
// Normalize to absolute path for consistency throughout.
320+
// See: https://github.com/rust-lang/cargo/issues/16023
319321
let dst = self.root.join("bin").into_path_unlocked();
322+
let cwd = self.gctx.cwd();
323+
let dst = if dst.is_absolute() {
324+
paths::normalize_path(dst.as_path())
325+
} else {
326+
paths::normalize_path(&cwd.join(&dst))
327+
};
320328

321329
let mut td_opt = None;
322330
let mut needs_cleanup = false;
@@ -655,7 +663,15 @@ pub fn install(
655663
lockfile_path: Option<&Path>,
656664
) -> CargoResult<()> {
657665
let root = resolve_root(root, gctx)?;
666+
// Normalize to absolute path for consistency throughout.
667+
// See: https://github.com/rust-lang/cargo/issues/16023
658668
let dst = root.join("bin").into_path_unlocked();
669+
let cwd = gctx.cwd();
670+
let dst = if dst.is_absolute() {
671+
paths::normalize_path(dst.as_path())
672+
} else {
673+
paths::normalize_path(&cwd.join(&dst))
674+
};
659675
let map = SourceConfigMap::new(gctx)?;
660676

661677
let current_rust_version = if opts.honor_rust_version.unwrap_or(true) {

src/cargo/ops/common_for_install_and_uninstall.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};
66
use std::rc::Rc;
77
use std::task::Poll;
88

9+
use annotate_snippets::Level;
910
use anyhow::{Context as _, bail, format_err};
1011
use cargo_util::paths;
1112
use cargo_util_schemas::core::PartialVersion;
@@ -21,6 +22,7 @@ use crate::sources::source::QueryKind;
2122
use crate::sources::source::Source;
2223
use crate::util::GlobalContext;
2324
use crate::util::cache_lock::CacheLockMode;
25+
use crate::util::context::{ConfigRelativePath, Definition};
2426
use crate::util::errors::CargoResult;
2527
use crate::util::{FileLock, Filesystem};
2628

@@ -545,11 +547,39 @@ impl InstallInfo {
545547

546548
/// Determines the root directory where installation is done.
547549
pub fn resolve_root(flag: Option<&str>, gctx: &GlobalContext) -> CargoResult<Filesystem> {
548-
let config_root = gctx.get_path("install.root")?;
550+
let config_root = match gctx.get::<Option<ConfigRelativePath>>("install.root")? {
551+
Some(p) => {
552+
let resolved = p.resolve_program(gctx);
553+
if resolved.is_relative() {
554+
let definition = p.value().definition.clone();
555+
if matches!(definition, Definition::Path(_)) {
556+
let suggested = format!("{}/", resolved.display());
557+
let notes = [
558+
Level::NOTE.message("a future version of Cargo will treat it as relative to the configuration directory"),
559+
Level::HELP.message(format!("add a trailing slash (`{}`) to adopt the correct behavior and silence this warning", suggested)),
560+
Level::NOTE.message("see more at https://doc.rust-lang.org/cargo/reference/config.html#config-relative-paths"),
561+
];
562+
gctx.shell().print_report(
563+
&[Level::WARNING
564+
.secondary_title(format!(
565+
"the `install.root` value `{}` defined in {} without a trailing slash is deprecated",
566+
resolved.display(),
567+
definition
568+
))
569+
.elements(notes)],
570+
false,
571+
)?;
572+
}
573+
}
574+
Some(resolved)
575+
}
576+
None => None,
577+
};
578+
549579
Ok(flag
550580
.map(PathBuf::from)
551581
.or_else(|| gctx.get_env_os("CARGO_INSTALL_ROOT").map(PathBuf::from))
552-
.or_else(move || config_root.map(|v| v.val))
582+
.or_else(|| config_root)
553583
.map(Filesystem::new)
554584
.unwrap_or_else(|| gctx.home().clone()))
555585
}

src/cargo/util/context/mod.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -989,20 +989,6 @@ impl GlobalContext {
989989
self.get::<OptValue<String>>(key)
990990
}
991991

992-
/// Get a config value that is expected to be a path.
993-
///
994-
/// This returns a relative path if the value does not contain any
995-
/// directory separators. See `ConfigRelativePath::resolve_program` for
996-
/// more details.
997-
pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
998-
self.get::<OptValue<ConfigRelativePath>>(key).map(|v| {
999-
v.map(|v| Value {
1000-
val: v.val.resolve_program(self),
1001-
definition: v.definition,
1002-
})
1003-
})
1004-
}
1005-
1006992
fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
1007993
let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
1008994
if is_path {

tests/testsuite/install.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,157 @@ fn install_location_precedence() {
500500
assert_has_installed_exe(&t4, "foo");
501501
}
502502

503+
#[cargo_test]
504+
fn relative_install_location_without_trailing_slash() {
505+
let p = project().file("src/main.rs", "fn main() {}").build();
506+
507+
let root = paths::root();
508+
let root_t1 = root.join("t1");
509+
let p_path = p.root().to_path_buf();
510+
let project_t1 = p_path.join("t1");
511+
512+
fs::create_dir(root.join(".cargo")).unwrap();
513+
fs::write(
514+
root.join(".cargo/config.toml"),
515+
r#"
516+
[install]
517+
root = "t1"
518+
"#,
519+
)
520+
.unwrap();
521+
522+
let mut cmd = cargo_process("install --path .");
523+
cmd.cwd(p.root());
524+
cmd.with_stderr_data(str![[r#"
525+
[WARNING] the `install.root` value `t1` defined in [ROOT]/.cargo/config.toml without a trailing slash is deprecated
526+
|
527+
= [NOTE] a future version of Cargo will treat it as relative to the configuration directory
528+
= [HELP] add a trailing slash (`t1/`) to adopt the correct behavior and silence this warning
529+
= [NOTE] see more at https://doc.rust-lang.org/cargo/reference/config.html#config-relative-paths
530+
[INSTALLING] foo v0.0.1 ([ROOT]/foo)
531+
[COMPILING] foo v0.0.1 ([ROOT]/foo)
532+
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
533+
[INSTALLING] [ROOT]/foo/t1/bin/foo[EXE]
534+
[INSTALLED] package `foo v0.0.1 ([ROOT]/foo)` (executable `foo[EXE]`)
535+
[WARNING] be sure to add `[ROOT]/foo/t1/bin` to your PATH to be able to run the installed binaries
536+
537+
"#]])
538+
.run();
539+
540+
// NOTE: the install location is relative to the CWD, not the config file
541+
assert_has_not_installed_exe(&root_t1, "foo");
542+
assert_has_installed_exe(&project_t1, "foo");
543+
}
544+
545+
#[cargo_test]
546+
fn cli_root_argument_without_deprecation_warning() {
547+
// Verify that using the --root CLI argument does not produce the deprecation warning.
548+
let p = project().file("src/main.rs", "fn main() {}").build();
549+
550+
let root = paths::root();
551+
let root_t1 = root.join("t1");
552+
let p_path = p.root().to_path_buf();
553+
let project_t1 = p_path.join("t1");
554+
555+
cargo_process("install --path . --root")
556+
.arg("t1")
557+
.cwd(p.root())
558+
.with_stderr_data(str![[r#"
559+
[INSTALLING] foo v0.0.1 ([ROOT]/foo)
560+
[COMPILING] foo v0.0.1 ([ROOT]/foo)
561+
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
562+
[INSTALLING] [ROOT]/foo/t1/bin/foo[EXE]
563+
[INSTALLED] package `foo v0.0.1 ([ROOT]/foo)` (executable `foo[EXE]`)
564+
[WARNING] be sure to add `[ROOT]/foo/t1/bin` to your PATH to be able to run the installed binaries
565+
566+
"#]])
567+
.run();
568+
assert_has_not_installed_exe(&root_t1, "foo");
569+
assert_has_installed_exe(&project_t1, "foo");
570+
}
571+
572+
#[cargo_test]
573+
fn relative_install_location_with_trailing_slash() {
574+
let p = project().file("src/main.rs", "fn main() {}").build();
575+
576+
let root = paths::root();
577+
let root_t1 = root.join("t1");
578+
let p_path = p.root().to_path_buf();
579+
let project_t1 = p_path.join("t1");
580+
581+
fs::create_dir(root.join(".cargo")).unwrap();
582+
fs::write(
583+
root.join(".cargo/config.toml"),
584+
r#"
585+
[install]
586+
root = "t1/"
587+
"#,
588+
)
589+
.unwrap();
590+
591+
let mut cmd = cargo_process("install --path .");
592+
cmd.cwd(p.root());
593+
cmd.with_stderr_data(str![[r#"
594+
[INSTALLING] foo v0.0.1 ([ROOT]/foo)
595+
[COMPILING] foo v0.0.1 ([ROOT]/foo)
596+
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
597+
[INSTALLING] [ROOT]/t1/bin/foo[EXE]
598+
[INSTALLED] package `foo v0.0.1 ([ROOT]/foo)` (executable `foo[EXE]`)
599+
[WARNING] be sure to add `[ROOT]/t1/bin` to your PATH to be able to run the installed binaries
600+
601+
"#]])
602+
.run();
603+
604+
assert_has_installed_exe(&root_t1, "foo");
605+
assert_has_not_installed_exe(&project_t1, "foo");
606+
}
607+
608+
#[cargo_test]
609+
fn relative_install_location_with_path_set() {
610+
// Test that when the absolute install path is in PATH, no warning is shown
611+
let p = project().file("src/main.rs", "fn main() {}").build();
612+
613+
let root = paths::root();
614+
let p_path = p.root().to_path_buf();
615+
let project_t1 = p_path.join("t1");
616+
617+
fs::create_dir(root.join(".cargo")).unwrap();
618+
fs::write(
619+
root.join(".cargo/config.toml"),
620+
r#"
621+
[install]
622+
root = "t1"
623+
"#,
624+
)
625+
.unwrap();
626+
627+
// Add the absolute path to PATH environment variable
628+
let install_bin_path = project_t1.join("bin");
629+
let mut path = path();
630+
path.push(install_bin_path);
631+
let new_path = env::join_paths(path).unwrap();
632+
633+
let mut cmd = cargo_process("install --path .");
634+
cmd.cwd(p.root());
635+
cmd.env("PATH", new_path);
636+
cmd.with_stderr_data(str![[r#"
637+
[WARNING] the `install.root` value `t1` defined in [ROOT]/.cargo/config.toml without a trailing slash is deprecated
638+
|
639+
= [NOTE] a future version of Cargo will treat it as relative to the configuration directory
640+
= [HELP] add a trailing slash (`t1/`) to adopt the correct behavior and silence this warning
641+
= [NOTE] see more at https://doc.rust-lang.org/cargo/reference/config.html#config-relative-paths
642+
[INSTALLING] foo v0.0.1 ([ROOT]/foo)
643+
[COMPILING] foo v0.0.1 ([ROOT]/foo)
644+
[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
645+
[INSTALLING] [ROOT]/foo/t1/bin/foo[EXE]
646+
[INSTALLED] package `foo v0.0.1 ([ROOT]/foo)` (executable `foo[EXE]`)
647+
648+
"#]])
649+
.run();
650+
651+
assert_has_installed_exe(&project_t1, "foo");
652+
}
653+
503654
#[cargo_test]
504655
fn install_path() {
505656
let p = project().file("src/main.rs", "fn main() {}").build();

0 commit comments

Comments
 (0)