Skip to content

Commit 8f71431

Browse files
committed
Implement invocation strategy config for build scripts
1 parent 1f92965 commit 8f71431

File tree

8 files changed

+345
-135
lines changed

8 files changed

+345
-135
lines changed

crates/project-model/src/build_scripts.rs

Lines changed: 224 additions & 124 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.
@@ -107,6 +107,7 @@ pub struct CargoConfig {
107107
pub run_build_script_command: Option<Vec<String>>,
108108
/// Extra env vars to set when invoking the cargo command
109109
pub extra_env: FxHashMap<String, String>,
110+
pub invocation_strategy: InvocationStrategy,
110111
}
111112

112113
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: 51 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.
@@ -285,23 +285,68 @@ impl ProjectWorkspace {
285285
Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
286286
}
287287

288+
/// Runs the build scripts for this [`ProjectWorkspace`].
288289
pub fn run_build_scripts(
289290
&self,
290291
config: &CargoConfig,
291292
progress: &dyn Fn(String),
292293
) -> Result<WorkspaceBuildScripts> {
293294
match self {
294295
ProjectWorkspace::Cargo { cargo, toolchain, .. } => {
295-
WorkspaceBuildScripts::run(config, cargo, progress, toolchain).with_context(|| {
296-
format!("Failed to run build scripts for {}", &cargo.workspace_root().display())
297-
})
296+
WorkspaceBuildScripts::run_for_workspace(config, cargo, progress, toolchain)
297+
.with_context(|| {
298+
format!(
299+
"Failed to run build scripts for {}",
300+
&cargo.workspace_root().display()
301+
)
302+
})
298303
}
299304
ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => {
300305
Ok(WorkspaceBuildScripts::default())
301306
}
302307
}
303308
}
304309

310+
/// Runs the build scripts for the given [`ProjectWorkspace`]s. Depending on the invocation
311+
/// strategy this may run a single build process for all project workspaces.
312+
pub fn run_all_build_scripts(
313+
workspaces: &[ProjectWorkspace],
314+
config: &CargoConfig,
315+
progress: &dyn Fn(String),
316+
) -> Vec<Result<WorkspaceBuildScripts>> {
317+
if let InvocationStrategy::PerWorkspaceWithManifestPath | InvocationStrategy::PerWorkspace =
318+
config.invocation_strategy
319+
{
320+
return workspaces.iter().map(|it| it.run_build_scripts(config, progress)).collect();
321+
}
322+
323+
let cargo_ws = workspaces.iter().filter_map(|it| match it {
324+
ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
325+
_ => None,
326+
});
327+
let ref mut outputs = match WorkspaceBuildScripts::run_once(config, cargo_ws, progress) {
328+
Ok(it) => Ok(it.into_iter()),
329+
// io::Error is not Clone?
330+
Err(e) => Err(Arc::new(e)),
331+
};
332+
333+
workspaces
334+
.iter()
335+
.map(|it| match it {
336+
ProjectWorkspace::Cargo { cargo, .. } => match outputs {
337+
Ok(outputs) => Ok(outputs.next().unwrap()),
338+
Err(e) => Err(e.clone()).with_context(|| {
339+
format!(
340+
"Failed to run build scripts for {}",
341+
&cargo.workspace_root().display()
342+
)
343+
}),
344+
},
345+
_ => Ok(WorkspaceBuildScripts::default()),
346+
})
347+
.collect()
348+
}
349+
305350
pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
306351
match self {
307352
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
@@ -1042,6 +1050,13 @@ impl Config {
10421050
rustc_source,
10431051
unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
10441052
wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
1053+
invocation_strategy: match self.data.cargo_buildScripts_invocationStrategy {
1054+
InvocationStrategy::OnceInRoot => project_model::InvocationStrategy::OnceInRoot,
1055+
InvocationStrategy::PerWorkspaceWithManifestPath => {
1056+
project_model::InvocationStrategy::PerWorkspaceWithManifestPath
1057+
}
1058+
InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace,
1059+
},
10451060
run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
10461061
extra_env: self.data.cargo_extraEnv.clone(),
10471062
}
@@ -1573,6 +1588,14 @@ enum CargoFeaturesDef {
15731588
Selected(Vec<String>),
15741589
}
15751590

1591+
#[derive(Deserialize, Debug, Clone)]
1592+
#[serde(rename_all = "snake_case")]
1593+
enum InvocationStrategy {
1594+
OnceInRoot,
1595+
PerWorkspaceWithManifestPath,
1596+
PerWorkspace,
1597+
}
1598+
15761599
#[derive(Deserialize, Debug, Clone)]
15771600
#[serde(untagged)]
15781601
enum LifetimeElisionDef {
@@ -1987,6 +2010,15 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
19872010
"Render annotations above the whole item, including documentation comments and attributes."
19882011
],
19892012
},
2013+
"InvocationStrategy" => set! {
2014+
"type": "string",
2015+
"enum": ["per_workspace", "per_workspace_with_manifest_path", "once_in_root"],
2016+
"enumDescriptions": [
2017+
"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.",
2018+
"The command will be executed for each workspace and the command will be executed from the corresponding workspace root.",
2019+
"The command will be executed once in the project root."
2020+
],
2021+
},
19902022
_ => panic!("missing entry for {}: {}", ty, default),
19912023
}
19922024

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)