]> git.lizzy.rs Git - rust.git/blobdiff - src/bootstrap/config.rs
Use the same message as type & const generics.
[rust.git] / src / bootstrap / config.rs
index b82c1505b249562f8015bb8802adb721480e9d55..8e94fc7c4bea5512395bbe24e1bbd9ef302ab29b 100644 (file)
@@ -7,9 +7,11 @@
 use std::cmp;
 use std::collections::{HashMap, HashSet};
 use std::env;
+use std::ffi::OsStr;
 use std::fmt;
 use std::fs;
 use std::path::{Path, PathBuf};
+use std::process::{exit, Command};
 use std::str::FromStr;
 
 use crate::builder::{Builder, TaskPath};
@@ -17,7 +19,8 @@
 use crate::channel::GitInfo;
 pub use crate::flags::Subcommand;
 use crate::flags::{Color, Flags};
-use crate::util::{exe, t};
+use crate::util::{exe, output, program_out_of_date, t};
+use once_cell::sync::OnceCell;
 use serde::{Deserialize, Deserializer};
 
 macro_rules! check_ci_llvm {
@@ -81,7 +84,11 @@ pub struct Config {
     pub cmd: Subcommand,
     pub incremental: bool,
     pub dry_run: bool,
-    pub download_rustc: bool,
+    /// `None` if we shouldn't download CI compiler artifacts, or the commit to download if we should.
+    #[cfg(not(test))]
+    download_rustc_commit: Option<String>,
+    #[cfg(test)]
+    pub download_rustc_commit: Option<String>,
 
     pub deny_warnings: bool,
     pub backtrace_on_ice: bool,
@@ -1080,7 +1087,8 @@ pub fn parse(args: &[String]) -> Config {
             config.rust_codegen_units_std = rust.codegen_units_std.map(threads_from_config);
             config.rust_profile_use = flags.rust_profile_use.or(rust.profile_use);
             config.rust_profile_generate = flags.rust_profile_generate.or(rust.profile_generate);
-            config.download_rustc = env::var("BOOTSTRAP_DOWNLOAD_RUSTC").as_deref() == Ok("1");
+            config.download_rustc_commit =
+                download_ci_rustc_commit(rust.download_rustc, config.verbose > 0);
         } else {
             config.rust_profile_use = flags.rust_profile_use;
             config.rust_profile_generate = flags.rust_profile_generate;
@@ -1192,7 +1200,7 @@ pub fn parse(args: &[String]) -> Config {
         let default = config.channel == "dev";
         config.ignore_git = ignore_git.unwrap_or(default);
 
-        let download_rustc = config.download_rustc;
+        let download_rustc = config.download_rustc_commit.is_some();
         // See https://github.com/rust-lang/compiler-team/issues/326
         config.stage = match config.cmd {
             Subcommand::Check { .. } => flags.stage.or(build.check_stage).unwrap_or(0),
@@ -1309,6 +1317,23 @@ pub(crate) fn llvm_link_shared(builder: &Builder<'_>) -> bool {
         llvm_link_shared
     }
 
+    /// Return whether we will use a downloaded, pre-compiled version of rustc, or just build from source.
+    pub(crate) fn download_rustc(builder: &Builder<'_>) -> bool {
+        static DOWNLOAD_RUSTC: OnceCell<bool> = OnceCell::new();
+        if builder.config.dry_run && DOWNLOAD_RUSTC.get().is_none() {
+            // avoid trying to actually download the commit
+            return false;
+        }
+
+        *DOWNLOAD_RUSTC.get_or_init(|| match &builder.config.download_rustc_commit {
+            None => false,
+            Some(commit) => {
+                download_ci_rustc(builder, commit);
+                true
+            }
+        })
+    }
+
     pub fn verbose(&self) -> bool {
         self.verbose > 0
     }
@@ -1358,3 +1383,116 @@ fn threads_from_config(v: u32) -> u32 {
         n => n,
     }
 }
+
+/// Returns the commit to download, or `None` if we shouldn't download CI artifacts.
+fn download_ci_rustc_commit(download_rustc: Option<StringOrBool>, verbose: bool) -> Option<String> {
+    // If `download-rustc` is not set, default to rebuilding.
+    let if_unchanged = match download_rustc {
+        None | Some(StringOrBool::Bool(false)) => return None,
+        Some(StringOrBool::Bool(true)) => false,
+        Some(StringOrBool::String(s)) if s == "if-unchanged" => true,
+        Some(StringOrBool::String(other)) => {
+            panic!("unrecognized option for download-rustc: {}", other)
+        }
+    };
+
+    // Handle running from a directory other than the top level
+    let top_level = output(Command::new("git").args(&["rev-parse", "--show-toplevel"]));
+    let top_level = top_level.trim_end();
+    let compiler = format!("{top_level}/compiler/");
+    let library = format!("{top_level}/library/");
+
+    // Look for a version to compare to based on the current commit.
+    // Only commits merged by bors will have CI artifacts.
+    let merge_base = output(Command::new("git").args(&[
+        "rev-list",
+        "--author=bors@rust-lang.org",
+        "-n1",
+        "--first-parent",
+        "HEAD",
+    ]));
+    let commit = merge_base.trim_end();
+    if commit.is_empty() {
+        println!("error: could not find commit hash for downloading rustc");
+        println!("help: maybe your repository history is too shallow?");
+        println!("help: consider disabling `download-rustc`");
+        println!("help: or fetch enough history to include one upstream commit");
+        exit(1);
+    }
+
+    // Warn if there were changes to the compiler or standard library since the ancestor commit.
+    let has_changes = !t!(Command::new("git")
+        .args(&["diff-index", "--quiet", &commit, "--", &compiler, &library])
+        .status())
+    .success();
+    if has_changes {
+        if if_unchanged {
+            if verbose {
+                println!(
+                    "warning: saw changes to compiler/ or library/ since {commit}; \
+                          ignoring `download-rustc`"
+                );
+            }
+            return None;
+        }
+        println!(
+            "warning: `download-rustc` is enabled, but there are changes to \
+                  compiler/ or library/"
+        );
+    }
+
+    Some(commit.to_string())
+}
+
+fn download_ci_rustc(builder: &Builder<'_>, commit: &str) {
+    builder.verbose(&format!("using downloaded stage2 artifacts from CI (commit {commit})"));
+    // FIXME: support downloading artifacts from the beta channel
+    const CHANNEL: &str = "nightly";
+    let host = builder.config.build.triple;
+    let bin_root = builder.out.join(host).join("ci-rustc");
+    let rustc_stamp = bin_root.join(".rustc-stamp");
+
+    if !bin_root.join("bin").join("rustc").exists() || program_out_of_date(&rustc_stamp, commit) {
+        if bin_root.exists() {
+            t!(fs::remove_dir_all(&bin_root));
+        }
+        let filename = format!("rust-std-{CHANNEL}-{host}.tar.xz");
+        let pattern = format!("rust-std-{host}");
+        download_component(builder, filename, &pattern, commit);
+        let filename = format!("rustc-{CHANNEL}-{host}.tar.xz");
+        download_component(builder, filename, "rustc", commit);
+        // download-rustc doesn't need its own cargo, it can just use beta's.
+        let filename = format!("rustc-dev-{CHANNEL}-{host}.tar.xz");
+        download_component(builder, filename, "rustc-dev", commit);
+
+        builder.fix_bin_or_dylib(&bin_root.join("bin").join("rustc"));
+        builder.fix_bin_or_dylib(&bin_root.join("bin").join("rustdoc"));
+        let lib_dir = bin_root.join("lib");
+        for lib in t!(fs::read_dir(lib_dir)) {
+            let lib = t!(lib);
+            if lib.path().extension() == Some(OsStr::new("so")) {
+                builder.fix_bin_or_dylib(&lib.path());
+            }
+        }
+        t!(fs::write(rustc_stamp, commit));
+    }
+}
+
+/// Download a single component of a CI-built toolchain (not necessarily a published nightly).
+// NOTE: intentionally takes an owned string to avoid downloading multiple times by accident
+fn download_component(builder: &Builder<'_>, filename: String, prefix: &str, commit: &str) {
+    let cache_dst = builder.out.join("cache");
+    let rustc_cache = cache_dst.join(commit);
+    if !rustc_cache.exists() {
+        t!(fs::create_dir_all(&rustc_cache));
+    }
+
+    let base = "https://ci-artifacts.rust-lang.org";
+    let url = format!("rustc-builds/{commit}");
+    let tarball = rustc_cache.join(&filename);
+    if !tarball.exists() {
+        builder.download_component(base, &format!("{url}/{filename}"), &tarball, "");
+    }
+    let bin_root = builder.out.join(builder.config.build.triple).join("ci-rustc");
+    builder.unpack(&tarball, &bin_root, prefix)
+}