]>
Commit | Line | Data |
---|---|---|
31ef2f64 FG |
1 | #![deny(unused_variables)] |
2 | ||
9c376795 | 3 | use anyhow::{Context, Error}; |
31ef2f64 | 4 | use build_helper::stage0_parser::{parse_stage0_file, Stage0Config, VersionMetadata}; |
c295e0f8 XL |
5 | use curl::easy::Easy; |
6 | use indexmap::IndexMap; | |
7 | use std::collections::HashMap; | |
c295e0f8 | 8 | |
31ef2f64 | 9 | const PATH: &str = "src/stage0"; |
4b012472 | 10 | const COMPILER_COMPONENTS: &[&str] = &["rustc", "rust-std", "cargo", "clippy-preview"]; |
9ffffee4 | 11 | const RUSTFMT_COMPONENTS: &[&str] = &["rustfmt-preview", "rustc"]; |
c295e0f8 XL |
12 | |
13 | struct Tool { | |
31ef2f64 | 14 | config: Stage0Config, |
923072b8 | 15 | |
c295e0f8 | 16 | channel: Channel, |
9c376795 | 17 | date: Option<String>, |
c295e0f8 XL |
18 | version: [u16; 3], |
19 | checksums: IndexMap<String, String>, | |
20 | } | |
21 | ||
22 | impl Tool { | |
9c376795 | 23 | fn new(date: Option<String>) -> Result<Self, Error> { |
c295e0f8 XL |
24 | let channel = match std::fs::read_to_string("src/ci/channel")?.trim() { |
25 | "stable" => Channel::Stable, | |
26 | "beta" => Channel::Beta, | |
27 | "nightly" => Channel::Nightly, | |
28 | other => anyhow::bail!("unsupported channel: {}", other), | |
29 | }; | |
30 | ||
31 | // Split "1.42.0" into [1, 42, 0] | |
32 | let version = std::fs::read_to_string("src/version")? | |
33 | .trim() | |
34 | .split('.') | |
35 | .map(|val| val.parse()) | |
36 | .collect::<Result<Vec<_>, _>>()? | |
37 | .try_into() | |
38 | .map_err(|_| anyhow::anyhow!("failed to parse version"))?; | |
39 | ||
31ef2f64 | 40 | let existing = parse_stage0_file(); |
923072b8 | 41 | |
31ef2f64 | 42 | Ok(Self { channel, version, date, config: existing.config, checksums: IndexMap::new() }) |
c295e0f8 XL |
43 | } |
44 | ||
31ef2f64 FG |
45 | fn update_stage0_file(mut self) -> Result<(), Error> { |
46 | const COMMENTS: &str = r#"# The configuration above this comment is editable, and can be changed | |
47 | # by forks of the repository if they have alternate values. | |
48 | # | |
49 | # The section below is generated by `./x.py run src/tools/bump-stage0`, | |
50 | # run that command again to update the bootstrap compiler. | |
51 | # | |
52 | # All changes below this comment will be overridden the next time the | |
53 | # tool is executed. | |
54 | "#; | |
55 | ||
56 | let mut file_content = String::new(); | |
57 | ||
58 | // Destructure `Stage0Config` here to ensure the stage0 file is synced with any new | |
59 | // fields when they are added. | |
60 | let Stage0Config { | |
61 | dist_server, | |
62 | artifacts_server, | |
63 | artifacts_with_llvm_assertions_server, | |
64 | git_merge_commit_email, | |
65 | git_repository, | |
66 | nightly_branch, | |
67 | } = &self.config; | |
68 | ||
69 | file_content.push_str(&format!("dist_server={}", dist_server)); | |
70 | file_content.push_str(&format!("\nartifacts_server={}", artifacts_server)); | |
71 | file_content.push_str(&format!( | |
72 | "\nartifacts_with_llvm_assertions_server={}", | |
73 | artifacts_with_llvm_assertions_server | |
74 | )); | |
75 | file_content.push_str(&format!("\ngit_merge_commit_email={}", git_merge_commit_email)); | |
76 | file_content.push_str(&format!("\ngit_repository={}", git_repository)); | |
77 | file_content.push_str(&format!("\nnightly_branch={}", nightly_branch)); | |
78 | ||
79 | file_content.push_str("\n\n"); | |
80 | file_content.push_str(COMMENTS); | |
81 | ||
82 | let compiler = self.detect_compiler()?; | |
83 | file_content.push_str(&format!("\ncompiler_date={}", compiler.date)); | |
84 | file_content.push_str(&format!("\ncompiler_version={}", compiler.version)); | |
85 | ||
86 | if let Some(rustfmt) = self.detect_rustfmt()? { | |
87 | file_content.push_str(&format!("\nrustfmt_date={}", rustfmt.date)); | |
88 | file_content.push_str(&format!("\nrustfmt_version={}", rustfmt.version)); | |
89 | } | |
90 | ||
91 | file_content.push_str("\n"); | |
92 | ||
93 | for (key, value) in self.checksums { | |
94 | file_content.push_str(&format!("\n{}={}", key, value)); | |
95 | } | |
96 | ||
97 | std::fs::write(PATH, file_content)?; | |
c295e0f8 XL |
98 | Ok(()) |
99 | } | |
100 | ||
101 | // Currently Rust always bootstraps from the previous stable release, and in our train model | |
102 | // this means that the master branch bootstraps from beta, beta bootstraps from current stable, | |
103 | // and stable bootstraps from the previous stable release. | |
104 | // | |
105 | // On the master branch the compiler version is configured to `beta` whereas if you're looking | |
106 | // at the beta or stable channel you'll likely see `1.x.0` as the version, with the previous | |
107 | // release's version number. | |
31ef2f64 | 108 | fn detect_compiler(&mut self) -> Result<VersionMetadata, Error> { |
c295e0f8 XL |
109 | let channel = match self.channel { |
110 | Channel::Stable | Channel::Beta => { | |
111 | // The 1.XX manifest points to the latest point release of that minor release. | |
112 | format!("{}.{}", self.version[0], self.version[1] - 1) | |
113 | } | |
114 | Channel::Nightly => "beta".to_string(), | |
115 | }; | |
116 | ||
9c376795 | 117 | let manifest = fetch_manifest(&self.config, &channel, self.date.as_deref())?; |
c295e0f8 | 118 | self.collect_checksums(&manifest, COMPILER_COMPONENTS)?; |
31ef2f64 | 119 | Ok(VersionMetadata { |
c295e0f8 XL |
120 | date: manifest.date, |
121 | version: if self.channel == Channel::Nightly { | |
122 | "beta".to_string() | |
123 | } else { | |
124 | // The version field is like "1.42.0 (abcdef1234 1970-01-01)" | |
125 | manifest.pkg["rust"] | |
126 | .version | |
127 | .split_once(' ') | |
128 | .expect("invalid version field") | |
129 | .0 | |
130 | .to_string() | |
131 | }, | |
132 | }) | |
133 | } | |
134 | ||
135 | /// We use a nightly rustfmt to format the source because it solves some bootstrapping issues | |
136 | /// with use of new syntax in this repo. For the beta/stable channels rustfmt is not provided, | |
137 | /// as we don't want to depend on rustfmt from nightly there. | |
31ef2f64 | 138 | fn detect_rustfmt(&mut self) -> Result<Option<VersionMetadata>, Error> { |
c295e0f8 XL |
139 | if self.channel != Channel::Nightly { |
140 | return Ok(None); | |
141 | } | |
142 | ||
9c376795 | 143 | let manifest = fetch_manifest(&self.config, "nightly", self.date.as_deref())?; |
c295e0f8 | 144 | self.collect_checksums(&manifest, RUSTFMT_COMPONENTS)?; |
31ef2f64 | 145 | Ok(Some(VersionMetadata { date: manifest.date, version: "nightly".into() })) |
c295e0f8 XL |
146 | } |
147 | ||
148 | fn collect_checksums(&mut self, manifest: &Manifest, components: &[&str]) -> Result<(), Error> { | |
923072b8 | 149 | let prefix = format!("{}/", self.config.dist_server); |
c295e0f8 XL |
150 | for component in components { |
151 | let pkg = manifest | |
152 | .pkg | |
153 | .get(*component) | |
154 | .ok_or_else(|| anyhow::anyhow!("missing component from manifest: {}", component))?; | |
155 | for target in pkg.target.values() { | |
156 | for pair in &[(&target.url, &target.hash), (&target.xz_url, &target.xz_hash)] { | |
157 | if let (Some(url), Some(sha256)) = pair { | |
158 | let url = url | |
159 | .strip_prefix(&prefix) | |
160 | .ok_or_else(|| { | |
161 | anyhow::anyhow!("url doesn't start with dist server base: {}", url) | |
162 | })? | |
163 | .to_string(); | |
164 | self.checksums.insert(url, sha256.clone()); | |
165 | } | |
166 | } | |
167 | } | |
168 | } | |
169 | Ok(()) | |
170 | } | |
171 | } | |
172 | ||
173 | fn main() -> Result<(), Error> { | |
9c376795 | 174 | let tool = Tool::new(std::env::args().nth(1))?; |
31ef2f64 | 175 | tool.update_stage0_file()?; |
c295e0f8 XL |
176 | Ok(()) |
177 | } | |
178 | ||
31ef2f64 FG |
179 | fn fetch_manifest( |
180 | config: &Stage0Config, | |
181 | channel: &str, | |
182 | date: Option<&str>, | |
183 | ) -> Result<Manifest, Error> { | |
9c376795 FG |
184 | let url = if let Some(date) = date { |
185 | format!("{}/dist/{}/channel-rust-{}.toml", config.dist_server, date, channel) | |
186 | } else { | |
187 | format!("{}/dist/channel-rust-{}.toml", config.dist_server, channel) | |
188 | }; | |
189 | ||
190 | Ok(toml::from_slice(&http_get(&url)?)?) | |
c295e0f8 XL |
191 | } |
192 | ||
193 | fn http_get(url: &str) -> Result<Vec<u8>, Error> { | |
194 | let mut data = Vec::new(); | |
195 | let mut handle = Easy::new(); | |
196 | handle.fail_on_error(true)?; | |
197 | handle.url(url)?; | |
198 | { | |
199 | let mut transfer = handle.transfer(); | |
200 | transfer.write_function(|new_data| { | |
201 | data.extend_from_slice(new_data); | |
202 | Ok(new_data.len()) | |
203 | })?; | |
9c376795 | 204 | transfer.perform().context(format!("failed to fetch {url}"))?; |
c295e0f8 XL |
205 | } |
206 | Ok(data) | |
207 | } | |
208 | ||
209 | #[derive(Debug, PartialEq, Eq)] | |
210 | enum Channel { | |
211 | Stable, | |
212 | Beta, | |
213 | Nightly, | |
214 | } | |
215 | ||
923072b8 | 216 | #[derive(Debug, serde::Serialize, serde::Deserialize)] |
c295e0f8 XL |
217 | struct Manifest { |
218 | date: String, | |
219 | pkg: HashMap<String, ManifestPackage>, | |
220 | } | |
221 | ||
923072b8 | 222 | #[derive(Debug, serde::Serialize, serde::Deserialize)] |
c295e0f8 XL |
223 | struct ManifestPackage { |
224 | version: String, | |
225 | target: HashMap<String, ManifestTargetPackage>, | |
226 | } | |
227 | ||
923072b8 | 228 | #[derive(Debug, serde::Serialize, serde::Deserialize)] |
c295e0f8 | 229 | struct ManifestTargetPackage { |
c295e0f8 XL |
230 | url: Option<String>, |
231 | hash: Option<String>, | |
232 | xz_url: Option<String>, | |
233 | xz_hash: Option<String>, | |
234 | } |