1use std::collections::HashSet;
5use std::env;
6use std::io::{BufReader, Read, Write};
7use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::sync::Arc;
10use std::thread::{self, JoinHandle};
11
12use anyhow::{Context, Error};
13use log::warn;
14use serde::{Deserialize, Serialize};
15
16use crate::util::errors::CargoResult;
17use crate::util::{Config, ProcessBuilder};
18
19const DIAGNOSICS_SERVER_VAR: &str = "__CARGO_FIX_DIAGNOSTICS_SERVER";
20const PLEASE_REPORT_THIS_BUG: &str =
21 "This likely indicates a bug in either rustc or cargo itself,\n\
22 and we would appreciate a bug report! You're likely to see \n\
23 a number of compiler warnings after this message which cargo\n\
24 attempted to fix but failed. If you could open an issue at\n\
25 https://github.com/rust-lang/rust/issues\n\
26 quoting the full output of this command we'd be very appreciative!\n\
27 Note that you may be able to make some more progress in the near-term\n\
28 fixing code with the `--broken-code` flag\n\n\
29 ";
30
31#[derive(Deserialize, Serialize)]
32pub enum Message {
33 Fixing {
34 file: String,
35 fixes: u32,
36 },
37 FixFailed {
38 files: Vec<String>,
39 krate: Option<String>,
40 errors: Vec<String>,
41 },
42 ReplaceFailed {
43 file: String,
44 message: String,
45 },
46 EditionAlreadyEnabled {
47 file: String,
48 edition: String,
49 },
50 IdiomEditionMismatch {
51 file: String,
52 idioms: String,
53 edition: Option<String>,
54 },
55}
56
57impl Message {
58 pub fn post(&self) -> Result<(), Error> {
59 let addr =
60 env::var(DIAGNOSICS_SERVER_VAR).context("diagnostics collector misconfigured")?;
61 let mut client =
62 TcpStream::connect(&addr).context("failed to connect to parent diagnostics target")?;
63
64 let s = serde_json::to_string(self).context("failed to serialize message")?;
65 client
66 .write_all(s.as_bytes())
67 .context("failed to write message to diagnostics target")?;
68 client
69 .shutdown(Shutdown::Write)
70 .context("failed to shutdown")?;
71
72 let mut tmp = Vec::new();
73 client
74 .read_to_end(&mut tmp)
75 .context("failed to receive a disconnect")?;
76
77 Ok(())
78 }
79}
80
81pub struct DiagnosticPrinter<'a> {
82 config: &'a Config,
83 edition_already_enabled: HashSet<String>,
84 idiom_mismatch: HashSet<String>,
85}
86
87impl<'a> DiagnosticPrinter<'a> {
88 pub fn new(config: &'a Config) -> DiagnosticPrinter<'a> {
89 DiagnosticPrinter {
90 config,
91 edition_already_enabled: HashSet::new(),
92 idiom_mismatch: HashSet::new(),
93 }
94 }
95
96 pub fn print(&mut self, msg: &Message) -> CargoResult<()> {
97 match msg {
98 Message::Fixing { file, fixes } => {
99 let msg = if *fixes == 1 { "fix" } else { "fixes" };
100 let msg = format!("{} ({} {})", file, fixes, msg);
101 self.config.shell().status("Fixing", msg)
102 }
103 Message::ReplaceFailed { file, message } => {
104 let msg = format!("error applying suggestions to `{}`\n", file);
105 self.config.shell().warn(&msg)?;
106 write!(
107 self.config.shell().err(),
108 "The full error message was:\n\n> {}\n\n",
109 message,
110 )?;
111 write!(self.config.shell().err(), "{}", PLEASE_REPORT_THIS_BUG)?;
112 Ok(())
113 }
114 Message::FixFailed {
115 files,
116 krate,
117 errors,
118 } => {
119 if let Some(ref krate) = *krate {
120 self.config.shell().warn(&format!(
121 "failed to automatically apply fixes suggested by rustc \
122 to crate `{}`",
123 krate,
124 ))?;
125 } else {
126 self.config
127 .shell()
128 .warn("failed to automatically apply fixes suggested by rustc")?;
129 }
130 if !files.is_empty() {
131 writeln!(
132 self.config.shell().err(),
133 "\nafter fixes were automatically applied the compiler \
134 reported errors within these files:\n"
135 )?;
136 for file in files {
137 writeln!(self.config.shell().err(), " * {}", file)?;
138 }
139 writeln!(self.config.shell().err())?;
140 }
141 write!(self.config.shell().err(), "{}", PLEASE_REPORT_THIS_BUG)?;
142 if !errors.is_empty() {
143 writeln!(
144 self.config.shell().err(),
145 "The following errors were reported:"
146 )?;
147 for error in errors {
148 write!(self.config.shell().err(), "{}", error)?;
149 if !error.ends_with('\n') {
150 writeln!(self.config.shell().err())?;
151 }
152 }
153 }
154 writeln!(
155 self.config.shell().err(),
156 "Original diagnostics will follow.\n"
157 )?;
158 Ok(())
159 }
160 Message::EditionAlreadyEnabled { file, edition } => {
161 if !self.edition_already_enabled.insert(file.clone()) {
163 return Ok(());
164 }
165
166 let msg = format!(
167 "\
168cannot prepare for the {} edition when it is enabled, so cargo cannot
169automatically fix errors in `{}`
170
171To prepare for the {0} edition you should first remove `edition = '{0}'` from
172your `Cargo.toml` and then rerun this command. Once all warnings have been fixed
173then you can re-enable the `edition` key in `Cargo.toml`. For some more
174information about transitioning to the {0} edition see:
175
176 https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html
177",
178 edition,
179 file,
180 );
181 self.config.shell().error(&msg)?;
182 Ok(())
183 }
184 Message::IdiomEditionMismatch {
185 file,
186 idioms,
187 edition,
188 } => {
189 if !self.idiom_mismatch.insert(file.clone()) {
191 return Ok(());
192 }
193 self.config.shell().error(&format!(
194 "\
195cannot migrate to the idioms of the {} edition for `{}`
196because it is compiled {}, which doesn't match {0}
197
198consider migrating to the {0} edition by adding `edition = '{0}'` to
199`Cargo.toml` and then rerunning this command; a more detailed transition
200guide can be found at
201
202 https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html
203",
204 idioms,
205 file,
206 match edition {
207 Some(s) => format!("with the {} edition", s),
208 None => "without an edition".to_string(),
209 },
210 ))?;
211 Ok(())
212 }
213 }
214 }
215}
216
217#[derive(Debug)]
218pub struct RustfixDiagnosticServer {
219 listener: TcpListener,
220 addr: SocketAddr,
221}
222
223pub struct StartedServer {
224 addr: SocketAddr,
225 done: Arc<AtomicBool>,
226 thread: Option<JoinHandle<()>>,
227}
228
229impl RustfixDiagnosticServer {
230 pub fn new() -> Result<Self, Error> {
231 let listener = TcpListener::bind("127.0.0.1:0")
232 .with_context(|| "failed to bind TCP listener to manage locking")?;
233 let addr = listener.local_addr()?;
234
235 Ok(RustfixDiagnosticServer { listener, addr })
236 }
237
238 pub fn configure(&self, process: &mut ProcessBuilder) {
239 process.env(DIAGNOSICS_SERVER_VAR, self.addr.to_string());
240 }
241
242 pub fn start<F>(self, on_message: F) -> Result<StartedServer, Error>
243 where
244 F: Fn(Message) + Send + 'static,
245 {
246 let addr = self.addr;
247 let done = Arc::new(AtomicBool::new(false));
248 let done2 = done.clone();
249 let thread = thread::spawn(move || {
250 self.run(&on_message, &done2);
251 });
252
253 Ok(StartedServer {
254 addr,
255 thread: Some(thread),
256 done,
257 })
258 }
259
260 fn run(self, on_message: &dyn Fn(Message), done: &AtomicBool) {
261 while let Ok((client, _)) = self.listener.accept() {
262 if done.load(Ordering::SeqCst) {
263 break;
264 }
265 let mut client = BufReader::new(client);
266 let mut s = String::new();
267 if let Err(e) = client.read_to_string(&mut s) {
268 warn!("diagnostic server failed to read: {}", e);
269 } else {
270 match serde_json::from_str(&s) {
271 Ok(message) => on_message(message),
272 Err(e) => warn!("invalid diagnostics message: {}", e),
273 }
274 }
275 drop(client);
279 }
280 }
281}
282
283impl Drop for StartedServer {
284 fn drop(&mut self) {
285 self.done.store(true, Ordering::SeqCst);
286 if TcpStream::connect(&self.addr).is_err() {
288 return;
289 }
290 drop(self.thread.take().unwrap().join());
291 }
292}