1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
mod flushing_writer; mod parsed_args; use anyhow::{Context, Result}; use cargo_metadata::{diagnostic::DiagnosticLevel, Message}; use flushing_writer::FlushingWriter; use parsed_args::ParsedArgs; use std::{ env, io::{self, BufRead, BufReader, Cursor}, iter, path::PathBuf, process::{Command, Stdio}, }; const MESSAGE_FORMAT: &str = "--message-format=json-diagnostic-rendered-ansi"; const CARGO_EXECUTABLE: &str = "cargo"; const CARGO_ENV_VAR: &str = "CARGO"; const NO_EXIT_CODE: i32 = 127; const BUILD_FINISHED_MESSAGE: &str = r#""build-finished""#; const ADDITIONAL_OPTIONS: &str = "\nADDITIONAL OPTIONS:\n --limit-messages <NUM> Limit number of compiler messages"; pub fn run_cargo_filtered(cargo_command: &str) -> Result<i32> { let ParsedArgs { cargo_args, limit_messages, help, } = ParsedArgs::parse(env::args().skip(2))?; let cargo_args = iter::once(cargo_command.to_owned()) .chain(iter::once(MESSAGE_FORMAT.to_owned())) .chain(cargo_args); let cargo = env::var(CARGO_ENV_VAR) .map(PathBuf::from) .ok() .unwrap_or_else(|| PathBuf::from(CARGO_EXECUTABLE)); let mut command = Command::new(cargo) .args(cargo_args) .stdout(Stdio::piped()) .spawn()?; let mut reader = BufReader::new(command.stdout.take().context("cannot read stdout")?); if !help { let raw_messages = read_raw_messages(&mut reader)?; parse_and_process_messages(raw_messages, limit_messages)?; } io::copy(&mut reader, &mut FlushingWriter::new(std::io::stdout()))?; if help { println!("{}", ADDITIONAL_OPTIONS); } let exit_code = command.wait()?.code().unwrap_or(NO_EXIT_CODE); Ok(exit_code) } fn parse_and_process_messages(raw_messages: Vec<u8>, limit_messages: usize) -> Result<()> { let mut internal_compiler_errors = Vec::new(); let mut errors = Vec::new(); let mut non_errors = Vec::new(); for message in cargo_metadata::Message::parse_stream(Cursor::new(raw_messages)) { if let Message::CompilerMessage(compiler_message) = message? { if let Some(rendered) = compiler_message.message.rendered { match compiler_message.message.level { DiagnosticLevel::Ice => { internal_compiler_errors.push(rendered); } DiagnosticLevel::Error => { errors.push(rendered); } _ => { non_errors.push(rendered); } } } } } for message in internal_compiler_errors .into_iter() .chain(errors.into_iter()) .chain(non_errors.into_iter()) .take(limit_messages) { print!("{}", message); } Ok(()) } fn read_raw_messages<R: io::Read>(reader: &mut BufReader<R>) -> Result<Vec<u8>> { let mut line = String::new(); let mut raw_messages = Vec::new(); loop { let len = reader.read_line(&mut line)?; raw_messages.extend(line.as_bytes()); if len == 0 || line.contains(BUILD_FINISHED_MESSAGE) { break; } line.clear(); } Ok(raw_messages) }