1use std::ffi::OsStr;
5use std::path::Path;
6use std::process::Command;
7use std::str::FromStr;
8
9use build_helper::ci::CiEnv;
10use build_helper::git::{GitConfig, get_closest_upstream_commit};
11use build_helper::stage0_parser::parse_stage0_file;
12
13const RUSTDOC_JSON_TYPES: &str = "src/rustdoc-json-types";
14
15fn git_diff<S: AsRef<OsStr>>(base_commit: &str, extra_arg: S) -> Option<String> {
16 let output = Command::new("git").arg("diff").arg(base_commit).arg(extra_arg).output().ok()?;
17 Some(String::from_utf8_lossy(&output.stdout).into())
18}
19
20fn error_if_in_ci(ci_env: CiEnv, msg: &str, bad: &mut bool) {
21 if ci_env.is_running_in_ci() {
22 *bad = true;
23 eprintln!("error in `rustdoc_json` tidy check: {msg}");
24 } else {
25 eprintln!("{msg}. Skipping `rustdoc_json` tidy check");
26 }
27}
28
29pub fn check(src_path: &Path, bad: &mut bool) {
30 println!("Checking tidy rustdoc_json...");
31 let stage0 = parse_stage0_file();
32 let ci_env = CiEnv::current();
33 let base_commit = match get_closest_upstream_commit(
34 None,
35 &GitConfig {
36 nightly_branch: &stage0.config.nightly_branch,
37 git_merge_commit_email: &stage0.config.git_merge_commit_email,
38 },
39 ci_env,
40 ) {
41 Ok(Some(commit)) => commit,
42 Ok(None) => {
43 error_if_in_ci(ci_env, "no base commit found", bad);
44 return;
45 }
46 Err(error) => {
47 error_if_in_ci(ci_env, &format!("failed to retrieve base commit: {error}"), bad);
48 return;
49 }
50 };
51
52 match git_diff(&base_commit, "--name-status") {
54 Some(output) => {
55 if !output
56 .lines()
57 .any(|line| line.starts_with("M") && line.contains(RUSTDOC_JSON_TYPES))
58 {
59 println!("`rustdoc-json-types` was not modified.");
61 return;
62 }
63 }
64 None => {
65 *bad = true;
66 eprintln!("error: failed to run `git diff` in rustdoc_json check");
67 return;
68 }
69 }
70 match git_diff(&base_commit, src_path.join("rustdoc-json-types")) {
72 Some(output) => {
73 let mut format_version_updated = false;
74 let mut latest_feature_comment_updated = false;
75 let mut new_version = None;
76 let mut old_version = None;
77 for line in output.lines() {
78 if line.starts_with("+pub const FORMAT_VERSION: u32 =") {
79 format_version_updated = true;
80 new_version = line
81 .split('=')
82 .nth(1)
83 .and_then(|s| s.trim().split(';').next())
84 .and_then(|s| u32::from_str(s.trim()).ok());
85 } else if line.starts_with("-pub const FORMAT_VERSION: u32 =") {
86 old_version = line
87 .split('=')
88 .nth(1)
89 .and_then(|s| s.trim().split(';').next())
90 .and_then(|s| u32::from_str(s.trim()).ok());
91 } else if line.starts_with("+// Latest feature:") {
92 latest_feature_comment_updated = true;
93 }
94 }
95 if format_version_updated != latest_feature_comment_updated {
96 *bad = true;
97 if latest_feature_comment_updated {
98 eprintln!(
99 "error in `rustdoc_json` tidy check: `Latest feature` comment was updated \
100 whereas `FORMAT_VERSION` wasn't in `{RUSTDOC_JSON_TYPES}/lib.rs`"
101 );
102 } else {
103 eprintln!(
104 "error in `rustdoc_json` tidy check: `Latest feature` comment was not \
105 updated whereas `FORMAT_VERSION` was in `{RUSTDOC_JSON_TYPES}/lib.rs`"
106 );
107 }
108 }
109 match (new_version, old_version) {
110 (Some(new_version), Some(old_version)) if new_version != old_version + 1 => {
111 *bad = true;
112 eprintln!(
113 "error in `rustdoc_json` tidy check: invalid `FORMAT_VERSION` increase in \
114 `{RUSTDOC_JSON_TYPES}/lib.rs`, should be `{}`, found `{new_version}`",
115 old_version + 1,
116 );
117 }
118 _ => {}
119 }
120 }
121 None => {
122 *bad = true;
123 eprintln!("error: failed to run `git diff` in rustdoc_json check");
124 }
125 }
126}