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