Skip to content

Commit 719a84e

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

File tree

7 files changed

+163
-8
lines changed

7 files changed

+163
-8
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
@@ -54,8 +54,9 @@ use la_arena::RawIdx;
5454
use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
5555
use rustc_hash::FxHashMap;
5656
use serde::{de, Deserialize};
57+
use std::path::PathBuf;
5758

58-
use crate::cfg_flag::CfgFlag;
59+
use crate::{cfg_flag::CfgFlag, TargetKind};
5960

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

91107
impl ProjectJson {
@@ -120,6 +136,15 @@ impl ProjectJson {
120136
None => (vec![root_module.parent().unwrap().to_path_buf()], Vec::new()),
121137
};
122138

139+
let build_info = match crate_data.build_info {
140+
Some(build_info) => Some(BuildInfo {
141+
label: build_info.label,
142+
target_kind: build_info.target_kind.into(),
143+
shell_runnables: build_info.shell_runnables,
144+
}),
145+
None => None,
146+
};
147+
123148
Crate {
124149
display_name: crate_data
125150
.display_name
@@ -148,6 +173,7 @@ impl ProjectJson {
148173
exclude,
149174
is_proc_macro: crate_data.is_proc_macro,
150175
repository: crate_data.repository,
176+
build_info,
151177
}
152178
})
153179
.collect(),
@@ -171,6 +197,14 @@ impl ProjectJson {
171197
pub fn path(&self) -> &AbsPath {
172198
&self.project_root
173199
}
200+
201+
pub fn crate_by_root(&self, root: &AbsPath) -> Option<Crate> {
202+
self.crates
203+
.iter()
204+
.filter(|krate| krate.is_workspace_member)
205+
.find(|krate| &krate.root_module == root)
206+
.cloned()
207+
}
174208
}
175209

176210
#[derive(Deserialize, Debug, Clone)]
@@ -200,6 +234,8 @@ struct CrateData {
200234
is_proc_macro: bool,
201235
#[serde(default)]
202236
repository: Option<String>,
237+
#[serde(default)]
238+
build_info: Option<BuildInfoData>,
203239
}
204240

205241
#[derive(Deserialize, Debug, Clone)]
@@ -215,6 +251,48 @@ enum EditionData {
215251
Edition2024,
216252
}
217253

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

0 commit comments

Comments
 (0)