rust_analyzer/
discover.rs1use std::{io, path::Path};
4
5use crossbeam_channel::Sender;
6use ide_db::FxHashMap;
7use paths::{AbsPathBuf, Utf8Path, Utf8PathBuf};
8use project_model::ProjectJsonData;
9use serde::{Deserialize, Serialize};
10use tracing::{info_span, span::EnteredSpan};
11
12use crate::command::{CommandHandle, JsonLinesParser};
13
14pub(crate) const ARG_PLACEHOLDER: &str = "{arg}";
15
16pub(crate) struct DiscoverCommand {
20 command: Vec<String>,
21 sender: Sender<DiscoverProjectMessage>,
22}
23
24#[derive(PartialEq, Clone, Debug, Serialize)]
25#[serde(rename_all = "camelCase")]
26pub(crate) enum DiscoverArgument {
27 Path(#[serde(serialize_with = "serialize_abs_pathbuf")] AbsPathBuf),
28 Buildfile(#[serde(serialize_with = "serialize_abs_pathbuf")] AbsPathBuf),
29}
30
31fn serialize_abs_pathbuf<S>(path: &AbsPathBuf, se: S) -> Result<S::Ok, S::Error>
32where
33 S: serde::Serializer,
34{
35 let path: &Utf8Path = path.as_ref();
36 se.serialize_str(path.as_str())
37}
38
39impl DiscoverCommand {
40 pub(crate) fn new(sender: Sender<DiscoverProjectMessage>, command: Vec<String>) -> Self {
42 Self { sender, command }
43 }
44
45 pub(crate) fn spawn(
47 &self,
48 discover_arg: DiscoverArgument,
49 current_dir: &Path,
50 ) -> io::Result<DiscoverHandle> {
51 let command = &self.command[0];
52 let args = &self.command[1..];
53
54 let args: Vec<String> = args
55 .iter()
56 .map(|arg| {
57 if arg == ARG_PLACEHOLDER {
58 serde_json::to_string(&discover_arg).expect("Unable to serialize args")
59 } else {
60 arg.to_owned()
61 }
62 })
63 .collect();
64
65 let mut cmd = toolchain::command(command, current_dir, &FxHashMap::default());
67 cmd.args(args);
68
69 Ok(DiscoverHandle {
70 handle: CommandHandle::spawn(cmd, DiscoverProjectParser, self.sender.clone(), None)?,
71 span: info_span!("discover_command").entered(),
72 })
73 }
74}
75
76#[derive(Debug)]
78pub(crate) struct DiscoverHandle {
79 pub(crate) handle: CommandHandle<DiscoverProjectMessage>,
80 #[allow(dead_code)] span: EnteredSpan,
82}
83
84#[derive(Debug, Clone, Deserialize, Serialize)]
87#[serde(tag = "kind")]
88#[serde(rename_all = "snake_case")]
89enum DiscoverProjectData {
90 Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },
91 Error { error: String, source: Option<String> },
92 Progress { message: String },
93}
94
95#[derive(Debug, PartialEq, Clone)]
96pub(crate) enum DiscoverProjectMessage {
97 Finished { project: ProjectJsonData, buildfile: AbsPathBuf },
98 Error { error: String, source: Option<String> },
99 Progress { message: String },
100}
101
102impl DiscoverProjectMessage {
103 fn new(data: DiscoverProjectData) -> Self {
104 match data {
105 DiscoverProjectData::Finished { project, buildfile, .. } => {
106 let buildfile = buildfile.try_into().expect("Unable to make path absolute");
107 DiscoverProjectMessage::Finished { project, buildfile }
108 }
109 DiscoverProjectData::Error { error, source } => {
110 DiscoverProjectMessage::Error { error, source }
111 }
112 DiscoverProjectData::Progress { message } => {
113 DiscoverProjectMessage::Progress { message }
114 }
115 }
116 }
117}
118
119struct DiscoverProjectParser;
120
121impl JsonLinesParser<DiscoverProjectMessage> for DiscoverProjectParser {
122 fn from_line(&self, line: &str, _error: &mut String) -> Option<DiscoverProjectMessage> {
123 match serde_json::from_str::<DiscoverProjectData>(line) {
124 Ok(data) => {
125 let msg = DiscoverProjectMessage::new(data);
126 Some(msg)
127 }
128 Err(err) => {
129 let err =
130 DiscoverProjectData::Error { error: format!("{err:#?}\n{line}"), source: None };
131 Some(DiscoverProjectMessage::new(err))
132 }
133 }
134 }
135
136 fn from_eof(&self) -> Option<DiscoverProjectMessage> {
137 None
138 }
139}
140
141#[test]
142fn test_deserialization() {
143 let message = r#"
144 {"kind": "progress", "message":"querying build system","input":{"files":["src/main.rs"]}}
145 "#;
146 let message: DiscoverProjectData =
147 serde_json::from_str(message).expect("Unable to deserialize message");
148 assert!(matches!(message, DiscoverProjectData::Progress { .. }));
149
150 let message = r#"
151 {"kind": "error", "error":"failed to deserialize command output","source":"command"}
152 "#;
153
154 let message: DiscoverProjectData =
155 serde_json::from_str(message).expect("Unable to deserialize message");
156 assert!(matches!(message, DiscoverProjectData::Error { .. }));
157
158 let message = r#"
159 {"kind": "finished", "project": {"sysroot": "foo", "crates": [], "runnables": []}, "buildfile":"rust-analyzer/BUILD"}
160 "#;
161
162 let message: DiscoverProjectData =
163 serde_json::from_str(message).expect("Unable to deserialize message");
164 assert!(matches!(message, DiscoverProjectData::Finished { .. }));
165}