diff options
Diffstat (limited to 'src/tools')
| -rw-r--r-- | src/tools/bump-stage0/Cargo.toml | 14 | ||||
| -rw-r--r-- | src/tools/bump-stage0/src/main.rs | 204 |
2 files changed, 218 insertions, 0 deletions
diff --git a/src/tools/bump-stage0/Cargo.toml b/src/tools/bump-stage0/Cargo.toml new file mode 100644 index 00000000000..7d64e20a3d3 --- /dev/null +++ b/src/tools/bump-stage0/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "bump-stage0" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.34" +curl = "0.4.38" +indexmap = { version = "1.7.0", features = ["serde"] } +serde = { version = "1.0.125", features = ["derive"] } +serde_json = "1.0.59" +toml = "0.5.7" diff --git a/src/tools/bump-stage0/src/main.rs b/src/tools/bump-stage0/src/main.rs new file mode 100644 index 00000000000..96d3c873843 --- /dev/null +++ b/src/tools/bump-stage0/src/main.rs @@ -0,0 +1,204 @@ +use anyhow::Error; +use curl::easy::Easy; +use indexmap::IndexMap; +use std::collections::HashMap; +use std::convert::TryInto; + +const DIST_SERVER: &str = "https://static.rust-lang.org"; +const COMPILER_COMPONENTS: &[&str] = &["rustc", "rust-std", "cargo"]; +const RUSTFMT_COMPONENTS: &[&str] = &["rustfmt-preview"]; + +struct Tool { + channel: Channel, + version: [u16; 3], + checksums: IndexMap<String, String>, +} + +impl Tool { + fn new() -> Result<Self, Error> { + let channel = match std::fs::read_to_string("src/ci/channel")?.trim() { + "stable" => Channel::Stable, + "beta" => Channel::Beta, + "nightly" => Channel::Nightly, + other => anyhow::bail!("unsupported channel: {}", other), + }; + + // Split "1.42.0" into [1, 42, 0] + let version = std::fs::read_to_string("src/version")? + .trim() + .split('.') + .map(|val| val.parse()) + .collect::<Result<Vec<_>, _>>()? + .try_into() + .map_err(|_| anyhow::anyhow!("failed to parse version"))?; + + Ok(Self { channel, version, checksums: IndexMap::new() }) + } + + fn update_json(mut self) -> Result<(), Error> { + std::fs::write( + "src/stage0.json", + format!( + "{}\n", + serde_json::to_string_pretty(&Stage0 { + comment: "Generated by `./x.py run src/tools/bump-stage0`. \ + Run that command again to update the bootstrap compiler.", + dist_server: DIST_SERVER.into(), + compiler: self.detect_compiler()?, + rustfmt: self.detect_rustfmt()?, + checksums_sha256: { + // Keys are sorted here instead of beforehand because values in this map + // are added while filling the other struct fields just above this block. + self.checksums.sort_keys(); + self.checksums + } + })? + ), + )?; + Ok(()) + } + + // Currently Rust always bootstraps from the previous stable release, and in our train model + // this means that the master branch bootstraps from beta, beta bootstraps from current stable, + // and stable bootstraps from the previous stable release. + // + // On the master branch the compiler version is configured to `beta` whereas if you're looking + // at the beta or stable channel you'll likely see `1.x.0` as the version, with the previous + // release's version number. + fn detect_compiler(&mut self) -> Result<Stage0Toolchain, Error> { + let channel = match self.channel { + Channel::Stable | Channel::Beta => { + // The 1.XX manifest points to the latest point release of that minor release. + format!("{}.{}", self.version[0], self.version[1] - 1) + } + Channel::Nightly => "beta".to_string(), + }; + + let manifest = fetch_manifest(&channel)?; + self.collect_checksums(&manifest, COMPILER_COMPONENTS)?; + Ok(Stage0Toolchain { + date: manifest.date, + version: if self.channel == Channel::Nightly { + "beta".to_string() + } else { + // The version field is like "1.42.0 (abcdef1234 1970-01-01)" + manifest.pkg["rust"] + .version + .split_once(' ') + .expect("invalid version field") + .0 + .to_string() + }, + }) + } + + /// We use a nightly rustfmt to format the source because it solves some bootstrapping issues + /// with use of new syntax in this repo. For the beta/stable channels rustfmt is not provided, + /// as we don't want to depend on rustfmt from nightly there. + fn detect_rustfmt(&mut self) -> Result<Option<Stage0Toolchain>, Error> { + if self.channel != Channel::Nightly { + return Ok(None); + } + + let manifest = fetch_manifest("nightly")?; + self.collect_checksums(&manifest, RUSTFMT_COMPONENTS)?; + Ok(Some(Stage0Toolchain { date: manifest.date, version: "nightly".into() })) + } + + fn collect_checksums(&mut self, manifest: &Manifest, components: &[&str]) -> Result<(), Error> { + let prefix = format!("{}/", DIST_SERVER); + for component in components { + let pkg = manifest + .pkg + .get(*component) + .ok_or_else(|| anyhow::anyhow!("missing component from manifest: {}", component))?; + for target in pkg.target.values() { + for pair in &[(&target.url, &target.hash), (&target.xz_url, &target.xz_hash)] { + if let (Some(url), Some(sha256)) = pair { + let url = url + .strip_prefix(&prefix) + .ok_or_else(|| { + anyhow::anyhow!("url doesn't start with dist server base: {}", url) + })? + .to_string(); + self.checksums.insert(url, sha256.clone()); + } + } + } + } + Ok(()) + } +} + +fn main() -> Result<(), Error> { + let tool = Tool::new()?; + tool.update_json()?; + Ok(()) +} + +fn fetch_manifest(channel: &str) -> Result<Manifest, Error> { + Ok(toml::from_slice(&http_get(&format!( + "{}/dist/channel-rust-{}.toml", + DIST_SERVER, channel + ))?)?) +} + +fn http_get(url: &str) -> Result<Vec<u8>, Error> { + let mut data = Vec::new(); + let mut handle = Easy::new(); + handle.fail_on_error(true)?; + handle.url(url)?; + { + let mut transfer = handle.transfer(); + transfer.write_function(|new_data| { + data.extend_from_slice(new_data); + Ok(new_data.len()) + })?; + transfer.perform()?; + } + Ok(data) +} + +#[derive(Debug, PartialEq, Eq)] +enum Channel { + Stable, + Beta, + Nightly, +} + +#[derive(Debug, serde::Serialize)] +struct Stage0 { + #[serde(rename = "__comment")] + comment: &'static str, + dist_server: String, + compiler: Stage0Toolchain, + rustfmt: Option<Stage0Toolchain>, + checksums_sha256: IndexMap<String, String>, +} + +#[derive(Debug, serde::Serialize)] +struct Stage0Toolchain { + date: String, + version: String, +} + +#[derive(Debug, serde::Deserialize)] +struct Manifest { + date: String, + pkg: HashMap<String, ManifestPackage>, +} + +#[derive(Debug, serde::Deserialize)] +struct ManifestPackage { + version: String, + target: HashMap<String, ManifestTargetPackage>, +} + +#[derive(Debug, serde::Deserialize)] +struct ManifestTargetPackage { + available: bool, + url: Option<String>, + hash: Option<String>, + xz_url: Option<String>, + xz_hash: Option<String>, +} |
