]>
Commit | Line | Data |
---|---|---|
f25598a0 | 1 | use anyhow::{Context, Error}; |
c295e0f8 XL |
2 | use curl::easy::Easy; |
3 | use indexmap::IndexMap; | |
4 | use std::collections::HashMap; | |
5 | use std::convert::TryInto; | |
6 | ||
923072b8 | 7 | const PATH: &str = "src/stage0.json"; |
c295e0f8 XL |
8 | const COMPILER_COMPONENTS: &[&str] = &["rustc", "rust-std", "cargo"]; |
9 | const RUSTFMT_COMPONENTS: &[&str] = &["rustfmt-preview"]; | |
10 | ||
11 | struct Tool { | |
923072b8 FG |
12 | config: Config, |
13 | comments: Vec<String>, | |
14 | ||
c295e0f8 | 15 | channel: Channel, |
f25598a0 | 16 | date: Option<String>, |
c295e0f8 XL |
17 | version: [u16; 3], |
18 | checksums: IndexMap<String, String>, | |
19 | } | |
20 | ||
21 | impl Tool { | |
f25598a0 | 22 | fn new(date: Option<String>) -> Result<Self, Error> { |
c295e0f8 XL |
23 | let channel = match std::fs::read_to_string("src/ci/channel")?.trim() { |
24 | "stable" => Channel::Stable, | |
25 | "beta" => Channel::Beta, | |
26 | "nightly" => Channel::Nightly, | |
27 | other => anyhow::bail!("unsupported channel: {}", other), | |
28 | }; | |
29 | ||
30 | // Split "1.42.0" into [1, 42, 0] | |
31 | let version = std::fs::read_to_string("src/version")? | |
32 | .trim() | |
33 | .split('.') | |
34 | .map(|val| val.parse()) | |
35 | .collect::<Result<Vec<_>, _>>()? | |
36 | .try_into() | |
37 | .map_err(|_| anyhow::anyhow!("failed to parse version"))?; | |
38 | ||
923072b8 FG |
39 | let existing: Stage0 = serde_json::from_slice(&std::fs::read(PATH)?)?; |
40 | ||
41 | Ok(Self { | |
42 | channel, | |
43 | version, | |
f25598a0 | 44 | date, |
923072b8 FG |
45 | config: existing.config, |
46 | comments: existing.comments, | |
47 | checksums: IndexMap::new(), | |
48 | }) | |
c295e0f8 XL |
49 | } |
50 | ||
51 | fn update_json(mut self) -> Result<(), Error> { | |
52 | std::fs::write( | |
923072b8 | 53 | PATH, |
c295e0f8 XL |
54 | format!( |
55 | "{}\n", | |
56 | serde_json::to_string_pretty(&Stage0 { | |
c295e0f8 XL |
57 | compiler: self.detect_compiler()?, |
58 | rustfmt: self.detect_rustfmt()?, | |
59 | checksums_sha256: { | |
60 | // Keys are sorted here instead of beforehand because values in this map | |
61 | // are added while filling the other struct fields just above this block. | |
62 | self.checksums.sort_keys(); | |
63 | self.checksums | |
923072b8 FG |
64 | }, |
65 | config: self.config, | |
66 | comments: self.comments, | |
c295e0f8 XL |
67 | })? |
68 | ), | |
69 | )?; | |
70 | Ok(()) | |
71 | } | |
72 | ||
73 | // Currently Rust always bootstraps from the previous stable release, and in our train model | |
74 | // this means that the master branch bootstraps from beta, beta bootstraps from current stable, | |
75 | // and stable bootstraps from the previous stable release. | |
76 | // | |
77 | // On the master branch the compiler version is configured to `beta` whereas if you're looking | |
78 | // at the beta or stable channel you'll likely see `1.x.0` as the version, with the previous | |
79 | // release's version number. | |
80 | fn detect_compiler(&mut self) -> Result<Stage0Toolchain, Error> { | |
81 | let channel = match self.channel { | |
82 | Channel::Stable | Channel::Beta => { | |
83 | // The 1.XX manifest points to the latest point release of that minor release. | |
84 | format!("{}.{}", self.version[0], self.version[1] - 1) | |
85 | } | |
86 | Channel::Nightly => "beta".to_string(), | |
87 | }; | |
88 | ||
f25598a0 | 89 | let manifest = fetch_manifest(&self.config, &channel, self.date.as_deref())?; |
c295e0f8 XL |
90 | self.collect_checksums(&manifest, COMPILER_COMPONENTS)?; |
91 | Ok(Stage0Toolchain { | |
92 | date: manifest.date, | |
93 | version: if self.channel == Channel::Nightly { | |
94 | "beta".to_string() | |
95 | } else { | |
96 | // The version field is like "1.42.0 (abcdef1234 1970-01-01)" | |
97 | manifest.pkg["rust"] | |
98 | .version | |
99 | .split_once(' ') | |
100 | .expect("invalid version field") | |
101 | .0 | |
102 | .to_string() | |
103 | }, | |
104 | }) | |
105 | } | |
106 | ||
107 | /// We use a nightly rustfmt to format the source because it solves some bootstrapping issues | |
108 | /// with use of new syntax in this repo. For the beta/stable channels rustfmt is not provided, | |
109 | /// as we don't want to depend on rustfmt from nightly there. | |
110 | fn detect_rustfmt(&mut self) -> Result<Option<Stage0Toolchain>, Error> { | |
111 | if self.channel != Channel::Nightly { | |
112 | return Ok(None); | |
113 | } | |
114 | ||
f25598a0 | 115 | let manifest = fetch_manifest(&self.config, "nightly", self.date.as_deref())?; |
c295e0f8 XL |
116 | self.collect_checksums(&manifest, RUSTFMT_COMPONENTS)?; |
117 | Ok(Some(Stage0Toolchain { date: manifest.date, version: "nightly".into() })) | |
118 | } | |
119 | ||
120 | fn collect_checksums(&mut self, manifest: &Manifest, components: &[&str]) -> Result<(), Error> { | |
923072b8 | 121 | let prefix = format!("{}/", self.config.dist_server); |
c295e0f8 XL |
122 | for component in components { |
123 | let pkg = manifest | |
124 | .pkg | |
125 | .get(*component) | |
126 | .ok_or_else(|| anyhow::anyhow!("missing component from manifest: {}", component))?; | |
127 | for target in pkg.target.values() { | |
128 | for pair in &[(&target.url, &target.hash), (&target.xz_url, &target.xz_hash)] { | |
129 | if let (Some(url), Some(sha256)) = pair { | |
130 | let url = url | |
131 | .strip_prefix(&prefix) | |
132 | .ok_or_else(|| { | |
133 | anyhow::anyhow!("url doesn't start with dist server base: {}", url) | |
134 | })? | |
135 | .to_string(); | |
136 | self.checksums.insert(url, sha256.clone()); | |
137 | } | |
138 | } | |
139 | } | |
140 | } | |
141 | Ok(()) | |
142 | } | |
143 | } | |
144 | ||
145 | fn main() -> Result<(), Error> { | |
f25598a0 | 146 | let tool = Tool::new(std::env::args().nth(1))?; |
c295e0f8 XL |
147 | tool.update_json()?; |
148 | Ok(()) | |
149 | } | |
150 | ||
f25598a0 FG |
151 | fn fetch_manifest(config: &Config, channel: &str, date: Option<&str>) -> Result<Manifest, Error> { |
152 | let url = if let Some(date) = date { | |
153 | format!("{}/dist/{}/channel-rust-{}.toml", config.dist_server, date, channel) | |
154 | } else { | |
155 | format!("{}/dist/channel-rust-{}.toml", config.dist_server, channel) | |
156 | }; | |
157 | ||
158 | Ok(toml::from_slice(&http_get(&url)?)?) | |
c295e0f8 XL |
159 | } |
160 | ||
161 | fn http_get(url: &str) -> Result<Vec<u8>, Error> { | |
162 | let mut data = Vec::new(); | |
163 | let mut handle = Easy::new(); | |
164 | handle.fail_on_error(true)?; | |
165 | handle.url(url)?; | |
166 | { | |
167 | let mut transfer = handle.transfer(); | |
168 | transfer.write_function(|new_data| { | |
169 | data.extend_from_slice(new_data); | |
170 | Ok(new_data.len()) | |
171 | })?; | |
f25598a0 | 172 | transfer.perform().context(format!("failed to fetch {url}"))?; |
c295e0f8 XL |
173 | } |
174 | Ok(data) | |
175 | } | |
176 | ||
177 | #[derive(Debug, PartialEq, Eq)] | |
178 | enum Channel { | |
179 | Stable, | |
180 | Beta, | |
181 | Nightly, | |
182 | } | |
183 | ||
923072b8 | 184 | #[derive(Debug, serde::Serialize, serde::Deserialize)] |
c295e0f8 | 185 | struct Stage0 { |
923072b8 FG |
186 | config: Config, |
187 | // Comments are explicitly below the config, do not move them above. | |
188 | // | |
189 | // Downstream forks of the compiler codebase can change the configuration values defined above, | |
190 | // but doing so would risk merge conflicts whenever they import new changes that include a | |
191 | // bootstrap compiler bump. | |
192 | // | |
193 | // To lessen the pain, a big block of comments is placed between the configuration and the | |
194 | // auto-generated parts of the file, preventing git diffs of the config to include parts of the | |
195 | // auto-generated content and vice versa. This should prevent merge conflicts. | |
196 | #[serde(rename = "__comments")] | |
197 | comments: Vec<String>, | |
c295e0f8 XL |
198 | compiler: Stage0Toolchain, |
199 | rustfmt: Option<Stage0Toolchain>, | |
200 | checksums_sha256: IndexMap<String, String>, | |
201 | } | |
202 | ||
923072b8 FG |
203 | #[derive(Debug, serde::Serialize, serde::Deserialize)] |
204 | struct Config { | |
205 | dist_server: String, | |
064997fb FG |
206 | // There are other fields in the configuration, which will be read by src/bootstrap or other |
207 | // tools consuming stage0.json. To avoid the need to update bump-stage0 every time a new field | |
208 | // is added, we collect all the fields in an untyped Value and serialize them back with the | |
209 | // same order and structure they were deserialized in. | |
210 | #[serde(flatten)] | |
211 | other: serde_json::Value, | |
923072b8 FG |
212 | } |
213 | ||
214 | #[derive(Debug, serde::Serialize, serde::Deserialize)] | |
c295e0f8 XL |
215 | struct Stage0Toolchain { |
216 | date: String, | |
217 | version: String, | |
218 | } | |
219 | ||
923072b8 | 220 | #[derive(Debug, serde::Serialize, serde::Deserialize)] |
c295e0f8 XL |
221 | struct Manifest { |
222 | date: String, | |
223 | pkg: HashMap<String, ManifestPackage>, | |
224 | } | |
225 | ||
923072b8 | 226 | #[derive(Debug, serde::Serialize, serde::Deserialize)] |
c295e0f8 XL |
227 | struct ManifestPackage { |
228 | version: String, | |
229 | target: HashMap<String, ManifestTargetPackage>, | |
230 | } | |
231 | ||
923072b8 | 232 | #[derive(Debug, serde::Serialize, serde::Deserialize)] |
c295e0f8 | 233 | struct ManifestTargetPackage { |
c295e0f8 XL |
234 | url: Option<String>, |
235 | hash: Option<String>, | |
236 | xz_url: Option<String>, | |
237 | xz_hash: Option<String>, | |
238 | } |