Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/project-model/src/build_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -434,6 +436,7 @@ impl WorkspaceBuildScripts {
allowed_features: &FxHashSet<String>,
manifest_path: &ManifestPath,
target_dir: &Utf8Path,
build_dir: &Utf8Path,
current_dir: &AbsPath,
sysroot: &Sysroot,
toolchain: Option<&semver::Version>,
Expand Down Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions crates/project-model/src/cargo_workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub struct CargoWorkspace {
targets: Arena<TargetData>,
workspace_root: AbsPathBuf,
target_directory: AbsPathBuf,
build_directory: AbsPathBuf,
manifest_path: ManifestPath,
is_virtual_workspace: bool,
/// Whether this workspace represents the sysroot workspace.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -517,6 +523,7 @@ impl CargoWorkspace {
targets,
workspace_root,
target_directory,
build_directory,
manifest_path: ws_manifest_path,
is_virtual_workspace,
requires_rustc_private,
Expand Down Expand Up @@ -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()
Expand Down
20 changes: 20 additions & 0 deletions crates/rust-analyzer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TargetDirectory> = 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.
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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),
}
Expand Down Expand Up @@ -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(),
Expand All @@ -2657,6 +2669,14 @@ impl Config {
}
}

fn build_dir_from_config(&self, source_root: Option<SourceRootId>) -> 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<SourceRootId>) -> bool {
*self.checkOnSave(source_root)
}
Expand Down
12 changes: 12 additions & 0 deletions crates/rust-analyzer/src/flycheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub(crate) struct CargoOptions {
pub(crate) extra_env: FxHashMap<String, Option<String>>,
pub(crate) config_path: Option<AbsPathBuf>,
pub(crate) target_dir_config: TargetDirectoryConfig,
pub(crate) build_dir_config: TargetDirectoryConfig,
}

#[derive(Clone, Debug)]
Expand All @@ -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>,
) {
Expand Down Expand Up @@ -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());

@Veykril Veykril Aug 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I take it there is no flag equivalent for this? Bit weird to see target dir and build dir being configured differently

View changes since the review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is no flag to set the build dir at the moment

}
}
}

Expand Down Expand Up @@ -226,6 +231,7 @@ impl FlycheckHandle {
workspace_root: AbsPathBuf,
manifest_path: Option<AbsPathBuf>,
ws_target_dir: Option<Utf8PathBuf>,
ws_build_dir: Option<Utf8PathBuf>,
toolchain_version: Option<semver::Version>,
) -> FlycheckHandle {
let actor = FlycheckActor::new(
Expand All @@ -238,6 +244,7 @@ impl FlycheckHandle {
workspace_root,
manifest_path,
ws_target_dir,
ws_build_dir,
toolchain_version,
);
let (sender, receiver) = unbounded::<StateChange>();
Expand Down Expand Up @@ -431,6 +438,7 @@ struct FlycheckActor {

manifest_path: Option<AbsPathBuf>,
ws_target_dir: Option<Utf8PathBuf>,
ws_build_dir: Option<Utf8PathBuf>,
/// Either the workspace root of the workspace we are flychecking,
/// or the project root of the project.
root: Arc<AbsPathBuf>,
Expand Down Expand Up @@ -533,6 +541,7 @@ impl FlycheckActor {
workspace_root: AbsPathBuf,
manifest_path: Option<AbsPathBuf>,
ws_target_dir: Option<Utf8PathBuf>,
ws_build_dir: Option<Utf8PathBuf>,
toolchain_version: Option<semver::Version>,
) -> FlycheckActor {
tracing::info!(%id, ?workspace_root, "Spawning flycheck");
Expand All @@ -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(),
Expand Down Expand Up @@ -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(),
);
Expand Down Expand Up @@ -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,
};
Expand Down
1 change: 1 addition & 0 deletions crates/rust-analyzer/src/handlers/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
9 changes: 6 additions & 3 deletions crates/rust-analyzer/src/reload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,7 @@ impl GlobalState {
None,
None,
None,
None,
)]
}
crate::flycheck::InvocationStrategy::PerWorkspace => {
Expand All @@ -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 {
Expand All @@ -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,
}
Expand All @@ -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,
)| {
Expand All @@ -963,6 +965,7 @@ impl GlobalState {
root.to_path_buf(),
manifest_path.map(|it| it.to_path_buf()),
target_dir.map(|it| AsRef::<Utf8Path>::as_ref(it).to_path_buf()),
build_dir.map(|it| AsRef::<Utf8Path>::as_ref(it).to_path_buf()),
toolchain,
)
},
Expand Down
2 changes: 2 additions & 0 deletions crates/rust-analyzer/src/test_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CargoTestMessage>,
toolchain_version: Option<&semver::Version>,
Expand Down Expand Up @@ -135,6 +136,7 @@ impl CargoTestHandle {
options.apply_on_command(
&mut cmd,
ws_target_dir,
ws_build_dir,
Some(&test_target.package),
toolchain_version,
);
Expand Down
14 changes: 14 additions & 0 deletions docs/book/src/configuration_generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
20 changes: 20 additions & 0 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down