1use std::ffi::OsStr;
7use std::path::{Path, PathBuf};
8use std::process::Command;
9use std::{env, io};
10
11use build_helper::ci::CiEnv;
12use build_helper::git::{GitConfig, get_closest_upstream_commit};
13use build_helper::stage0_parser::{Stage0Config, parse_stage0_file};
14use termcolor::WriteColor;
15
16macro_rules! static_regex {
17 ($re:literal) => {{
18 static RE: ::std::sync::LazyLock<::regex::Regex> =
19 ::std::sync::LazyLock::new(|| ::regex::Regex::new($re).unwrap());
20 &*RE
21 }};
22}
23
24#[macro_export]
30macro_rules! t {
31 ($e:expr, $p:expr) => {
32 match $e {
33 Ok(e) => e,
34 Err(e) => panic!("{} failed on {} with {}", stringify!($e), ($p).display(), e),
35 }
36 };
37
38 ($e:expr) => {
39 match $e {
40 Ok(e) => e,
41 Err(e) => panic!("{} failed with {}", stringify!($e), e),
42 }
43 };
44}
45
46macro_rules! tidy_error {
47 ($bad:expr, $($fmt:tt)*) => ({
48 $crate::tidy_error(&format_args!($($fmt)*).to_string()).expect("failed to output error");
49 *$bad = true;
50 });
51}
52
53macro_rules! tidy_error_ext {
54 ($tidy_error:path, $bad:expr, $($fmt:tt)*) => ({
55 $tidy_error(&format_args!($($fmt)*).to_string()).expect("failed to output error");
56 *$bad = true;
57 });
58}
59
60fn tidy_error(args: &str) -> std::io::Result<()> {
61 use std::io::Write;
62
63 use termcolor::{Color, ColorChoice, ColorSpec, StandardStream};
64
65 let mut stderr = StandardStream::stdout(ColorChoice::Auto);
66 stderr.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
67
68 write!(&mut stderr, "tidy error")?;
69 stderr.set_color(&ColorSpec::new())?;
70
71 writeln!(&mut stderr, ": {args}")?;
72 Ok(())
73}
74
75pub struct CiInfo {
76 pub git_merge_commit_email: String,
77 pub nightly_branch: String,
78 pub base_commit: Option<String>,
79 pub ci_env: CiEnv,
80}
81
82impl CiInfo {
83 pub fn new(bad: &mut bool) -> Self {
84 let stage0 = parse_stage0_file();
85 let Stage0Config { nightly_branch, git_merge_commit_email, .. } = stage0.config;
86
87 let mut info = Self {
88 nightly_branch,
89 git_merge_commit_email,
90 ci_env: CiEnv::current(),
91 base_commit: None,
92 };
93 let base_commit = match get_closest_upstream_commit(None, &info.git_config(), info.ci_env) {
94 Ok(Some(commit)) => Some(commit),
95 Ok(None) => {
96 info.error_if_in_ci("no base commit found", bad);
97 None
98 }
99 Err(error) => {
100 info.error_if_in_ci(&format!("failed to retrieve base commit: {error}"), bad);
101 None
102 }
103 };
104 info.base_commit = base_commit;
105 info
106 }
107
108 pub fn git_config(&self) -> GitConfig<'_> {
109 GitConfig {
110 nightly_branch: &self.nightly_branch,
111 git_merge_commit_email: &self.git_merge_commit_email,
112 }
113 }
114
115 pub fn error_if_in_ci(&self, msg: &str, bad: &mut bool) {
116 if self.ci_env.is_running_in_ci() {
117 *bad = true;
118 eprintln!("tidy check error: {msg}");
119 } else {
120 eprintln!("tidy check warning: {msg}. Some checks will be skipped.");
121 }
122 }
123}
124
125pub fn git_diff<S: AsRef<OsStr>>(base_commit: &str, extra_arg: S) -> Option<String> {
126 let output = Command::new("git").arg("diff").arg(base_commit).arg(extra_arg).output().ok()?;
127 Some(String::from_utf8_lossy(&output.stdout).into())
128}
129
130pub fn files_modified_batch_filter<T>(
136 ci_info: &CiInfo,
137 items: &mut Vec<T>,
138 pred: impl Fn(&T, &str) -> bool,
139) {
140 if CiEnv::is_ci() {
141 return;
143 }
144 let Some(base_commit) = &ci_info.base_commit else {
145 eprintln!("No base commit, assuming all files are modified");
146 return;
147 };
148 match crate::git_diff(base_commit, "--name-status") {
149 Some(output) => {
150 let modified_files: Vec<_> = output
151 .lines()
152 .filter_map(|ln| {
153 let (status, name) = ln
154 .trim_end()
155 .split_once('\t')
156 .expect("bad format from `git diff --name-status`");
157 if status == "M" { Some(name) } else { None }
158 })
159 .collect();
160 items.retain(|item| {
161 for modified_file in &modified_files {
162 if pred(item, modified_file) {
163 return true;
165 }
166 }
167 false
169 });
170 }
171 None => {
172 eprintln!("warning: failed to run `git diff` to check for changes");
173 eprintln!("warning: assuming all files are modified");
174 }
175 }
176}
177
178pub fn files_modified(ci_info: &CiInfo, pred: impl Fn(&str) -> bool) -> bool {
180 let mut v = vec![()];
181 files_modified_batch_filter(ci_info, &mut v, |_, p| pred(p));
182 !v.is_empty()
183}
184
185pub fn ensure_version_or_cargo_install(
188 build_dir: &Path,
189 cargo: &Path,
190 pkg_name: &str,
191 bin_name: &str,
192 version: &str,
193) -> io::Result<PathBuf> {
194 'ck: {
198 let Ok(output) = Command::new(bin_name).arg("--version").output() else {
200 break 'ck;
201 };
202 let Ok(s) = str::from_utf8(&output.stdout) else {
203 break 'ck;
204 };
205 let Some(v) = s.trim().split_whitespace().last() else {
206 break 'ck;
207 };
208 if v == version {
209 return Ok(PathBuf::from(bin_name));
210 }
211 }
212
213 let tool_root_dir = build_dir.join("misc-tools");
214 let tool_bin_dir = tool_root_dir.join("bin");
215 eprintln!("building external tool {bin_name} from package {pkg_name}@{version}");
216 let cargo_exit_code = Command::new(cargo)
220 .args(["install", "--locked", "--force", "--quiet"])
221 .arg("--root")
222 .arg(&tool_root_dir)
223 .arg("--target-dir")
224 .arg(tool_root_dir.join("target"))
225 .arg(format!("{pkg_name}@{version}"))
226 .env(
227 "PATH",
228 env::join_paths(
229 env::split_paths(&env::var("PATH").unwrap())
230 .chain(std::iter::once(tool_bin_dir.clone())),
231 )
232 .expect("build dir contains invalid char"),
233 )
234 .env("RUSTFLAGS", "-Copt-level=0")
235 .spawn()?
236 .wait()?;
237 if !cargo_exit_code.success() {
238 return Err(io::Error::other("cargo install failed"));
239 }
240 let bin_path = tool_bin_dir.join(bin_name);
241 assert!(
242 matches!(bin_path.try_exists(), Ok(true)),
243 "cargo install did not produce the expected binary"
244 );
245 eprintln!("finished building tool {bin_name}");
246 Ok(bin_path)
247}
248
249pub mod alphabetical;
250pub mod bins;
251pub mod debug_artifacts;
252pub mod deps;
253pub mod edition;
254pub mod error_codes;
255pub mod extdeps;
256pub mod extra_checks;
257pub mod features;
258pub mod filenames;
259pub mod fluent_alphabetical;
260pub mod fluent_period;
261mod fluent_used;
262pub mod gcc_submodule;
263pub(crate) mod iter_header;
264pub mod known_bug;
265pub mod mir_opt_tests;
266pub mod pal;
267pub mod rustdoc_css_themes;
268pub mod rustdoc_gui_tests;
269pub mod rustdoc_json;
270pub mod rustdoc_templates;
271pub mod style;
272pub mod target_policy;
273pub mod target_specific_tests;
274pub mod tests_placement;
275pub mod tests_revision_unpaired_stdout_stderr;
276pub mod triagebot;
277pub mod ui_tests;
278pub mod unit_tests;
279pub mod unknown_revision;
280pub mod unstable_book;
281pub mod walk;
282pub mod x_version;