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