From 5f3539a56662d4612a39f6dc04ad1c61796847aa Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Tue, 28 Oct 2025 03:53:50 +0900 Subject: [PATCH 1/2] fix: Resolve `target-dir` more precisely --- .../project-model/src/build_dependencies.rs | 10 ++- crates/project-model/src/cargo_workspace.rs | 34 ++++++--- crates/project-model/src/lib.rs | 2 +- crates/project-model/src/sysroot.rs | 7 +- crates/project-model/src/tests.rs | 8 +-- crates/project-model/src/workspace.rs | 72 ++++--------------- crates/rust-analyzer/src/cli/rustc_tests.rs | 9 +-- crates/rust-analyzer/src/config.rs | 61 +++++++++------- crates/rust-analyzer/src/flycheck.rs | 46 ++++++++---- crates/rust-analyzer/src/handlers/request.rs | 1 + crates/rust-analyzer/src/reload.rs | 13 +++- crates/rust-analyzer/src/test_runner.rs | 5 +- 12 files changed, 132 insertions(+), 136 deletions(-) diff --git a/crates/project-model/src/build_dependencies.rs b/crates/project-model/src/build_dependencies.rs index 3a682d5a4d..fedc6944f5 100644 --- a/crates/project-model/src/build_dependencies.rs +++ b/crates/project-model/src/build_dependencies.rs @@ -86,6 +86,7 @@ impl WorkspaceBuildScripts { config, &allowed_features, workspace.manifest_path(), + workspace.target_directory().as_ref(), current_dir, sysroot, toolchain, @@ -106,8 +107,9 @@ impl WorkspaceBuildScripts { let (_guard, cmd) = Self::build_command( config, &Default::default(), - // This is not gonna be used anyways, so just construct a dummy here + // These are not gonna be used anyways, so just construct a dummy here &ManifestPath::try_from(working_directory.clone()).unwrap(), + working_directory.as_ref(), working_directory, &Sysroot::empty(), None, @@ -430,6 +432,7 @@ impl WorkspaceBuildScripts { config: &CargoConfig, allowed_features: &FxHashSet, manifest_path: &ManifestPath, + target_dir: &Utf8Path, current_dir: &AbsPath, sysroot: &Sysroot, toolchain: Option<&semver::Version>, @@ -450,8 +453,9 @@ impl WorkspaceBuildScripts { cmd.arg("--manifest-path"); cmd.arg(manifest_path); - if let Some(target_dir) = &config.target_dir { - cmd.arg("--target-dir").arg(target_dir); + if let Some(target_dir) = config.target_dir_config.target_dir(Some(target_dir)) { + cmd.arg("--target-dir"); + cmd.arg(target_dir.as_ref()); } if let Some(target) = &config.target { diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs index 76ba01f3a2..731104981a 100644 --- a/crates/project-model/src/cargo_workspace.rs +++ b/crates/project-model/src/cargo_workspace.rs @@ -1,7 +1,6 @@ //! See [`CargoWorkspace`]. -use std::ops; -use std::str::from_utf8; +use std::{borrow::Cow, ops, str::from_utf8}; use anyhow::Context; use base_db::Env; @@ -95,6 +94,29 @@ impl Default for CargoFeatures { } } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub enum TargetDirectoryConfig { + #[default] + None, + UseSubdirectory, + Directory(Utf8PathBuf), +} + +impl TargetDirectoryConfig { + pub fn target_dir<'a>( + &'a self, + ws_target_dir: Option<&'a Utf8Path>, + ) -> Option> { + match self { + TargetDirectoryConfig::None => None, + TargetDirectoryConfig::UseSubdirectory => { + Some(Cow::Owned(ws_target_dir?.join("rust-analyzer"))) + } + TargetDirectoryConfig::Directory(dir) => Some(Cow::Borrowed(dir)), + } + } +} + #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct CargoConfig { /// Whether to pass `--all-targets` to cargo invocations. @@ -121,7 +143,7 @@ pub struct CargoConfig { pub extra_env: FxHashMap>, pub invocation_strategy: InvocationStrategy, /// Optional path to use instead of `target` when building - pub target_dir: Option, + pub target_dir_config: TargetDirectoryConfig, /// Gate `#[test]` behind `#[cfg(test)]` pub set_test: bool, /// Load the project without any dependencies @@ -715,21 +737,15 @@ impl FetchMetadata { } } - pub(crate) fn no_deps_metadata(&self) -> Option<&cargo_metadata::Metadata> { - self.no_deps_result.as_ref().ok() - } - /// Executes the metadata-fetching command. /// /// A successful result may still contain a metadata error if the full fetch failed, /// but the fallback `--no-deps` pre-fetch succeeded during command construction. pub(crate) fn exec( self, - target_dir: &Utf8Path, locked: bool, progress: &dyn Fn(String), ) -> anyhow::Result<(cargo_metadata::Metadata, Option)> { - _ = target_dir; let Self { mut command, manifest_path: _, diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs index e36b904881..910bc0a96b 100644 --- a/crates/project-model/src/lib.rs +++ b/crates/project-model/src/lib.rs @@ -62,7 +62,7 @@ pub use crate::{ build_dependencies::{ProcMacroDylibPath, WorkspaceBuildScripts}, cargo_workspace::{ CargoConfig, CargoFeatures, CargoMetadataConfig, CargoWorkspace, Package, PackageData, - PackageDependency, RustLibSource, Target, TargetData, TargetKind, + PackageDependency, RustLibSource, Target, TargetData, TargetDirectoryConfig, TargetKind, }, manifest_path::ManifestPath, project_json::{ProjectJson, ProjectJsonData}, diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs index 5cc399bfe7..920afe65d7 100644 --- a/crates/project-model/src/sysroot.rs +++ b/crates/project-model/src/sysroot.rs @@ -9,7 +9,7 @@ use std::{env, fs, ops::Not, path::Path, process::Command}; use anyhow::{Result, format_err}; use itertools::Itertools; -use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf}; +use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rustc_hash::FxHashMap; use stdx::format_to; use toolchain::{Tool, probe_for_binary}; @@ -219,7 +219,6 @@ impl Sysroot { &self, sysroot_source_config: &RustSourceWorkspaceConfig, no_deps: bool, - target_dir: &Utf8Path, progress: &dyn Fn(String), ) -> Option { assert!(matches!(self.workspace, RustLibSrcWorkspace::Empty), "workspace already loaded"); @@ -233,7 +232,6 @@ impl Sysroot { match self.load_library_via_cargo( &library_manifest, src_root, - target_dir, cargo_config, no_deps, progress, @@ -328,7 +326,6 @@ impl Sysroot { &self, library_manifest: &ManifestPath, current_dir: &AbsPath, - target_dir: &Utf8Path, cargo_config: &CargoMetadataConfig, no_deps: bool, progress: &dyn Fn(String), @@ -345,7 +342,7 @@ impl Sysroot { let locked = true; let (mut res, err) = FetchMetadata::new(library_manifest, current_dir, &cargo_config, self, no_deps) - .exec(target_dir, locked, progress)?; + .exec(locked, progress)?; // Patch out `rustc-std-workspace-*` crates to point to the real crates. // This is done prior to `CrateGraph` construction to prevent de-duplication logic from failing. diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs index 711cdd11b9..1908fc0290 100644 --- a/crates/project-model/src/tests.rs +++ b/crates/project-model/src/tests.rs @@ -238,12 +238,8 @@ fn smoke_test_real_sysroot_cargo() { ); let cwd = AbsPathBuf::assert_utf8(temp_dir().join("smoke_test_real_sysroot_cargo")); std::fs::create_dir_all(&cwd).unwrap(); - let loaded_sysroot = sysroot.load_workspace( - &RustSourceWorkspaceConfig::default_cargo(), - false, - &Utf8PathBuf::default(), - &|_| (), - ); + let loaded_sysroot = + sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo(), false, &|_| ()); if let Some(loaded_sysroot) = loaded_sysroot { sysroot.set_workspace(loaded_sysroot); } diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index b88db41957..3c69625685 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -16,7 +16,7 @@ use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rustc_hash::{FxHashMap, FxHashSet}; use semver::Version; use span::{Edition, FileId}; -use toolchain::{NO_RUSTUP_AUTO_INSTALL_ENV, Tool}; +use toolchain::Tool; use tracing::instrument; use tracing::{debug, error, info}; use triomphe::Arc; @@ -295,11 +295,6 @@ impl ProjectWorkspace { &sysroot, *no_deps, ); - let target_dir = config - .target_dir - .clone() - .or_else(|| fetch_metadata.no_deps_metadata().map(|m| m.target_directory.clone())) - .unwrap_or_else(|| workspace_dir.join("target").into()); // We spawn a bunch of processes to query various information about the workspace's // toolchain and sysroot @@ -345,7 +340,7 @@ impl ProjectWorkspace { }, &sysroot, *no_deps, - ).exec(&target_dir, true, progress) { + ).exec(true, progress) { Ok((meta, _error)) => { let workspace = CargoWorkspace::new( meta, @@ -374,7 +369,7 @@ impl ProjectWorkspace { }) }); - let cargo_metadata = s.spawn(|| fetch_metadata.exec(&target_dir, false, progress)); + let cargo_metadata = s.spawn(|| fetch_metadata.exec(false, progress)); let loaded_sysroot = s.spawn(|| { sysroot.load_workspace( &RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config( @@ -383,7 +378,6 @@ impl ProjectWorkspace { toolchain.clone(), )), config.no_deps, - &target_dir, progress, ) }); @@ -463,12 +457,6 @@ impl ProjectWorkspace { let targets = target_tuple::get(query_config, config.target.as_deref(), &config.extra_env) .unwrap_or_default(); let toolchain = version::get(query_config, &config.extra_env).ok().flatten(); - let project_root = project_json.project_root(); - let target_dir = config - .target_dir - .clone() - .or_else(|| cargo_target_dir(project_json.manifest()?, &config.extra_env, &sysroot)) - .unwrap_or_else(|| project_root.join("target").into()); // We spawn a bunch of processes to query various information about the workspace's // toolchain and sysroot @@ -486,7 +474,6 @@ impl ProjectWorkspace { sysroot.load_workspace( &RustSourceWorkspaceConfig::Json(*sysroot_project), config.no_deps, - &target_dir, progress, ) } else { @@ -497,7 +484,6 @@ impl ProjectWorkspace { toolchain.clone(), )), config.no_deps, - &target_dir, progress, ) } @@ -545,11 +531,6 @@ impl ProjectWorkspace { .unwrap_or_default(); let rustc_cfg = rustc_cfg::get(query_config, None, &config.extra_env); let target_data = target_data::get(query_config, None, &config.extra_env); - let target_dir = config - .target_dir - .clone() - .or_else(|| cargo_target_dir(detached_file, &config.extra_env, &sysroot)) - .unwrap_or_else(|| dir.join("target").into()); let loaded_sysroot = sysroot.load_workspace( &RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config( @@ -558,7 +539,6 @@ impl ProjectWorkspace { toolchain.clone(), )), config.no_deps, - &target_dir, &|_| (), ); if let Some(loaded_sysroot) = loaded_sysroot { @@ -579,21 +559,15 @@ impl ProjectWorkspace { &sysroot, config.no_deps, ); - let target_dir = config - .target_dir - .clone() - .or_else(|| fetch_metadata.no_deps_metadata().map(|m| m.target_directory.clone())) - .unwrap_or_else(|| dir.join("target").into()); - let cargo_script = - fetch_metadata.exec(&target_dir, false, &|_| ()).ok().map(|(ws, error)| { - let cargo_config_extra_env = - cargo_config_env(detached_file, &config_file, &config.extra_env); - ( - CargoWorkspace::new(ws, detached_file.clone(), cargo_config_extra_env, false), - WorkspaceBuildScripts::default(), - error.map(Arc::new), - ) - }); + let cargo_script = fetch_metadata.exec(false, &|_| ()).ok().map(|(ws, error)| { + let cargo_config_extra_env = + cargo_config_env(detached_file, &config_file, &config.extra_env); + ( + CargoWorkspace::new(ws, detached_file.clone(), cargo_config_extra_env, false), + WorkspaceBuildScripts::default(), + error.map(Arc::new), + ) + }); Ok(ProjectWorkspace { kind: ProjectWorkspaceKind::DetachedFile { @@ -1902,25 +1876,3 @@ fn sysroot_metadata_config( kind: "sysroot", } } - -fn cargo_target_dir( - manifest: &ManifestPath, - extra_env: &FxHashMap>, - sysroot: &Sysroot, -) -> Option { - 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. - // So we can use it to get `target_directory` before copying lockfiles - meta.no_deps(); - let mut other_options = vec![]; - if manifest.is_rust_manifest() { - meta.env("RUSTC_BOOTSTRAP", "1"); - other_options.push("-Zscript".to_owned()); - } - meta.other_options(other_options); - meta.exec().map(|m| m.target_directory).ok() -} diff --git a/crates/rust-analyzer/src/cli/rustc_tests.rs b/crates/rust-analyzer/src/cli/rustc_tests.rs index 20567149bb..eb28a47ec0 100644 --- a/crates/rust-analyzer/src/cli/rustc_tests.rs +++ b/crates/rust-analyzer/src/cli/rustc_tests.rs @@ -9,7 +9,6 @@ use hir::{ChangeWithProcMacros, Crate}; use ide::{AnalysisHost, DiagnosticCode, DiagnosticsConfig}; use ide_db::base_db; use itertools::Either; -use paths::Utf8PathBuf; use profile::StopWatch; use project_model::toolchain_info::{QueryConfig, target_data}; use project_model::{ @@ -75,12 +74,8 @@ impl Tester { }; let mut sysroot = Sysroot::discover(tmp_file.parent().unwrap(), &cargo_config.extra_env); - let loaded_sysroot = sysroot.load_workspace( - &RustSourceWorkspaceConfig::default_cargo(), - false, - &Utf8PathBuf::default(), - &|_| (), - ); + let loaded_sysroot = + sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo(), false, &|_| ()); if let Some(loaded_sysroot) = loaded_sysroot { sysroot.set_workspace(loaded_sysroot); } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 6d2907ee56..10a392a5b7 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -23,7 +23,7 @@ use itertools::{Either, Itertools}; use paths::{Utf8Path, Utf8PathBuf}; use project_model::{ CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectJsonFromCommand, - ProjectManifest, RustLibSource, + ProjectManifest, RustLibSource, TargetDirectoryConfig, }; use rustc_hash::{FxHashMap, FxHashSet}; use semver::Version; @@ -2285,7 +2285,7 @@ impl Config { run_build_script_command: self.cargo_buildScripts_overrideCommand(source_root).clone(), extra_args: self.cargo_extraArgs(source_root).clone(), extra_env: self.cargo_extraEnv(source_root).clone(), - target_dir: self.target_dir_from_config(source_root), + target_dir_config: self.target_dir_from_config(source_root), set_test: *self.cfg_setTest(source_root), no_deps: *self.cargo_noDeps(source_root), } @@ -2373,7 +2373,7 @@ impl Config { extra_args: self.extra_args(source_root).clone(), extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(), extra_env: self.extra_env(source_root).clone(), - target_dir: self.target_dir_from_config(source_root), + target_dir_config: self.target_dir_from_config(source_root), set_test: true, } } @@ -2431,7 +2431,7 @@ impl Config { extra_args: self.check_extra_args(source_root), extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(), extra_env: self.check_extra_env(source_root), - target_dir: self.target_dir_from_config(source_root), + target_dir_config: self.target_dir_from_config(source_root), set_test: *self.cfg_setTest(source_root), }, ansi_color_output: self.color_diagnostic_output(), @@ -2439,17 +2439,12 @@ impl Config { } } - fn target_dir_from_config(&self, source_root: Option) -> Option { - self.cargo_targetDir(source_root).as_ref().and_then(|target_dir| match target_dir { - TargetDirectory::UseSubdirectory(true) => { - let env_var = env::var("CARGO_TARGET_DIR").ok(); - let mut path = Utf8PathBuf::from(env_var.as_deref().unwrap_or("target")); - path.push("rust-analyzer"); - Some(path) - } - TargetDirectory::UseSubdirectory(false) => None, - TargetDirectory::Directory(dir) => Some(dir.clone()), - }) + fn target_dir_from_config(&self, source_root: Option) -> TargetDirectoryConfig { + match &self.cargo_targetDir(source_root) { + Some(TargetDirectory::UseSubdirectory(true)) => TargetDirectoryConfig::UseSubdirectory, + Some(TargetDirectory::UseSubdirectory(false)) | None => TargetDirectoryConfig::None, + Some(TargetDirectory::Directory(dir)) => TargetDirectoryConfig::Directory(dir.clone()), + } } pub fn check_on_save(&self, source_root: Option) -> bool { @@ -3966,7 +3961,7 @@ fn doc_comment_to_string(doc: &[&str]) -> String { #[cfg(test)] mod tests { - use std::fs; + use std::{borrow::Cow, fs}; use test_utils::{ensure_file_contents, project_root}; @@ -4101,9 +4096,13 @@ mod tests { (config, _, _) = config.apply_change(change); assert_eq!(config.cargo_targetDir(None), &None); - assert!( - matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none()) - ); + assert!(matches!( + config.flycheck(None), + FlycheckConfig::CargoCommand { + options: CargoOptions { target_dir_config: TargetDirectoryConfig::None, .. }, + .. + } + )); } #[test] @@ -4119,11 +4118,16 @@ mod tests { (config, _, _) = config.apply_change(change); assert_eq!(config.cargo_targetDir(None), &Some(TargetDirectory::UseSubdirectory(true))); - let target = + let ws_target_dir = Utf8PathBuf::from(std::env::var("CARGO_TARGET_DIR").unwrap_or("target".to_owned())); - assert!( - matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(target.join("rust-analyzer"))) - ); + assert!(matches!( + config.flycheck(None), + FlycheckConfig::CargoCommand { + options: CargoOptions { target_dir_config, .. }, + .. + } if target_dir_config.target_dir(Some(&ws_target_dir)).map(Cow::into_owned) + == Some(ws_target_dir.join("rust-analyzer")) + )); } #[test] @@ -4142,8 +4146,13 @@ mod tests { config.cargo_targetDir(None), &Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder"))) ); - assert!( - matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("other_folder"))) - ); + assert!(matches!( + config.flycheck(None), + FlycheckConfig::CargoCommand { + options: CargoOptions { target_dir_config, .. }, + .. + } if target_dir_config.target_dir(None).map(Cow::into_owned) + == Some(Utf8PathBuf::from("other_folder")) + )); } } diff --git a/crates/rust-analyzer/src/flycheck.rs b/crates/rust-analyzer/src/flycheck.rs index 73a51bba3d..db6743d4e5 100644 --- a/crates/rust-analyzer/src/flycheck.rs +++ b/crates/rust-analyzer/src/flycheck.rs @@ -13,6 +13,7 @@ use crossbeam_channel::{Receiver, Sender, select_biased, unbounded}; use ide_db::FxHashSet; use itertools::Itertools; use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf}; +use project_model::TargetDirectoryConfig; use rustc_hash::FxHashMap; use serde::Deserialize as _; use serde_derive::Deserialize; @@ -46,7 +47,7 @@ pub(crate) struct CargoOptions { pub(crate) extra_args: Vec, pub(crate) extra_test_bin_args: Vec, pub(crate) extra_env: FxHashMap>, - pub(crate) target_dir: Option, + pub(crate) target_dir_config: TargetDirectoryConfig, } #[derive(Clone, Debug)] @@ -58,7 +59,7 @@ pub(crate) enum Target { } impl CargoOptions { - pub(crate) fn apply_on_command(&self, cmd: &mut Command) { + pub(crate) fn apply_on_command(&self, cmd: &mut Command, ws_target_dir: Option<&Utf8Path>) { for target in &self.target_tuples { cmd.args(["--target", target.as_str()]); } @@ -82,8 +83,8 @@ impl CargoOptions { cmd.arg(self.features.join(" ")); } } - if let Some(target_dir) = &self.target_dir { - cmd.arg("--target-dir").arg(target_dir); + if let Some(target_dir) = self.target_dir_config.target_dir(ws_target_dir) { + cmd.arg("--target-dir").arg(target_dir.as_ref()); } } } @@ -158,6 +159,7 @@ impl FlycheckHandle { sysroot_root: Option, workspace_root: AbsPathBuf, manifest_path: Option, + ws_target_dir: Option, ) -> FlycheckHandle { let actor = FlycheckActor::new( id, @@ -167,6 +169,7 @@ impl FlycheckHandle { sysroot_root, workspace_root, manifest_path, + ws_target_dir, ); let (sender, receiver) = unbounded::(); let thread = @@ -314,6 +317,7 @@ struct FlycheckActor { sender: Sender, config: FlycheckConfig, manifest_path: Option, + ws_target_dir: Option, /// Either the workspace root of the workspace we are flychecking, /// or the project root of the project. root: Arc, @@ -355,6 +359,7 @@ impl FlycheckActor { sysroot_root: Option, workspace_root: AbsPathBuf, manifest_path: Option, + ws_target_dir: Option, ) -> FlycheckActor { tracing::info!(%id, ?workspace_root, "Spawning flycheck"); FlycheckActor { @@ -366,6 +371,7 @@ impl FlycheckActor { root: Arc::new(workspace_root), scope: FlycheckScope::Workspace, manifest_path, + ws_target_dir, command_handle: None, command_receiver: None, diagnostics_cleared_for: Default::default(), @@ -428,15 +434,24 @@ impl FlycheckActor { CargoCheckParser, sender, match &self.config { - FlycheckConfig::CargoCommand { options, .. } => Some( - options - .target_dir - .as_deref() - .unwrap_or( - Utf8Path::new("target").join("rust-analyzer").as_path(), - ) - .join(format!("flycheck{}", self.id)), - ), + FlycheckConfig::CargoCommand { options, .. } => { + let ws_target_dir = + self.ws_target_dir.as_ref().map(Utf8PathBuf::as_path); + let target_dir = + options.target_dir_config.target_dir(ws_target_dir); + + // If `"rust-analyzer.cargo.targetDir": null`, we should use + // workspace's target dir instead of hard-coded fallback. + let target_dir = target_dir.as_deref().or(ws_target_dir); + + Some( + target_dir + .unwrap_or( + Utf8Path::new("target").join("rust-analyzer").as_path(), + ) + .join(format!("flycheck{}", self.id)), + ) + } _ => None, }, ) { @@ -672,7 +687,10 @@ impl FlycheckActor { cmd.arg("--keep-going"); - options.apply_on_command(&mut cmd); + options.apply_on_command( + &mut cmd, + self.ws_target_dir.as_ref().map(Utf8PathBuf::as_path), + ); cmd.args(&options.extra_args); Some(cmd) } diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 2976441d76..14817df376 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -264,6 +264,7 @@ pub(crate) fn handle_run_test( path, state.config.cargo_test_options(None), cargo.workspace_root(), + Some(cargo.target_directory().as_ref()), target, state.test_run_sender.clone(), )?; diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 1475f02447..bb971eb13b 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -23,6 +23,7 @@ use ide_db::{ use itertools::Itertools; use load_cargo::{ProjectFolders, load_proc_macro}; use lsp_types::FileSystemWatcher; +use paths::Utf8Path; use proc_macro_api::ProcMacroClient; use project_model::{ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, WorkspaceBuildScripts}; use stdx::{format_to, thread::ThreadIntent}; @@ -876,6 +877,7 @@ impl GlobalState { None, self.config.root_path().clone(), None, + None, )] } crate::flycheck::InvocationStrategy::PerWorkspace => { @@ -890,13 +892,17 @@ impl GlobalState { | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. - } => (cargo.workspace_root(), Some(cargo.manifest_path())), + } => ( + cargo.workspace_root(), + Some(cargo.manifest_path()), + Some(cargo.target_directory()), + ), ProjectWorkspaceKind::Json(project) => { // Enable flychecks for json projects if a custom flycheck command was supplied // in the workspace configuration. match config { FlycheckConfig::CustomCommand { .. } => { - (project.path(), None) + (project.path(), None, None) } _ => return None, } @@ -906,7 +912,7 @@ impl GlobalState { ws.sysroot.root().map(ToOwned::to_owned), )) }) - .map(|(id, (root, manifest_path), sysroot_root)| { + .map(|(id, (root, manifest_path, target_dir), sysroot_root)| { FlycheckHandle::spawn( id, next_gen, @@ -915,6 +921,7 @@ impl GlobalState { sysroot_root, root.to_path_buf(), manifest_path.map(|it| it.to_path_buf()), + target_dir.map(|it| AsRef::::as_ref(it).to_path_buf()), ) }) .collect() diff --git a/crates/rust-analyzer/src/test_runner.rs b/crates/rust-analyzer/src/test_runner.rs index 0c8658c75d..9a65e708a0 100644 --- a/crates/rust-analyzer/src/test_runner.rs +++ b/crates/rust-analyzer/src/test_runner.rs @@ -2,7 +2,7 @@ //! thread and report the result of each test in a channel. use crossbeam_channel::Sender; -use paths::AbsPath; +use paths::{AbsPath, Utf8Path}; use project_model::TargetKind; use serde::Deserialize as _; use serde_derive::Deserialize; @@ -98,6 +98,7 @@ impl CargoTestHandle { path: Option<&str>, options: CargoOptions, root: &AbsPath, + ws_target_dir: Option<&Utf8Path>, test_target: TestTarget, sender: Sender, ) -> std::io::Result { @@ -123,7 +124,7 @@ impl CargoTestHandle { cmd.arg("--no-fail-fast"); cmd.arg("--manifest-path"); cmd.arg(root.join("Cargo.toml")); - options.apply_on_command(&mut cmd); + options.apply_on_command(&mut cmd, ws_target_dir); cmd.arg("--"); if let Some(path) = path { cmd.arg(path); From b50af95188e6d4db41e0f2b7cc0f7924d3ffd7b0 Mon Sep 17 00:00:00 2001 From: "Shoyu Vanilla (Flint)" Date: Tue, 28 Oct 2025 15:49:38 +0900 Subject: [PATCH 2/2] fix: Canonicalize flycheck output path --- crates/rust-analyzer/src/flycheck.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/rust-analyzer/src/flycheck.rs b/crates/rust-analyzer/src/flycheck.rs index db6743d4e5..68337ddf1c 100644 --- a/crates/rust-analyzer/src/flycheck.rs +++ b/crates/rust-analyzer/src/flycheck.rs @@ -445,11 +445,19 @@ impl FlycheckActor { let target_dir = target_dir.as_deref().or(ws_target_dir); Some( - target_dir - .unwrap_or( + // As `CommandHandle::spawn`'s working directory is + // rust-analyzer's working directory, which might be different + // from the flycheck's working directory, we should canonicalize + // the output directory, otherwise we might write it into the + // wrong target dir. + // If `target_dir` is an absolute path, it will replace + // `self.root` and that's an intended behavior. + self.root + .join(target_dir.unwrap_or( Utf8Path::new("target").join("rust-analyzer").as_path(), - ) - .join(format!("flycheck{}", self.id)), + )) + .join(format!("flycheck{}", self.id)) + .into(), ) } _ => None,