Skip to content

Commit b9e951e

Browse files
committed
Add clang-format to the external tool check
1 parent 6c0b02d commit b9e951e

File tree

3 files changed

+196
-12
lines changed

3 files changed

+196
-12
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -5621,6 +5621,7 @@ version = "0.1.0"
56215621
dependencies = [
56225622
"build_helper",
56235623
"cargo_metadata 0.15.4",
5624+
"diff",
56245625
"fluent-syntax",
56255626
"ignore",
56265627
"miropt-test-tools",

src/tools/tidy/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ semver = "1.0"
1515
termcolor = "1.1.3"
1616
rustc-hash = "1.1.0"
1717
fluent-syntax = "0.11.1"
18+
diff = "0.1.13"
1819

1920
[[bin]]
2021
name = "rust-tidy"

src/tools/tidy/src/ext_tool_checks.rs

+194-12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
//! 3. Print the output of the given command. If it fails and `TIDY_PRINT_DIFF`
1818
//! is set, rerun the tool to print a suggestion diff (for e.g. CI)
1919
20+
use std::collections::VecDeque;
2021
use std::ffi::OsStr;
2122
use std::fmt;
2223
use std::fs;
@@ -39,6 +40,85 @@ const BLACK_CONFIG_PATH: &[&str] = &["src", "tools", "tidy", "config", "black.to
3940
const RUFF_CACH_PATH: &[&str] = &["cache", "ruff_cache"];
4041
const PIP_REQ_PATH: &[&str] = &["src", "tools", "tidy", "config", "requirements.txt"];
4142

43+
#[derive(Debug, PartialEq)]
44+
pub enum DiffLine {
45+
Context(String),
46+
Expected(String),
47+
Resulting(String),
48+
}
49+
50+
#[derive(Debug, PartialEq)]
51+
pub struct Mismatch {
52+
pub line_number: u32,
53+
pub lines: Vec<DiffLine>,
54+
}
55+
56+
impl Mismatch {
57+
fn new(line_number: u32) -> Mismatch {
58+
Mismatch { line_number, lines: Vec::new() }
59+
}
60+
}
61+
62+
// Produces a diff between the expected output and actual output.
63+
pub fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> {
64+
let mut line_number = 1;
65+
let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size);
66+
let mut lines_since_mismatch = context_size + 1;
67+
let mut results = Vec::new();
68+
let mut mismatch = Mismatch::new(0);
69+
70+
for result in diff::lines(expected, actual) {
71+
match result {
72+
diff::Result::Left(str) => {
73+
if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
74+
results.push(mismatch);
75+
mismatch = Mismatch::new(line_number - context_queue.len() as u32);
76+
}
77+
78+
while let Some(line) = context_queue.pop_front() {
79+
mismatch.lines.push(DiffLine::Context(line.to_owned()));
80+
}
81+
82+
mismatch.lines.push(DiffLine::Expected(str.to_owned()));
83+
line_number += 1;
84+
lines_since_mismatch = 0;
85+
}
86+
diff::Result::Right(str) => {
87+
if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
88+
results.push(mismatch);
89+
mismatch = Mismatch::new(line_number - context_queue.len() as u32);
90+
}
91+
92+
while let Some(line) = context_queue.pop_front() {
93+
mismatch.lines.push(DiffLine::Context(line.to_owned()));
94+
}
95+
96+
mismatch.lines.push(DiffLine::Resulting(str.to_owned()));
97+
lines_since_mismatch = 0;
98+
}
99+
diff::Result::Both(str, _) => {
100+
if context_queue.len() >= context_size {
101+
let _ = context_queue.pop_front();
102+
}
103+
104+
if lines_since_mismatch < context_size {
105+
mismatch.lines.push(DiffLine::Context(str.to_owned()));
106+
} else if context_size > 0 {
107+
context_queue.push_back(str);
108+
}
109+
110+
line_number += 1;
111+
lines_since_mismatch += 1;
112+
}
113+
}
114+
}
115+
116+
results.push(mismatch);
117+
results.remove(0);
118+
119+
results
120+
}
121+
42122
pub fn check(
43123
root_path: &Path,
44124
outdir: &Path,
@@ -73,6 +153,8 @@ fn check_impl(
73153
let python_fmt = lint_args.contains(&"py:fmt") || python_all;
74154
let shell_all = lint_args.contains(&"shell");
75155
let shell_lint = lint_args.contains(&"shell:lint") || shell_all;
156+
let clang_all = lint_args.contains(&"clang");
157+
let clang_fmt = lint_args.contains(&"clang:fmt") || clang_all;
76158

77159
let mut py_path = None;
78160

@@ -81,7 +163,7 @@ fn check_impl(
81163
.map(OsStr::new)
82164
.partition(|arg| arg.to_str().is_some_and(|s| s.starts_with('-')));
83165

84-
if python_lint || python_fmt {
166+
if python_lint || python_fmt || clang_fmt {
85167
let venv_path = outdir.join("venv");
86168
let mut reqs_path = root_path.to_owned();
87169
reqs_path.extend(PIP_REQ_PATH);
@@ -111,13 +193,13 @@ fn check_impl(
111193

112194
let mut args = merge_args(&cfg_args_ruff, &file_args_ruff);
113195
args.insert(0, "check".as_ref());
114-
let res = py_runner(py_path.as_ref().unwrap(), "ruff", &args);
196+
let res = py_runner(py_path.as_ref().unwrap(), true, None, "ruff", &args);
115197

116198
if res.is_err() && show_diff {
117199
eprintln!("\npython linting failed! Printing diff suggestions:");
118200

119201
args.insert(1, "--diff".as_ref());
120-
let _ = py_runner(py_path.as_ref().unwrap(), "ruff", &args);
202+
let _ = py_runner(py_path.as_ref().unwrap(), true, None, "ruff", &args);
121203
}
122204
// Rethrow error
123205
let _ = res?;
@@ -144,13 +226,78 @@ fn check_impl(
144226
}
145227

146228
let mut args = merge_args(&cfg_args_black, &file_args_black);
147-
let res = py_runner(py_path.as_ref().unwrap(), "black", &args);
229+
let res = py_runner(py_path.as_ref().unwrap(), true, None, "black", &args);
148230

149231
if res.is_err() && show_diff {
150232
eprintln!("\npython formatting does not match! Printing diff:");
151233

152234
args.insert(0, "--diff".as_ref());
153-
let _ = py_runner(py_path.as_ref().unwrap(), "black", &args);
235+
let _ = py_runner(py_path.as_ref().unwrap(), true, None, "black", &args);
236+
}
237+
// Rethrow error
238+
let _ = res?;
239+
}
240+
241+
if clang_fmt {
242+
let mut cfg_args_clang_format = cfg_args.clone();
243+
let mut file_args_clang_format = file_args.clone();
244+
let config_path = root_path.join(".clang-format");
245+
let config_file_arg = format!("file:{}", config_path.display());
246+
cfg_args_clang_format.extend(&["--style".as_ref(), config_file_arg.as_ref()]);
247+
if bless {
248+
eprintln!("formatting C++ files");
249+
cfg_args_clang_format.push("-i".as_ref());
250+
} else {
251+
eprintln!("checking C++ file formatting");
252+
cfg_args_clang_format.extend(&["--dry-run".as_ref(), "--Werror".as_ref()]);
253+
}
254+
let files;
255+
if file_args_clang_format.is_empty() {
256+
let llvm_wrapper = root_path.join("compiler/rustc_llvm/llvm-wrapper");
257+
files = find_with_extension(
258+
root_path,
259+
Some(llvm_wrapper.as_path()),
260+
&[OsStr::new("h"), OsStr::new("cpp")],
261+
)?;
262+
file_args_clang_format.extend(files.iter().map(|p| p.as_os_str()));
263+
}
264+
let args = merge_args(&cfg_args_clang_format, &file_args_clang_format);
265+
let res = py_runner(py_path.as_ref().unwrap(), false, None, "clang-format", &args);
266+
267+
if res.is_err() && show_diff {
268+
eprintln!("\nclang-format linting failed! Printing diff suggestions:");
269+
270+
let mut cfg_args_clang_format_diff = cfg_args.clone();
271+
cfg_args_clang_format_diff.extend(&["--style".as_ref(), config_file_arg.as_ref()]);
272+
for file in file_args_clang_format {
273+
let mut expected = String::new();
274+
let mut diff_args = cfg_args_clang_format_diff.clone();
275+
diff_args.push(file);
276+
let _ = py_runner(
277+
py_path.as_ref().unwrap(),
278+
false,
279+
Some(&mut expected),
280+
"clang-format",
281+
&diff_args,
282+
);
283+
if expected.is_empty() {
284+
eprintln!("failed to obtain the expected content");
285+
continue;
286+
}
287+
let actual = std::fs::read_to_string(file).expect("failed to read the C++ file");
288+
if expected != actual {
289+
for result in make_diff(&expected, &actual, 3) {
290+
eprintln!("Diff in {:?} at line {}:", file, result.line_number);
291+
for line in result.lines {
292+
match line {
293+
DiffLine::Expected(str) => eprintln!("-{}", str),
294+
DiffLine::Context(str) => eprintln!(" {}", str),
295+
DiffLine::Resulting(str) => eprintln!("+{}", str),
296+
}
297+
}
298+
}
299+
}
300+
}
154301
}
155302
// Rethrow error
156303
let _ = res?;
@@ -162,7 +309,7 @@ fn check_impl(
162309
let mut file_args_shc = file_args.clone();
163310
let files;
164311
if file_args_shc.is_empty() {
165-
files = find_with_extension(root_path, "sh")?;
312+
files = find_with_extension(root_path, None, &[OsStr::new("sh")])?;
166313
file_args_shc.extend(files.iter().map(|p| p.as_os_str()));
167314
}
168315

@@ -181,8 +328,29 @@ fn merge_args<'a>(cfg_args: &[&'a OsStr], file_args: &[&'a OsStr]) -> Vec<&'a Os
181328
}
182329

183330
/// Run a python command with given arguments. `py_path` should be a virtualenv.
184-
fn py_runner(py_path: &Path, bin: &'static str, args: &[&OsStr]) -> Result<(), Error> {
185-
let status = Command::new(py_path).arg("-m").arg(bin).args(args).status()?;
331+
fn py_runner(
332+
py_path: &Path,
333+
as_module: bool,
334+
stdout: Option<&mut String>,
335+
bin: &'static str,
336+
args: &[&OsStr],
337+
) -> Result<(), Error> {
338+
let mut cmd = Command::new(py_path);
339+
if as_module {
340+
cmd.arg("-m").arg(bin).args(args);
341+
} else {
342+
let bin_path = py_path.with_file_name(bin);
343+
cmd.arg(bin_path).args(args);
344+
}
345+
let status = if let Some(stdout) = stdout {
346+
let output = cmd.output()?;
347+
if let Ok(s) = std::str::from_utf8(&output.stdout) {
348+
stdout.push_str(s);
349+
}
350+
output.status
351+
} else {
352+
cmd.status()?
353+
};
186354
if status.success() { Ok(()) } else { Err(Error::FailedCheck(bin)) }
187355
}
188356

@@ -357,7 +525,11 @@ fn shellcheck_runner(args: &[&OsStr]) -> Result<(), Error> {
357525
}
358526

359527
/// Check git for tracked files matching an extension
360-
fn find_with_extension(root_path: &Path, extension: &str) -> Result<Vec<PathBuf>, Error> {
528+
fn find_with_extension(
529+
root_path: &Path,
530+
find_dir: Option<&Path>,
531+
extensions: &[&OsStr],
532+
) -> Result<Vec<PathBuf>, Error> {
361533
// Untracked files show up for short status and are indicated with a leading `?`
362534
// -C changes git to be as if run from that directory
363535
let stat_output =
@@ -368,15 +540,25 @@ fn find_with_extension(root_path: &Path, extension: &str) -> Result<Vec<PathBuf>
368540
}
369541

370542
let mut output = Vec::new();
371-
let binding = Command::new("git").arg("-C").arg(root_path).args(["ls-files"]).output()?;
543+
let binding = {
544+
let mut command = Command::new("git");
545+
command.arg("-C").arg(root_path).args(["ls-files"]);
546+
if let Some(find_dir) = find_dir {
547+
command.arg(find_dir);
548+
}
549+
command.output()?
550+
};
372551
let tracked = String::from_utf8_lossy(&binding.stdout);
373552

374553
for line in tracked.lines() {
375554
let line = line.trim();
376555
let path = Path::new(line);
377556

378-
if path.extension() == Some(OsStr::new(extension)) {
379-
output.push(path.to_owned());
557+
let Some(ref extension) = path.extension() else {
558+
continue;
559+
};
560+
if extensions.contains(extension) {
561+
output.push(root_path.join(path));
380562
}
381563
}
382564

0 commit comments

Comments
 (0)