1use std::collections::HashSet;
5use std::io::{BufReader, Read, Write};
6use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
7use std::path::PathBuf;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::sync::Arc;
10use std::thread::{self, JoinHandle};
11
12use anyhow::{Context as _, Error};
13use cargo_util::ProcessBuilder;
14use serde::{Deserialize, Serialize};
15use tracing::warn;
16
17use crate::core::Edition;
18use crate::util::errors::CargoResult;
19use crate::util::network::LOCALHOST;
20use crate::util::GlobalContext;
21
22const DIAGNOSTICS_SERVER_VAR: &str = "__CARGO_FIX_DIAGNOSTICS_SERVER";
23
24#[derive(Deserialize, Serialize, Hash, Eq, PartialEq, Clone)]
25pub enum Message {
26 Migrating {
27 file: String,
28 from_edition: Edition,
29 to_edition: Edition,
30 },
31 Fixing {
32 file: String,
33 },
34 Fixed {
35 file: String,
36 fixes: u32,
37 },
38 FixFailed {
39 files: Vec<String>,
40 krate: Option<String>,
41 errors: Vec<String>,
42 abnormal_exit: Option<String>,
43 },
44 ReplaceFailed {
45 file: String,
46 message: String,
47 },
48 EditionAlreadyEnabled {
49 message: String,
50 edition: Edition,
51 },
52}
53
54impl Message {
55 pub fn post(&self, gctx: &GlobalContext) -> Result<(), Error> {
56 let addr = gctx
57 .get_env(DIAGNOSTICS_SERVER_VAR)
58 .context("diagnostics collector misconfigured")?;
59 let mut client =
60 TcpStream::connect(&addr).context("failed to connect to parent diagnostics target")?;
61
62 let s = serde_json::to_string(self).context("failed to serialize message")?;
63 client
64 .write_all(s.as_bytes())
65 .context("failed to write message to diagnostics target")?;
66 client
67 .shutdown(Shutdown::Write)
68 .context("failed to shutdown")?;
69
70 client
71 .read_to_end(&mut Vec::new())
72 .context("failed to receive a disconnect")?;
73
74 Ok(())
75 }
76}
77
78pub struct DiagnosticPrinter<'a> {
80 gctx: &'a GlobalContext,
82 workspace_wrapper: &'a Option<PathBuf>,
87 dedupe: HashSet<Message>,
89}
90
91impl<'a> DiagnosticPrinter<'a> {
92 pub fn new(
93 gctx: &'a GlobalContext,
94 workspace_wrapper: &'a Option<PathBuf>,
95 ) -> DiagnosticPrinter<'a> {
96 DiagnosticPrinter {
97 gctx,
98 workspace_wrapper,
99 dedupe: HashSet::new(),
100 }
101 }
102
103 pub fn print(&mut self, msg: &Message) -> CargoResult<()> {
104 match msg {
105 Message::Migrating {
106 file,
107 from_edition,
108 to_edition,
109 } => {
110 if !self.dedupe.insert(msg.clone()) {
111 return Ok(());
112 }
113 self.gctx.shell().status(
114 "Migrating",
115 &format!("{} from {} edition to {}", file, from_edition, to_edition),
116 )
117 }
118 Message::Fixing { file } => self
119 .gctx
120 .shell()
121 .verbose(|shell| shell.status("Fixing", file)),
122 Message::Fixed { file, fixes } => {
123 let msg = if *fixes == 1 { "fix" } else { "fixes" };
124 let msg = format!("{} ({} {})", file, fixes, msg);
125 self.gctx.shell().status("Fixed", msg)
126 }
127 Message::ReplaceFailed { file, message } => {
128 let msg = format!("error applying suggestions to `{}`\n", file);
129 self.gctx.shell().warn(&msg)?;
130 write!(
131 self.gctx.shell().err(),
132 "The full error message was:\n\n> {}\n\n",
133 message,
134 )?;
135 let issue_link = get_bug_report_url(self.workspace_wrapper);
136 write!(
137 self.gctx.shell().err(),
138 "{}",
139 gen_please_report_this_bug_text(issue_link)
140 )?;
141 Ok(())
142 }
143 Message::FixFailed {
144 files,
145 krate,
146 errors,
147 abnormal_exit,
148 } => {
149 if let Some(ref krate) = *krate {
150 self.gctx.shell().warn(&format!(
151 "failed to automatically apply fixes suggested by rustc \
152 to crate `{}`",
153 krate,
154 ))?;
155 } else {
156 self.gctx
157 .shell()
158 .warn("failed to automatically apply fixes suggested by rustc")?;
159 }
160 if !files.is_empty() {
161 writeln!(
162 self.gctx.shell().err(),
163 "\nafter fixes were automatically applied the compiler \
164 reported errors within these files:\n"
165 )?;
166 for file in files {
167 writeln!(self.gctx.shell().err(), " * {}", file)?;
168 }
169 writeln!(self.gctx.shell().err())?;
170 }
171 let issue_link = get_bug_report_url(self.workspace_wrapper);
172 write!(
173 self.gctx.shell().err(),
174 "{}",
175 gen_please_report_this_bug_text(issue_link)
176 )?;
177 if !errors.is_empty() {
178 writeln!(
179 self.gctx.shell().err(),
180 "The following errors were reported:"
181 )?;
182 for error in errors {
183 write!(self.gctx.shell().err(), "{}", error)?;
184 if !error.ends_with('\n') {
185 writeln!(self.gctx.shell().err())?;
186 }
187 }
188 }
189 if let Some(exit) = abnormal_exit {
190 writeln!(self.gctx.shell().err(), "rustc exited abnormally: {}", exit)?;
191 }
192 writeln!(
193 self.gctx.shell().err(),
194 "Original diagnostics will follow.\n"
195 )?;
196 Ok(())
197 }
198 Message::EditionAlreadyEnabled { message, edition } => {
199 if !self.dedupe.insert(msg.clone()) {
200 return Ok(());
201 }
202 if self.dedupe.insert(Message::EditionAlreadyEnabled {
204 message: "".to_string(), edition: *edition,
206 }) {
207 self.gctx.shell().warn(&format!("\
208{}
209
210If you are trying to migrate from the previous edition ({prev_edition}), the
211process requires following these steps:
212
2131. Start with `edition = \"{prev_edition}\"` in `Cargo.toml`
2142. Run `cargo fix --edition`
2153. Modify `Cargo.toml` to set `edition = \"{this_edition}\"`
2164. Run `cargo build` or `cargo test` to verify the fixes worked
217
218More details may be found at
219https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html
220",
221 message, this_edition=edition, prev_edition=edition.previous().unwrap()
222 ))
223 } else {
224 self.gctx.shell().warn(message)
225 }
226 }
227 }
228 }
229}
230
231fn gen_please_report_this_bug_text(url: &str) -> String {
232 format!(
233 "This likely indicates a bug in either rustc or cargo itself,\n\
234 and we would appreciate a bug report! You're likely to see\n\
235 a number of compiler warnings after this message which cargo\n\
236 attempted to fix but failed. If you could open an issue at\n\
237 {}\n\
238 quoting the full output of this command we'd be very appreciative!\n\
239 Note that you may be able to make some more progress in the near-term\n\
240 fixing code with the `--broken-code` flag\n\n\
241 ",
242 url
243 )
244}
245
246fn get_bug_report_url(rustc_workspace_wrapper: &Option<PathBuf>) -> &str {
247 let clippy = std::ffi::OsStr::new("clippy-driver");
248 let issue_link = match rustc_workspace_wrapper.as_ref().and_then(|x| x.file_stem()) {
249 Some(wrapper) if wrapper == clippy => "https://github.com/rust-lang/rust-clippy/issues",
250 _ => "https://github.com/rust-lang/rust/issues",
251 };
252
253 issue_link
254}
255
256#[derive(Debug)]
257pub struct RustfixDiagnosticServer {
258 listener: TcpListener,
259 addr: SocketAddr,
260}
261
262pub struct StartedServer {
263 addr: SocketAddr,
264 done: Arc<AtomicBool>,
265 thread: Option<JoinHandle<()>>,
266}
267
268impl RustfixDiagnosticServer {
269 pub fn new() -> Result<Self, Error> {
270 let listener = TcpListener::bind(&LOCALHOST[..])
271 .context("failed to bind TCP listener to manage locking")?;
272 let addr = listener.local_addr()?;
273
274 Ok(RustfixDiagnosticServer { listener, addr })
275 }
276
277 pub fn configure(&self, process: &mut ProcessBuilder) {
278 process.env(DIAGNOSTICS_SERVER_VAR, self.addr.to_string());
279 }
280
281 pub fn start<F>(self, on_message: F) -> Result<StartedServer, Error>
282 where
283 F: Fn(Message) + Send + 'static,
284 {
285 let addr = self.addr;
286 let done = Arc::new(AtomicBool::new(false));
287 let done2 = done.clone();
288 let thread = thread::spawn(move || {
289 self.run(&on_message, &done2);
290 });
291
292 Ok(StartedServer {
293 addr,
294 thread: Some(thread),
295 done,
296 })
297 }
298
299 fn run(self, on_message: &dyn Fn(Message), done: &AtomicBool) {
300 while let Ok((client, _)) = self.listener.accept() {
301 if done.load(Ordering::SeqCst) {
302 break;
303 }
304 let mut client = BufReader::new(client);
305 let mut s = String::new();
306 if let Err(e) = client.read_to_string(&mut s) {
307 warn!("diagnostic server failed to read: {}", e);
308 } else {
309 match serde_json::from_str(&s) {
310 Ok(message) => on_message(message),
311 Err(e) => warn!("invalid diagnostics message: {}", e),
312 }
313 }
314 drop(client);
318 }
319 }
320}
321
322impl Drop for StartedServer {
323 fn drop(&mut self) {
324 self.done.store(true, Ordering::SeqCst);
325 if TcpStream::connect(&self.addr).is_err() {
327 return;
328 }
329 drop(self.thread.take().unwrap().join());
330 }
331}