Skip to content

Commit 7e2c41d

Browse files
committed
Implement invocation strategy config for build scripts
1 parent 40cbeb5 commit 7e2c41d

File tree

8 files changed

+348
-137
lines changed

8 files changed

+348
-137
lines changed

crates/project-model/src/build_scripts.rs

Lines changed: 224 additions & 126 deletions
Large diffs are not rendered by default.

crates/project-model/src/cargo_workspace.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use rustc_hash::FxHashMap;
1414
use serde::Deserialize;
1515
use serde_json::from_value;
1616

17-
use crate::CfgOverrides;
1817
use crate::{utf8_stdout, ManifestPath};
18+
use crate::{CfgOverrides, InvocationStrategy};
1919

2020
/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
2121
/// workspace. It pretty closely mirrors `cargo metadata` output.
@@ -106,6 +106,7 @@ pub struct CargoConfig {
106106
pub run_build_script_command: Option<Vec<String>>,
107107
/// Extra env vars to set when invoking the cargo command
108108
pub extra_env: FxHashMap<String, String>,
109+
pub invocation_strategy: InvocationStrategy,
109110
}
110111

111112
impl CargoConfig {

crates/project-model/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,11 @@ fn utf8_stdout(mut cmd: Command) -> Result<String> {
157157
let stdout = String::from_utf8(output.stdout)?;
158158
Ok(stdout.trim().to_string())
159159
}
160+
161+
#[derive(Clone, Debug, Default, PartialEq, Eq)]
162+
pub enum InvocationStrategy {
163+
OnceInRoot,
164+
PerWorkspaceWithManifestPath,
165+
#[default]
166+
PerWorkspace,
167+
}

crates/project-model/src/workspace.rs

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! metadata` or `rust-project.json`) into representation stored in the salsa
33
//! database -- `CrateGraph`.
44
5-
use std::{collections::VecDeque, fmt, fs, process::Command};
5+
use std::{collections::VecDeque, fmt, fs, process::Command, sync::Arc};
66

77
use anyhow::{format_err, Context, Result};
88
use base_db::{
@@ -21,8 +21,8 @@ use crate::{
2121
cfg_flag::CfgFlag,
2222
rustc_cfg,
2323
sysroot::SysrootCrate,
24-
utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath, Package, ProjectJson, ProjectManifest,
25-
Sysroot, TargetKind, WorkspaceBuildScripts,
24+
utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath, Package,
25+
ProjectJson, ProjectManifest, Sysroot, TargetKind, WorkspaceBuildScripts,
2626
};
2727

2828
/// A set of cfg-overrides per crate.
@@ -294,23 +294,71 @@ impl ProjectWorkspace {
294294
Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
295295
}
296296

297+
/// Runs the build scripts for this [`ProjectWorkspace`].
297298
pub fn run_build_scripts(
298299
&self,
299300
config: &CargoConfig,
300301
progress: &dyn Fn(String),
301302
) -> Result<WorkspaceBuildScripts> {
302303
match self {
303304
ProjectWorkspace::Cargo { cargo, toolchain, .. } => {
304-
WorkspaceBuildScripts::run(config, cargo, progress, toolchain).with_context(|| {
305-
format!("Failed to run build scripts for {}", &cargo.workspace_root().display())
306-
})
305+
WorkspaceBuildScripts::run_for_workspace(config, cargo, progress, toolchain)
306+
.with_context(|| {
307+
format!(
308+
"Failed to run build scripts for {}",
309+
&cargo.workspace_root().display()
310+
)
311+
})
307312
}
308313
ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => {
309314
Ok(WorkspaceBuildScripts::default())
310315
}
311316
}
312317
}
313318

319+
/// Runs the build scripts for the given [`ProjectWorkspace`]s. Depending on the invocation
320+
/// strategy this may run a single build process for all project workspaces.
321+
pub fn run_all_build_scripts(
322+
workspaces: &[ProjectWorkspace],
323+
config: &CargoConfig,
324+
progress: &dyn Fn(String),
325+
) -> Vec<Result<WorkspaceBuildScripts>> {
326+
if let InvocationStrategy::PerWorkspaceWithManifestPath | InvocationStrategy::PerWorkspace =
327+
config.invocation_strategy
328+
{
329+
return workspaces.iter().map(|it| it.run_build_scripts(config, progress)).collect();
330+
}
331+
332+
let cargo_ws: Vec<_> = workspaces
333+
.iter()
334+
.filter_map(|it| match it {
335+
ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
336+
_ => None,
337+
})
338+
.collect();
339+
let ref mut outputs = match WorkspaceBuildScripts::run_once(config, &cargo_ws, progress) {
340+
Ok(it) => Ok(it.into_iter()),
341+
// io::Error is not Clone?
342+
Err(e) => Err(Arc::new(e)),
343+
};
344+
345+
workspaces
346+
.iter()
347+
.map(|it| match it {
348+
ProjectWorkspace::Cargo { cargo, .. } => match outputs {
349+
Ok(outputs) => Ok(outputs.next().unwrap()),
350+
Err(e) => Err(e.clone()).with_context(|| {
351+
format!(
352+
"Failed to run build scripts for {}",
353+
&cargo.workspace_root().display()
354+
)
355+
}),
356+
},
357+
_ => Ok(WorkspaceBuildScripts::default()),
358+
})
359+
.collect()
360+
}
361+
314362
pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
315363
match self {
316364
ProjectWorkspace::Cargo { build_scripts, .. } => *build_scripts = bs,

crates/rust-analyzer/src/config.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ config_data! {
6969
cargo_autoreload: bool = "true",
7070
/// Run build scripts (`build.rs`) for more precise code analysis.
7171
cargo_buildScripts_enable: bool = "true",
72+
/// Specifies the invocation strategy to use when running the build scripts command.
73+
/// If `per_workspace_with_manifest_path` is set, the command will be executed for each
74+
/// workspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and
75+
/// the command will be executed from the project root.
76+
/// If `per_workspace` is set, the command will be executed for each workspace and the
77+
/// command will be executed from the corresponding workspace root.
78+
/// If `once_in_root` is set, the command will be executed once in the project root.
79+
cargo_buildScripts_invocationStrategy: InvocationStrategy = "\"per_workspace\"",
7280
/// Override the command rust-analyzer uses to run build scripts and
7381
/// build procedural macros. The command is required to output json
7482
/// and should therefore include `--message-format=json` or a similar
@@ -1056,6 +1064,13 @@ impl Config {
10561064
rustc_source,
10571065
unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
10581066
wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
1067+
invocation_strategy: match self.data.cargo_buildScripts_invocationStrategy {
1068+
InvocationStrategy::OnceInRoot => project_model::InvocationStrategy::OnceInRoot,
1069+
InvocationStrategy::PerWorkspaceWithManifestPath => {
1070+
project_model::InvocationStrategy::PerWorkspaceWithManifestPath
1071+
}
1072+
InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace,
1073+
},
10591074
run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
10601075
extra_env: self.data.cargo_extraEnv.clone(),
10611076
}
@@ -1587,6 +1602,14 @@ enum CargoFeaturesDef {
15871602
Selected(Vec<String>),
15881603
}
15891604

1605+
#[derive(Deserialize, Debug, Clone)]
1606+
#[serde(rename_all = "snake_case")]
1607+
enum InvocationStrategy {
1608+
OnceInRoot,
1609+
PerWorkspaceWithManifestPath,
1610+
PerWorkspace,
1611+
}
1612+
15901613
#[derive(Deserialize, Debug, Clone)]
15911614
#[serde(untagged)]
15921615
enum LifetimeElisionDef {
@@ -2001,6 +2024,15 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
20012024
"Render annotations above the whole item, including documentation comments and attributes."
20022025
],
20032026
},
2027+
"InvocationStrategy" => set! {
2028+
"type": "string",
2029+
"enum": ["per_workspace", "per_workspace_with_manifest_path", "once_in_root"],
2030+
"enumDescriptions": [
2031+
"The command will be executed for each workspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and the command will be executed from the project root.",
2032+
"The command will be executed for each workspace and the command will be executed from the corresponding workspace root.",
2033+
"The command will be executed once in the project root."
2034+
],
2035+
},
20042036
_ => panic!("missing entry for {}: {}", ty, default),
20052037
}
20062038

crates/rust-analyzer/src/reload.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,8 @@ impl GlobalState {
175175
sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap()
176176
}
177177
};
178-
let mut res = Vec::new();
179-
for ws in workspaces.iter() {
180-
res.push(ws.run_build_scripts(&config, &progress));
181-
}
178+
let res = ProjectWorkspace::run_all_build_scripts(&workspaces, &config, &progress);
179+
182180
sender.send(Task::FetchBuildData(BuildDataProgress::End((workspaces, res)))).unwrap();
183181
});
184182
}

docs/user/generated_config.adoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ Automatically refresh project info via `cargo metadata` on
2424
--
2525
Run build scripts (`build.rs`) for more precise code analysis.
2626
--
27+
[[rust-analyzer.cargo.buildScripts.invocationStrategy]]rust-analyzer.cargo.buildScripts.invocationStrategy (default: `"per_workspace"`)::
28+
+
29+
--
30+
Specifies the invocation strategy to use when running the build scripts command.
31+
If `per_workspace_with_manifest_path` is set, the command will be executed for each
32+
workspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and
33+
the command will be executed from the project root.
34+
If `per_workspace` is set, the command will be executed for each workspace and the
35+
command will be executed from the corresponding workspace root.
36+
If `once_in_root` is set, the command will be executed once in the project root.
37+
--
2738
[[rust-analyzer.cargo.buildScripts.overrideCommand]]rust-analyzer.cargo.buildScripts.overrideCommand (default: `null`)::
2839
+
2940
--

editors/code/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,21 @@
421421
"default": true,
422422
"type": "boolean"
423423
},
424+
"rust-analyzer.cargo.buildScripts.invocationStrategy": {
425+
"markdownDescription": "Specifies the invocation strategy to use when running the build scripts command.\nIf `per_workspace_with_manifest_path` is set, the command will be executed for each\nworkspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and\nthe command will be executed from the project root.\nIf `per_workspace` is set, the command will be executed for each workspace and the\ncommand will be executed from the corresponding workspace root.\nIf `once_in_root` is set, the command will be executed once in the project root.",
426+
"default": "per_workspace",
427+
"type": "string",
428+
"enum": [
429+
"per_workspace",
430+
"per_workspace_with_manifest_path",
431+
"once_in_root"
432+
],
433+
"enumDescriptions": [
434+
"The command will be executed for each workspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and the command will be executed from the project root.",
435+
"The command will be executed for each workspace and the command will be executed from the corresponding workspace root.",
436+
"The command will be executed once in the project root."
437+
]
438+
},
424439
"rust-analyzer.cargo.buildScripts.overrideCommand": {
425440
"markdownDescription": "Override the command rust-analyzer uses to run build scripts and\nbuild procedural macros. The command is required to output json\nand should therefore include `--message-format=json` or a similar\noption.\n\nBy default, a cargo invocation will be constructed for the configured\ntargets and features, with the following base command line:\n\n```bash\ncargo check --quiet --workspace --message-format=json --all-targets\n```\n.",
426441
"default": null,

0 commit comments

Comments
 (0)