]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/cargo_target_spec.rs
Merge #5732
[rust.git] / crates / rust-analyzer / src / cargo_target_spec.rs
1 //! See `CargoTargetSpec`
2
3 use cfg::CfgExpr;
4 use project_model::{self, TargetKind};
5 use ra_ide::{FileId, RunnableKind, TestId};
6 use vfs::AbsPathBuf;
7
8 use crate::{global_state::GlobalStateSnapshot, Result};
9
10 /// Abstract representation of Cargo target.
11 ///
12 /// We use it to cook up the set of cli args we need to pass to Cargo to
13 /// build/test/run the target.
14 #[derive(Clone)]
15 pub(crate) struct CargoTargetSpec {
16     pub(crate) workspace_root: AbsPathBuf,
17     pub(crate) package: String,
18     pub(crate) target: String,
19     pub(crate) target_kind: TargetKind,
20 }
21
22 impl CargoTargetSpec {
23     pub(crate) fn runnable_args(
24         snap: &GlobalStateSnapshot,
25         spec: Option<CargoTargetSpec>,
26         kind: &RunnableKind,
27         cfgs: &[CfgExpr],
28     ) -> Result<(Vec<String>, Vec<String>)> {
29         let mut args = Vec::new();
30         let mut extra_args = Vec::new();
31         match kind {
32             RunnableKind::Test { test_id, attr } => {
33                 args.push("test".to_string());
34                 if let Some(spec) = spec {
35                     spec.push_to(&mut args, kind);
36                 }
37                 extra_args.push(test_id.to_string());
38                 if let TestId::Path(_) = test_id {
39                     extra_args.push("--exact".to_string());
40                 }
41                 extra_args.push("--nocapture".to_string());
42                 if attr.ignore {
43                     extra_args.push("--ignored".to_string());
44                 }
45             }
46             RunnableKind::TestMod { path } => {
47                 args.push("test".to_string());
48                 if let Some(spec) = spec {
49                     spec.push_to(&mut args, kind);
50                 }
51                 extra_args.push(path.to_string());
52                 extra_args.push("--nocapture".to_string());
53             }
54             RunnableKind::Bench { test_id } => {
55                 args.push("bench".to_string());
56                 if let Some(spec) = spec {
57                     spec.push_to(&mut args, kind);
58                 }
59                 extra_args.push(test_id.to_string());
60                 if let TestId::Path(_) = test_id {
61                     extra_args.push("--exact".to_string());
62                 }
63                 extra_args.push("--nocapture".to_string());
64             }
65             RunnableKind::DocTest { test_id } => {
66                 args.push("test".to_string());
67                 args.push("--doc".to_string());
68                 if let Some(spec) = spec {
69                     spec.push_to(&mut args, kind);
70                 }
71                 extra_args.push(test_id.to_string());
72                 extra_args.push("--nocapture".to_string());
73             }
74             RunnableKind::Bin => {
75                 args.push("run".to_string());
76                 if let Some(spec) = spec {
77                     spec.push_to(&mut args, kind);
78                 }
79             }
80         }
81
82         if snap.config.cargo.all_features {
83             args.push("--all-features".to_string());
84         } else {
85             let mut features = Vec::new();
86             for cfg in cfgs {
87                 required_features(cfg, &mut features);
88             }
89             for feature in &snap.config.cargo.features {
90                 features.push(feature.clone());
91             }
92             features.dedup();
93             for feature in features {
94                 args.push("--features".to_string());
95                 args.push(feature);
96             }
97         }
98
99         Ok((args, extra_args))
100     }
101
102     pub(crate) fn for_file(
103         global_state_snapshot: &GlobalStateSnapshot,
104         file_id: FileId,
105     ) -> Result<Option<CargoTargetSpec>> {
106         let crate_id = match global_state_snapshot.analysis.crate_for(file_id)?.first() {
107             Some(crate_id) => *crate_id,
108             None => return Ok(None),
109         };
110         let (cargo_ws, target) = match global_state_snapshot.cargo_target_for_crate_root(crate_id) {
111             Some(it) => it,
112             None => return Ok(None),
113         };
114         let res = CargoTargetSpec {
115             workspace_root: cargo_ws.workspace_root().to_path_buf(),
116             package: cargo_ws.package_flag(&cargo_ws[cargo_ws[target].package]),
117             target: cargo_ws[target].name.clone(),
118             target_kind: cargo_ws[target].kind,
119         };
120         Ok(Some(res))
121     }
122
123     pub(crate) fn push_to(self, buf: &mut Vec<String>, kind: &RunnableKind) {
124         buf.push("--package".to_string());
125         buf.push(self.package);
126
127         // Can't mix --doc with other target flags
128         if let RunnableKind::DocTest { .. } = kind {
129             return;
130         }
131         match self.target_kind {
132             TargetKind::Bin => {
133                 buf.push("--bin".to_string());
134                 buf.push(self.target);
135             }
136             TargetKind::Test => {
137                 buf.push("--test".to_string());
138                 buf.push(self.target);
139             }
140             TargetKind::Bench => {
141                 buf.push("--bench".to_string());
142                 buf.push(self.target);
143             }
144             TargetKind::Example => {
145                 buf.push("--example".to_string());
146                 buf.push(self.target);
147             }
148             TargetKind::Lib => {
149                 buf.push("--lib".to_string());
150             }
151             TargetKind::Other => (),
152         }
153     }
154 }
155
156 /// Fill minimal features needed
157 fn required_features(cfg_expr: &CfgExpr, features: &mut Vec<String>) {
158     match cfg_expr {
159         CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.to_string()),
160         CfgExpr::All(preds) => {
161             preds.iter().for_each(|cfg| required_features(cfg, features));
162         }
163         CfgExpr::Any(preds) => {
164             for cfg in preds {
165                 let len_features = features.len();
166                 required_features(cfg, features);
167                 if len_features != features.len() {
168                     break;
169                 }
170             }
171         }
172         _ => {}
173     }
174 }
175
176 #[cfg(test)]
177 mod tests {
178     use super::*;
179
180     use cfg::CfgExpr;
181     use mbe::ast_to_token_tree;
182     use syntax::{
183         ast::{self, AstNode},
184         SmolStr,
185     };
186
187     fn check(cfg: &str, expected_features: &[&str]) {
188         let cfg_expr = {
189             let source_file = ast::SourceFile::parse(cfg).ok().unwrap();
190             let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
191             let (tt, _) = ast_to_token_tree(&tt).unwrap();
192             CfgExpr::parse(&tt)
193         };
194
195         let mut features = vec![];
196         required_features(&cfg_expr, &mut features);
197
198         let expected_features =
199             expected_features.iter().map(|&it| SmolStr::new(it)).collect::<Vec<_>>();
200
201         assert_eq!(features, expected_features);
202     }
203
204     #[test]
205     fn test_cfg_expr_minimal_features_needed() {
206         check(r#"#![cfg(feature = "baz")]"#, &["baz"]);
207         check(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#, &["baz", "foo"]);
208         check(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#, &["baz"]);
209         check(r#"#![cfg(foo)]"#, &[]);
210     }
211 }