From 8424b5f3f40b4a65d2e09bb2467cef59adf93319 Mon Sep 17 00:00:00 2001 From: Flowrey Date: Mon, 22 Jul 2024 20:39:36 +0200 Subject: [PATCH 1/3] test: add test for install without dry-run option --- tests/testsuite/install.rs | 159 +++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/tests/testsuite/install.rs b/tests/testsuite/install.rs index a0487dbfd74..1ae61870f1b 100644 --- a/tests/testsuite/install.rs +++ b/tests/testsuite/install.rs @@ -2734,3 +2734,162 @@ fn uninstall_running_binary() { "#]]).run(); } + +#[cargo_test] +fn dry_run() { + pkg("foo", "0.0.1"); + + cargo_process("install foo") + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index +[DOWNLOADING] crates ... +[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`) +[INSTALLING] foo v0.0.1 +[COMPILING] foo v0.0.1 +[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s +[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE] +[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`) +[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries + +"#]]) + .run(); + assert_has_installed_exe(paths::cargo_home(), "foo"); +} + +#[cargo_test] +fn dry_run_incompatible_package() { + Package::new("some-package-from-the-distant-future", "0.0.1") + .rust_version("1.2345.0") + .file("src/main.rs", "fn main() {}") + .publish(); + + cargo_process("install some-package-from-the-distant-future") + .with_status(101) + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index +[ERROR] cannot install package `some-package-from-the-distant-future 0.0.1`, it requires rustc 1.2345.0 or newer, while the currently active rustc version is [..] + +"#]]) + .run(); + assert_has_not_installed_exe(paths::cargo_home(), "some-package-from-the-distant-future"); +} + +#[cargo_test] +fn dry_run_incompatible_package_dependecy() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + some-package-from-the-distant-future = { path = "a" } + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "a/Cargo.toml", + r#" + [package] + name = "some-package-from-the-distant-future" + version = "0.1.0" + authors = [] + rust-version = "1.2345.0" + "#, + ) + .file("a/src/lib.rs", "") + .build(); + + cargo_process("install --path") + .arg(p.root()) + .arg("foo") + .with_status(101) + .with_stderr_data(str![[r#" +[INSTALLING] foo v0.1.0 ([ROOT]/foo) +[LOCKING] 1 package to latest compatible version +[ERROR] failed to compile `foo v0.1.0 ([ROOT]/foo)`, intermediate artifacts can be found at `[ROOT]/foo/target`. +To reuse those artifacts with a future compilation, set the environment variable `CARGO_TARGET_DIR` to that path. + +Caused by: + rustc [..] is not supported by the following package: + some-package-from-the-distant-future@0.1.0 requires rustc 1.2345.0 + +"#]]) + .run(); + assert_has_not_installed_exe(paths::cargo_home(), "foo"); +} + +#[cargo_test] +fn dry_run_upgrade() { + pkg("foo", "0.0.1"); + cargo_process("install foo").run(); + assert_has_installed_exe(paths::cargo_home(), "foo"); + + pkg("foo", "0.0.2"); + cargo_process("install foo") + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index +[DOWNLOADING] crates ... +[DOWNLOADED] foo v0.0.2 (registry `dummy-registry`) +[INSTALLING] foo v0.0.2 +[COMPILING] foo v0.0.2 +[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s +[REPLACING] [ROOT]/home/.cargo/bin/foo[EXE] +[REPLACED] package `foo v0.0.1` with `foo v0.0.2` (executable `foo[EXE]`) +[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries + +"#]]) + .run(); + assert_has_installed_exe(paths::cargo_home(), "foo"); +} + +#[cargo_test] +fn dry_run_remove_orphan() { + Package::new("bar", "1.0.0") + .file("src/bin/client.rs", "fn main() {}") + .file("src/bin/server.rs", "fn main() {}") + .publish(); + + cargo_process("install bar") + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index +[DOWNLOADING] crates ... +[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) +[INSTALLING] bar v1.0.0 +[COMPILING] bar v1.0.0 +[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s +[INSTALLING] [ROOT]/home/.cargo/bin/client[EXE] +[INSTALLING] [ROOT]/home/.cargo/bin/server[EXE] +[INSTALLED] package `bar v1.0.0` (executables `client[EXE]`, `server[EXE]`) +[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries + +"#]]) + .run(); + assert_has_installed_exe(paths::cargo_home(), "client"); + assert_has_installed_exe(paths::cargo_home(), "server"); + + Package::new("bar", "2.0.0") + .file("src/bin/client.rs", "fn main() {}") + .publish(); + + cargo_process("install bar") + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index +[DOWNLOADING] crates ... +[DOWNLOADED] bar v2.0.0 (registry `dummy-registry`) +[INSTALLING] bar v2.0.0 +[COMPILING] bar v2.0.0 +[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s +[REPLACING] [ROOT]/home/.cargo/bin/client[EXE] +[REMOVING] executable `[ROOT]/home/.cargo/bin/server[EXE]` from previous version bar v1.0.0 +[REPLACED] package `bar v1.0.0` with `bar v2.0.0` (executable `client[EXE]`) +[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries + +"#]]) + .run(); + assert_has_installed_exe(paths::cargo_home(), "client"); + assert_has_not_installed_exe(paths::cargo_home(), "server"); +} From 874a8a04bf737f6749ff96bf3fb2d8af73f273da Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 27 Aug 2024 14:56:33 -0500 Subject: [PATCH 2/3] refactor(compile): Extract test collection --- src/cargo/core/compiler/build_runner/mod.rs | 53 +++++++++++---------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/cargo/core/compiler/build_runner/mod.rs b/src/cargo/core/compiler/build_runner/mod.rs index 43d00e1ec85..3a6cd2a707b 100644 --- a/src/cargo/core/compiler/build_runner/mod.rs +++ b/src/cargo/core/compiler/build_runner/mod.rs @@ -214,31 +214,7 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> { // Collect the result of the build into `self.compilation`. for unit in &self.bcx.roots { - // Collect tests and executables. - for output in self.outputs(unit)?.iter() { - if output.flavor == FileFlavor::DebugInfo || output.flavor == FileFlavor::Auxiliary - { - continue; - } - - let bindst = output.bin_dst(); - - if unit.mode == CompileMode::Test { - self.compilation - .tests - .push(self.unit_output(unit, &output.path)); - } else if unit.target.is_executable() { - self.compilation - .binaries - .push(self.unit_output(unit, bindst)); - } else if unit.target.is_cdylib() - && !self.compilation.cdylibs.iter().any(|uo| uo.unit == *unit) - { - self.compilation - .cdylibs - .push(self.unit_output(unit, bindst)); - } - } + self.collect_tests_and_executables(unit)?; // Collect information for `rustdoc --test`. if unit.mode.is_doc_test() { @@ -307,6 +283,33 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> { Ok(self.compilation) } + fn collect_tests_and_executables(&mut self, unit: &Unit) -> CargoResult<()> { + for output in self.outputs(unit)?.iter() { + if output.flavor == FileFlavor::DebugInfo || output.flavor == FileFlavor::Auxiliary { + continue; + } + + let bindst = output.bin_dst(); + + if unit.mode == CompileMode::Test { + self.compilation + .tests + .push(self.unit_output(unit, &output.path)); + } else if unit.target.is_executable() { + self.compilation + .binaries + .push(self.unit_output(unit, bindst)); + } else if unit.target.is_cdylib() + && !self.compilation.cdylibs.iter().any(|uo| uo.unit == *unit) + { + self.compilation + .cdylibs + .push(self.unit_output(unit, bindst)); + } + } + Ok(()) + } + /// Returns the executable for the specified unit (if any). pub fn get_executable(&mut self, unit: &Unit) -> CargoResult> { let is_binary = unit.target.is_executable(); From b0e08fcd3278b727a4492832316c39efd5433d77 Mon Sep 17 00:00:00 2001 From: Flowrey Date: Mon, 22 Jul 2024 20:39:59 +0200 Subject: [PATCH 3/3] feat: add --dry-run to install command --- src/bin/cargo/commands/install.rs | 6 +- src/cargo/core/compiler/build_config.rs | 3 + src/cargo/core/compiler/build_runner/mod.rs | 21 +++++ src/cargo/ops/cargo_compile/mod.rs | 6 +- src/cargo/ops/cargo_install.rs | 67 +++++++++------ src/doc/man/cargo-install.md | 4 + src/doc/man/generated_txt/cargo-install.txt | 3 + src/doc/src/commands/cargo-install.md | 5 ++ src/etc/man/cargo-install.1 | 6 ++ .../cargo_install/help/stdout.term.svg | 82 ++++++++++--------- tests/testsuite/install.rs | 32 ++++---- 11 files changed, 153 insertions(+), 82 deletions(-) diff --git a/src/bin/cargo/commands/install.rs b/src/bin/cargo/commands/install.rs index 8aaaeb87b0e..dabdc79c353 100644 --- a/src/bin/cargo/commands/install.rs +++ b/src/bin/cargo/commands/install.rs @@ -69,6 +69,7 @@ pub fn cli() -> Command { ) .arg(opt("root", "Directory to install packages into").value_name("DIR")) .arg(flag("force", "Force overwriting existing crates or binaries").short('f')) + .arg_dry_run("Perform all checks without installing (unstable)") .arg(flag("no-track", "Do not save tracking information")) .arg(flag( "list", @@ -200,7 +201,9 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { compile_opts.build_config.requested_profile = args.get_profile_name("release", ProfileChecking::Custom)?; - + if args.dry_run() { + gctx.cli_unstable().fail_if_stable_opt("--dry-run", 11123)?; + } if args.flag("list") { ops::install_list(root, gctx)?; } else { @@ -213,6 +216,7 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { &compile_opts, args.flag("force"), args.flag("no-track"), + args.dry_run(), )?; } Ok(()) diff --git a/src/cargo/core/compiler/build_config.rs b/src/cargo/core/compiler/build_config.rs index 0f66d6dbbc6..4c804f27b68 100644 --- a/src/cargo/core/compiler/build_config.rs +++ b/src/cargo/core/compiler/build_config.rs @@ -31,6 +31,8 @@ pub struct BuildConfig { pub build_plan: bool, /// Output the unit graph to stdout instead of actually compiling. pub unit_graph: bool, + /// `true` to avoid really compiling. + pub dry_run: bool, /// An optional override of the rustc process for primary units pub primary_unit_rustc: Option, /// A thread used by `cargo fix` to receive messages on a socket regarding @@ -112,6 +114,7 @@ impl BuildConfig { force_rebuild: false, build_plan: false, unit_graph: false, + dry_run: false, primary_unit_rustc: None, rustfix_diagnostic_server: Rc::new(RefCell::new(None)), export_dir: None, diff --git a/src/cargo/core/compiler/build_runner/mod.rs b/src/cargo/core/compiler/build_runner/mod.rs index 3a6cd2a707b..65314115af0 100644 --- a/src/cargo/core/compiler/build_runner/mod.rs +++ b/src/cargo/core/compiler/build_runner/mod.rs @@ -126,6 +126,27 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> { }) } + /// Dry-run the compilation without actually running it. + /// + /// This is expected to collect information like the location of output artifacts. + /// Please keep in sync with non-compilation part in [`BuildRunner::compile`]. + pub fn dry_run(mut self) -> CargoResult> { + let _lock = self + .bcx + .gctx + .acquire_package_cache_lock(CacheLockMode::Shared)?; + self.lto = super::lto::generate(self.bcx)?; + self.prepare_units()?; + self.prepare()?; + self.check_collisions()?; + + for unit in &self.bcx.roots { + self.collect_tests_and_executables(unit)?; + } + + Ok(self.compilation) + } + /// Starts compilation, waits for it to finish, and returns information /// about the result of compilation. /// diff --git a/src/cargo/ops/cargo_compile/mod.rs b/src/cargo/ops/cargo_compile/mod.rs index 77f6266355f..17aaa922b8a 100644 --- a/src/cargo/ops/cargo_compile/mod.rs +++ b/src/cargo/ops/cargo_compile/mod.rs @@ -156,7 +156,11 @@ pub fn compile_ws<'a>( } crate::core::gc::auto_gc(bcx.gctx); let build_runner = BuildRunner::new(&bcx)?; - build_runner.compile(exec) + if options.build_config.dry_run { + build_runner.dry_run() + } else { + build_runner.compile(exec) + } } /// Executes `rustc --print `. diff --git a/src/cargo/ops/cargo_install.rs b/src/cargo/ops/cargo_install.rs index 7abc1b39375..2c20d29c23d 100644 --- a/src/cargo/ops/cargo_install.rs +++ b/src/cargo/ops/cargo_install.rs @@ -297,7 +297,7 @@ impl<'gctx> InstallablePackage<'gctx> { Ok(duplicates) } - fn install_one(mut self) -> CargoResult { + fn install_one(mut self, dry_run: bool) -> CargoResult { self.gctx.shell().status("Installing", &self.pkg)?; let dst = self.root.join("bin").into_path_unlocked(); @@ -321,6 +321,7 @@ impl<'gctx> InstallablePackage<'gctx> { self.check_yanked_install()?; let exec: Arc = Arc::new(DefaultExecutor); + self.opts.build_config.dry_run = dry_run; let compile = ops::compile_ws(&self.ws, &self.opts, &exec).with_context(|| { if let Some(td) = td_opt.take() { // preserve the temporary directory, so the user can inspect it @@ -419,13 +420,15 @@ impl<'gctx> InstallablePackage<'gctx> { let staging_dir = TempFileBuilder::new() .prefix("cargo-install") .tempdir_in(&dst)?; - for &(bin, src) in binaries.iter() { - let dst = staging_dir.path().join(bin); - // Try to move if `target_dir` is transient. - if !self.source_id.is_path() && fs::rename(src, &dst).is_ok() { - continue; + if !dry_run { + for &(bin, src) in binaries.iter() { + let dst = staging_dir.path().join(bin); + // Try to move if `target_dir` is transient. + if !self.source_id.is_path() && fs::rename(src, &dst).is_ok() { + continue; + } + paths::copy(src, &dst)?; } - paths::copy(src, &dst)?; } let (to_replace, to_install): (Vec<&str>, Vec<&str>) = binaries @@ -441,11 +444,13 @@ impl<'gctx> InstallablePackage<'gctx> { let src = staging_dir.path().join(bin); let dst = dst.join(bin); self.gctx.shell().status("Installing", dst.display())?; - fs::rename(&src, &dst).with_context(|| { - format!("failed to move `{}` to `{}`", src.display(), dst.display()) - })?; - installed.bins.push(dst); - successful_bins.insert(bin.to_string()); + if !dry_run { + fs::rename(&src, &dst).with_context(|| { + format!("failed to move `{}` to `{}`", src.display(), dst.display()) + })?; + installed.bins.push(dst); + successful_bins.insert(bin.to_string()); + } } // Repeat for binaries which replace existing ones but don't pop the error @@ -456,10 +461,12 @@ impl<'gctx> InstallablePackage<'gctx> { let src = staging_dir.path().join(bin); let dst = dst.join(bin); self.gctx.shell().status("Replacing", dst.display())?; - fs::rename(&src, &dst).with_context(|| { - format!("failed to move `{}` to `{}`", src.display(), dst.display()) - })?; - successful_bins.insert(bin.to_string()); + if !dry_run { + fs::rename(&src, &dst).with_context(|| { + format!("failed to move `{}` to `{}`", src.display(), dst.display()) + })?; + successful_bins.insert(bin.to_string()); + } } Ok(()) }; @@ -476,9 +483,14 @@ impl<'gctx> InstallablePackage<'gctx> { &self.rustc.verbose_version, ); - if let Err(e) = - remove_orphaned_bins(&self.ws, &mut tracker, &duplicates, &self.pkg, &dst) - { + if let Err(e) = remove_orphaned_bins( + &self.ws, + &mut tracker, + &duplicates, + &self.pkg, + &dst, + dry_run, + ) { // Don't hard error on remove. self.gctx .shell() @@ -515,7 +527,10 @@ impl<'gctx> InstallablePackage<'gctx> { } } - if duplicates.is_empty() { + if dry_run { + self.gctx.shell().warn("aborting install due to dry run")?; + Ok(true) + } else if duplicates.is_empty() { self.gctx.shell().status( "Installed", format!( @@ -620,6 +635,7 @@ pub fn install( opts: &ops::CompileOptions, force: bool, no_track: bool, + dry_run: bool, ) -> CargoResult<()> { let root = resolve_root(root, gctx)?; let dst = root.join("bin").into_path_unlocked(); @@ -654,7 +670,7 @@ pub fn install( )?; let mut installed_anything = true; if let Some(installable_pkg) = installable_pkg { - installed_anything = installable_pkg.install_one()?; + installed_anything = installable_pkg.install_one(dry_run)?; } (installed_anything, false) } else { @@ -705,7 +721,7 @@ pub fn install( let install_results: Vec<_> = pkgs_to_install .into_iter() - .map(|(krate, installable_pkg)| (krate, installable_pkg.install_one())) + .map(|(krate, installable_pkg)| (krate, installable_pkg.install_one(dry_run))) .collect(); for (krate, result) in install_results { @@ -857,6 +873,7 @@ fn remove_orphaned_bins( duplicates: &BTreeMap>, pkg: &Package, dst: &Path, + dry_run: bool, ) -> CargoResult<()> { let filter = ops::CompileFilter::new_all_targets(); let all_self_names = exe_names(pkg, &filter); @@ -894,8 +911,10 @@ fn remove_orphaned_bins( old_pkg ), )?; - paths::remove_file(&full_path) - .with_context(|| format!("failed to remove {:?}", full_path))?; + if !dry_run { + paths::remove_file(&full_path) + .with_context(|| format!("failed to remove {:?}", full_path))?; + } } } } diff --git a/src/doc/man/cargo-install.md b/src/doc/man/cargo-install.md index fa593206612..114284f5d4e 100644 --- a/src/doc/man/cargo-install.md +++ b/src/doc/man/cargo-install.md @@ -121,6 +121,10 @@ Filesystem path to local crate to install from. List all installed packages and their versions. {{/option}} +{{#option "`-n`" "`--dry-run`" }} +(unstable) Perform all checks without installing. +{{/option}} + {{#option "`-f`" "`--force`" }} Force overwriting existing crates or binaries. This can be used if a package has installed a binary with the same name as another package. This is also diff --git a/src/doc/man/generated_txt/cargo-install.txt b/src/doc/man/generated_txt/cargo-install.txt index e5d1182ff05..a9deef68143 100644 --- a/src/doc/man/generated_txt/cargo-install.txt +++ b/src/doc/man/generated_txt/cargo-install.txt @@ -120,6 +120,9 @@ OPTIONS --list List all installed packages and their versions. + -n, --dry-run + (unstable) Perform all checks without installing. + -f, --force Force overwriting existing crates or binaries. This can be used if a package has installed a binary with the same name as another diff --git a/src/doc/src/commands/cargo-install.md b/src/doc/src/commands/cargo-install.md index 78efb92e8c0..64693b50c7d 100644 --- a/src/doc/src/commands/cargo-install.md +++ b/src/doc/src/commands/cargo-install.md @@ -124,6 +124,11 @@ treated as a caret requirement like Cargo dependencies are.
List all installed packages and their versions.
+
-n
+
--dry-run
+
(unstable) Perform all checks without installing.
+ +
-f
--force
Force overwriting existing crates or binaries. This can be used if a package diff --git a/src/etc/man/cargo-install.1 b/src/etc/man/cargo-install.1 index 8f524bc7cea..8bf4ded8bc1 100644 --- a/src/etc/man/cargo-install.1 +++ b/src/etc/man/cargo-install.1 @@ -151,6 +151,12 @@ Filesystem path to local crate to install from. List all installed packages and their versions. .RE .sp +\fB\-n\fR, +\fB\-\-dry\-run\fR +.RS 4 +(unstable) Perform all checks without installing. +.RE +.sp \fB\-f\fR, \fB\-\-force\fR .RS 4 diff --git a/tests/testsuite/cargo_install/help/stdout.term.svg b/tests/testsuite/cargo_install/help/stdout.term.svg index e021922563f..38ef23809e7 100644 --- a/tests/testsuite/cargo_install/help/stdout.term.svg +++ b/tests/testsuite/cargo_install/help/stdout.term.svg @@ -1,4 +1,4 @@ - +