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<ProcessBuilder>,
     /// 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 43d00e1ec85..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<Compilation<'gctx>> {
+        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.
     ///
@@ -214,31 +235,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 +304,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<Option<PathBuf>> {
         let is_binary = unit.target.is_executable();
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 <VALUE>`.
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<bool> {
+    fn install_one(mut self, dry_run: bool) -> CargoResult<bool> {
         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<dyn Executor> = 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<String, Option<PackageId>>,
     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.</dd>
 <dd class="option-desc">List all installed packages and their versions.</dd>
 
 
+<dt class="option-term" id="option-cargo-install--n"><a class="option-anchor" href="#option-cargo-install--n"></a><code>-n</code></dt>
+<dt class="option-term" id="option-cargo-install---dry-run"><a class="option-anchor" href="#option-cargo-install---dry-run"></a><code>--dry-run</code></dt>
+<dd class="option-desc">(unstable) Perform all checks without installing.</dd>
+
+
 <dt class="option-term" id="option-cargo-install--f"><a class="option-anchor" href="#option-cargo-install--f"></a><code>-f</code></dt>
 <dt class="option-term" id="option-cargo-install---force"><a class="option-anchor" href="#option-cargo-install---force"></a><code>--force</code></dt>
 <dd class="option-desc">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 @@
-<svg width="844px" height="1046px" xmlns="http://www.w3.org/2000/svg">
+<svg width="844px" height="1064px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -55,83 +55,85 @@
 </tspan>
     <tspan x="10px" y="334px"><tspan>  </tspan><tspan class="fg-cyan bold">-f</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--force</tspan><tspan>                 Force overwriting existing crates or binaries</tspan>
 </tspan>
-    <tspan x="10px" y="352px"><tspan>      </tspan><tspan class="fg-cyan bold">--no-track</tspan><tspan>              Do not save tracking information</tspan>
+    <tspan x="10px" y="352px"><tspan>  </tspan><tspan class="fg-cyan bold">-n</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--dry-run</tspan><tspan>               Perform all checks without installing (unstable)</tspan>
 </tspan>
-    <tspan x="10px" y="370px"><tspan>      </tspan><tspan class="fg-cyan bold">--list</tspan><tspan>                  List all installed packages and their versions</tspan>
+    <tspan x="10px" y="370px"><tspan>      </tspan><tspan class="fg-cyan bold">--no-track</tspan><tspan>              Do not save tracking information</tspan>
 </tspan>
-    <tspan x="10px" y="388px"><tspan>      </tspan><tspan class="fg-cyan bold">--message-format</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FMT&gt;</tspan><tspan>  Error format</tspan>
+    <tspan x="10px" y="388px"><tspan>      </tspan><tspan class="fg-cyan bold">--list</tspan><tspan>                  List all installed packages and their versions</tspan>
 </tspan>
-    <tspan x="10px" y="406px"><tspan>      </tspan><tspan class="fg-cyan bold">--debug</tspan><tspan>                 Build in debug mode (with the 'dev' profile) instead of release mode</tspan>
+    <tspan x="10px" y="406px"><tspan>      </tspan><tspan class="fg-cyan bold">--message-format</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FMT&gt;</tspan><tspan>  Error format</tspan>
 </tspan>
-    <tspan x="10px" y="424px"><tspan>  </tspan><tspan class="fg-cyan bold">-v</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--verbose</tspan><tspan class="fg-cyan">...</tspan><tspan>            Use verbose output (-vv very verbose/build.rs output)</tspan>
+    <tspan x="10px" y="424px"><tspan>      </tspan><tspan class="fg-cyan bold">--debug</tspan><tspan>                 Build in debug mode (with the 'dev' profile) instead of release mode</tspan>
 </tspan>
-    <tspan x="10px" y="442px"><tspan>  </tspan><tspan class="fg-cyan bold">-q</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--quiet</tspan><tspan>                 Do not print cargo log messages</tspan>
+    <tspan x="10px" y="442px"><tspan>  </tspan><tspan class="fg-cyan bold">-v</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--verbose</tspan><tspan class="fg-cyan">...</tspan><tspan>            Use verbose output (-vv very verbose/build.rs output)</tspan>
 </tspan>
-    <tspan x="10px" y="460px"><tspan>      </tspan><tspan class="fg-cyan bold">--color</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;WHEN&gt;</tspan><tspan>          Coloring: auto, always, never</tspan>
+    <tspan x="10px" y="460px"><tspan>  </tspan><tspan class="fg-cyan bold">-q</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--quiet</tspan><tspan>                 Do not print cargo log messages</tspan>
 </tspan>
-    <tspan x="10px" y="478px"><tspan>      </tspan><tspan class="fg-cyan bold">--config</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;KEY=VALUE&gt;</tspan><tspan>    Override a configuration value</tspan>
+    <tspan x="10px" y="478px"><tspan>      </tspan><tspan class="fg-cyan bold">--color</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;WHEN&gt;</tspan><tspan>          Coloring: auto, always, never</tspan>
 </tspan>
-    <tspan x="10px" y="496px"><tspan>  </tspan><tspan class="fg-cyan bold">-Z</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FLAG&gt;</tspan><tspan>                   Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for</tspan>
+    <tspan x="10px" y="496px"><tspan>      </tspan><tspan class="fg-cyan bold">--config</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;KEY=VALUE&gt;</tspan><tspan>    Override a configuration value</tspan>
 </tspan>
-    <tspan x="10px" y="514px"><tspan>                              details</tspan>
+    <tspan x="10px" y="514px"><tspan>  </tspan><tspan class="fg-cyan bold">-Z</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FLAG&gt;</tspan><tspan>                   Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for</tspan>
 </tspan>
-    <tspan x="10px" y="532px"><tspan>  </tspan><tspan class="fg-cyan bold">-h</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--help</tspan><tspan>                  Print help</tspan>
+    <tspan x="10px" y="532px"><tspan>                              details</tspan>
 </tspan>
-    <tspan x="10px" y="550px">
+    <tspan x="10px" y="550px"><tspan>  </tspan><tspan class="fg-cyan bold">-h</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--help</tspan><tspan>                  Print help</tspan>
 </tspan>
-    <tspan x="10px" y="568px"><tspan class="fg-green bold">Manifest Options:</tspan>
+    <tspan x="10px" y="568px">
 </tspan>
-    <tspan x="10px" y="586px"><tspan>      </tspan><tspan class="fg-cyan bold">--ignore-rust-version</tspan><tspan>  Ignore `rust-version` specification in packages</tspan>
+    <tspan x="10px" y="586px"><tspan class="fg-green bold">Manifest Options:</tspan>
 </tspan>
-    <tspan x="10px" y="604px"><tspan>      </tspan><tspan class="fg-cyan bold">--locked</tspan><tspan>               Assert that `Cargo.lock` will remain unchanged</tspan>
+    <tspan x="10px" y="604px"><tspan>      </tspan><tspan class="fg-cyan bold">--ignore-rust-version</tspan><tspan>  Ignore `rust-version` specification in packages</tspan>
 </tspan>
-    <tspan x="10px" y="622px"><tspan>      </tspan><tspan class="fg-cyan bold">--offline</tspan><tspan>              Run without accessing the network</tspan>
+    <tspan x="10px" y="622px"><tspan>      </tspan><tspan class="fg-cyan bold">--locked</tspan><tspan>               Assert that `Cargo.lock` will remain unchanged</tspan>
 </tspan>
-    <tspan x="10px" y="640px"><tspan>      </tspan><tspan class="fg-cyan bold">--frozen</tspan><tspan>               Equivalent to specifying both --locked and --offline</tspan>
+    <tspan x="10px" y="640px"><tspan>      </tspan><tspan class="fg-cyan bold">--offline</tspan><tspan>              Run without accessing the network</tspan>
 </tspan>
-    <tspan x="10px" y="658px">
+    <tspan x="10px" y="658px"><tspan>      </tspan><tspan class="fg-cyan bold">--frozen</tspan><tspan>               Equivalent to specifying both --locked and --offline</tspan>
 </tspan>
-    <tspan x="10px" y="676px"><tspan class="fg-green bold">Target Selection:</tspan>
+    <tspan x="10px" y="676px">
 </tspan>
-    <tspan x="10px" y="694px"><tspan>      </tspan><tspan class="fg-cyan bold">--bin</tspan><tspan class="fg-cyan"> [</tspan><tspan class="fg-cyan">&lt;NAME&gt;</tspan><tspan class="fg-cyan">]</tspan><tspan>      Install only the specified binary</tspan>
+    <tspan x="10px" y="694px"><tspan class="fg-green bold">Target Selection:</tspan>
 </tspan>
-    <tspan x="10px" y="712px"><tspan>      </tspan><tspan class="fg-cyan bold">--bins</tspan><tspan>              Install all binaries</tspan>
+    <tspan x="10px" y="712px"><tspan>      </tspan><tspan class="fg-cyan bold">--bin</tspan><tspan class="fg-cyan"> [</tspan><tspan class="fg-cyan">&lt;NAME&gt;</tspan><tspan class="fg-cyan">]</tspan><tspan>      Install only the specified binary</tspan>
 </tspan>
-    <tspan x="10px" y="730px"><tspan>      </tspan><tspan class="fg-cyan bold">--example</tspan><tspan class="fg-cyan"> [</tspan><tspan class="fg-cyan">&lt;NAME&gt;</tspan><tspan class="fg-cyan">]</tspan><tspan>  Install only the specified example</tspan>
+    <tspan x="10px" y="730px"><tspan>      </tspan><tspan class="fg-cyan bold">--bins</tspan><tspan>              Install all binaries</tspan>
 </tspan>
-    <tspan x="10px" y="748px"><tspan>      </tspan><tspan class="fg-cyan bold">--examples</tspan><tspan>          Install all examples</tspan>
+    <tspan x="10px" y="748px"><tspan>      </tspan><tspan class="fg-cyan bold">--example</tspan><tspan class="fg-cyan"> [</tspan><tspan class="fg-cyan">&lt;NAME&gt;</tspan><tspan class="fg-cyan">]</tspan><tspan>  Install only the specified example</tspan>
 </tspan>
-    <tspan x="10px" y="766px">
+    <tspan x="10px" y="766px"><tspan>      </tspan><tspan class="fg-cyan bold">--examples</tspan><tspan>          Install all examples</tspan>
 </tspan>
-    <tspan x="10px" y="784px"><tspan class="fg-green bold">Feature Selection:</tspan>
+    <tspan x="10px" y="784px">
 </tspan>
-    <tspan x="10px" y="802px"><tspan>  </tspan><tspan class="fg-cyan bold">-F</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--features</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FEATURES&gt;</tspan><tspan>  Space or comma separated list of features to activate</tspan>
+    <tspan x="10px" y="802px"><tspan class="fg-green bold">Feature Selection:</tspan>
 </tspan>
-    <tspan x="10px" y="820px"><tspan>      </tspan><tspan class="fg-cyan bold">--all-features</tspan><tspan>         Activate all available features</tspan>
+    <tspan x="10px" y="820px"><tspan>  </tspan><tspan class="fg-cyan bold">-F</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--features</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FEATURES&gt;</tspan><tspan>  Space or comma separated list of features to activate</tspan>
 </tspan>
-    <tspan x="10px" y="838px"><tspan>      </tspan><tspan class="fg-cyan bold">--no-default-features</tspan><tspan>  Do not activate the `default` feature</tspan>
+    <tspan x="10px" y="838px"><tspan>      </tspan><tspan class="fg-cyan bold">--all-features</tspan><tspan>         Activate all available features</tspan>
 </tspan>
-    <tspan x="10px" y="856px">
+    <tspan x="10px" y="856px"><tspan>      </tspan><tspan class="fg-cyan bold">--no-default-features</tspan><tspan>  Do not activate the `default` feature</tspan>
 </tspan>
-    <tspan x="10px" y="874px"><tspan class="fg-green bold">Compilation Options:</tspan>
+    <tspan x="10px" y="874px">
 </tspan>
-    <tspan x="10px" y="892px"><tspan>  </tspan><tspan class="fg-cyan bold">-j</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--jobs</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;N&gt;</tspan><tspan>                Number of parallel jobs, defaults to # of CPUs.</tspan>
+    <tspan x="10px" y="892px"><tspan class="fg-green bold">Compilation Options:</tspan>
 </tspan>
-    <tspan x="10px" y="910px"><tspan>      </tspan><tspan class="fg-cyan bold">--keep-going</tspan><tspan>              Do not abort the build as soon as there is an error</tspan>
+    <tspan x="10px" y="910px"><tspan>  </tspan><tspan class="fg-cyan bold">-j</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--jobs</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;N&gt;</tspan><tspan>                Number of parallel jobs, defaults to # of CPUs.</tspan>
 </tspan>
-    <tspan x="10px" y="928px"><tspan>      </tspan><tspan class="fg-cyan bold">--profile</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;PROFILE-NAME&gt;</tspan><tspan>  Install artifacts with the specified profile</tspan>
+    <tspan x="10px" y="928px"><tspan>      </tspan><tspan class="fg-cyan bold">--keep-going</tspan><tspan>              Do not abort the build as soon as there is an error</tspan>
 </tspan>
-    <tspan x="10px" y="946px"><tspan>      </tspan><tspan class="fg-cyan bold">--target</tspan><tspan class="fg-cyan"> [</tspan><tspan class="fg-cyan">&lt;TRIPLE&gt;</tspan><tspan class="fg-cyan">]</tspan><tspan>       Build for the target triple</tspan>
+    <tspan x="10px" y="946px"><tspan>      </tspan><tspan class="fg-cyan bold">--profile</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;PROFILE-NAME&gt;</tspan><tspan>  Install artifacts with the specified profile</tspan>
 </tspan>
-    <tspan x="10px" y="964px"><tspan>      </tspan><tspan class="fg-cyan bold">--target-dir</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;DIRECTORY&gt;</tspan><tspan>  Directory for all generated artifacts</tspan>
+    <tspan x="10px" y="964px"><tspan>      </tspan><tspan class="fg-cyan bold">--target</tspan><tspan class="fg-cyan"> [</tspan><tspan class="fg-cyan">&lt;TRIPLE&gt;</tspan><tspan class="fg-cyan">]</tspan><tspan>       Build for the target triple</tspan>
 </tspan>
-    <tspan x="10px" y="982px"><tspan>      </tspan><tspan class="fg-cyan bold">--timings</tspan><tspan class="fg-cyan">[=</tspan><tspan class="fg-cyan">&lt;FMTS&gt;</tspan><tspan class="fg-cyan">]</tspan><tspan>        Timing output formats (unstable) (comma separated): html, json</tspan>
+    <tspan x="10px" y="982px"><tspan>      </tspan><tspan class="fg-cyan bold">--target-dir</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;DIRECTORY&gt;</tspan><tspan>  Directory for all generated artifacts</tspan>
 </tspan>
-    <tspan x="10px" y="1000px">
+    <tspan x="10px" y="1000px"><tspan>      </tspan><tspan class="fg-cyan bold">--timings</tspan><tspan class="fg-cyan">[=</tspan><tspan class="fg-cyan">&lt;FMTS&gt;</tspan><tspan class="fg-cyan">]</tspan><tspan>        Timing output formats (unstable) (comma separated): html, json</tspan>
 </tspan>
-    <tspan x="10px" y="1018px"><tspan>Run `</tspan><tspan class="fg-cyan bold">cargo help install</tspan><tspan class="bold">` for more detailed information.</tspan>
+    <tspan x="10px" y="1018px">
 </tspan>
-    <tspan x="10px" y="1036px">
+    <tspan x="10px" y="1036px"><tspan>Run `</tspan><tspan class="fg-cyan bold">cargo help install</tspan><tspan class="bold">` for more detailed information.</tspan>
+</tspan>
+    <tspan x="10px" y="1054px">
 </tspan>
   </text>
 
diff --git a/tests/testsuite/install.rs b/tests/testsuite/install.rs
index a0487dbfd74..4bfe2e9a3df 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("-Z unstable-options install --dry-run foo")
+        .masquerade_as_nightly_cargo(&["install::dry-run"])
+        .with_stderr_data(str![[r#"
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
+[INSTALLING] foo v0.0.1
+[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
+[WARNING] aborting install due to dry run
+[WARNING] be sure to add `[ROOT]/home/.cargo/bin` to your PATH to be able to run the installed binaries
+
+"#]])
+        .run();
+    assert_has_not_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("-Z unstable-options install --dry-run some-package-from-the-distant-future")
+        .masquerade_as_nightly_cargo(&["install::dry-run"])
+        .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("-Z unstable-options install --dry-run --path")
+        .arg(p.root())
+        .arg("foo")
+        .masquerade_as_nightly_cargo(&["install::dry-run"])
+        .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("-Z unstable-options install --dry-run foo")
+        .masquerade_as_nightly_cargo(&["install::dry-run"])
+        .with_stderr_data(str![[r#"
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.0.2 (registry `dummy-registry`)
+[INSTALLING] foo v0.0.2
+[REPLACING] [ROOT]/home/.cargo/bin/foo[EXE]
+[WARNING] aborting install due to dry run
+[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("-Z unstable-options install --dry-run bar")
+        .masquerade_as_nightly_cargo(&["install::dry-run"])
+        .with_stderr_data(str![[r#"
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v2.0.0 (registry `dummy-registry`)
+[INSTALLING] bar v2.0.0
+[REPLACING] [ROOT]/home/.cargo/bin/client[EXE]
+[REMOVING] executable `[ROOT]/home/.cargo/bin/server[EXE]` from previous version bar v1.0.0
+[WARNING] aborting install due to dry run
+[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");
+    // Ensure server is still installed after the dry run
+    assert_has_installed_exe(paths::cargo_home(), "server");
+}