Skip to content

Commit

Permalink
Allow rust-project.json to include ShellRunnable configuration
Browse files Browse the repository at this point in the history
Co-authored-by: David Barsky <me@davidbarsky.com>
  • Loading branch information
Wilfred and davidbarsky committed Mar 15, 2024
1 parent b1ba4a4 commit a20f6eb
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 8 deletions.
2 changes: 1 addition & 1 deletion crates/project-model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ mod build_scripts;
mod cargo_workspace;
mod cfg_flag;
mod manifest_path;
mod project_json;
pub mod project_json;
mod rustc_cfg;
mod sysroot;
pub mod target_data_layout;
Expand Down
79 changes: 78 additions & 1 deletion crates/project-model/src/project_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ use rustc_hash::FxHashMap;
use serde::{de, Deserialize};
use std::path::PathBuf;

use crate::cfg_flag::CfgFlag;
use crate::{cfg_flag::CfgFlag, TargetKind};

/// Roots and crates that compose this Rust project.
#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -87,6 +87,21 @@ pub struct Crate {
pub(crate) exclude: Vec<AbsPathBuf>,
pub(crate) is_proc_macro: bool,
pub(crate) repository: Option<String>,
pub build_info: Option<BuildInfo>,
}

/// Additional metadata about a crate, used to configure runnables.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BuildInfo {
/// The name associated with this crate, according to the custom
/// build system being used.
pub label: String,
/// What kind of target is this crate? For example, we don't want
/// to offer a 'run' button for library crates.
pub target_kind: TargetKind,
/// Configuration for shell commands, such as CLI invocations for
/// a check build or a test run.
pub shell_runnables: Vec<ShellRunnableArgs>,
}

impl ProjectJson {
Expand Down Expand Up @@ -121,6 +136,15 @@ impl ProjectJson {
None => (vec![root_module.parent().unwrap().to_path_buf()], Vec::new()),
};

let build_info = match crate_data.build_info {
Some(build_info) => Some(BuildInfo {
label: build_info.label,
target_kind: build_info.target_kind.into(),
shell_runnables: build_info.shell_runnables,
}),
None => None,
};

Crate {
display_name: crate_data
.display_name
Expand Down Expand Up @@ -149,6 +173,7 @@ impl ProjectJson {
exclude,
is_proc_macro: crate_data.is_proc_macro,
repository: crate_data.repository,
build_info,
}
})
.collect(),
Expand All @@ -172,6 +197,14 @@ impl ProjectJson {
pub fn path(&self) -> &AbsPath {
&self.project_root
}

pub fn crate_by_root(&self, root: &AbsPath) -> Option<Crate> {
self.crates
.iter()
.filter(|krate| krate.is_workspace_member)
.find(|krate| &krate.root_module == root)
.cloned()
}
}

#[derive(Deserialize, Debug, Clone)]
Expand Down Expand Up @@ -201,6 +234,8 @@ struct CrateData {
is_proc_macro: bool,
#[serde(default)]
repository: Option<String>,
#[serde(default)]
build_info: Option<BuildInfoData>,
}

#[derive(Deserialize, Debug, Clone)]
Expand All @@ -216,6 +251,48 @@ enum EditionData {
Edition2024,
}

#[derive(Deserialize, Debug, Clone)]
pub struct BuildInfoData {
label: String,
target_kind: TargetKindData,
shell_runnables: Vec<ShellRunnableArgs>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ShellRunnableArgs {
pub program: String,
pub args: Vec<String>,
pub cwd: PathBuf,
pub kind: ShellRunnableKind,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ShellRunnableKind {
Check,
Run,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TargetKindData {
Bin,
/// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
Lib,
Test,
}

impl From<TargetKindData> for TargetKind {
fn from(value: TargetKindData) -> Self {
match value {
TargetKindData::Bin => TargetKind::Bin,
TargetKindData::Lib => TargetKind::Lib { is_proc_macro: false },
TargetKindData::Test => TargetKind::Test,
}
}
}

impl From<EditionData> for Edition {
fn from(data: EditionData) -> Self {
match data {
Expand Down
17 changes: 15 additions & 2 deletions crates/rust-analyzer/src/global_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use crate::{
mem_docs::MemDocs,
op_queue::OpQueue,
reload,
target_spec::{CargoTargetSpec, TargetSpec},
target_spec::{CargoTargetSpec, ProjectJsonTargetSpec, TargetSpec},
task_pool::{TaskPool, TaskQueue},
};

Expand Down Expand Up @@ -527,7 +527,20 @@ impl GlobalStateSnapshot {
features: package_data.features.keys().cloned().collect(),
}));
}
ProjectWorkspace::Json { .. } => {}
ProjectWorkspace::Json { project, .. } => {
let Some(krate) = project.crate_by_root(path) else {
continue;
};
let Some(build_info) = krate.build_info else {
continue;
};

return Some(TargetSpec::ProjectJson(ProjectJsonTargetSpec {
target_kind: build_info.target_kind,
label: build_info.label,
shell_runnables: build_info.shell_runnables,
}));
}
ProjectWorkspace::DetachedFiles { .. } => {}
}
}
Expand Down
8 changes: 4 additions & 4 deletions crates/rust-analyzer/src/handlers/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@ pub(crate) fn handle_parent_module(
};
let cargo_spec = match TargetSpec::for_file(&snap, file_id)? {
Some(TargetSpec::Cargo(it)) => it,
None => return Ok(None),
Some(TargetSpec::ProjectJson(_)) | None => return Ok(None),
};

if snap.analysis.crate_root(crate_id)? == file_id {
Expand Down Expand Up @@ -826,7 +826,6 @@ pub(crate) fn handle_runnables(
}
if let Some(mut runnable) = to_proto::runnable(&snap, runnable)? {
if expect_test {
#[allow(irrefutable_let_patterns)]
if let lsp_ext::RunnableArgs::Cargo(r) = &mut runnable.args {
runnable.label = format!("{} + expect", runnable.label);
r.expect_test = Some(true);
Expand Down Expand Up @@ -866,6 +865,7 @@ pub(crate) fn handle_runnables(
})
}
}
Some(TargetSpec::ProjectJson(_)) => {}
None => {
if !snap.config.linked_or_discovered_projects().is_empty() {
res.push(lsp_ext::Runnable {
Expand Down Expand Up @@ -1770,7 +1770,7 @@ pub(crate) fn handle_open_cargo_toml(

let cargo_spec = match TargetSpec::for_file(&snap, file_id)? {
Some(TargetSpec::Cargo(it)) => it,
None => return Ok(None),
Some(TargetSpec::ProjectJson(_)) | None => return Ok(None),
};

let cargo_toml_url = to_proto::url_from_abs_path(&cargo_spec.cargo_toml);
Expand Down Expand Up @@ -2062,7 +2062,7 @@ fn run_rustfmt(
};
process::Command::new(cmd_path)
}
None => process::Command::new(cmd),
_ => process::Command::new(cmd),
};

cmd.envs(snap.config.extra_env());
Expand Down
10 changes: 10 additions & 0 deletions crates/rust-analyzer/src/lsp/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,12 +431,14 @@ pub struct Runnable {
#[serde(untagged)]
pub enum RunnableArgs {
Cargo(CargoRunnableArgs),
Shell(ShellRunnableArgs),
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum RunnableKind {
Cargo,
Shell,
}

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

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ShellRunnableArgs {
pub program: String,
pub args: Vec<String>,
pub cwd: PathBuf,
}

pub enum RelatedTests {}

impl Request for RelatedTests {
Expand Down
23 changes: 23 additions & 0 deletions crates/rust-analyzer/src/lsp/to_proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::{
global_state::GlobalStateSnapshot,
line_index::{LineEndings, LineIndex, PositionEncoding},
lsp::{
ext::ShellRunnableArgs,
semantic_tokens::{self, standard_fallback_type},
utils::invalid_params_error,
LspError,
Expand Down Expand Up @@ -1371,6 +1372,27 @@ pub(crate) fn runnable(
}),
}))
}
Some(TargetSpec::ProjectJson(spec)) => {
let label = runnable.label(Some(spec.label.clone()));
let location = location_link(snap, None, runnable.nav)?;

match spec.runnable_args(&runnable.kind) {
Some(json_shell_runnable_args) => {
let runnable_args = ShellRunnableArgs {
program: json_shell_runnable_args.program,
args: json_shell_runnable_args.args,
cwd: json_shell_runnable_args.cwd,
};
Ok(Some(lsp_ext::Runnable {
label,
location: Some(location),
kind: lsp_ext::RunnableKind::Shell,
args: lsp_ext::RunnableArgs::Shell(runnable_args),
}))
}
None => Ok(None),
}
}
None => {
let (cargo_args, executable_args) =
CargoTargetSpec::runnable_args(snap, None, &runnable.kind, &runnable.cfg);
Expand Down Expand Up @@ -1418,6 +1440,7 @@ pub(crate) fn code_lens(
if let Some(r) = r {
let has_root = match &r.args {
lsp_ext::RunnableArgs::Cargo(c) => c.workspace_root.is_some(),
lsp_ext::RunnableArgs::Shell(_) => true,
};

let lens_config = snap.config.lens();
Expand Down
31 changes: 31 additions & 0 deletions crates/rust-analyzer/src/target_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::mem;

use cfg::{CfgAtom, CfgExpr};
use ide::{Cancellable, CrateId, FileId, RunnableKind, TestId};
use project_model::project_json::ShellRunnableArgs;
use project_model::project_json::ShellRunnableKind;
use project_model::{CargoFeatures, ManifestPath, TargetKind};
use rustc_hash::FxHashSet;
use vfs::AbsPathBuf;
Expand All @@ -17,6 +19,7 @@ use crate::global_state::GlobalStateSnapshot;
#[derive(Clone)]
pub(crate) enum TargetSpec {
Cargo(CargoTargetSpec),
ProjectJson(ProjectJsonTargetSpec),
}

impl TargetSpec {
Expand All @@ -35,6 +38,7 @@ impl TargetSpec {
pub(crate) fn target_kind(&self) -> TargetKind {
match self {
TargetSpec::Cargo(cargo) => cargo.target_kind,
TargetSpec::ProjectJson(project_json) => project_json.target_kind,
}
}
}
Expand All @@ -55,6 +59,33 @@ pub(crate) struct CargoTargetSpec {
pub(crate) features: FxHashSet<String>,
}

#[derive(Clone)]
pub(crate) struct ProjectJsonTargetSpec {
pub(crate) label: String,
pub(crate) target_kind: TargetKind,
pub(crate) shell_runnables: Vec<ShellRunnableArgs>,
}

impl ProjectJsonTargetSpec {
pub(crate) fn runnable_args(&self, kind: &RunnableKind) -> Option<ShellRunnableArgs> {
match kind {
RunnableKind::Bin => {
for runnable in &self.shell_runnables {
if matches!(runnable.kind, ShellRunnableKind::Run) {
return Some(runnable.clone());
}
}

None
}
RunnableKind::Test { .. } => None,
RunnableKind::TestMod { .. } => None,
RunnableKind::Bench { .. } => None,
RunnableKind::DocTest { .. } => None,
}
}
}

impl CargoTargetSpec {
pub(crate) fn runnable_args(
snap: &GlobalStateSnapshot,
Expand Down

0 comments on commit a20f6eb

Please sign in to comment.