]> git.lizzy.rs Git - rust.git/blob - src/tools/bump-stage0/src/main.rs
Rollup merge of #90202 - matthewjasper:xcrate-hygiene, r=petrochenkov
[rust.git] / src / tools / bump-stage0 / src / main.rs
1 use anyhow::Error;
2 use curl::easy::Easy;
3 use indexmap::IndexMap;
4 use std::collections::HashMap;
5 use std::convert::TryInto;
6
7 const DIST_SERVER: &str = "https://static.rust-lang.org";
8 const COMPILER_COMPONENTS: &[&str] = &["rustc", "rust-std", "cargo"];
9 const RUSTFMT_COMPONENTS: &[&str] = &["rustfmt-preview"];
10
11 struct Tool {
12     channel: Channel,
13     version: [u16; 3],
14     checksums: IndexMap<String, String>,
15 }
16
17 impl Tool {
18     fn new() -> Result<Self, Error> {
19         let channel = match std::fs::read_to_string("src/ci/channel")?.trim() {
20             "stable" => Channel::Stable,
21             "beta" => Channel::Beta,
22             "nightly" => Channel::Nightly,
23             other => anyhow::bail!("unsupported channel: {}", other),
24         };
25
26         // Split "1.42.0" into [1, 42, 0]
27         let version = std::fs::read_to_string("src/version")?
28             .trim()
29             .split('.')
30             .map(|val| val.parse())
31             .collect::<Result<Vec<_>, _>>()?
32             .try_into()
33             .map_err(|_| anyhow::anyhow!("failed to parse version"))?;
34
35         Ok(Self { channel, version, checksums: IndexMap::new() })
36     }
37
38     fn update_json(mut self) -> Result<(), Error> {
39         std::fs::write(
40             "src/stage0.json",
41             format!(
42                 "{}\n",
43                 serde_json::to_string_pretty(&Stage0 {
44                     comment: "Generated by `./x.py run src/tools/bump-stage0`. \
45                               Run that command again to update the bootstrap compiler.",
46                     dist_server: DIST_SERVER.into(),
47                     compiler: self.detect_compiler()?,
48                     rustfmt: self.detect_rustfmt()?,
49                     checksums_sha256: {
50                         // Keys are sorted here instead of beforehand because values in this map
51                         // are added while filling the other struct fields just above this block.
52                         self.checksums.sort_keys();
53                         self.checksums
54                     }
55                 })?
56             ),
57         )?;
58         Ok(())
59     }
60
61     // Currently Rust always bootstraps from the previous stable release, and in our train model
62     // this means that the master branch bootstraps from beta, beta bootstraps from current stable,
63     // and stable bootstraps from the previous stable release.
64     //
65     // On the master branch the compiler version is configured to `beta` whereas if you're looking
66     // at the beta or stable channel you'll likely see `1.x.0` as the version, with the previous
67     // release's version number.
68     fn detect_compiler(&mut self) -> Result<Stage0Toolchain, Error> {
69         let channel = match self.channel {
70             Channel::Stable | Channel::Beta => {
71                 // The 1.XX manifest points to the latest point release of that minor release.
72                 format!("{}.{}", self.version[0], self.version[1] - 1)
73             }
74             Channel::Nightly => "beta".to_string(),
75         };
76
77         let manifest = fetch_manifest(&channel)?;
78         self.collect_checksums(&manifest, COMPILER_COMPONENTS)?;
79         Ok(Stage0Toolchain {
80             date: manifest.date,
81             version: if self.channel == Channel::Nightly {
82                 "beta".to_string()
83             } else {
84                 // The version field is like "1.42.0 (abcdef1234 1970-01-01)"
85                 manifest.pkg["rust"]
86                     .version
87                     .split_once(' ')
88                     .expect("invalid version field")
89                     .0
90                     .to_string()
91             },
92         })
93     }
94
95     /// We use a nightly rustfmt to format the source because it solves some bootstrapping issues
96     /// with use of new syntax in this repo. For the beta/stable channels rustfmt is not provided,
97     /// as we don't want to depend on rustfmt from nightly there.
98     fn detect_rustfmt(&mut self) -> Result<Option<Stage0Toolchain>, Error> {
99         if self.channel != Channel::Nightly {
100             return Ok(None);
101         }
102
103         let manifest = fetch_manifest("nightly")?;
104         self.collect_checksums(&manifest, RUSTFMT_COMPONENTS)?;
105         Ok(Some(Stage0Toolchain { date: manifest.date, version: "nightly".into() }))
106     }
107
108     fn collect_checksums(&mut self, manifest: &Manifest, components: &[&str]) -> Result<(), Error> {
109         let prefix = format!("{}/", DIST_SERVER);
110         for component in components {
111             let pkg = manifest
112                 .pkg
113                 .get(*component)
114                 .ok_or_else(|| anyhow::anyhow!("missing component from manifest: {}", component))?;
115             for target in pkg.target.values() {
116                 for pair in &[(&target.url, &target.hash), (&target.xz_url, &target.xz_hash)] {
117                     if let (Some(url), Some(sha256)) = pair {
118                         let url = url
119                             .strip_prefix(&prefix)
120                             .ok_or_else(|| {
121                                 anyhow::anyhow!("url doesn't start with dist server base: {}", url)
122                             })?
123                             .to_string();
124                         self.checksums.insert(url, sha256.clone());
125                     }
126                 }
127             }
128         }
129         Ok(())
130     }
131 }
132
133 fn main() -> Result<(), Error> {
134     let tool = Tool::new()?;
135     tool.update_json()?;
136     Ok(())
137 }
138
139 fn fetch_manifest(channel: &str) -> Result<Manifest, Error> {
140     Ok(toml::from_slice(&http_get(&format!(
141         "{}/dist/channel-rust-{}.toml",
142         DIST_SERVER, channel
143     ))?)?)
144 }
145
146 fn http_get(url: &str) -> Result<Vec<u8>, Error> {
147     let mut data = Vec::new();
148     let mut handle = Easy::new();
149     handle.fail_on_error(true)?;
150     handle.url(url)?;
151     {
152         let mut transfer = handle.transfer();
153         transfer.write_function(|new_data| {
154             data.extend_from_slice(new_data);
155             Ok(new_data.len())
156         })?;
157         transfer.perform()?;
158     }
159     Ok(data)
160 }
161
162 #[derive(Debug, PartialEq, Eq)]
163 enum Channel {
164     Stable,
165     Beta,
166     Nightly,
167 }
168
169 #[derive(Debug, serde::Serialize)]
170 struct Stage0 {
171     #[serde(rename = "__comment")]
172     comment: &'static str,
173     dist_server: String,
174     compiler: Stage0Toolchain,
175     rustfmt: Option<Stage0Toolchain>,
176     checksums_sha256: IndexMap<String, String>,
177 }
178
179 #[derive(Debug, serde::Serialize)]
180 struct Stage0Toolchain {
181     date: String,
182     version: String,
183 }
184
185 #[derive(Debug, serde::Deserialize)]
186 struct Manifest {
187     date: String,
188     pkg: HashMap<String, ManifestPackage>,
189 }
190
191 #[derive(Debug, serde::Deserialize)]
192 struct ManifestPackage {
193     version: String,
194     target: HashMap<String, ManifestTargetPackage>,
195 }
196
197 #[derive(Debug, serde::Deserialize)]
198 struct ManifestTargetPackage {
199     url: Option<String>,
200     hash: Option<String>,
201     xz_url: Option<String>,
202     xz_hash: Option<String>,
203 }