Prevent rustup from automatically installing toolchains

By setting RUSTUP_AUTO_INSTALL=0.
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index adc0cc5..04fb227 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -13,12 +13,12 @@
 use serde_json::from_value;
 use span::Edition;
 use stdx::process::spawn_with_streaming_output;
-use toolchain::Tool;
+use toolchain::{NO_RUSTUP_AUTO_INSTALL_ENV, Tool};
 use triomphe::Arc;
 
-use crate::cargo_config_file::make_lockfile_copy;
-use crate::{CfgOverrides, InvocationStrategy};
-use crate::{ManifestPath, Sysroot};
+use crate::{
+    CfgOverrides, InvocationStrategy, ManifestPath, Sysroot, cargo_config_file::make_lockfile_copy,
+};
 
 pub(crate) const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version =
     semver::Version {
@@ -632,6 +632,7 @@
     ) -> Self {
         let cargo = sysroot.tool(Tool::Cargo, current_dir, &config.extra_env);
         let mut command = MetadataCommand::new();
+        command.env(NO_RUSTUP_AUTO_INSTALL_ENV.0, NO_RUSTUP_AUTO_INSTALL_ENV.1);
         command.cargo_path(cargo.get_program());
         cargo.get_envs().for_each(|(var, val)| _ = command.env(var, val.unwrap_or_default()));
         command.manifest_path(cargo_toml.to_path_buf());
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index e0d2105..0649ce9 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -16,8 +16,9 @@
 use rustc_hash::{FxHashMap, FxHashSet};
 use semver::Version;
 use span::{Edition, FileId};
-use toolchain::Tool;
+use toolchain::{NO_RUSTUP_AUTO_INSTALL_ENV, Tool};
 use tracing::instrument;
+use tracing::{debug, error, info};
 use triomphe::Arc;
 
 use crate::{
@@ -33,7 +34,6 @@
     toolchain_info::{QueryConfig, rustc_cfg, target_data, target_tuple, version},
     utf8_stdout,
 };
-use tracing::{debug, error, info};
 
 pub type FileLoader<'a> = &'a mut dyn for<'b> FnMut(&'b AbsPath) -> Option<FileId>;
 
@@ -1907,6 +1907,7 @@
 ) -> Option<Utf8PathBuf> {
     let cargo = sysroot.tool(Tool::Cargo, manifest.parent(), extra_env);
     let mut meta = cargo_metadata::MetadataCommand::new();
+    meta.env(NO_RUSTUP_AUTO_INSTALL_ENV.0, NO_RUSTUP_AUTO_INSTALL_ENV.1);
     meta.cargo_path(cargo.get_program());
     meta.manifest_path(manifest);
     // `--no-deps` doesn't (over)write lockfiles as it doesn't do any package resolve.
diff --git a/crates/toolchain/src/lib.rs b/crates/toolchain/src/lib.rs
index 8b7bf1a..3931988 100644
--- a/crates/toolchain/src/lib.rs
+++ b/crates/toolchain/src/lib.rs
@@ -71,6 +71,9 @@
     }
 }
 
+// Prevent rustup from automatically installing toolchains, see https://github.com/rust-lang/rust-analyzer/issues/20719.
+pub const NO_RUSTUP_AUTO_INSTALL_ENV: (&str, &str) = ("RUSTUP_AUTO_INSTALL", "0");
+
 #[allow(clippy::disallowed_types)] /* generic parameter allows for FxHashMap */
 pub fn command<H>(
     cmd: impl AsRef<OsStr>,
@@ -81,6 +84,7 @@
     #[allow(clippy::disallowed_methods)]
     let mut cmd = Command::new(cmd);
     cmd.current_dir(working_directory);
+    cmd.env(NO_RUSTUP_AUTO_INSTALL_ENV.0, NO_RUSTUP_AUTO_INSTALL_ENV.1);
     for env in extra_env {
         match env {
             (key, Some(val)) => cmd.env(key, val),