1use std::ffi::OsStr;
7use std::process::Command;
8
9use build_helper::ci::CiEnv;
10use build_helper::git::{GitConfig, get_closest_upstream_commit};
11use build_helper::stage0_parser::{Stage0Config, parse_stage0_file};
12use termcolor::WriteColor;
13
14macro_rules! static_regex {
15 ($re:literal) => {{
16 static RE: ::std::sync::LazyLock<::regex::Regex> =
17 ::std::sync::LazyLock::new(|| ::regex::Regex::new($re).unwrap());
18 &*RE
19 }};
20}
21
22#[macro_export]
28macro_rules! t {
29 ($e:expr, $p:expr) => {
30 match $e {
31 Ok(e) => e,
32 Err(e) => panic!("{} failed on {} with {}", stringify!($e), ($p).display(), e),
33 }
34 };
35
36 ($e:expr) => {
37 match $e {
38 Ok(e) => e,
39 Err(e) => panic!("{} failed with {}", stringify!($e), e),
40 }
41 };
42}
43
44macro_rules! tidy_error {
45 ($bad:expr, $($fmt:tt)*) => ({
46 $crate::tidy_error(&format_args!($($fmt)*).to_string()).expect("failed to output error");
47 *$bad = true;
48 });
49}
50
51macro_rules! tidy_error_ext {
52 ($tidy_error:path, $bad:expr, $($fmt:tt)*) => ({
53 $tidy_error(&format_args!($($fmt)*).to_string()).expect("failed to output error");
54 *$bad = true;
55 });
56}
57
58fn tidy_error(args: &str) -> std::io::Result<()> {
59 use std::io::Write;
60
61 use termcolor::{Color, ColorChoice, ColorSpec, StandardStream};
62
63 let mut stderr = StandardStream::stdout(ColorChoice::Auto);
64 stderr.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
65
66 write!(&mut stderr, "tidy error")?;
67 stderr.set_color(&ColorSpec::new())?;
68
69 writeln!(&mut stderr, ": {args}")?;
70 Ok(())
71}
72
73pub struct CiInfo {
74 pub git_merge_commit_email: String,
75 pub nightly_branch: String,
76 pub base_commit: Option<String>,
77 pub ci_env: CiEnv,
78}
79
80impl CiInfo {
81 pub fn new(bad: &mut bool) -> Self {
82 let stage0 = parse_stage0_file();
83 let Stage0Config { nightly_branch, git_merge_commit_email, .. } = stage0.config;
84
85 let mut info = Self {
86 nightly_branch,
87 git_merge_commit_email,
88 ci_env: CiEnv::current(),
89 base_commit: None,
90 };
91 let base_commit = match get_closest_upstream_commit(None, &info.git_config(), info.ci_env) {
92 Ok(Some(commit)) => Some(commit),
93 Ok(None) => {
94 info.error_if_in_ci("no base commit found", bad);
95 None
96 }
97 Err(error) => {
98 info.error_if_in_ci(&format!("failed to retrieve base commit: {error}"), bad);
99 None
100 }
101 };
102 info.base_commit = base_commit;
103 info
104 }
105
106 pub fn git_config(&self) -> GitConfig<'_> {
107 GitConfig {
108 nightly_branch: &self.nightly_branch,
109 git_merge_commit_email: &self.git_merge_commit_email,
110 }
111 }
112
113 pub fn error_if_in_ci(&self, msg: &str, bad: &mut bool) {
114 if self.ci_env.is_running_in_ci() {
115 *bad = true;
116 eprintln!("tidy check error: {msg}");
117 } else {
118 eprintln!("tidy check warning: {msg}. Some checks will be skipped.");
119 }
120 }
121}
122
123pub fn git_diff<S: AsRef<OsStr>>(base_commit: &str, extra_arg: S) -> Option<String> {
124 let output = Command::new("git").arg("diff").arg(base_commit).arg(extra_arg).output().ok()?;
125 Some(String::from_utf8_lossy(&output.stdout).into())
126}
127
128pub fn files_modified_batch_filter<T>(
134 ci_info: &CiInfo,
135 items: &mut Vec<T>,
136 pred: impl Fn(&T, &str) -> bool,
137) {
138 if CiEnv::is_ci() {
139 return;
141 }
142 let Some(base_commit) = &ci_info.base_commit else {
143 eprintln!("No base commit, assuming all files are modified");
144 return;
145 };
146 match crate::git_diff(base_commit, "--name-status") {
147 Some(output) => {
148 let modified_files: Vec<_> = output
149 .lines()
150 .filter_map(|ln| {
151 let (status, name) = ln
152 .trim_end()
153 .split_once('\t')
154 .expect("bad format from `git diff --name-status`");
155 if status == "M" { Some(name) } else { None }
156 })
157 .collect();
158 items.retain(|item| {
159 for modified_file in &modified_files {
160 if pred(item, modified_file) {
161 return true;
163 }
164 }
165 false
167 });
168 }
169 None => {
170 eprintln!("warning: failed to run `git diff` to check for changes");
171 eprintln!("warning: assuming all files are modified");
172 }
173 }
174}
175
176pub fn files_modified(ci_info: &CiInfo, pred: impl Fn(&str) -> bool) -> bool {
178 let mut v = vec![()];
179 files_modified_batch_filter(ci_info, &mut v, |_, p| pred(p));
180 !v.is_empty()
181}
182
183pub mod alphabetical;
184pub mod bins;
185pub mod debug_artifacts;
186pub mod deps;
187pub mod edition;
188pub mod error_codes;
189pub mod extdeps;
190pub mod extra_checks;
191pub mod features;
192pub mod filenames;
193pub mod fluent_alphabetical;
194pub mod fluent_period;
195mod fluent_used;
196pub mod gcc_submodule;
197pub(crate) mod iter_header;
198pub mod known_bug;
199pub mod mir_opt_tests;
200pub mod pal;
201pub mod rustdoc_css_themes;
202pub mod rustdoc_gui_tests;
203pub mod rustdoc_json;
204pub mod rustdoc_templates;
205pub mod style;
206pub mod target_policy;
207pub mod target_specific_tests;
208pub mod tests_placement;
209pub mod tests_revision_unpaired_stdout_stderr;
210pub mod triagebot;
211pub mod ui_tests;
212pub mod unit_tests;
213pub mod unknown_revision;
214pub mod unstable_book;
215pub mod walk;
216pub mod x_version;