Skip to main content

cargo_buildw/
lib.rs

1//! # Hide warnings
2//!
3//! `cargo-buildw` is a `cargo` command to disable some/all warnings to make it easier
4//! to read build output.
5//!
6//! - `-W<warning>`/`-W <warning>`/`--silence-warning <warning>`: disable warnings `<warnings>`
7//! - `-Wall`/`--silence-warnings`: disable all warnings
8//!
9//! All the other parameters are passed to `cargo` as usual.
10//!
11//! (Also adding this crate to your project is completely useless. There is no API.)
12
13use std::{
14    borrow::Cow,
15    io::{self, BufRead, BufReader, Read, Write},
16    process::{Command, Stdio, exit},
17};
18
19use serde::Deserialize;
20
21#[derive(Deserialize)]
22struct CompilerMessageCode<'a> {
23    code: Cow<'a, str>,
24}
25
26#[derive(Deserialize)]
27struct CompilerMessage<'a> {
28    code: CompilerMessageCode<'a>,
29    rendered: Cow<'a, str>,
30    level: Cow<'a, str>,
31}
32
33#[derive(Deserialize)]
34#[serde(tag = "reason", rename_all = "kebab-case")]
35enum RustBuildMessage<'a> {
36    /// Stop analyzing rustc output.
37    BuildFinished,
38    CompilerMessage {
39        message: CompilerMessage<'a>,
40    },
41}
42
43struct Args {
44    hidden_warnings: Vec<String>,
45    hide_all: bool,
46    disable_this_thing_entirely: bool,
47    args: Vec<String>,
48}
49
50fn parse_args(name: &str) -> Args {
51    let mut iter = std::env::args().peekable();
52    iter.next();
53    let mut args = vec![];
54    if iter.peek() == Some(&format!("{name}w")) {
55        iter.next();
56    }
57    let mut hide_all = false;
58    let mut hidden_warnings = vec![];
59    let mut disable_this_thing_entirely = false;
60    while let Some(x) = iter.next() {
61        if x == "--silence-warnings" || x == "-Wall" {
62            hide_all = true;
63            hidden_warnings.clear();
64        } else if x == "-W" || x == "--silence-warning" {
65            let Some(x) = iter.next() else {
66                eprintln!("usage: {x} <warning>");
67                exit(1);
68            };
69            if hide_all {
70                continue;
71            }
72            hidden_warnings.push(x);
73        } else if let Some(x) = x.strip_prefix("-W") {
74            if x.is_empty() {
75                eprintln!("usage: {x} <warning>");
76                exit(1);
77            };
78            if hide_all {
79                continue;
80            }
81            hidden_warnings.push(x.to_string());
82        } else if x == "--" {
83            args.push(x);
84            break;
85        } else if x == "--message-format" || x.starts_with("--message-format=") {
86            disable_this_thing_entirely = true;
87            args.push(x);
88            break;
89        } else {
90            args.push(x);
91        }
92    }
93    for x in iter {
94        args.push(x);
95    }
96    Args {
97        args,
98        hide_all,
99        hidden_warnings,
100        disable_this_thing_entirely,
101    }
102}
103
104#[doc(hidden)]
105pub fn run(name: &str) {
106    let args = parse_args(name);
107
108    if args.disable_this_thing_entirely {
109        let mut command = Command::new("cargo");
110        command.arg(name).args(args.args);
111        #[cfg(unix)]
112        {
113            use std::os::unix::process::CommandExt;
114            panic!("{:#}", command.exec());
115        }
116        #[allow(unreachable_code)]
117        if let Some(code) = command
118            .spawn()
119            .ok()
120            .and_then(|mut x| x.wait().ok())
121            .and_then(|x| x.code())
122        {
123            exit(code);
124        } else {
125            exit(-1);
126        }
127    }
128
129    let mut proc = Command::new("cargo")
130        .arg(name)
131        .arg("--message-format=json-diagnostic-rendered-ansi")
132        .args(args.args)
133        .stdout(Stdio::piped())
134        .spawn()
135        .unwrap();
136
137    let mut stdout = BufReader::new(proc.stdout.take().unwrap());
138
139    let mut line = String::new();
140    loop {
141        line.clear();
142        if stdout.read_line(&mut line).unwrap() == 0 {
143            break;
144        }
145        if line.starts_with('{') && line.trim_end().ends_with('}') {
146            if let Ok(x) = serde_json::from_str::<RustBuildMessage<'_>>(line.trim()) {
147                match x {
148                    RustBuildMessage::BuildFinished => break,
149                    RustBuildMessage::CompilerMessage { message } => 'a: {
150                        'b: {
151                            if message.level != "warning" {
152                                break 'b;
153                            }
154                            if args.hide_all {
155                                break 'a;
156                            }
157                            if args
158                                .hidden_warnings
159                                .iter()
160                                .any(|x| x.as_str() == message.code.code.as_ref())
161                            {
162                                break 'a;
163                            }
164                        }
165                        io::stdout().write_all(message.rendered.as_bytes()).ok();
166                    }
167                }
168            }
169        } else {
170            io::stdout().write_all(line.as_bytes()).ok();
171        }
172    }
173
174    let mut buf = vec![0; 4096];
175    let mut cursor = 0;
176    let mut stdout = Some(stdout);
177    while stdout.is_some() || cursor > 0 {
178        if let Some(x) = &mut stdout
179            && cursor != buf.len()
180        {
181            let Ok(v) = x.read(&mut buf[cursor..]) else {
182                stdout.take();
183                continue;
184            };
185            if v == 0 {
186                stdout.take();
187                continue;
188            }
189            cursor += v;
190        }
191
192        if cursor > 0 {
193            let Ok(v) = io::stdout().write(&buf[..cursor]) else {
194                break;
195            };
196            if v == 0 {
197                break;
198            }
199            cursor -= v;
200        }
201    }
202
203    match proc.wait().map(|x| x.code()) {
204        Ok(Some(x)) => exit(x),
205        _ => exit(-1),
206    }
207}