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