Skip to content

Commit 3af6708

Browse files
committed
Implement invocation strategy config for build scripts
1 parent 2b61be2 commit 3af6708

File tree

8 files changed

+341
-132
lines changed

8 files changed

+341
-132
lines changed

crates/project-model/src/build_scripts.rs

Lines changed: 220 additions & 121 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
@@ -13,8 +13,8 @@ use rustc_hash::FxHashMap;
1313
use serde::Deserialize;
1414
use serde_json::from_value;
1515

16-
use crate::CfgOverrides;
1716
use crate::{utf8_stdout, ManifestPath};
17+
use crate::{CfgOverrides, InvocationStrategy};
1818

1919
/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
2020
/// workspace. It pretty closely mirrors `cargo metadata` output.
@@ -98,6 +98,7 @@ pub struct CargoConfig {
9898
pub wrap_rustc_in_build_scripts: bool,
9999

100100
pub run_build_script_command: Option<Vec<String>>,
101+
pub invocation_strategy: InvocationStrategy,
101102
}
102103

103104
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, ProjectJson, ProjectManifest, Sysroot,
25-
TargetKind, WorkspaceBuildScripts,
24+
utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath, ProjectJson,
25+
ProjectManifest, Sysroot, TargetKind, WorkspaceBuildScripts,
2626
};
2727

2828
/// A set of cfg-overrides per crate.
@@ -273,23 +273,68 @@ impl ProjectWorkspace {
273273
Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
274274
}
275275

276+
/// Runs the build scripts for this [`ProjectWorkspace`].
276277
pub fn run_build_scripts(
277278
&self,
278279
config: &CargoConfig,
279280
progress: &dyn Fn(String),
280281
) -> Result<WorkspaceBuildScripts> {
281282
match self {
282283
ProjectWorkspace::Cargo { cargo, toolchain, .. } => {
283-
WorkspaceBuildScripts::run(config, cargo, progress, toolchain).with_context(|| {
284-
format!("Failed to run build scripts for {}", &cargo.workspace_root().display())
285-
})
284+
WorkspaceBuildScripts::run_for_workspace(config, cargo, progress, toolchain)
285+
.with_context(|| {
286+
format!(
287+
"Failed to run build scripts for {}",
288+
&cargo.workspace_root().display()
289+
)
290+
})
286291
}
287292
ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => {
288293
Ok(WorkspaceBuildScripts::default())
289294
}
290295
}
291296
}
292297

298+
/// Runs the build scripts for the given [`ProjectWorkspace`]s. Depending on the invocation
299+
/// strategy this may run a single build process for all project workspaces.
300+
pub fn run_all_build_scripts(
301+
workspaces: &[ProjectWorkspace],
302+
config: &CargoConfig,
303+
progress: &dyn Fn(String),
304+
) -> Vec<Result<WorkspaceBuildScripts>> {
305+
if let InvocationStrategy::PerWorkspaceWithManifestPath | InvocationStrategy::PerWorkspace =
306+
config.invocation_strategy
307+
{
308+
return workspaces.iter().map(|it| it.run_build_scripts(config, progress)).collect();
309+
}
310+
311+
let cargo_ws = workspaces.iter().filter_map(|it| match it {
312+
ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
313+
_ => None,
314+
});
315+
let ref mut outputs = match WorkspaceBuildScripts::run_once(config, cargo_ws, progress) {
316+
Ok(it) => Ok(it.into_iter()),
317+
// io::Error is not Clone?
318+
Err(e) => Err(Arc::new(e)),
319+
};
320+
321+
workspaces
322+
.iter()
323+
.map(|it| match it {
324+
ProjectWorkspace::Cargo { cargo, .. } => match outputs {
325+
Ok(outputs) => Ok(outputs.next().unwrap()),
326+
Err(e) => Err(e.clone()).with_context(|| {
327+
format!(
328+
"Failed to run build scripts for {}",
329+
&cargo.workspace_root().display()
330+
)
331+
}),
332+
},
333+
_ => Ok(WorkspaceBuildScripts::default()),
334+
})
335+
.collect()
336+
}
337+
293338
pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
294339
match self {
295340
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
@@ -68,6 +68,14 @@ config_data! {
6868
cargo_autoreload: bool = "true",
6969
/// Run build scripts (`build.rs`) for more precise code analysis.
7070
cargo_buildScripts_enable: bool = "true",
71+
/// Specifies the invocation strategy to use when running the build scripts command.
72+
/// If `per_workspace_with_manifest_path` is set, the command will be executed for each
73+
/// workspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and
74+
/// the command will be executed from the project root.
75+
/// If `per_workspace` is set, the command will be executed for each workspace and the
76+
/// command will be executed from the corresponding workspace root.
77+
/// If `once_in_root` is set, the command will be executed once in the project root.
78+
cargo_buildScripts_invocationStrategy: InvocationStrategy = "\"per_workspace\"",
7179
/// Override the command rust-analyzer uses to run build scripts and
7280
/// build procedural macros. The command is required to output json
7381
/// and should therefore include `--message-format=json` or a similar
@@ -1024,6 +1032,13 @@ impl Config {
10241032
rustc_source,
10251033
unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
10261034
wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
1035+
invocation_strategy: match self.data.cargo_buildScripts_invocationStrategy {
1036+
InvocationStrategy::OnceInRoot => project_model::InvocationStrategy::OnceInRoot,
1037+
InvocationStrategy::PerWorkspaceWithManifestPath => {
1038+
project_model::InvocationStrategy::PerWorkspaceWithManifestPath
1039+
}
1040+
InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace,
1041+
},
10271042
run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
10281043
}
10291044
}
@@ -1549,6 +1564,14 @@ enum CargoFeatures {
15491564
Listed(Vec<String>),
15501565
}
15511566

1567+
#[derive(Deserialize, Debug, Clone)]
1568+
#[serde(rename_all = "snake_case")]
1569+
enum InvocationStrategy {
1570+
OnceInRoot,
1571+
PerWorkspaceWithManifestPath,
1572+
PerWorkspace,
1573+
}
1574+
15521575
#[derive(Deserialize, Debug, Clone)]
15531576
#[serde(untagged)]
15541577
enum LifetimeElisionDef {
@@ -1963,6 +1986,15 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
19631986
"Render annotations above the whole item, including documentation comments and attributes."
19641987
],
19651988
},
1989+
"InvocationStrategy" => set! {
1990+
"type": "string",
1991+
"enum": ["per_workspace", "per_workspace_with_manifest_path", "once_in_root"],
1992+
"enumDescriptions": [
1993+
"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.",
1994+
"The command will be executed for each workspace and the command will be executed from the corresponding workspace root.",
1995+
"The command will be executed once in the project root."
1996+
],
1997+
},
19661998
_ => panic!("missing entry for {}: {}", ty, default),
19671999
}
19682000

crates/rust-analyzer/src/reload.rs

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

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)