3333//!
3434//! * file on disk
3535//! * a field in the config (ie, you can send a JSON request with the contents
36- //! of rust-project.json to rust-analyzer, no need to write anything to disk)
36+ //! of ` rust-project.json` to rust-analyzer, no need to write anything to disk)
3737//!
3838//! Another possible thing we don't do today, but which would be totally valid,
3939//! is to add an extension point to VS Code extension to register custom
@@ -55,8 +55,7 @@ use rustc_hash::FxHashMap;
5555use serde:: { de, Deserialize , Serialize } ;
5656use span:: Edition ;
5757
58- use crate :: cfg:: CfgFlag ;
59- use crate :: ManifestPath ;
58+ use crate :: { cfg:: CfgFlag , ManifestPath , TargetKind } ;
6059
6160/// Roots and crates that compose this Rust project.
6261#[ derive( Clone , Debug , Eq , PartialEq ) ]
@@ -68,6 +67,10 @@ pub struct ProjectJson {
6867 project_root : AbsPathBuf ,
6968 manifest : Option < ManifestPath > ,
7069 crates : Vec < Crate > ,
70+ /// Configuration for CLI commands.
71+ ///
72+ /// Examples include a check build or a test run.
73+ runnables : Vec < Runnable > ,
7174}
7275
7376/// A crate points to the root module of a crate and lists the dependencies of the crate. This is
@@ -88,13 +91,94 @@ pub struct Crate {
8891 pub ( crate ) exclude : Vec < AbsPathBuf > ,
8992 pub ( crate ) is_proc_macro : bool ,
9093 pub ( crate ) repository : Option < String > ,
94+ pub build : Option < Build > ,
95+ }
96+
97+ /// Additional, build-specific data about a crate.
98+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
99+ pub struct Build {
100+ /// The name associated with this crate.
101+ ///
102+ /// This is determined by the build system that produced
103+ /// the `rust-project.json` in question. For instance, if buck were used,
104+ /// the label might be something like `//ide/rust/rust-analyzer:rust-analyzer`.
105+ ///
106+ /// Do not attempt to parse the contents of this string; it is a build system-specific
107+ /// identifier similar to [`Crate::display_name`].
108+ pub label : String ,
109+ /// Path corresponding to the build system-specific file defining the crate.
110+ ///
111+ /// It is roughly analogous to [`ManifestPath`], but it should *not* be used with
112+ /// [`crate::ProjectManifest::from_manifest_file`], as the build file may not be
113+ /// be in the `rust-project.json`.
114+ pub build_file : Utf8PathBuf ,
115+ /// The kind of target.
116+ ///
117+ /// Examples (non-exhaustively) include [`TargetKind::Bin`], [`TargetKind::Lib`],
118+ /// and [`TargetKind::Test`]. This information is used to determine what sort
119+ /// of runnable codelens to provide, if any.
120+ pub target_kind : TargetKind ,
121+ }
122+
123+ /// A template-like structure for describing runnables.
124+ ///
125+ /// These are used for running and debugging binaries and tests without encoding
126+ /// build system-specific knowledge into rust-analyzer.
127+ ///
128+ /// # Example
129+ ///
130+ /// Below is an example of a test runnable. `{label}` and `{test_id}`
131+ /// are explained in [`Runnable::args`]'s documentation.
132+ ///
133+ /// ```json
134+ /// {
135+ /// "program": "buck",
136+ /// "args": [
137+ /// "test",
138+ /// "{label}",
139+ /// "--",
140+ /// "{test_id}",
141+ /// "--print-passing-details"
142+ /// ],
143+ /// "cwd": "/home/user/repo-root/",
144+ /// "kind": "testOne"
145+ /// }
146+ /// ```
147+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
148+ pub struct Runnable {
149+ /// The program invoked by the runnable.
150+ ///
151+ /// For example, this might be `cargo`, `buck`, or `bazel`.
152+ pub program : String ,
153+ /// The arguments passed to [`Runnable::program`].
154+ ///
155+ /// The args can contain two template strings: `{label}` and `{test_id}`.
156+ /// rust-analyzer will find and replace `{label}` with [`Build::label`] and
157+ /// `{test_id}` with the test name.
158+ pub args : Vec < String > ,
159+ /// The current working directory of the runnable.
160+ pub cwd : Utf8PathBuf ,
161+ pub kind : RunnableKind ,
162+ }
163+
164+ /// The kind of runnable.
165+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
166+ pub enum RunnableKind {
167+ Check ,
168+
169+ /// Can run a binary.
170+ Run ,
171+
172+ /// Run a single test.
173+ TestOne ,
91174}
92175
93176impl ProjectJson {
94177 /// Create a new ProjectJson instance.
95178 ///
96179 /// # Arguments
97180 ///
181+ /// * `manifest` - The path to the `rust-project.json`.
98182 /// * `base` - The path to the workspace root (i.e. the folder containing `rust-project.json`)
99183 /// * `data` - The parsed contents of `rust-project.json`, or project json that's passed via
100184 /// configuration.
@@ -109,6 +193,7 @@ impl ProjectJson {
109193 sysroot_src : data. sysroot_src . map ( absolutize_on_base) ,
110194 project_root : base. to_path_buf ( ) ,
111195 manifest,
196+ runnables : data. runnables . into_iter ( ) . map ( Runnable :: from) . collect ( ) ,
112197 crates : data
113198 . crates
114199 . into_iter ( )
@@ -127,6 +212,15 @@ impl ProjectJson {
127212 None => ( vec ! [ root_module. parent( ) . unwrap( ) . to_path_buf( ) ] , Vec :: new ( ) ) ,
128213 } ;
129214
215+ let build = match crate_data. build {
216+ Some ( build) => Some ( Build {
217+ label : build. label ,
218+ build_file : build. build_file ,
219+ target_kind : build. target_kind . into ( ) ,
220+ } ) ,
221+ None => None ,
222+ } ;
223+
130224 Crate {
131225 display_name : crate_data
132226 . display_name
@@ -146,6 +240,7 @@ impl ProjectJson {
146240 exclude,
147241 is_proc_macro : crate_data. is_proc_macro ,
148242 repository : crate_data. repository ,
243+ build,
149244 }
150245 } )
151246 . collect ( ) ,
@@ -167,7 +262,15 @@ impl ProjectJson {
167262 & self . project_root
168263 }
169264
170- /// Returns the path to the project's manifest file, if it exists.
265+ pub fn crate_by_root ( & self , root : & AbsPath ) -> Option < Crate > {
266+ self . crates
267+ . iter ( )
268+ . filter ( |krate| krate. is_workspace_member )
269+ . find ( |krate| krate. root_module == root)
270+ . cloned ( )
271+ }
272+
273+ /// Returns the path to the project's manifest, if it exists.
171274 pub fn manifest ( & self ) -> Option < & ManifestPath > {
172275 self . manifest . as_ref ( )
173276 }
@@ -176,13 +279,19 @@ impl ProjectJson {
176279 pub fn manifest_or_root ( & self ) -> & AbsPath {
177280 self . manifest . as_ref ( ) . map_or ( & self . project_root , |manifest| manifest. as_ref ( ) )
178281 }
282+
283+ pub fn runnables ( & self ) -> & [ Runnable ] {
284+ & self . runnables
285+ }
179286}
180287
181288#[ derive( Serialize , Deserialize , Debug , Clone ) ]
182289pub struct ProjectJsonData {
183290 sysroot : Option < Utf8PathBuf > ,
184291 sysroot_src : Option < Utf8PathBuf > ,
185292 crates : Vec < CrateData > ,
293+ #[ serde( default ) ]
294+ runnables : Vec < RunnableData > ,
186295}
187296
188297#[ derive( Serialize , Deserialize , Debug , Clone ) ]
@@ -205,6 +314,8 @@ struct CrateData {
205314 is_proc_macro : bool ,
206315 #[ serde( default ) ]
207316 repository : Option < String > ,
317+ #[ serde( default ) ]
318+ build : Option < BuildData > ,
208319}
209320
210321#[ derive( Serialize , Deserialize , Debug , Clone ) ]
@@ -220,6 +331,48 @@ enum EditionData {
220331 Edition2024 ,
221332}
222333
334+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
335+ pub struct BuildData {
336+ label : String ,
337+ build_file : Utf8PathBuf ,
338+ target_kind : TargetKindData ,
339+ }
340+
341+ #[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
342+ pub struct RunnableData {
343+ pub program : String ,
344+ pub args : Vec < String > ,
345+ pub cwd : Utf8PathBuf ,
346+ pub kind : RunnableKindData ,
347+ }
348+
349+ #[ derive( Debug , Clone , PartialEq , Eq , Deserialize , Serialize ) ]
350+ #[ serde( rename_all = "camelCase" ) ]
351+ pub enum RunnableKindData {
352+ Check ,
353+ Run ,
354+ TestOne ,
355+ }
356+
357+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , Deserialize , Serialize ) ]
358+ #[ serde( rename_all = "camelCase" ) ]
359+ pub enum TargetKindData {
360+ Bin ,
361+ /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
362+ Lib ,
363+ Test ,
364+ }
365+
366+ impl From < TargetKindData > for TargetKind {
367+ fn from ( data : TargetKindData ) -> Self {
368+ match data {
369+ TargetKindData :: Bin => TargetKind :: Bin ,
370+ TargetKindData :: Lib => TargetKind :: Lib { is_proc_macro : false } ,
371+ TargetKindData :: Test => TargetKind :: Test ,
372+ }
373+ }
374+ }
375+
223376impl From < EditionData > for Edition {
224377 fn from ( data : EditionData ) -> Self {
225378 match data {
@@ -231,6 +384,22 @@ impl From<EditionData> for Edition {
231384 }
232385}
233386
387+ impl From < RunnableData > for Runnable {
388+ fn from ( data : RunnableData ) -> Self {
389+ Runnable { program : data. program , args : data. args , cwd : data. cwd , kind : data. kind . into ( ) }
390+ }
391+ }
392+
393+ impl From < RunnableKindData > for RunnableKind {
394+ fn from ( data : RunnableKindData ) -> Self {
395+ match data {
396+ RunnableKindData :: Check => RunnableKind :: Check ,
397+ RunnableKindData :: Run => RunnableKind :: Run ,
398+ RunnableKindData :: TestOne => RunnableKind :: TestOne ,
399+ }
400+ }
401+ }
402+
234403/// Identifies a crate by position in the crates array.
235404///
236405/// This will differ from `CrateId` when multiple `ProjectJson`
0 commit comments