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};
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 {
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,
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;
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),
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
}
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)
+}