diff --git a/crates/project-model/src/build_dependencies.rs b/crates/project-model/src/build_dependencies.rs index 8da0a574e764..2955af31b57a 100644 --- a/crates/project-model/src/build_dependencies.rs +++ b/crates/project-model/src/build_dependencies.rs @@ -88,6 +88,7 @@ impl WorkspaceBuildScripts { &allowed_features, workspace.manifest_path(), workspace.target_directory().as_ref(), + workspace.build_directory().as_ref(), current_dir, sysroot, toolchain, @@ -111,6 +112,7 @@ impl WorkspaceBuildScripts { // 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.as_ref(), working_directory, &Sysroot::empty(), None, @@ -434,6 +436,7 @@ impl WorkspaceBuildScripts { allowed_features: &FxHashSet, manifest_path: &ManifestPath, target_dir: &Utf8Path, + build_dir: &Utf8Path, current_dir: &AbsPath, sysroot: &Sysroot, toolchain: Option<&semver::Version>, @@ -461,6 +464,9 @@ impl WorkspaceBuildScripts { cmd.arg("--target-dir"); cmd.arg(target_dir.as_ref()); } + if let Some(build_dir) = config.build_dir_config.target_dir(Some(build_dir)) { + cmd.env("CARGO_BUILD_BUILD_DIR", build_dir.as_ref()); + } toolchain::cargo_use_targets(toolchain, &mut cmd, config.target.as_slice()); let mut lockfile_copy = None; diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs index a0a9815d8756..014036b94bd6 100644 --- a/crates/project-model/src/cargo_workspace.rs +++ b/crates/project-model/src/cargo_workspace.rs @@ -36,6 +36,7 @@ pub struct CargoWorkspace { targets: Arena, workspace_root: AbsPathBuf, target_directory: AbsPathBuf, + build_directory: AbsPathBuf, manifest_path: ManifestPath, is_virtual_workspace: bool, /// Whether this workspace represents the sysroot workspace. @@ -140,6 +141,8 @@ pub struct CargoConfig { pub invocation_strategy: InvocationStrategy, /// Optional path to use instead of `target` when building pub target_dir_config: TargetDirectoryConfig, + /// Optional path to use instead of `target` when building + pub build_dir_config: TargetDirectoryConfig, /// Gate `#[test]` behind `#[cfg(test)]` pub set_test: bool, /// Load the project without any dependencies @@ -358,6 +361,9 @@ impl CargoWorkspace { let ws_members = &meta.workspace_members; let workspace_root = AbsPathBuf::assert(meta.workspace_root); + let build_directory = AbsPathBuf::assert( + meta.build_directory.unwrap_or_else(|| meta.target_directory.clone()), + ); let target_directory = AbsPathBuf::assert(meta.target_directory); let mut is_virtual_workspace = true; let mut requires_rustc_private = false; @@ -517,6 +523,7 @@ impl CargoWorkspace { targets, workspace_root, target_directory, + build_directory, manifest_path: ws_manifest_path, is_virtual_workspace, requires_rustc_private, @@ -548,6 +555,10 @@ impl CargoWorkspace { &self.target_directory } + pub fn build_directory(&self) -> &AbsPath { + &self.build_directory + } + pub fn package_flag(&self, package: &PackageData) -> String { if self.is_unique(&package.name) { package.name.clone() diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 1e7c8257d15b..fad65c015429 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -788,6 +788,15 @@ config_data! { /// Automatically refresh project info via `cargo metadata` on /// `Cargo.toml` or `.cargo/config.toml` changes. cargo_autoreload: bool = true, + /// Optional path to a rust-analyzer specific build directory. + /// Since cargo's build-directory defaults to the target-directory, it should only + /// be needed to set this if a custom build-directory is configured. + /// Otherwise setting `cargo.targetDir` is sufficient to prevent rust-analyzer from + /// locking the `Cargo.lock`. + /// + /// Set to `true` to use a subdirectory of the existing build directory or + /// set to a path relative to the workspace to use that path. + cargo_buildDir | rust_analyzerBuildDir: Option = None, /// Run build scripts (`build.rs`) for more precise code analysis. cargo_buildScripts_enable: bool = true, /// Specifies the invocation strategy to use when running the build scripts command. @@ -2486,6 +2495,7 @@ impl Config { extra_args: self.cargo_extraArgs(source_root).clone(), extra_env: self.cargo_extraEnv(source_root).clone(), target_dir_config: self.target_dir_from_config(source_root), + build_dir_config: self.build_dir_from_config(source_root), set_test: *self.cfg_setTest(source_root), no_deps: *self.cargo_noDeps(source_root), metadata_extra_args: self.cargo_metadataExtraArgs(source_root).clone(), @@ -2578,6 +2588,7 @@ impl Config { extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(), extra_env: self.extra_env(source_root).clone(), target_dir_config: self.target_dir_from_config(source_root), + build_dir_config: self.build_dir_from_config(source_root), set_test: true, config_path: self.cargo_config_path(source_root), } @@ -2638,6 +2649,7 @@ impl Config { extra_env: self.check_extra_env(source_root), config_path: self.cargo_config_path(source_root), target_dir_config: self.target_dir_from_config(source_root), + build_dir_config: self.build_dir_from_config(source_root), set_test: *self.cfg_setTest(source_root), }, ansi_color_output: self.color_diagnostic_output(), @@ -2657,6 +2669,14 @@ impl Config { } } + fn build_dir_from_config(&self, source_root: Option) -> TargetDirectoryConfig { + match &self.cargo_buildDir(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 { *self.checkOnSave(source_root) } diff --git a/crates/rust-analyzer/src/flycheck.rs b/crates/rust-analyzer/src/flycheck.rs index 99e640a3cecb..09ecde5509f5 100644 --- a/crates/rust-analyzer/src/flycheck.rs +++ b/crates/rust-analyzer/src/flycheck.rs @@ -53,6 +53,7 @@ pub(crate) struct CargoOptions { pub(crate) extra_env: FxHashMap>, pub(crate) config_path: Option, pub(crate) target_dir_config: TargetDirectoryConfig, + pub(crate) build_dir_config: TargetDirectoryConfig, } #[derive(Clone, Debug)] @@ -68,6 +69,7 @@ impl CargoOptions { &self, cmd: &mut Command, ws_target_dir: Option<&Utf8Path>, + ws_build_dir: Option<&Utf8Path>, package_repr: Option<&str>, toolchain_version: Option<&semver::Version>, ) { @@ -114,6 +116,9 @@ impl CargoOptions { if let Some(target_dir) = self.target_dir_config.target_dir(ws_target_dir) { cmd.arg("--target-dir").arg(target_dir.as_ref()); } + if let Some(build_dir) = self.build_dir_config.target_dir(ws_build_dir) { + cmd.env("CARGO_BUILD_BUILD_DIR", build_dir.as_ref()); + } } } @@ -226,6 +231,7 @@ impl FlycheckHandle { workspace_root: AbsPathBuf, manifest_path: Option, ws_target_dir: Option, + ws_build_dir: Option, toolchain_version: Option, ) -> FlycheckHandle { let actor = FlycheckActor::new( @@ -238,6 +244,7 @@ impl FlycheckHandle { workspace_root, manifest_path, ws_target_dir, + ws_build_dir, toolchain_version, ); let (sender, receiver) = unbounded::(); @@ -431,6 +438,7 @@ struct FlycheckActor { manifest_path: Option, ws_target_dir: Option, + ws_build_dir: Option, /// Either the workspace root of the workspace we are flychecking, /// or the project root of the project. root: Arc, @@ -533,6 +541,7 @@ impl FlycheckActor { workspace_root: AbsPathBuf, manifest_path: Option, ws_target_dir: Option, + ws_build_dir: Option, toolchain_version: Option, ) -> FlycheckActor { tracing::info!(%id, ?workspace_root, "Spawning flycheck"); @@ -547,6 +556,7 @@ impl FlycheckActor { scope: FlycheckScope::Workspace, manifest_path, ws_target_dir, + ws_build_dir, command_handle: None, command_receiver: None, diagnostics_cleared_for: Default::default(), @@ -961,6 +971,7 @@ impl FlycheckActor { cargo_options.apply_on_command( &mut cmd, self.ws_target_dir.as_ref().map(Utf8PathBuf::as_path), + self.ws_build_dir.as_ref().map(Utf8PathBuf::as_path), package_repr, self.toolchain_version.as_ref(), ); @@ -1181,6 +1192,7 @@ mod tests { extra_env: FxHashMap::default(), config_path: None, target_dir_config: TargetDirectoryConfig::default(), + build_dir_config: TargetDirectoryConfig::default(), }, ansi_color_output: true, }; diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index d88eb358d385..531f7879dd01 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -285,6 +285,7 @@ pub(crate) fn handle_run_test( state.config.cargo_test_options(None), cargo.workspace_root(), Some(cargo.target_directory().as_ref()), + Some(cargo.build_directory().as_ref()), target, state.test_run_sender.clone(), ws.toolchain.as_ref(), diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 173409b32585..6755286ba467 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -902,6 +902,7 @@ impl GlobalState { None, None, None, + None, )] } crate::flycheck::InvocationStrategy::PerWorkspace => { @@ -921,6 +922,7 @@ impl GlobalState { cargo.workspace_root(), Some(cargo.manifest_path()), Some(cargo.target_directory()), + Some(cargo.build_directory()), ), ProjectWorkspaceKind::Json(project) => { let config_json = crate::flycheck::FlycheckConfigJson { @@ -932,10 +934,10 @@ impl GlobalState { // in the workspace configuration. match config { _ if config_json.any_configured() => { - (config_json, project.path(), None, None) + (config_json, project.path(), None, None, None) } FlycheckConfig::CustomCommand { .. } => { - (config_json, project.path(), None, None) + (config_json, project.path(), None, None, None) } _ => return None, } @@ -949,7 +951,7 @@ impl GlobalState { .map( |( id, - (config_json, root, manifest_path, target_dir), + (config_json, root, manifest_path, target_dir, build_dir), sysroot_root, toolchain, )| { @@ -963,6 +965,7 @@ impl GlobalState { root.to_path_buf(), manifest_path.map(|it| it.to_path_buf()), target_dir.map(|it| AsRef::::as_ref(it).to_path_buf()), + build_dir.map(|it| AsRef::::as_ref(it).to_path_buf()), toolchain, ) }, diff --git a/crates/rust-analyzer/src/test_runner.rs b/crates/rust-analyzer/src/test_runner.rs index 62d242f9d083..c53f88d20d40 100644 --- a/crates/rust-analyzer/src/test_runner.rs +++ b/crates/rust-analyzer/src/test_runner.rs @@ -106,6 +106,7 @@ impl CargoTestHandle { options: CargoOptions, root: &AbsPath, ws_target_dir: Option<&Utf8Path>, + ws_build_dir: Option<&Utf8Path>, test_target: TestTarget, sender: Sender, toolchain_version: Option<&semver::Version>, @@ -135,6 +136,7 @@ impl CargoTestHandle { options.apply_on_command( &mut cmd, ws_target_dir, + ws_build_dir, Some(&test_target.package), toolchain_version, ); diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md index 1c4642751f48..4a69d751047c 100644 --- a/docs/book/src/configuration_generated.md +++ b/docs/book/src/configuration_generated.md @@ -56,6 +56,20 @@ Automatically refresh project info via `cargo metadata` on `Cargo.toml` or `.cargo/config.toml` changes. +## rust-analyzer.cargo.buildDir {#cargo.buildDir} + +Default: `null` + +Optional path to a rust-analyzer specific build directory. +Since cargo's build-directory defaults to the target-directory, it should only +be needed to set this if a custom build-directory is configured. +Otherwise setting `cargo.targetDir` is sufficient to prevent rust-analyzer from +locking the `Cargo.lock`. + +Set to `true` to use a subdirectory of the existing build directory or +set to a path relative to the workspace to use that path. + + ## rust-analyzer.cargo.buildScripts.enable {#cargo.buildScripts.enable} Default: `true` diff --git a/editors/code/package.json b/editors/code/package.json index de9f17b9ccbf..4f04c10d87ea 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -881,6 +881,26 @@ } } }, + { + "title": "Cargo", + "properties": { + "rust-analyzer.cargo.buildDir": { + "markdownDescription": "Optional path to a rust-analyzer specific build directory.\nSince cargo's build-directory defaults to the target-directory, it should only\nbe needed to set this if a custom build-directory is configured.\nOtherwise setting `cargo.targetDir` is sufficient to prevent rust-analyzer from\nlocking the `Cargo.lock`.\n\nSet to `true` to use a subdirectory of the existing build directory or\nset to a path relative to the workspace to use that path.", + "default": null, + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "string" + } + ] + } + } + }, { "title": "Cargo", "properties": {