1use std::{
4 io::{self, BufRead, BufReader, Read, Write},
5 panic::AssertUnwindSafe,
6 process::{Child, ChildStdin, ChildStdout, Command, Stdio},
7 sync::{Arc, Mutex, OnceLock},
8};
9
10use paths::AbsPath;
11use semver::Version;
12use stdx::JodChild;
13
14use crate::{
15 ProcMacroKind, ServerError,
16 legacy_protocol::{self, SpanMode},
17 version,
18};
19
20#[derive(Debug)]
22pub(crate) struct ProcMacroServerProcess {
23 state: Mutex<ProcessSrvState>,
26 version: u32,
27 protocol: Protocol,
28 exited: OnceLock<AssertUnwindSafe<ServerError>>,
30}
31
32#[derive(Debug, Clone)]
33pub(crate) enum Protocol {
34 LegacyJson { mode: SpanMode },
35 LegacyPostcard { mode: SpanMode },
36}
37
38#[derive(Debug)]
40struct ProcessSrvState {
41 process: Process,
42 stdin: ChildStdin,
43 stdout: BufReader<ChildStdout>,
44}
45
46impl ProcMacroServerProcess {
47 pub(crate) fn run<'a>(
49 process_path: &AbsPath,
50 env: impl IntoIterator<
51 Item = (impl AsRef<std::ffi::OsStr>, &'a Option<impl 'a + AsRef<std::ffi::OsStr>>),
52 > + Clone,
53 version: Option<&Version>,
54 ) -> io::Result<ProcMacroServerProcess> {
55 const VERSION: Version = Version::new(1, 93, 0);
56 let has_working_format_flag = version.map_or(false, |v| {
58 if v.pre.as_str() == "nightly" { *v > VERSION } else { *v >= VERSION }
59 });
60
61 let formats: &[_] = if std::env::var_os("RUST_ANALYZER_USE_POSTCARD").is_some()
62 && has_working_format_flag
63 {
64 &[
65 (Some("postcard-legacy"), Protocol::LegacyPostcard { mode: SpanMode::Id }),
66 (Some("json-legacy"), Protocol::LegacyJson { mode: SpanMode::Id }),
67 ]
68 } else {
69 &[(None, Protocol::LegacyJson { mode: SpanMode::Id })]
70 };
71
72 let mut err = None;
73 for &(format, ref protocol) in formats {
74 let create_srv = || {
75 let mut process = Process::run(process_path, env.clone(), format)?;
76 let (stdin, stdout) = process.stdio().expect("couldn't access child stdio");
77
78 io::Result::Ok(ProcMacroServerProcess {
79 state: Mutex::new(ProcessSrvState { process, stdin, stdout }),
80 version: 0,
81 protocol: protocol.clone(),
82 exited: OnceLock::new(),
83 })
84 };
85 let mut srv = create_srv()?;
86 tracing::info!("sending proc-macro server version check");
87 match srv.version_check() {
88 Ok(v) if v > version::CURRENT_API_VERSION => {
89 #[allow(clippy::disallowed_methods)]
90 let process_version = Command::new(process_path)
91 .arg("--version")
92 .output()
93 .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_owned())
94 .unwrap_or_else(|_| "unknown version".to_owned());
95 err = Some(io::Error::other(format!(
96 "Your installed proc-macro server is too new for your rust-analyzer. API version: {}, server version: {process_version}. \
97 This will prevent proc-macro expansion from working. Please consider updating your rust-analyzer to ensure compatibility with your current toolchain.",
98 version::CURRENT_API_VERSION
99 )));
100 }
101 Ok(v) => {
102 tracing::info!("Proc-macro server version: {v}");
103 srv.version = v;
104 if srv.version >= version::RUST_ANALYZER_SPAN_SUPPORT
105 && let Ok(new_mode) = srv.enable_rust_analyzer_spans()
106 {
107 match &mut srv.protocol {
108 Protocol::LegacyJson { mode } | Protocol::LegacyPostcard { mode } => {
109 *mode = new_mode
110 }
111 }
112 }
113 tracing::info!("Proc-macro server protocol: {:?}", srv.protocol);
114 return Ok(srv);
115 }
116 Err(e) => {
117 tracing::info!(%e, "proc-macro version check failed");
118 err = Some(io::Error::other(format!(
119 "proc-macro server version check failed: {e}"
120 )))
121 }
122 }
123 }
124 Err(err.unwrap())
125 }
126
127 pub(crate) fn exited(&self) -> Option<&ServerError> {
129 self.exited.get().map(|it| &it.0)
130 }
131
132 pub(crate) fn use_postcard(&self) -> bool {
133 matches!(self.protocol, Protocol::LegacyPostcard { .. })
134 }
135
136 pub(crate) fn version(&self) -> u32 {
138 self.version
139 }
140
141 pub(crate) fn rust_analyzer_spans(&self) -> bool {
143 match self.protocol {
144 Protocol::LegacyJson { mode } => mode == SpanMode::RustAnalyzer,
145 Protocol::LegacyPostcard { mode } => mode == SpanMode::RustAnalyzer,
146 }
147 }
148
149 fn version_check(&self) -> Result<u32, ServerError> {
151 match self.protocol {
152 Protocol::LegacyJson { .. } => legacy_protocol::version_check(self),
153 Protocol::LegacyPostcard { .. } => legacy_protocol::version_check(self),
154 }
155 }
156
157 fn enable_rust_analyzer_spans(&self) -> Result<SpanMode, ServerError> {
159 match self.protocol {
160 Protocol::LegacyJson { .. } => legacy_protocol::enable_rust_analyzer_spans(self),
161 Protocol::LegacyPostcard { .. } => legacy_protocol::enable_rust_analyzer_spans(self),
162 }
163 }
164
165 pub(crate) fn find_proc_macros(
167 &self,
168 dylib_path: &AbsPath,
169 ) -> Result<Result<Vec<(String, ProcMacroKind)>, String>, ServerError> {
170 match self.protocol {
171 Protocol::LegacyJson { .. } => legacy_protocol::find_proc_macros(self, dylib_path),
172 Protocol::LegacyPostcard { .. } => legacy_protocol::find_proc_macros(self, dylib_path),
173 }
174 }
175
176 pub(crate) fn send_task<Request, Response, Buf>(
177 &self,
178 serialize_req: impl FnOnce(
179 &mut dyn Write,
180 &mut dyn BufRead,
181 Request,
182 &mut Buf,
183 ) -> Result<Option<Response>, ServerError>,
184 req: Request,
185 ) -> Result<Response, ServerError>
186 where
187 Buf: Default,
188 {
189 let state = &mut *self.state.lock().unwrap();
190 let mut buf = Buf::default();
191 serialize_req(&mut state.stdin, &mut state.stdout, req, &mut buf)
192 .and_then(|res| {
193 res.ok_or_else(|| {
194 let message = "proc-macro server did not respond with data".to_owned();
195 ServerError {
196 io: Some(Arc::new(io::Error::new(
197 io::ErrorKind::BrokenPipe,
198 message.clone(),
199 ))),
200 message,
201 }
202 })
203 })
204 .map_err(|e| {
205 if e.io.as_ref().map(|it| it.kind()) == Some(io::ErrorKind::BrokenPipe) {
206 match state.process.child.try_wait() {
207 Ok(None) | Err(_) => e,
208 Ok(Some(status)) => {
209 let mut msg = String::new();
210 if !status.success()
211 && let Some(stderr) = state.process.child.stderr.as_mut()
212 {
213 _ = stderr.read_to_string(&mut msg);
214 }
215 let server_error = ServerError {
216 message: format!(
217 "proc-macro server exited with {status}{}{msg}",
218 if msg.is_empty() { "" } else { ": " }
219 ),
220 io: None,
221 };
222 self.exited.get_or_init(|| AssertUnwindSafe(server_error)).0.clone()
225 }
226 }
227 } else {
228 e
229 }
230 })
231 }
232}
233
234#[derive(Debug)]
236struct Process {
237 child: JodChild,
238}
239
240impl Process {
241 fn run<'a>(
243 path: &AbsPath,
244 env: impl IntoIterator<
245 Item = (impl AsRef<std::ffi::OsStr>, &'a Option<impl 'a + AsRef<std::ffi::OsStr>>),
246 >,
247 format: Option<&str>,
248 ) -> io::Result<Process> {
249 let child = JodChild(mk_child(path, env, format)?);
250 Ok(Process { child })
251 }
252
253 fn stdio(&mut self) -> Option<(ChildStdin, BufReader<ChildStdout>)> {
255 let stdin = self.child.stdin.take()?;
256 let stdout = self.child.stdout.take()?;
257 let read = BufReader::new(stdout);
258
259 Some((stdin, read))
260 }
261}
262
263fn mk_child<'a>(
265 path: &AbsPath,
266 extra_env: impl IntoIterator<
267 Item = (impl AsRef<std::ffi::OsStr>, &'a Option<impl 'a + AsRef<std::ffi::OsStr>>),
268 >,
269 format: Option<&str>,
270) -> io::Result<Child> {
271 #[allow(clippy::disallowed_methods)]
272 let mut cmd = Command::new(path);
273 for env in extra_env {
274 match env {
275 (key, Some(val)) => cmd.env(key, val),
276 (key, None) => cmd.env_remove(key),
277 };
278 }
279 if let Some(format) = format {
280 cmd.arg("--format");
281 cmd.arg(format);
282 }
283 cmd.env("RUST_ANALYZER_INTERNALS_DO_NOT_USE", "this is unstable")
284 .stdin(Stdio::piped())
285 .stdout(Stdio::piped())
286 .stderr(Stdio::inherit());
287 if cfg!(windows) {
288 let mut path_var = std::ffi::OsString::new();
289 path_var.push(path.parent().unwrap().parent().unwrap());
290 path_var.push("\\bin;");
291 path_var.push(std::env::var_os("PATH").unwrap_or_default());
292 cmd.env("PATH", path_var);
293 }
294 cmd.spawn()
295}