Skip to content

Commit 0ee4faa

Browse files
KobzolMuscraft
authored andcommitted
Implement output of colored messages with optional check context
1 parent 030beab commit 0ee4faa

File tree

10 files changed

+152
-55
lines changed

10 files changed

+152
-55
lines changed

src/tools/features-status-dump/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::path::PathBuf;
55

66
use anyhow::{Context, Result};
77
use clap::Parser;
8+
use tidy::diagnostics::RunningCheck;
89
use tidy::features::{Feature, collect_lang_features, collect_lib_features};
910

1011
#[derive(Debug, Parser)]
@@ -29,7 +30,7 @@ struct FeaturesStatus {
2930
fn main() -> Result<()> {
3031
let Cli { compiler_path, library_path, output_path } = Cli::parse();
3132

32-
let lang_features_status = collect_lang_features(&compiler_path, &mut false);
33+
let lang_features_status = collect_lang_features(&compiler_path, &mut RunningCheck::new_noop());
3334
let lib_features_status = collect_lib_features(&library_path)
3435
.into_iter()
3536
.filter(|&(ref name, _)| !lang_features_status.contains_key(name))

src/tools/tidy/src/alphabetical/tests.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1-
use std::io::Write;
2-
use std::str::from_utf8;
1+
use std::path::Path;
32

4-
use super::*;
3+
use crate::alphabetical::check_lines;
4+
use crate::diagnostics::DiagCtx;
55

66
#[track_caller]
77
fn test(lines: &str, name: &str, expected_msg: &str, expected_bad: bool) {
8-
let mut actual_msg = Vec::new();
9-
let mut actual_bad = false;
10-
let mut err = |args: &_| {
11-
write!(&mut actual_msg, "{args}")?;
12-
Ok(())
13-
};
14-
check_lines(&name, lines.lines().enumerate(), &mut err, &mut actual_bad);
15-
assert_eq!(expected_msg, from_utf8(&actual_msg).unwrap());
16-
assert_eq!(expected_bad, actual_bad);
8+
let diag_ctx = DiagCtx::new(Path::new("/"), false);
9+
let mut check = diag_ctx.start_check("alphabetical-test");
10+
check_lines(&name, lines.lines().enumerate(), &mut check);
11+
12+
assert_eq!(expected_bad, check.is_bad());
13+
let errors = check.get_errors();
14+
if expected_bad {
15+
assert_eq!(errors.len(), 1);
16+
assert_eq!(expected_msg, errors[0]);
17+
} else {
18+
assert!(errors.is_empty());
19+
}
1720
}
1821

1922
#[track_caller]

src/tools/tidy/src/deps.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use build_helper::ci::CiEnv;
99
use cargo_metadata::semver::Version;
1010
use cargo_metadata::{Metadata, Package, PackageId};
1111

12-
use crate::diagnostics::{CheckId, DiagCtx, RunningCheck};
12+
use crate::diagnostics::{DiagCtx, RunningCheck};
1313

1414
#[path = "../../../bootstrap/src/utils/proc_macro_deps.rs"]
1515
mod proc_macro_deps;
@@ -664,7 +664,7 @@ const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[
664664
/// `root` is path to the directory with the root `Cargo.toml` (for the workspace). `cargo` is path
665665
/// to the cargo executable.
666666
pub fn check(root: &Path, cargo: &Path, bless: bool, diag_ctx: DiagCtx) {
667-
let mut check = diag_ctx.start_check(CheckId::new("deps").path(root));
667+
let mut check = diag_ctx.start_check("deps");
668668

669669
let mut checked_runtime_licenses = false;
670670

src/tools/tidy/src/diagnostics.rs

Lines changed: 105 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use std::collections::HashSet;
2-
use std::fmt::Display;
2+
use std::fmt::{Display, Formatter};
33
use std::path::{Path, PathBuf};
44
use std::sync::{Arc, Mutex};
55

6-
use termcolor::WriteColor;
6+
use termcolor::{Color, WriteColor};
77

88
/// Collects diagnostics from all tidy steps, and contains shared information
99
/// that determines how should message and logs be presented.
@@ -14,40 +14,56 @@ use termcolor::WriteColor;
1414
pub struct DiagCtx(Arc<Mutex<DiagCtxInner>>);
1515

1616
impl DiagCtx {
17-
pub fn new(verbose: bool) -> Self {
17+
pub fn new(root_path: &Path, verbose: bool) -> Self {
1818
Self(Arc::new(Mutex::new(DiagCtxInner {
1919
running_checks: Default::default(),
2020
finished_checks: Default::default(),
21+
root_path: root_path.to_path_buf(),
2122
verbose,
2223
})))
2324
}
2425

2526
pub fn start_check<Id: Into<CheckId>>(&self, id: Id) -> RunningCheck {
26-
let id = id.into();
27+
let mut id = id.into();
2728

2829
let mut ctx = self.0.lock().unwrap();
30+
31+
// Shorten path for shorter diagnostics
32+
id.path = match id.path {
33+
Some(path) => Some(path.strip_prefix(&ctx.root_path).unwrap_or(&path).to_path_buf()),
34+
None => None,
35+
};
36+
2937
ctx.start_check(id.clone());
30-
RunningCheck { id, bad: false, ctx: self.0.clone() }
38+
RunningCheck {
39+
id,
40+
bad: false,
41+
ctx: self.0.clone(),
42+
#[cfg(test)]
43+
errors: vec![],
44+
}
3145
}
3246

33-
pub fn into_conclusion(self) -> bool {
34-
let ctx = self.0.lock().unwrap();
47+
pub fn into_failed_checks(self) -> Vec<FinishedCheck> {
48+
let ctx = Arc::into_inner(self.0).unwrap().into_inner().unwrap();
3549
assert!(ctx.running_checks.is_empty(), "Some checks are still running");
36-
ctx.finished_checks.iter().any(|c| c.bad)
50+
ctx.finished_checks.into_iter().filter(|c| c.bad).collect()
3751
}
3852
}
3953

4054
struct DiagCtxInner {
4155
running_checks: HashSet<CheckId>,
4256
finished_checks: HashSet<FinishedCheck>,
4357
verbose: bool,
58+
root_path: PathBuf,
4459
}
4560

4661
impl DiagCtxInner {
4762
fn start_check(&mut self, id: CheckId) {
4863
if self.has_check_id(&id) {
4964
panic!("Starting a check named `{id:?}` for the second time");
5065
}
66+
5167
self.running_checks.insert(id);
5268
}
5369

@@ -57,6 +73,13 @@ impl DiagCtxInner {
5773
"Finishing check `{:?}` that was not started",
5874
check.id
5975
);
76+
77+
if check.bad {
78+
output_message("FAIL", Some(&check.id), Some(COLOR_ERROR));
79+
} else if self.verbose {
80+
output_message("OK", Some(&check.id), Some(COLOR_SUCCESS));
81+
}
82+
6083
self.finished_checks.insert(check);
6184
}
6285

@@ -71,8 +94,8 @@ impl DiagCtxInner {
7194
/// Identifies a single step
7295
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
7396
pub struct CheckId {
74-
name: String,
75-
path: Option<PathBuf>,
97+
pub name: String,
98+
pub path: Option<PathBuf>,
7699
}
77100

78101
impl CheckId {
@@ -91,40 +114,70 @@ impl From<&'static str> for CheckId {
91114
}
92115
}
93116

117+
impl Display for CheckId {
118+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
119+
write!(f, "{}", self.name)?;
120+
if let Some(path) = &self.path {
121+
write!(f, " ({})", path.display())?;
122+
}
123+
Ok(())
124+
}
125+
}
126+
94127
#[derive(PartialEq, Eq, Hash, Debug)]
95-
struct FinishedCheck {
128+
pub struct FinishedCheck {
96129
id: CheckId,
97130
bad: bool,
98131
}
99132

133+
impl FinishedCheck {
134+
pub fn id(&self) -> &CheckId {
135+
&self.id
136+
}
137+
}
138+
100139
/// Represents a single tidy check, identified by its `name`, running.
101140
pub struct RunningCheck {
102141
id: CheckId,
103142
bad: bool,
104143
ctx: Arc<Mutex<DiagCtxInner>>,
144+
#[cfg(test)]
145+
errors: Vec<String>,
105146
}
106147

107148
impl RunningCheck {
149+
/// Creates a new instance of a running check without going through the diag
150+
/// context.
151+
/// Useful if you want to run some functions from tidy without configuring
152+
/// diagnostics.
153+
pub fn new_noop() -> Self {
154+
let ctx = DiagCtx::new(Path::new(""), false);
155+
ctx.start_check("noop")
156+
}
157+
108158
/// Immediately output an error and mark the check as failed.
109-
pub fn error<T: Display>(&mut self, t: T) {
159+
pub fn error<T: Display>(&mut self, msg: T) {
110160
self.mark_as_bad();
111-
tidy_error(&t.to_string()).expect("failed to output error");
161+
let msg = msg.to_string();
162+
output_message(&msg, Some(&self.id), Some(COLOR_ERROR));
163+
#[cfg(test)]
164+
self.errors.push(msg);
112165
}
113166

114167
/// Immediately output a warning.
115-
pub fn warning<T: Display>(&mut self, t: T) {
116-
eprintln!("WARNING: {t}");
168+
pub fn warning<T: Display>(&mut self, msg: T) {
169+
output_message(&msg.to_string(), Some(&self.id), Some(COLOR_WARNING));
117170
}
118171

119172
/// Output an informational message
120-
pub fn message<T: Display>(&mut self, t: T) {
121-
eprintln!("{t}");
173+
pub fn message<T: Display>(&mut self, msg: T) {
174+
output_message(&msg.to_string(), Some(&self.id), None);
122175
}
123176

124177
/// Output a message only if verbose output is enabled.
125-
pub fn verbose_msg<T: Display>(&mut self, t: T) {
178+
pub fn verbose_msg<T: Display>(&mut self, msg: T) {
126179
if self.is_verbose_enabled() {
127-
self.message(t);
180+
self.message(msg);
128181
}
129182
}
130183

@@ -138,6 +191,11 @@ impl RunningCheck {
138191
self.ctx.lock().unwrap().verbose
139192
}
140193

194+
#[cfg(test)]
195+
pub fn get_errors(&self) -> Vec<String> {
196+
self.errors.clone()
197+
}
198+
141199
fn mark_as_bad(&mut self) {
142200
self.bad = true;
143201
}
@@ -149,17 +207,37 @@ impl Drop for RunningCheck {
149207
}
150208
}
151209

152-
fn tidy_error(args: &str) -> std::io::Result<()> {
210+
pub const COLOR_SUCCESS: Color = Color::Green;
211+
pub const COLOR_ERROR: Color = Color::Red;
212+
pub const COLOR_WARNING: Color = Color::Yellow;
213+
214+
/// Output a message to stderr.
215+
/// The message can be optionally scoped to a certain check, and it can also have a certain color.
216+
pub fn output_message(msg: &str, id: Option<&CheckId>, color: Option<Color>) {
153217
use std::io::Write;
154218

155-
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream};
219+
use termcolor::{ColorChoice, ColorSpec, StandardStream};
156220

157-
let mut stderr = StandardStream::stdout(ColorChoice::Auto);
158-
stderr.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
221+
let mut stderr = StandardStream::stderr(ColorChoice::Auto);
222+
if let Some(color) = &color {
223+
stderr.set_color(ColorSpec::new().set_fg(Some(*color))).unwrap();
224+
}
159225

160-
write!(&mut stderr, "tidy error")?;
161-
stderr.set_color(&ColorSpec::new())?;
226+
match id {
227+
Some(id) => {
228+
write!(&mut stderr, "tidy [{}", id.name).unwrap();
229+
if let Some(path) = &id.path {
230+
write!(&mut stderr, " ({})", path.display()).unwrap();
231+
}
232+
write!(&mut stderr, "]").unwrap();
233+
}
234+
None => {
235+
write!(&mut stderr, "tidy").unwrap();
236+
}
237+
}
238+
if color.is_some() {
239+
stderr.set_color(&ColorSpec::new()).unwrap();
240+
}
162241

163-
writeln!(&mut stderr, ": {args}")?;
164-
Ok(())
242+
writeln!(&mut stderr, ": {msg}").unwrap();
165243
}

src/tools/tidy/src/extdeps.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::fs;
44
use std::path::Path;
55

66
use crate::deps::WorkspaceInfo;
7-
use crate::diagnostics::{CheckId, DiagCtx};
7+
use crate::diagnostics::DiagCtx;
88

99
/// List of allowed sources for packages.
1010
const ALLOWED_SOURCES: &[&str] = &[
@@ -16,7 +16,7 @@ const ALLOWED_SOURCES: &[&str] = &[
1616
/// Checks for external package sources. `root` is the path to the directory that contains the
1717
/// workspace `Cargo.toml`.
1818
pub fn check(root: &Path, diag_ctx: DiagCtx) {
19-
let mut check = diag_ctx.start_check(CheckId::new("extdeps").path(root));
19+
let mut check = diag_ctx.start_check("extdeps");
2020

2121
for &WorkspaceInfo { path, submodules, .. } in crate::deps::WORKSPACES {
2222
if crate::deps::has_missing_submodule(root, submodules) {

src/tools/tidy/src/filenames.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
use std::path::Path;
1111
use std::process::Command;
1212

13-
use crate::diagnostics::{CheckId, DiagCtx};
13+
use crate::diagnostics::DiagCtx;
1414

1515
pub fn check(root_path: &Path, diag_ctx: DiagCtx) {
16-
let mut check = diag_ctx.start_check(CheckId::new("filenames").path(root_path));
16+
let mut check = diag_ctx.start_check("filenames");
1717
let stat_output = Command::new("git")
1818
.arg("-C")
1919
.arg(root_path)

src/tools/tidy/src/main.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::str::FromStr;
1111
use std::thread::{self, ScopedJoinHandle, scope};
1212
use std::{env, process};
1313

14-
use tidy::diagnostics::DiagCtx;
14+
use tidy::diagnostics::{COLOR_ERROR, COLOR_SUCCESS, DiagCtx, output_message};
1515
use tidy::*;
1616

1717
fn main() {
@@ -50,7 +50,7 @@ fn main() {
5050
let extra_checks =
5151
cfg_args.iter().find(|s| s.starts_with("--extra-checks=")).map(String::as_str);
5252

53-
let diag_ctx = DiagCtx::new(verbose);
53+
let diag_ctx = DiagCtx::new(&root_path, verbose);
5454
let ci_info = CiInfo::new(diag_ctx.clone());
5555

5656
let drain_handles = |handles: &mut VecDeque<ScopedJoinHandle<'_, ()>>| {
@@ -175,8 +175,22 @@ fn main() {
175175
);
176176
});
177177

178-
if diag_ctx.into_conclusion() {
179-
eprintln!("some tidy checks failed");
178+
let failed_checks = diag_ctx.into_failed_checks();
179+
if !failed_checks.is_empty() {
180+
let mut failed: Vec<String> =
181+
failed_checks.into_iter().map(|c| c.id().to_string()).collect();
182+
failed.sort();
183+
output_message(
184+
&format!(
185+
"The following check{} failed: {}",
186+
if failed.len() > 1 { "s" } else { "" },
187+
failed.join(", ")
188+
),
189+
None,
190+
Some(COLOR_ERROR),
191+
);
180192
process::exit(1);
193+
} else {
194+
output_message("All tidy checks succeeded", None, Some(COLOR_SUCCESS));
181195
}
182196
}

src/tools/tidy/src/tests_placement.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use std::path::Path;
22

3-
use crate::diagnostics::{CheckId, DiagCtx};
3+
use crate::diagnostics::DiagCtx;
44

55
const FORBIDDEN_PATH: &str = "src/test";
66
const ALLOWED_PATH: &str = "tests";
77

88
pub fn check(root_path: &Path, diag_ctx: DiagCtx) {
9-
let mut check = diag_ctx.start_check(CheckId::new("tests-placement").path(root_path));
9+
let mut check = diag_ctx.start_check("tests_placement");
1010

1111
if root_path.join(FORBIDDEN_PATH).exists() {
1212
check.error(format!(

0 commit comments

Comments
 (0)