Skip to content

Commit d4423e8

Browse files
Wilfreddavidbarsky
andcommitted
Allow rust-project.json to include ShellRunnable configuration
Co-authored-by: David Barsky <[email protected]>
1 parent 5cc19d5 commit d4423e8

File tree

8 files changed

+164
-9
lines changed

8 files changed

+164
-9
lines changed

crates/project-model/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ mod build_scripts;
2121
mod cargo_workspace;
2222
mod cfg_flag;
2323
mod manifest_path;
24-
mod project_json;
24+
pub mod project_json;
2525
mod rustc_cfg;
2626
mod sysroot;
2727
pub mod target_data_layout;

crates/project-model/src/project_json.rs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
5555
use rustc_hash::FxHashMap;
5656
use serde::{de, Deserialize};
5757
use span::Edition;
58+
use std::path::PathBuf;
5859

59-
use crate::cfg_flag::CfgFlag;
60+
use crate::{cfg_flag::CfgFlag, TargetKind};
6061

6162
/// Roots and crates that compose this Rust project.
6263
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -87,6 +88,21 @@ pub struct Crate {
8788
pub(crate) exclude: Vec<AbsPathBuf>,
8889
pub(crate) is_proc_macro: bool,
8990
pub(crate) repository: Option<String>,
91+
pub build_info: Option<BuildInfo>,
92+
}
93+
94+
/// Additional metadata about a crate, used to configure runnables.
95+
#[derive(Clone, Debug, Eq, PartialEq)]
96+
pub struct BuildInfo {
97+
/// The name associated with this crate, according to the custom
98+
/// build system being used.
99+
pub label: String,
100+
/// What kind of target is this crate? For example, we don't want
101+
/// to offer a 'run' button for library crates.
102+
pub target_kind: TargetKind,
103+
/// Configuration for shell commands, such as CLI invocations for
104+
/// a check build or a test run.
105+
pub shell_runnables: Vec<ShellRunnableArgs>,
90106
}
91107

92108
impl ProjectJson {
@@ -121,6 +137,15 @@ impl ProjectJson {
121137
None => (vec![root_module.parent().unwrap().to_path_buf()], Vec::new()),
122138
};
123139

140+
let build_info = match crate_data.build_info {
141+
Some(build_info) => Some(BuildInfo {
142+
label: build_info.label,
143+
target_kind: build_info.target_kind.into(),
144+
shell_runnables: build_info.shell_runnables,
145+
}),
146+
None => None,
147+
};
148+
124149
Crate {
125150
display_name: crate_data
126151
.display_name
@@ -149,6 +174,7 @@ impl ProjectJson {
149174
exclude,
150175
is_proc_macro: crate_data.is_proc_macro,
151176
repository: crate_data.repository,
177+
build_info,
152178
}
153179
})
154180
.collect(),
@@ -172,6 +198,14 @@ impl ProjectJson {
172198
pub fn path(&self) -> &AbsPath {
173199
&self.project_root
174200
}
201+
202+
pub fn crate_by_root(&self, root: &AbsPath) -> Option<Crate> {
203+
self.crates
204+
.iter()
205+
.filter(|krate| krate.is_workspace_member)
206+
.find(|krate| &krate.root_module == root)
207+
.cloned()
208+
}
175209
}
176210

177211
#[derive(Deserialize, Debug, Clone)]
@@ -201,6 +235,8 @@ struct CrateData {
201235
is_proc_macro: bool,
202236
#[serde(default)]
203237
repository: Option<String>,
238+
#[serde(default)]
239+
build_info: Option<BuildInfoData>,
204240
}
205241

206242
#[derive(Deserialize, Debug, Clone)]
@@ -216,6 +252,48 @@ enum EditionData {
216252
Edition2024,
217253
}
218254

255+
#[derive(Deserialize, Debug, Clone)]
256+
pub struct BuildInfoData {
257+
label: String,
258+
target_kind: TargetKindData,
259+
shell_runnables: Vec<ShellRunnableArgs>,
260+
}
261+
262+
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
263+
#[serde(rename_all = "camelCase")]
264+
pub struct ShellRunnableArgs {
265+
pub program: String,
266+
pub args: Vec<String>,
267+
pub cwd: PathBuf,
268+
pub kind: ShellRunnableKind,
269+
}
270+
271+
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
272+
#[serde(rename_all = "camelCase")]
273+
pub enum ShellRunnableKind {
274+
Check,
275+
Run,
276+
}
277+
278+
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
279+
#[serde(rename_all = "camelCase")]
280+
pub enum TargetKindData {
281+
Bin,
282+
/// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
283+
Lib,
284+
Test,
285+
}
286+
287+
impl From<TargetKindData> for TargetKind {
288+
fn from(value: TargetKindData) -> Self {
289+
match value {
290+
TargetKindData::Bin => TargetKind::Bin,
291+
TargetKindData::Lib => TargetKind::Lib { is_proc_macro: false },
292+
TargetKindData::Test => TargetKind::Test,
293+
}
294+
}
295+
}
296+
219297
impl From<EditionData> for Edition {
220298
fn from(data: EditionData) -> Self {
221299
match data {

crates/rust-analyzer/src/global_state.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use crate::{
3333
mem_docs::MemDocs,
3434
op_queue::OpQueue,
3535
reload,
36-
target_spec::{CargoTargetSpec, TargetSpec},
36+
target_spec::{CargoTargetSpec, ProjectJsonTargetSpec, TargetSpec},
3737
task_pool::{TaskPool, TaskQueue},
3838
};
3939

@@ -529,7 +529,20 @@ impl GlobalStateSnapshot {
529529
features: package_data.features.keys().cloned().collect(),
530530
}));
531531
}
532-
ProjectWorkspace::Json { .. } => {}
532+
ProjectWorkspace::Json { project, .. } => {
533+
let Some(krate) = project.crate_by_root(path) else {
534+
continue;
535+
};
536+
let Some(build_info) = krate.build_info else {
537+
continue;
538+
};
539+
540+
return Some(TargetSpec::ProjectJson(ProjectJsonTargetSpec {
541+
target_kind: build_info.target_kind,
542+
label: build_info.label,
543+
shell_runnables: build_info.shell_runnables,
544+
}));
545+
}
533546
ProjectWorkspace::DetachedFiles { .. } => {}
534547
}
535548
}

crates/rust-analyzer/src/handlers/request.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ pub(crate) fn handle_parent_module(
773773
};
774774
let cargo_spec = match TargetSpec::for_file(&snap, file_id)? {
775775
Some(TargetSpec::Cargo(it)) => it,
776-
None => return Ok(None),
776+
Some(TargetSpec::ProjectJson(_)) | None => return Ok(None),
777777
};
778778

779779
if snap.analysis.crate_root(crate_id)? == file_id {
@@ -826,7 +826,6 @@ pub(crate) fn handle_runnables(
826826
}
827827
if let Some(mut runnable) = to_proto::runnable(&snap, runnable)? {
828828
if expect_test {
829-
#[allow(irrefutable_let_patterns)]
830829
if let lsp_ext::RunnableArgs::Cargo(r) = &mut runnable.args {
831830
runnable.label = format!("{} + expect", runnable.label);
832831
r.expect_test = Some(true);
@@ -866,6 +865,7 @@ pub(crate) fn handle_runnables(
866865
})
867866
}
868867
}
868+
Some(TargetSpec::ProjectJson(_)) => {}
869869
None => {
870870
if !snap.config.linked_or_discovered_projects().is_empty() {
871871
res.push(lsp_ext::Runnable {
@@ -1771,7 +1771,7 @@ pub(crate) fn handle_open_cargo_toml(
17711771

17721772
let cargo_spec = match TargetSpec::for_file(&snap, file_id)? {
17731773
Some(TargetSpec::Cargo(it)) => it,
1774-
None => return Ok(None),
1774+
Some(TargetSpec::ProjectJson(_)) | None => return Ok(None),
17751775
};
17761776

17771777
let cargo_toml_url = to_proto::url_from_abs_path(&cargo_spec.cargo_toml);
@@ -2063,7 +2063,7 @@ fn run_rustfmt(
20632063
};
20642064
process::Command::new(cmd_path)
20652065
}
2066-
None => process::Command::new(cmd),
2066+
_ => process::Command::new(cmd),
20672067
};
20682068

20692069
cmd.envs(snap.config.extra_env());

crates/rust-analyzer/src/lsp/ext.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,12 +431,14 @@ pub struct Runnable {
431431
#[serde(untagged)]
432432
pub enum RunnableArgs {
433433
Cargo(CargoRunnableArgs),
434+
Shell(ShellRunnableArgs),
434435
}
435436

436437
#[derive(Serialize, Deserialize, Debug)]
437438
#[serde(rename_all = "lowercase")]
438439
pub enum RunnableKind {
439440
Cargo,
441+
Shell,
440442
}
441443

442444
#[derive(Deserialize, Serialize, Debug)]
@@ -456,6 +458,14 @@ pub struct CargoRunnableArgs {
456458
pub expect_test: Option<bool>,
457459
}
458460

461+
#[derive(Deserialize, Serialize, Debug)]
462+
#[serde(rename_all = "camelCase")]
463+
pub struct ShellRunnableArgs {
464+
pub program: String,
465+
pub args: Vec<String>,
466+
pub cwd: PathBuf,
467+
}
468+
459469
pub enum RelatedTests {}
460470

461471
impl Request for RelatedTests {

crates/rust-analyzer/src/lsp/to_proto.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::{
2525
global_state::GlobalStateSnapshot,
2626
line_index::{LineEndings, LineIndex, PositionEncoding},
2727
lsp::{
28+
ext::ShellRunnableArgs,
2829
semantic_tokens::{self, standard_fallback_type},
2930
utils::invalid_params_error,
3031
LspError,
@@ -1374,6 +1375,27 @@ pub(crate) fn runnable(
13741375
}),
13751376
}))
13761377
}
1378+
Some(TargetSpec::ProjectJson(spec)) => {
1379+
let label = runnable.label(Some(spec.label.clone()));
1380+
let location = location_link(snap, None, runnable.nav)?;
1381+
1382+
match spec.runnable_args(&runnable.kind) {
1383+
Some(json_shell_runnable_args) => {
1384+
let runnable_args = ShellRunnableArgs {
1385+
program: json_shell_runnable_args.program,
1386+
args: json_shell_runnable_args.args,
1387+
cwd: json_shell_runnable_args.cwd,
1388+
};
1389+
Ok(Some(lsp_ext::Runnable {
1390+
label,
1391+
location: Some(location),
1392+
kind: lsp_ext::RunnableKind::Shell,
1393+
args: lsp_ext::RunnableArgs::Shell(runnable_args),
1394+
}))
1395+
}
1396+
None => Ok(None),
1397+
}
1398+
}
13771399
None => {
13781400
let (cargo_args, executable_args) =
13791401
CargoTargetSpec::runnable_args(snap, None, &runnable.kind, &runnable.cfg);
@@ -1421,6 +1443,7 @@ pub(crate) fn code_lens(
14211443
if let Some(r) = r {
14221444
let has_root = match &r.args {
14231445
lsp_ext::RunnableArgs::Cargo(c) => c.workspace_root.is_some(),
1446+
lsp_ext::RunnableArgs::Shell(_) => true,
14241447
};
14251448

14261449
let lens_config = snap.config.lens();

crates/rust-analyzer/src/target_spec.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use std::mem;
44

55
use cfg::{CfgAtom, CfgExpr};
66
use ide::{Cancellable, CrateId, FileId, RunnableKind, TestId};
7+
use project_model::project_json::ShellRunnableArgs;
8+
use project_model::project_json::ShellRunnableKind;
79
use project_model::{CargoFeatures, ManifestPath, TargetKind};
810
use rustc_hash::FxHashSet;
911
use vfs::AbsPathBuf;
@@ -17,6 +19,7 @@ use crate::global_state::GlobalStateSnapshot;
1719
#[derive(Clone)]
1820
pub(crate) enum TargetSpec {
1921
Cargo(CargoTargetSpec),
22+
ProjectJson(ProjectJsonTargetSpec),
2023
}
2124

2225
impl TargetSpec {
@@ -35,6 +38,7 @@ impl TargetSpec {
3538
pub(crate) fn target_kind(&self) -> TargetKind {
3639
match self {
3740
TargetSpec::Cargo(cargo) => cargo.target_kind,
41+
TargetSpec::ProjectJson(project_json) => project_json.target_kind,
3842
}
3943
}
4044
}
@@ -55,6 +59,33 @@ pub(crate) struct CargoTargetSpec {
5559
pub(crate) features: FxHashSet<String>,
5660
}
5761

62+
#[derive(Clone)]
63+
pub(crate) struct ProjectJsonTargetSpec {
64+
pub(crate) label: String,
65+
pub(crate) target_kind: TargetKind,
66+
pub(crate) shell_runnables: Vec<ShellRunnableArgs>,
67+
}
68+
69+
impl ProjectJsonTargetSpec {
70+
pub(crate) fn runnable_args(&self, kind: &RunnableKind) -> Option<ShellRunnableArgs> {
71+
match kind {
72+
RunnableKind::Bin => {
73+
for runnable in &self.shell_runnables {
74+
if matches!(runnable.kind, ShellRunnableKind::Run) {
75+
return Some(runnable.clone());
76+
}
77+
}
78+
79+
None
80+
}
81+
RunnableKind::Test { .. } => None,
82+
RunnableKind::TestMod { .. } => None,
83+
RunnableKind::Bench { .. } => None,
84+
RunnableKind::DocTest { .. } => None,
85+
}
86+
}
87+
}
88+
5889
impl CargoTargetSpec {
5990
pub(crate) fn runnable_args(
6091
snap: &GlobalStateSnapshot,

docs/dev/lsp-extensions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!---
2-
lsp/ext.rs hash: 219a5cf0b54d9153
2+
lsp/ext.rs hash: 1927e6ba10f4bad6
33
44
If you need to change the above hash to make the test pass, please check if you
55
need to adjust this doc as well and ping this issue:

0 commit comments

Comments
 (0)