about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs44
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs22
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs83
3 files changed, 76 insertions, 73 deletions
diff --git a/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs b/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs
index 664665625f9..dec1ffc72d6 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs
@@ -16,12 +16,13 @@ use la_arena::ArenaMap;
 use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
 use rustc_hash::{FxHashMap, FxHashSet};
 use serde::Deserialize as _;
-use stdx::{always, never};
+use stdx::never;
 use toolchain::Tool;
 
 use crate::{
     CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, ManifestPath, Package, Sysroot,
-    TargetKind, utf8_stdout,
+    TargetKind, cargo_config_file::make_lockfile_copy,
+    cargo_workspace::MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH, utf8_stdout,
 };
 
 /// Output of the build script and proc-macro building steps for a workspace.
@@ -77,7 +78,7 @@ impl WorkspaceBuildScripts {
         let current_dir = workspace.workspace_root();
 
         let allowed_features = workspace.workspace_features();
-        let cmd = Self::build_command(
+        let (_guard, cmd) = Self::build_command(
             config,
             &allowed_features,
             workspace.manifest_path(),
@@ -98,7 +99,7 @@ impl WorkspaceBuildScripts {
     ) -> io::Result<Vec<WorkspaceBuildScripts>> {
         assert_eq!(config.invocation_strategy, InvocationStrategy::Once);
 
-        let cmd = Self::build_command(
+        let (_guard, cmd) = Self::build_command(
             config,
             &Default::default(),
             // This is not gonna be used anyways, so just construct a dummy here
@@ -379,10 +380,6 @@ impl WorkspaceBuildScripts {
                             progress(format!(
                                 "building compile-time-deps: proc-macro {name} built"
                             ));
-                            always!(
-                                data.proc_macro_dylib_path == ProcMacroDylibPath::NotBuilt,
-                                "received multiple compiler artifacts for the same package: {message:?}"
-                            );
                             if data.proc_macro_dylib_path == ProcMacroDylibPath::NotBuilt {
                                 data.proc_macro_dylib_path = ProcMacroDylibPath::NotProcMacro;
                             }
@@ -434,14 +431,15 @@ impl WorkspaceBuildScripts {
         current_dir: &AbsPath,
         sysroot: &Sysroot,
         toolchain: Option<&semver::Version>,
-    ) -> io::Result<Command> {
+    ) -> io::Result<(Option<temp_dir::TempDir>, Command)> {
         match config.run_build_script_command.as_deref() {
             Some([program, args @ ..]) => {
                 let mut cmd = toolchain::command(program, current_dir, &config.extra_env);
                 cmd.args(args);
-                Ok(cmd)
+                Ok((None, cmd))
             }
             _ => {
+                let mut requires_unstable_options = false;
                 let mut cmd = sysroot.tool(Tool::Cargo, current_dir, &config.extra_env);
 
                 cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]);
@@ -457,7 +455,19 @@ impl WorkspaceBuildScripts {
                 if let Some(target) = &config.target {
                     cmd.args(["--target", target]);
                 }
-
+                let mut temp_dir_guard = None;
+                if toolchain
+                    .is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH)
+                {
+                    let lockfile_path =
+                        <_ as AsRef<Utf8Path>>::as_ref(manifest_path).with_extension("lock");
+                    if let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile_path) {
+                        temp_dir_guard = Some(temp_dir);
+                        cmd.arg("--lockfile-path");
+                        cmd.arg(target_lockfile.as_str());
+                        requires_unstable_options = true;
+                    }
+                }
                 match &config.features {
                     CargoFeatures::All => {
                         cmd.arg("--all-features");
@@ -479,6 +489,7 @@ impl WorkspaceBuildScripts {
                 }
 
                 if manifest_path.is_rust_manifest() {
+                    requires_unstable_options = true;
                     cmd.arg("-Zscript");
                 }
 
@@ -488,7 +499,7 @@ impl WorkspaceBuildScripts {
                 // available in current toolchain's cargo, use it to build compile time deps only.
                 const COMP_TIME_DEPS_MIN_TOOLCHAIN_VERSION: semver::Version = semver::Version {
                     major: 1,
-                    minor: 89,
+                    minor: 189,
                     patch: 0,
                     pre: semver::Prerelease::EMPTY,
                     build: semver::BuildMetadata::EMPTY,
@@ -498,8 +509,7 @@ impl WorkspaceBuildScripts {
                     toolchain.is_some_and(|v| *v >= COMP_TIME_DEPS_MIN_TOOLCHAIN_VERSION);
 
                 if cargo_comp_time_deps_available {
-                    cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
-                    cmd.arg("-Zunstable-options");
+                    requires_unstable_options = true;
                     cmd.arg("--compile-time-deps");
                     // we can pass this unconditionally, because we won't actually build the
                     // binaries, and as such, this will succeed even on targets without libtest
@@ -522,7 +532,11 @@ impl WorkspaceBuildScripts {
                         cmd.env("RA_RUSTC_WRAPPER", "1");
                     }
                 }
-                Ok(cmd)
+                if requires_unstable_options {
+                    cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
+                    cmd.arg("-Zunstable-options");
+                }
+                Ok((temp_dir_guard, cmd))
             }
         }
     }
diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs
index 7966f74df30..a1e7ed09232 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs
@@ -1,4 +1,5 @@
 //! Read `.cargo/config.toml` as a JSON object
+use paths::{Utf8Path, Utf8PathBuf};
 use rustc_hash::FxHashMap;
 use toolchain::Tool;
 
@@ -32,3 +33,24 @@ pub(crate) fn read(
 
     Some(json)
 }
+
+pub(crate) fn make_lockfile_copy(
+    lockfile_path: &Utf8Path,
+) -> Option<(temp_dir::TempDir, Utf8PathBuf)> {
+    let temp_dir = temp_dir::TempDir::with_prefix("rust-analyzer").ok()?;
+    let target_lockfile = temp_dir.path().join("Cargo.lock").try_into().ok()?;
+    match std::fs::copy(lockfile_path, &target_lockfile) {
+        Ok(_) => {
+            tracing::debug!("Copied lock file from `{}` to `{}`", lockfile_path, target_lockfile);
+            Some((temp_dir, target_lockfile))
+        }
+        // lockfile does not yet exist, so we can just create a new one in the temp dir
+        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Some((temp_dir, target_lockfile)),
+        Err(e) => {
+            tracing::warn!(
+                "Failed to copy lock file from `{lockfile_path}` to `{target_lockfile}`: {e}",
+            );
+            None
+        }
+    }
+}
diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs
index 767a4aade09..b5f68b2435d 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs
@@ -15,16 +15,18 @@ use span::Edition;
 use stdx::process::spawn_with_streaming_output;
 use toolchain::Tool;
 
+use crate::cargo_config_file::make_lockfile_copy;
 use crate::{CfgOverrides, InvocationStrategy};
 use crate::{ManifestPath, Sysroot};
 
-const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version = semver::Version {
-    major: 1,
-    minor: 82,
-    patch: 0,
-    pre: semver::Prerelease::EMPTY,
-    build: semver::BuildMetadata::EMPTY,
-};
+pub(crate) const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version =
+    semver::Version {
+        major: 1,
+        minor: 82,
+        patch: 0,
+        pre: semver::Prerelease::EMPTY,
+        build: semver::BuildMetadata::EMPTY,
+    };
 
 /// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
 /// workspace. It pretty closely mirrors `cargo metadata` output.
@@ -552,8 +554,10 @@ impl CargoWorkspace {
 
 pub(crate) struct FetchMetadata {
     command: cargo_metadata::MetadataCommand,
+    #[expect(dead_code)]
     manifest_path: ManifestPath,
     lockfile_path: Option<Utf8PathBuf>,
+    #[expect(dead_code)]
     kind: &'static str,
     no_deps: bool,
     no_deps_result: anyhow::Result<cargo_metadata::Metadata>,
@@ -634,7 +638,7 @@ impl FetchMetadata {
         command.other_options(other_options.clone());
 
         if needs_nightly {
-            command.env("RUSTC_BOOTSTRAP", "1");
+            command.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
         }
 
         // Pre-fetch basic metadata using `--no-deps`, which:
@@ -681,11 +685,12 @@ impl FetchMetadata {
         locked: bool,
         progress: &dyn Fn(String),
     ) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> {
+        _ = target_dir;
         let Self {
             mut command,
-            manifest_path,
+            manifest_path: _,
             lockfile_path,
-            kind,
+            kind: _,
             no_deps,
             no_deps_result,
             mut other_options,
@@ -696,54 +701,18 @@ impl FetchMetadata {
         }
 
         let mut using_lockfile_copy = false;
-        let mut _temp_dir_guard = None;
-        // The manifest is a rust file, so this means its a script manifest
-        if let Some(lockfile) = lockfile_path {
-            _temp_dir_guard = temp_dir::TempDir::with_prefix("rust-analyzer").ok();
-            let target_lockfile = _temp_dir_guard
-                .and_then(|tmp| tmp.path().join("Cargo.lock").try_into().ok())
-                .unwrap_or_else(|| {
-                    // When multiple workspaces share the same target dir, they might overwrite into a
-                    // single lockfile path.
-                    // See https://github.com/rust-lang/rust-analyzer/issues/20189#issuecomment-3073520255
-                    let manifest_path_hash = std::hash::BuildHasher::hash_one(
-                        &std::hash::BuildHasherDefault::<rustc_hash::FxHasher>::default(),
-                        &manifest_path,
-                    );
-                    let disambiguator = format!(
-                        "{}_{manifest_path_hash}",
-                        manifest_path.components().nth_back(1).map_or("", |c| c.as_str())
-                    );
-
-                    target_dir
-                        .join("rust-analyzer")
-                        .join("metadata")
-                        .join(kind)
-                        .join(disambiguator)
-                        .join("Cargo.lock")
-                });
-            match std::fs::copy(&lockfile, &target_lockfile) {
-                Ok(_) => {
-                    using_lockfile_copy = true;
-                    other_options.push("--lockfile-path".to_owned());
-                    other_options.push(target_lockfile.to_string());
-                }
-                Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
-                    // There exists no lockfile yet
-                    using_lockfile_copy = true;
-                    other_options.push("--lockfile-path".to_owned());
-                    other_options.push(target_lockfile.to_string());
-                }
-                Err(e) => {
-                    tracing::warn!(
-                        "Failed to copy lock file from `{lockfile}` to `{target_lockfile}`: {e}",
-                    );
-                }
-            }
+        let mut _temp_dir_guard;
+        if let Some(lockfile) = lockfile_path
+            && let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile)
+        {
+            _temp_dir_guard = temp_dir;
+            other_options.push("--lockfile-path".to_owned());
+            other_options.push(target_lockfile.to_string());
+            using_lockfile_copy = true;
         }
         if using_lockfile_copy {
+            command.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
             other_options.push("-Zunstable-options".to_owned());
-            command.env("RUSTC_BOOTSTRAP", "1");
         }
         // No need to lock it if we copied the lockfile, we won't modify the original after all/
         // This way cargo cannot error out on us if the lockfile requires updating.
@@ -752,13 +721,11 @@ impl FetchMetadata {
         }
         command.other_options(other_options);
 
-        // FIXME: Fetching metadata is a slow process, as it might require
-        // calling crates.io. We should be reporting progress here, but it's
-        // unclear whether cargo itself supports it.
         progress("cargo metadata: started".to_owned());
 
         let res = (|| -> anyhow::Result<(_, _)> {
             let mut errored = false;
+            tracing::debug!("Running `{:?}`", command.cargo_command());
             let output =
                 spawn_with_streaming_output(command.cargo_command(), &mut |_| (), &mut |line| {
                     errored = errored || line.starts_with("error") || line.starts_with("warning");