use std::{
borrow::Cow,
io::{self, BufRead, BufReader, Read, Write},
process::{Command, Stdio, exit},
};
use serde::Deserialize;
#[derive(Deserialize)]
struct CompilerMessageCode<'a> {
code: Cow<'a, str>,
}
#[derive(Deserialize)]
struct CompilerMessage<'a> {
code: CompilerMessageCode<'a>,
rendered: Cow<'a, str>,
level: Cow<'a, str>,
}
#[derive(Deserialize)]
#[serde(tag = "reason", rename_all = "kebab-case")]
enum RustBuildMessage<'a> {
BuildFinished,
CompilerMessage {
message: CompilerMessage<'a>,
},
}
struct Args {
hidden_warnings: Vec<String>,
hide_all: bool,
disable_this_thing_entirely: bool,
args: Vec<String>,
}
fn parse_args(name: &str) -> Args {
let mut iter = std::env::args().peekable();
iter.next();
let mut args = vec![];
if iter.peek() == Some(&format!("{name}w")) {
iter.next();
}
let mut hide_all = false;
let mut hidden_warnings = vec![];
let mut disable_this_thing_entirely = false;
while let Some(x) = iter.next() {
if x == "--silence-warnings" || x == "-Wall" {
hide_all = true;
hidden_warnings.clear();
} else if x == "-W" || x == "--silence-warning" {
let Some(x) = iter.next() else {
eprintln!("usage: {x} <warning>");
exit(1);
};
if hide_all {
continue;
}
hidden_warnings.push(x);
} else if let Some(x) = x.strip_prefix("-W") {
if x.is_empty() {
eprintln!("usage: {x} <warning>");
exit(1);
};
if hide_all {
continue;
}
hidden_warnings.push(x.to_string());
} else if x == "--" {
args.push(x);
break;
} else if x == "--message-format" || x.starts_with("--message-format=") {
disable_this_thing_entirely = true;
args.push(x);
break;
} else {
args.push(x);
}
}
for x in iter {
args.push(x);
}
Args {
args,
hide_all,
hidden_warnings,
disable_this_thing_entirely,
}
}
#[doc(hidden)]
pub fn run(name: &str) {
let args = parse_args(name);
if args.disable_this_thing_entirely {
let mut command = Command::new("cargo");
command.arg(name).args(args.args);
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
panic!("{:#}", command.exec());
}
#[allow(unreachable_code)]
if let Some(code) = command
.spawn()
.ok()
.and_then(|mut x| x.wait().ok())
.and_then(|x| x.code())
{
exit(code);
} else {
exit(-1);
}
}
let mut proc = Command::new("cargo")
.arg(name)
.arg("--message-format=json-diagnostic-rendered-ansi")
.args(args.args)
.stdout(Stdio::piped())
.spawn()
.unwrap();
let mut stdout = BufReader::new(proc.stdout.take().unwrap());
let mut line = String::new();
loop {
line.clear();
if stdout.read_line(&mut line).unwrap() == 0 {
break;
}
if line.starts_with('{') && line.trim_end().ends_with('}') {
if let Ok(x) = serde_json::from_str::<RustBuildMessage<'_>>(line.trim()) {
match x {
RustBuildMessage::BuildFinished => break,
RustBuildMessage::CompilerMessage { message } => 'a: {
'b: {
if message.level != "warning" {
break 'b;
}
if args.hide_all {
break 'a;
}
if args
.hidden_warnings
.iter()
.any(|x| x.as_str() == message.code.code.as_ref())
{
break 'a;
}
}
io::stdout().write_all(message.rendered.as_bytes()).ok();
}
}
}
} else {
io::stdout().write_all(line.as_bytes()).ok();
}
}
let mut buf = vec![0; 4096];
let mut cursor = 0;
let mut stdout = Some(stdout);
while stdout.is_some() || cursor > 0 {
if let Some(x) = &mut stdout
&& cursor != buf.len()
{
let Ok(v) = x.read(&mut buf[cursor..]) else {
stdout.take();
continue;
};
if v == 0 {
stdout.take();
continue;
}
cursor += v;
}
if cursor > 0 {
let Ok(v) = io::stdout().write(&buf[..cursor]) else {
break;
};
if v == 0 {
break;
}
cursor -= v;
}
}
match proc.wait().map(|x| x.code()) {
Ok(Some(x)) => exit(x),
_ => exit(-1),
}
}