Skip to main content

tidy/
lib.rs

1//! Library used by tidy and other tools.
2//!
3//! This library contains the tidy lints and exposes it
4//! to be used by tools.
5
6use std::ffi::OsStr;
7use std::num::NonZeroUsize;
8use std::process::Command;
9
10macro_rules! static_regex {
11    ($re:literal) => {{
12        static RE: ::std::sync::LazyLock<::regex::Regex> =
13            ::std::sync::LazyLock::new(|| ::regex::Regex::new($re).unwrap());
14        &*RE
15    }};
16}
17
18/// A helper macro to `unwrap` a result except also print out details like:
19///
20/// * The expression that failed
21/// * The error itself
22/// * (optionally) a path connected to the error (e.g. failure to open a file)
23#[macro_export]
24macro_rules! t {
25    ($e:expr, $p:expr) => {
26        match $e {
27            Ok(e) => e,
28            Err(e) => panic!("{} failed on {} with {}", stringify!($e), ($p).display(), e),
29        }
30    };
31
32    ($e:expr) => {
33        match $e {
34            Ok(e) => e,
35            Err(e) => panic!("{} failed with {}", stringify!($e), e),
36        }
37    };
38}
39
40pub fn git_diff<S: AsRef<OsStr>>(base_commit: &str, extra_arg: S) -> Option<String> {
41    let output = Command::new("git").arg("diff").arg(base_commit).arg(extra_arg).output().ok()?;
42    Some(String::from_utf8_lossy(&output.stdout).into())
43}
44
45/// Similar to `files_modified`, but only involves a single call to `git`.
46///
47/// removes all elements from `items` that do not cause any match when `pred` is called with the list of modifed files.
48///
49/// if in CI, no elements will be removed.
50pub fn files_modified_batch_filter<T>(
51    base_commit: &Option<String>,
52    is_ci: bool,
53    items: &mut Vec<T>,
54    pred: impl Fn(&T, &str) -> bool,
55) {
56    if is_ci {
57        // assume everything is modified on CI because we really don't want false positives there.
58        return;
59    }
60    let Some(base_commit) = base_commit else {
61        eprintln!("No base commit, assuming all files are modified");
62        return;
63    };
64    match crate::git_diff(base_commit, "--name-status") {
65        Some(output) => {
66            let modified_files: Vec<_> = output
67                .lines()
68                .filter_map(|ln| {
69                    let (status, name) = ln
70                        .trim_end()
71                        .split_once('\t')
72                        .expect("bad format from `git diff --name-status`");
73                    if status == "M" { Some(name) } else { None }
74                })
75                .collect();
76            items.retain(|item| {
77                for modified_file in &modified_files {
78                    if pred(item, modified_file) {
79                        // at least one predicate matches, keep this item.
80                        return true;
81                    }
82                }
83                // no predicates matched, remove this item.
84                false
85            });
86        }
87        None => {
88            eprintln!("warning: failed to run `git diff` to check for changes");
89            eprintln!("warning: assuming all files are modified");
90        }
91    }
92}
93
94/// Returns true if any modified file matches the predicate, if we are in CI, or if unable to list modified files.
95pub fn files_modified(
96    base_commit: &Option<String>,
97    is_ci: bool,
98    pred: impl Fn(&str) -> bool,
99) -> bool {
100    let mut v = vec![()];
101    files_modified_batch_filter(base_commit, is_ci, &mut v, |_, p| pred(p));
102    !v.is_empty()
103}
104
105pub mod alphabetical;
106pub mod arg_parser;
107pub mod bins;
108pub mod codegen;
109pub mod debug_artifacts;
110pub mod deps;
111pub mod diagnostics;
112pub mod edition;
113pub mod error_codes;
114pub mod extdeps;
115pub mod extra_checks;
116pub mod features;
117pub mod filenames;
118pub mod gcc_submodule;
119pub(crate) mod iter_header;
120pub mod known_bug;
121pub mod mir_opt_tests;
122pub mod pal;
123pub mod rustdoc_css_themes;
124pub mod rustdoc_gui_tests;
125pub mod rustdoc_html_tests;
126pub mod rustdoc_json;
127pub mod rustdoc_templates;
128pub mod style;
129pub mod target_policy;
130pub mod target_specific_tests;
131pub mod tests_placement;
132pub mod tests_revision_unpaired_stdout_stderr;
133pub mod triagebot;
134pub mod ui_tests;
135pub mod unit_tests;
136pub mod unknown_revision;
137pub mod unstable_book;
138pub mod walk;
139pub mod x_version;
140
141pub struct Semaphore {
142    sem: std::sync::Mutex<usize>,
143    cond: std::sync::Condvar,
144}
145
146impl Semaphore {
147    pub fn new(count: NonZeroUsize) -> Self {
148        Self { sem: std::sync::Mutex::new(count.get()), cond: std::sync::Condvar::new() }
149    }
150
151    pub fn acquire(&self) -> Guard<'_> {
152        let mut count = self.sem.lock().unwrap();
153        while *count == 0 {
154            count = self.cond.wait(count).unwrap();
155        }
156        *count -= 1;
157        Guard { sem: self }
158    }
159
160    fn release(&self) {
161        let mut count = self.sem.lock().unwrap();
162        *count += 1;
163        self.cond.notify_one();
164    }
165}
166
167pub struct Guard<'a> {
168    sem: &'a Semaphore,
169}
170
171impl Drop for Guard<'_> {
172    fn drop(&mut self) {
173        self.sem.release();
174    }
175}