about summary refs log tree commit diff
path: root/src/tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/bump-stage0/Cargo.toml14
-rw-r--r--src/tools/bump-stage0/src/main.rs204
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>,
+}