diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-19 09:25:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-19 09:25:56 +0000 |
commit | 018c4950b9406055dec02ef0fb52f132e2bb1e2c (patch) | |
tree | a835ebdf2088ef88fa681f8fad45f09922c1ae9a /src/bootstrap | |
parent | Adding debian version 1.75.0+dfsg1-5. (diff) | |
download | rustc-018c4950b9406055dec02ef0fb52f132e2bb1e2c.tar.xz rustc-018c4950b9406055dec02ef0fb52f132e2bb1e2c.zip |
Merging upstream version 1.76.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bootstrap')
37 files changed, 1442 insertions, 620 deletions
diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock index 57113b0ec..f8e6d629b 100644 --- a/src/bootstrap/Cargo.lock +++ b/src/bootstrap/Cargo.lock @@ -55,7 +55,6 @@ dependencies = [ "cmake", "fd-lock", "filetime", - "hex", "home", "ignore", "junction", @@ -314,12 +313,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] name = "home" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -370,9 +363,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml index e4d359141..077d1954b 100644 --- a/src/bootstrap/Cargo.toml +++ b/src/bootstrap/Cargo.toml @@ -39,10 +39,9 @@ clap = { version = "4.4.7", default-features = false, features = ["std", "usage" clap_complete = "4.4.3" cmake = "0.1.38" filetime = "0.2" -hex = "0.4" home = "0.5.4" ignore = "0.4.10" -libc = "0.2" +libc = "0.2.150" object = { version = "0.32.0", default-features = false, features = ["archive", "coff", "read_core", "unaligned"] } once_cell = "1.7.2" opener = "0.5" diff --git a/src/bootstrap/README.md b/src/bootstrap/README.md index e7998a40a..d0a069f45 100644 --- a/src/bootstrap/README.md +++ b/src/bootstrap/README.md @@ -183,7 +183,7 @@ Some general areas that you may be interested in modifying are: If you make a major change on bootstrap configuration, please remember to: -+ Update `CONFIG_CHANGE_HISTORY` in `src/bootstrap/lib.rs`. ++ Update `CONFIG_CHANGE_HISTORY` in `src/bootstrap/src/utils/change_tracker.rs`. * Update `change-id = {pull-request-id}` in `config.example.toml`. A 'major change' includes diff --git a/src/bootstrap/bootstrap.py b/src/bootstrap/bootstrap.py index 5a84e37f8..fea194a80 100644 --- a/src/bootstrap/bootstrap.py +++ b/src/bootstrap/bootstrap.py @@ -616,22 +616,6 @@ class RustBuild(object): with output(self.rustc_stamp()) as rust_stamp: rust_stamp.write(key) - def _download_component_helper( - self, filename, pattern, tarball_suffix, rustc_cache, - ): - key = self.stage0_compiler.date - - tarball = os.path.join(rustc_cache, filename) - if not os.path.exists(tarball): - get( - self.download_url, - "dist/{}/{}".format(key, filename), - tarball, - self.checksums_sha256, - verbose=self.verbose, - ) - unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose) - def should_fix_bins_and_dylibs(self): """Whether or not `fix_bin_or_dylib` needs to be run; can only be True on NixOS or if config.toml has `build.patch-binaries-for-nix` set. @@ -946,6 +930,8 @@ class RustBuild(object): target_linker = self.get_toml("linker", build_section) if target_linker is not None: env["RUSTFLAGS"] += " -C linker=" + target_linker + # When changing this list, also update the corresponding list in `Builder::cargo` + # in `src/bootstrap/src/core/builder.rs`. env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes" if self.warnings == "default": deny_warnings = self.get_toml("deny-warnings", "rust") != "false" @@ -1083,6 +1069,11 @@ def bootstrap(args): include_file = 'config.{}.toml'.format(profile_aliases.get(profile) or profile) include_dir = os.path.join(rust_root, 'src', 'bootstrap', 'defaults') include_path = os.path.join(include_dir, include_file) + + if not os.path.exists(include_path): + raise Exception("Unrecognized config profile '{}'. Check src/bootstrap/defaults" + " for available options.".format(profile)) + # HACK: This works because `self.get_toml()` returns the first match it finds for a # specific key, so appending our defaults at the end allows the user to override them with open(include_path) as included_toml: diff --git a/src/bootstrap/defaults/config.compiler.toml b/src/bootstrap/defaults/config.compiler.toml index b98b13119..b27b524b8 100644 --- a/src/bootstrap/defaults/config.compiler.toml +++ b/src/bootstrap/defaults/config.compiler.toml @@ -17,4 +17,4 @@ lto = "off" [llvm] # Will download LLVM from CI if available on your platform. -download-ci-llvm = "if-available" +download-ci-llvm = "if-unchanged" diff --git a/src/bootstrap/defaults/config.library.toml b/src/bootstrap/defaults/config.library.toml index f362c4111..087544397 100644 --- a/src/bootstrap/defaults/config.library.toml +++ b/src/bootstrap/defaults/config.library.toml @@ -13,4 +13,4 @@ lto = "off" [llvm] # Will download LLVM from CI if available on your platform. -download-ci-llvm = "if-available" +download-ci-llvm = "if-unchanged" diff --git a/src/bootstrap/defaults/config.tools.toml b/src/bootstrap/defaults/config.tools.toml index 79424f28d..6e6c36600 100644 --- a/src/bootstrap/defaults/config.tools.toml +++ b/src/bootstrap/defaults/config.tools.toml @@ -21,4 +21,4 @@ compiler-docs = true [llvm] # Will download LLVM from CI if available on your platform. -download-ci-llvm = "if-available" +download-ci-llvm = "if-unchanged" diff --git a/src/bootstrap/src/bin/main.rs b/src/bootstrap/src/bin/main.rs index 0a6072ae1..b1ab8dae5 100644 --- a/src/bootstrap/src/bin/main.rs +++ b/src/bootstrap/src/bin/main.rs @@ -5,15 +5,18 @@ //! parent directory, and otherwise documentation can be found throughout the `build` //! directory in each respective module. -#[cfg(all(any(unix, windows), not(target_os = "solaris")))] use std::io::Write; #[cfg(all(any(unix, windows), not(target_os = "solaris")))] use std::process; -use std::{env, fs}; +use std::{ + env, + fs::{self, OpenOptions}, + io::{self, BufRead, BufReader, IsTerminal}, +}; -#[cfg(all(any(unix, windows), not(target_os = "solaris")))] -use bootstrap::t; -use bootstrap::{find_recent_config_change_ids, Build, Config, Subcommand, CONFIG_CHANGE_HISTORY}; +use bootstrap::{ + find_recent_config_change_ids, t, Build, Config, Subcommand, CONFIG_CHANGE_HISTORY, +}; fn main() { let args = env::args().skip(1).collect::<Vec<_>>(); @@ -23,35 +26,40 @@ fn main() { let mut build_lock; #[cfg(all(any(unix, windows), not(target_os = "solaris")))] let _build_lock_guard; - #[cfg(all(any(unix, windows), not(target_os = "solaris")))] - // Display PID of process holding the lock - // PID will be stored in a lock file - { - let path = config.out.join("lock"); - let pid = match fs::read_to_string(&path) { - Ok(contents) => contents, - Err(_) => String::new(), - }; - - build_lock = - fd_lock::RwLock::new(t!(fs::OpenOptions::new().write(true).create(true).open(&path))); - _build_lock_guard = match build_lock.try_write() { - Ok(mut lock) => { - t!(lock.write(&process::id().to_string().as_ref())); - lock - } - err => { - drop(err); - println!("WARNING: build directory locked by process {pid}, waiting for lock"); - let mut lock = t!(build_lock.write()); - t!(lock.write(&process::id().to_string().as_ref())); - lock - } - }; - } - #[cfg(any(not(any(unix, windows)), target_os = "solaris"))] - println!("WARNING: file locking not supported for target, not locking build directory"); + if !config.bypass_bootstrap_lock { + // Display PID of process holding the lock + // PID will be stored in a lock file + #[cfg(all(any(unix, windows), not(target_os = "solaris")))] + { + let path = config.out.join("lock"); + let pid = match fs::read_to_string(&path) { + Ok(contents) => contents, + Err(_) => String::new(), + }; + + build_lock = fd_lock::RwLock::new(t!(fs::OpenOptions::new() + .write(true) + .create(true) + .open(&path))); + _build_lock_guard = match build_lock.try_write() { + Ok(mut lock) => { + t!(lock.write(&process::id().to_string().as_ref())); + lock + } + err => { + drop(err); + println!("WARNING: build directory locked by process {pid}, waiting for lock"); + let mut lock = t!(build_lock.write()); + t!(lock.write(&process::id().to_string().as_ref())); + lock + } + }; + } + + #[cfg(any(not(any(unix, windows)), target_os = "solaris"))] + println!("WARNING: file locking not supported for target, not locking build directory"); + } // check_version warnings are not printed during setup let changelog_suggestion = @@ -71,6 +79,9 @@ fn main() { } let pre_commit = config.src.join(".git").join("hooks").join("pre-commit"); + let dump_bootstrap_shims = config.dump_bootstrap_shims; + let out_dir = config.out.clone(); + Build::new(config).build(); if suggest_setup { @@ -99,6 +110,29 @@ fn main() { if suggest_setup || changelog_suggestion.is_some() { println!("NOTE: this message was printed twice to make it more likely to be seen"); } + + if dump_bootstrap_shims { + let dump_dir = out_dir.join("bootstrap-shims-dump"); + assert!(dump_dir.exists()); + + for entry in walkdir::WalkDir::new(&dump_dir) { + let entry = t!(entry); + + if !entry.file_type().is_file() { + continue; + } + + let file = t!(fs::File::open(&entry.path())); + + // To ensure deterministic results we must sort the dump lines. + // This is necessary because the order of rustc invocations different + // almost all the time. + let mut lines: Vec<String> = t!(BufReader::new(&file).lines().collect()); + lines.sort_by_key(|t| t.to_lowercase()); + let mut file = t!(OpenOptions::new().write(true).truncate(true).open(&entry.path())); + t!(file.write_all(lines.join("\n").as_bytes())); + } + } } fn check_version(config: &Config) -> Option<String> { @@ -108,35 +142,46 @@ fn check_version(config: &Config) -> Option<String> { msg.push_str("WARNING: The use of `changelog-seen` is deprecated. Please refer to `change-id` option in `config.example.toml` instead.\n"); } - let latest_config_id = CONFIG_CHANGE_HISTORY.last().unwrap(); + let latest_change_id = CONFIG_CHANGE_HISTORY.last().unwrap().change_id; + let warned_id_path = config.out.join("bootstrap").join(".last-warned-change-id"); + if let Some(id) = config.change_id { - if &id == latest_config_id { + if id == latest_change_id { return None; } - let change_links: Vec<String> = find_recent_config_change_ids(id) - .iter() - .map(|id| format!("https://github.com/rust-lang/rust/pull/{id}")) - .collect(); - if !change_links.is_empty() { - msg.push_str("WARNING: there have been changes to x.py since you last updated.\n"); - msg.push_str("To see more detail about these changes, visit the following PRs:\n"); - - for link in change_links { - msg.push_str(&format!(" - {link}\n")); + if let Ok(last_warned_id) = fs::read_to_string(&warned_id_path) { + if latest_change_id.to_string() == last_warned_id { + return None; } + } + + let changes = find_recent_config_change_ids(id); + + if !changes.is_empty() { + msg.push_str("There have been changes to x.py since you last updated:\n"); - msg.push_str("WARNING: there have been changes to x.py since you last updated.\n"); + for change in changes { + msg.push_str(&format!(" [{}] {}\n", change.severity.to_string(), change.summary)); + msg.push_str(&format!( + " - PR Link https://github.com/rust-lang/rust/pull/{}\n", + change.change_id + )); + } msg.push_str("NOTE: to silence this warning, "); msg.push_str(&format!( - "update `config.toml` to use `change-id = {latest_config_id}` instead" + "update `config.toml` to use `change-id = {latest_change_id}` instead" )); + + if io::stdout().is_terminal() && !config.dry_run() { + t!(fs::write(warned_id_path, latest_change_id.to_string())); + } } } else { msg.push_str("WARNING: The `change-id` is missing in the `config.toml`. This means that you will not be able to track the major changes made to the bootstrap configurations.\n"); msg.push_str("NOTE: to silence this warning, "); - msg.push_str(&format!("add `change-id = {latest_config_id}` at the top of `config.toml`")); + msg.push_str(&format!("add `change-id = {latest_change_id}` at the top of `config.toml`")); }; Some(msg) diff --git a/src/bootstrap/src/bin/rustc.rs b/src/bootstrap/src/bin/rustc.rs index 070a2da6a..38c55b203 100644 --- a/src/bootstrap/src/bin/rustc.rs +++ b/src/bootstrap/src/bin/rustc.rs @@ -16,11 +16,11 @@ //! never get replaced. use std::env; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::{Child, Command}; use std::time::Instant; -use dylib_util::{dylib_path, dylib_path_var}; +use dylib_util::{dylib_path, dylib_path_var, exe}; #[path = "../utils/bin_helpers.rs"] mod bin_helpers; @@ -29,12 +29,12 @@ mod bin_helpers; mod dylib_util; fn main() { - let args = env::args_os().skip(1).collect::<Vec<_>>(); - let arg = |name| args.windows(2).find(|args| args[0] == name).and_then(|args| args[1].to_str()); + let orig_args = env::args_os().skip(1).collect::<Vec<_>>(); + let mut args = orig_args.clone(); + let arg = + |name| orig_args.windows(2).find(|args| args[0] == name).and_then(|args| args[1].to_str()); - // We don't use the stage in this shim, but let's parse it to make sure that we're invoked - // by bootstrap, or that we provide a helpful error message if not. - bin_helpers::parse_rustc_stage(); + let stage = bin_helpers::parse_rustc_stage(); let verbose = bin_helpers::parse_rustc_verbose(); // Detect whether or not we're a build script depending on whether --target @@ -47,7 +47,8 @@ fn main() { // determine the version of the compiler, the real compiler needs to be // used. Currently, these two states are differentiated based on whether // --target and -vV is/isn't passed. - let (rustc, libdir) = if target.is_none() && version.is_none() { + let is_build_script = target.is_none() && version.is_none(); + let (rustc, libdir) = if is_build_script { ("RUSTC_SNAPSHOT", "RUSTC_SNAPSHOT_LIBDIR") } else { ("RUSTC_REAL", "RUSTC_LIBDIR") @@ -56,12 +57,47 @@ fn main() { let sysroot = env::var_os("RUSTC_SYSROOT").expect("RUSTC_SYSROOT was not set"); let on_fail = env::var_os("RUSTC_ON_FAIL").map(Command::new); - let rustc = env::var_os(rustc).unwrap_or_else(|| panic!("{:?} was not set", rustc)); + let rustc_real = env::var_os(rustc).unwrap_or_else(|| panic!("{:?} was not set", rustc)); let libdir = env::var_os(libdir).unwrap_or_else(|| panic!("{:?} was not set", libdir)); let mut dylib_path = dylib_path(); dylib_path.insert(0, PathBuf::from(&libdir)); - let mut cmd = Command::new(rustc); + // if we're running clippy, trust cargo-clippy to set clippy-driver appropriately (and don't override it with rustc). + // otherwise, substitute whatever cargo thinks rustc should be with RUSTC_REAL. + // NOTE: this means we ignore RUSTC in the environment. + // FIXME: We might want to consider removing RUSTC_REAL and setting RUSTC directly? + // NOTE: we intentionally pass the name of the host, not the target. + let host = env::var("CFG_COMPILER_BUILD_TRIPLE").unwrap(); + let is_clippy = args[0].to_string_lossy().ends_with(&exe("clippy-driver", &host)); + let rustc_driver = if is_clippy { + if is_build_script { + // Don't run clippy on build scripts (for one thing, we may not have libstd built with + // the appropriate version yet, e.g. for stage 1 std). + // Also remove the `clippy-driver` param in addition to the RUSTC param. + args.drain(..2); + rustc_real + } else { + args.remove(0) + } + } else { + // Cargo doesn't respect RUSTC_WRAPPER for version information >:( + // don't remove the first arg if we're being run as RUSTC instead of RUSTC_WRAPPER. + // Cargo also sometimes doesn't pass the `.exe` suffix on Windows - add it manually. + let current_exe = env::current_exe().expect("couldn't get path to rustc shim"); + let arg0 = exe(args[0].to_str().expect("only utf8 paths are supported"), &host); + if Path::new(&arg0) == current_exe { + args.remove(0); + } + rustc_real + }; + + let mut cmd = if let Some(wrapper) = env::var_os("RUSTC_WRAPPER_REAL") { + let mut cmd = Command::new(wrapper); + cmd.arg(rustc_driver); + cmd + } else { + Command::new(rustc_driver) + }; cmd.args(&args).env(dylib_path_var(), env::join_paths(&dylib_path).unwrap()); // Get the name of the crate we're compiling, if any. @@ -114,7 +150,7 @@ fn main() { { cmd.arg("-Ztls-model=initial-exec"); } - } else { + } else if std::env::var("MIRI").is_err() { // Find any host flags that were passed by bootstrap. // The flags are stored in a RUSTC_HOST_FLAGS variable, separated by spaces. if let Ok(flags) = std::env::var("RUSTC_HOST_FLAGS") { @@ -214,6 +250,8 @@ fn main() { } } + bin_helpers::maybe_dump(format!("stage{stage}-rustc"), &cmd); + let start = Instant::now(); let (child, status) = { let errmsg = format!("\nFailed to run:\n{cmd:?}\n-------------"); diff --git a/src/bootstrap/src/bin/rustdoc.rs b/src/bootstrap/src/bin/rustdoc.rs index dbbce6fe2..b4d141518 100644 --- a/src/bootstrap/src/bin/rustdoc.rs +++ b/src/bootstrap/src/bin/rustdoc.rs @@ -3,7 +3,6 @@ //! See comments in `src/bootstrap/rustc.rs` for more information. use std::env; -use std::ffi::OsString; use std::path::PathBuf; use std::process::Command; @@ -52,15 +51,6 @@ fn main() { if env::var_os("RUSTC_FORCE_UNSTABLE").is_some() { cmd.arg("-Z").arg("force-unstable-if-unmarked"); } - if let Some(linker) = env::var_os("RUSTDOC_LINKER") { - let mut arg = OsString::from("-Clinker="); - arg.push(&linker); - cmd.arg(arg); - } - if let Ok(no_threads) = env::var("RUSTDOC_LLD_NO_THREADS") { - cmd.arg("-Clink-arg=-fuse-ld=lld"); - cmd.arg(format!("-Clink-arg=-Wl,{no_threads}")); - } // Cargo doesn't pass RUSTDOCFLAGS to proc_macros: // https://github.com/rust-lang/cargo/issues/4423 // Thus, if we are on stage 0, we explicitly set `--cfg=bootstrap`. @@ -70,9 +60,9 @@ fn main() { cmd.arg("--cfg=bootstrap"); } cmd.arg("-Zunstable-options"); - // #[cfg(bootstrap)] - cmd.arg("--check-cfg=values(bootstrap)"); - // cmd.arg("--check-cfg=cfg(bootstrap)"); + cmd.arg("--check-cfg=cfg(bootstrap)"); + + bin_helpers::maybe_dump(format!("stage{stage}-rustdoc"), &cmd); if verbose > 1 { eprintln!( diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index 121925b56..ecaaf91ae 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -376,12 +376,12 @@ impl Step for RustAnalyzer { let compiler = builder.compiler(builder.top_stage, builder.config.build); let target = self.target; - builder.ensure(Std::new(target)); + builder.ensure(Rustc::new(target, builder)); let mut cargo = prepare_tool_cargo( builder, compiler, - Mode::ToolStd, + Mode::ToolRustc, target, cargo_subcommand(builder.kind), "src/tools/rust-analyzer", @@ -414,7 +414,7 @@ impl Step for RustAnalyzer { /// Cargo's output path in a given stage, compiled by a particular /// compiler for the specified target. fn stamp(builder: &Builder<'_>, compiler: Compiler, target: TargetSelection) -> PathBuf { - builder.cargo_out(compiler, Mode::ToolStd, target).join(".rust-analyzer-check.stamp") + builder.cargo_out(compiler, Mode::ToolRustc, target).join(".rust-analyzer-check.stamp") } } } @@ -463,10 +463,6 @@ macro_rules! tool_check_step { cargo.arg("--all-targets"); } - // Enable internal lints for clippy and rustdoc - // NOTE: this doesn't enable lints for any other tools unless they explicitly add `#![warn(rustc::internal)]` - // See https://github.com/rust-lang/rust/pull/80573#issuecomment-754010776 - cargo.rustflag("-Zunstable-options"); let _guard = builder.msg_check(&concat!(stringify!($name), " artifacts").to_lowercase(), target); run_cargo( builder, diff --git a/src/bootstrap/src/core/build_steps/clean.rs b/src/bootstrap/src/core/build_steps/clean.rs index cbb6b5f46..4b993945f 100644 --- a/src/bootstrap/src/core/build_steps/clean.rs +++ b/src/bootstrap/src/core/build_steps/clean.rs @@ -145,10 +145,18 @@ fn clean_specific_stage(build: &Build, stage: u32) { fn clean_default(build: &Build) { rm_rf(&build.out.join("tmp")); rm_rf(&build.out.join("dist")); + rm_rf(&build.out.join("bootstrap").join(".last-warned-change-id")); + rm_rf(&build.out.join("bootstrap-shims-dump")); rm_rf(&build.out.join("rustfmt.stamp")); - for host in &build.hosts { - let entries = match build.out.join(host.triple).read_dir() { + let mut hosts: Vec<_> = build.hosts.iter().map(|t| build.out.join(t.triple)).collect(); + // After cross-compilation, artifacts of the host architecture (which may differ from build.host) + // might not get removed. + // Adding its path (linked one for easier accessibility) will solve this problem. + hosts.push(build.out.join("host")); + + for host in hosts { + let entries = match host.read_dir() { Ok(iter) => iter, Err(_) => continue, }; diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 7021a9543..df4d1a43d 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -592,7 +592,9 @@ impl Step for StdLink { .join("stage0/lib/rustlib") .join(&host) .join("codegen-backends"); - builder.cp_r(&stage0_codegen_backends, &sysroot_codegen_backends); + if stage0_codegen_backends.exists() { + builder.cp_r(&stage0_codegen_backends, &sysroot_codegen_backends); + } } } } @@ -868,7 +870,7 @@ impl Step for Rustc { // is already on by default in MSVC optimized builds, which is interpreted as --icf=all: // https://github.com/llvm/llvm-project/blob/3329cec2f79185bafd678f310fafadba2a8c76d2/lld/COFF/Driver.cpp#L1746 // https://github.com/rust-lang/rust/blob/f22819bcce4abaff7d1246a56eec493418f9f4ee/compiler/rustc_codegen_ssa/src/back/linker.rs#L827 - if builder.config.use_lld && !compiler.host.contains("msvc") { + if builder.config.lld_mode.is_used() && !compiler.host.is_msvc() { cargo.rustflag("-Clink-args=-Wl,--icf=all"); } @@ -885,7 +887,9 @@ impl Step for Rustc { } else if let Some(path) = &builder.config.rust_profile_use { if compiler.stage == 1 { cargo.rustflag(&format!("-Cprofile-use={path}")); - cargo.rustflag("-Cllvm-args=-pgo-warn-missing-function"); + if builder.is_verbose() { + cargo.rustflag("-Cllvm-args=-pgo-warn-missing-function"); + } true } else { false @@ -1001,6 +1005,13 @@ pub fn rustc_cargo_env( .env("CFG_RELEASE_CHANNEL", &builder.config.channel) .env("CFG_VERSION", builder.rust_version()); + // Some tools like Cargo detect their own git information in build scripts. When omit-git-hash + // is enabled in config.toml, we pass this environment variable to tell build scripts to avoid + // detecting git information on their own. + if builder.config.omit_git_hash { + cargo.env("CFG_OMIT_GIT_HASH", "1"); + } + if let Some(backend) = builder.config.default_codegen_backend() { cargo.env("CFG_DEFAULT_CODEGEN_BACKEND", backend); } @@ -1078,7 +1089,7 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect // found. This is to avoid the linker errors about undefined references to // `__llvm_profile_instrument_memop` when linking `rustc_driver`. let mut llvm_linker_flags = String::new(); - if builder.config.llvm_profile_generate && target.contains("msvc") { + if builder.config.llvm_profile_generate && target.is_msvc() { if let Some(ref clang_cl_path) = builder.config.llvm_clang_cl { // Add clang's runtime library directory to the search path let clang_rt_dir = get_clang_cl_resource_dir(clang_cl_path); @@ -1103,7 +1114,7 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect // not for MSVC or macOS if builder.config.llvm_static_stdcpp && !target.contains("freebsd") - && !target.contains("msvc") + && !target.is_msvc() && !target.contains("apple") && !target.contains("solaris") { @@ -1711,7 +1722,7 @@ impl Step for Assemble { let dst_exe = exe("rust-lld", target_compiler.host); builder.copy(&lld_install.join("bin").join(&src_exe), &libdir_bin.join(&dst_exe)); let self_contained_lld_dir = libdir_bin.join("gcc-ld"); - t!(fs::create_dir(&self_contained_lld_dir)); + t!(fs::create_dir_all(&self_contained_lld_dir)); let lld_wrapper_exe = builder.ensure(crate::core::build_steps::tool::LldWrapper { compiler: build_compiler, target: target_compiler.host, @@ -1798,10 +1809,6 @@ pub fn run_cargo( is_check: bool, rlib_only_metadata: bool, ) -> Vec<PathBuf> { - if builder.config.dry_run() { - return Vec::new(); - } - // `target_root_dir` looks like $dir/$target/release let target_root_dir = stamp.parent().unwrap(); // `target_deps_dir` looks like $dir/$target/release/deps @@ -1908,6 +1915,10 @@ pub fn run_cargo( crate::exit!(1); } + if builder.config.dry_run() { + return Vec::new(); + } + // Ok now we need to actually find all the files listed in `toplevel`. We've // got a list of prefix/extensions and we basically just need to find the // most recent file in the `deps` folder corresponding to each one. @@ -1963,9 +1974,6 @@ pub fn stream_cargo( cb: &mut dyn FnMut(CargoMessage<'_>), ) -> bool { let mut cargo = Command::from(cargo); - if builder.config.dry_run() { - return true; - } // Instruct Cargo to give us json messages on stdout, critically leaving // stderr as piped so we can get those pretty colors. let mut message_format = if builder.config.json_output { @@ -1984,6 +1992,11 @@ pub fn stream_cargo( } builder.verbose(&format!("running: {cargo:?}")); + + if builder.config.dry_run() { + return true; + } + let mut child = match cargo.spawn() { Ok(child) => child, Err(e) => panic!("failed to execute command: {cargo:?}\nERROR: {e}"), diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index c485481b9..98e267713 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -1319,7 +1319,7 @@ impl Step for CodegenBackend { return None; } - if self.compiler.host.contains("windows") { + if self.compiler.host.is_windows() { builder.info( "dist currently disabled for windows by rustc_codegen_cranelift. skipping", ); @@ -1658,7 +1658,7 @@ impl Step for Extended { builder.run(&mut cmd); } - if target.contains("windows") { + if target.is_windows() { let exe = tmp.join("exe"); let _ = fs::remove_dir_all(&exe); diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index e3fd942fc..cf3f5bc98 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -7,8 +7,9 @@ //! Everything here is basically just a shim around calling either `rustbook` or //! `rustdoc`. -use std::fs; +use std::io::{self, Write}; use std::path::{Path, PathBuf}; +use std::{fs, mem}; use crate::core::build_steps::compile; use crate::core::build_steps::tool::{self, prepare_tool_cargo, SourceType, Tool}; @@ -388,6 +389,104 @@ impl Step for Standalone { } } +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct Releases { + compiler: Compiler, + target: TargetSelection, +} + +impl Step for Releases { + type Output = (); + const DEFAULT: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + let builder = run.builder; + run.path("RELEASES.md").alias("releases").default_condition(builder.config.docs) + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(Releases { + compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.build), + target: run.target, + }); + } + + /// Generates HTML release notes to include in the final docs bundle. + /// + /// This uses the same stylesheet and other tools as Standalone, but the + /// RELEASES.md file is included at the root of the repository and gets + /// the headline added. In the end, the conversion is done by Rustdoc. + fn run(self, builder: &Builder<'_>) { + let target = self.target; + let compiler = self.compiler; + let _guard = builder.msg_doc(compiler, "releases", target); + let out = builder.doc_out(target); + t!(fs::create_dir_all(&out)); + + builder.ensure(Standalone { + compiler: builder.compiler(builder.top_stage, builder.config.build), + target, + }); + + let version_info = builder.ensure(SharedAssets { target: self.target }).version_info; + + let favicon = builder.src.join("src/doc/favicon.inc"); + let footer = builder.src.join("src/doc/footer.inc"); + let full_toc = builder.src.join("src/doc/full-toc.inc"); + + let html = out.join("releases.html"); + let tmppath = out.join("releases.md"); + let inpath = builder.src.join("RELEASES.md"); + let rustdoc = builder.rustdoc(compiler); + if !up_to_date(&inpath, &html) + || !up_to_date(&footer, &html) + || !up_to_date(&favicon, &html) + || !up_to_date(&full_toc, &html) + || !(builder.config.dry_run() + || up_to_date(&version_info, &html) + || up_to_date(&rustdoc, &html)) + { + let mut tmpfile = t!(fs::File::create(&tmppath)); + t!(tmpfile.write_all(b"% Rust Release Notes\n\n")); + t!(io::copy(&mut t!(fs::File::open(&inpath)), &mut tmpfile)); + mem::drop(tmpfile); + let mut cmd = builder.rustdoc_cmd(compiler); + + // Needed for --index-page flag + cmd.arg("-Z").arg("unstable-options"); + + cmd.arg("--html-after-content") + .arg(&footer) + .arg("--html-before-content") + .arg(&version_info) + .arg("--html-in-header") + .arg(&favicon) + .arg("--markdown-no-toc") + .arg("--markdown-css") + .arg("rust.css") + .arg("--index-page") + .arg(&builder.src.join("src/doc/index.md")) + .arg("--markdown-playground-url") + .arg("https://play.rust-lang.org/") + .arg("-o") + .arg(&out) + .arg(&tmppath); + + if !builder.config.docs_minification { + cmd.arg("--disable-minification"); + } + + builder.run(&mut cmd); + } + + // We open doc/RELEASES.html as the default if invoked as `x.py doc --open RELEASES.md` + // with no particular explicit doc requested (e.g. library/core). + if builder.was_invoked_explicitly::<Self>(Kind::Doc) { + builder.open_in_browser(&html); + } + } +} + #[derive(Debug, Clone)] pub struct SharedAssetsPaths { pub version_info: PathBuf, @@ -897,15 +996,14 @@ tool_doc!( in_tree = false, crates = [ "cargo", + "cargo-credential", "cargo-platform", - "cargo-util", - "crates-io", "cargo-test-macro", "cargo-test-support", - "cargo-credential", + "cargo-util", + "crates-io", "mdman", - // FIXME: this trips a license check in tidy. - // "resolver-tests", + "rustfix", ] ); tool_doc!(Tidy, "tidy", "src/tools/tidy", rustc_tool = false, crates = ["tidy"]); diff --git a/src/bootstrap/src/core/build_steps/format.rs b/src/bootstrap/src/core/build_steps/format.rs index 86f1d925f..e792d38b7 100644 --- a/src/bootstrap/src/core/build_steps/format.rs +++ b/src/bootstrap/src/core/build_steps/format.rs @@ -142,14 +142,17 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) { }; if in_working_tree { let untracked_paths_output = output( - build.config.git().arg("status").arg("--porcelain").arg("--untracked-files=normal"), + build + .config + .git() + .arg("status") + .arg("--porcelain") + .arg("-z") + .arg("--untracked-files=normal"), + ); + let untracked_paths = untracked_paths_output.split_terminator('\0').filter_map( + |entry| entry.strip_prefix("?? "), // returns None if the prefix doesn't match ); - let untracked_paths = untracked_paths_output - .lines() - .filter(|entry| entry.starts_with("??")) - .map(|entry| { - entry.split(' ').nth(1).expect("every git status entry should list a path") - }); let mut untracked_count = 0; for untracked_path in untracked_paths { println!("skip untracked path {untracked_path} during rustfmt invocations"); diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index a1f6fac8a..4b2d3e9ab 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -15,12 +15,13 @@ use std::fs::{self, File}; use std::io; use std::path::{Path, PathBuf}; use std::process::Command; +use std::sync::OnceLock; use crate::core::builder::{Builder, RunConfig, ShouldRun, Step}; use crate::core::config::{Config, TargetSelection}; use crate::utils::channel; use crate::utils::helpers::{self, exe, get_clang_cl_resource_dir, output, t, up_to_date}; -use crate::{CLang, GitRepo, Kind}; +use crate::{generate_smart_stamp_hash, CLang, GitRepo, Kind}; use build_helper::ci::CiEnv; use build_helper::git::get_git_merge_base; @@ -97,7 +98,7 @@ pub fn prebuilt_llvm_config( let out_dir = builder.llvm_out(target); let mut llvm_config_ret_dir = builder.llvm_out(builder.config.build); - if !builder.config.build.contains("msvc") || builder.ninja() { + if !builder.config.build.is_msvc() || builder.ninja() { llvm_config_ret_dir.push("build"); } llvm_config_ret_dir.push("bin"); @@ -105,8 +106,16 @@ pub fn prebuilt_llvm_config( let llvm_cmake_dir = out_dir.join("lib/cmake/llvm"); let res = LlvmResult { llvm_config: build_llvm_config, llvm_cmake_dir }; + static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new(); + let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| { + generate_smart_stamp_hash( + &builder.config.src.join("src/llvm-project"), + &builder.in_tree_llvm_info.sha().unwrap_or_default(), + ) + }); + let stamp = out_dir.join("llvm-finished-building"); - let stamp = HashStamp::new(stamp, builder.in_tree_llvm_info.sha()); + let stamp = HashStamp::new(stamp, Some(smart_stamp_hash)); if stamp.is_done() { if stamp.hash.is_none() { @@ -274,7 +283,7 @@ impl Step for Llvm { }; builder.update_submodule(&Path::new("src").join("llvm-project")); - if builder.llvm_link_shared() && target.contains("windows") { + if builder.llvm_link_shared() && target.is_windows() { panic!("shared linking to LLVM is not currently supported on {}", target.triple); } @@ -352,7 +361,7 @@ impl Step for Llvm { // Disable zstd to avoid a dependency on libzstd.so. cfg.define("LLVM_ENABLE_ZSTD", "OFF"); - if !target.contains("windows") { + if !target.is_windows() { cfg.define("LLVM_ENABLE_ZLIB", "ON"); } else { cfg.define("LLVM_ENABLE_ZLIB", "OFF"); @@ -402,7 +411,7 @@ impl Step for Llvm { ldflags.shared.push(" -latomic"); } - if target.contains("msvc") { + if target.is_msvc() { cfg.define("LLVM_USE_CRT_DEBUG", "MT"); cfg.define("LLVM_USE_CRT_RELEASE", "MT"); cfg.define("LLVM_USE_CRT_RELWITHDEBINFO", "MT"); @@ -560,11 +569,11 @@ fn check_llvm_version(builder: &Builder<'_>, llvm_config: &Path) { let version = output(cmd.arg("--version")); let mut parts = version.split('.').take(2).filter_map(|s| s.parse::<u32>().ok()); if let (Some(major), Some(_minor)) = (parts.next(), parts.next()) { - if major >= 15 { + if major >= 16 { return; } } - panic!("\n\nbad LLVM version: {version}, need >=15.0\n\n") + panic!("\n\nbad LLVM version: {version}, need >=16.0\n\n") } fn configure_cmake( @@ -598,7 +607,7 @@ fn configure_cmake( cfg.define("CMAKE_SYSTEM_NAME", "DragonFly"); } else if target.contains("freebsd") { cfg.define("CMAKE_SYSTEM_NAME", "FreeBSD"); - } else if target.contains("windows") { + } else if target.is_windows() { cfg.define("CMAKE_SYSTEM_NAME", "Windows"); } else if target.contains("haiku") { cfg.define("CMAKE_SYSTEM_NAME", "Haiku"); @@ -635,7 +644,7 @@ fn configure_cmake( } let sanitize_cc = |cc: &Path| { - if target.contains("msvc") { + if target.is_msvc() { OsString::from(cc.to_str().unwrap().replace("\\", "/")) } else { cc.as_os_str().to_owned() @@ -645,7 +654,7 @@ fn configure_cmake( // MSVC with CMake uses msbuild by default which doesn't respect these // vars that we'd otherwise configure. In that case we just skip this // entirely. - if target.contains("msvc") && !builder.ninja() { + if target.is_msvc() && !builder.ninja() { return; } @@ -655,7 +664,7 @@ fn configure_cmake( }; // Handle msvc + ninja + ccache specially (this is what the bots use) - if target.contains("msvc") && builder.ninja() && builder.config.ccache.is_some() { + if target.is_msvc() && builder.ninja() && builder.config.ccache.is_some() { let mut wrap_cc = env::current_exe().expect("failed to get cwd"); wrap_cc.set_file_name("sccache-plus-cl.exe"); @@ -759,11 +768,11 @@ fn configure_cmake( // For distribution we want the LLVM tools to be *statically* linked to libstdc++. // We also do this if the user explicitly requested static libstdc++. if builder.config.llvm_static_stdcpp - && !target.contains("msvc") + && !target.is_msvc() && !target.contains("netbsd") && !target.contains("solaris") { - if target.contains("apple") || target.contains("windows") { + if target.contains("apple") || target.is_windows() { ldflags.push_all("-static-libstdc++"); } else { ldflags.push_all("-Wl,-Bsymbolic -static-libstdc++"); @@ -865,7 +874,7 @@ impl Step for Lld { // when doing PGO on CI, cmake or clang-cl don't automatically link clang's // profiler runtime in. In that case, we need to manually ask cmake to do it, to avoid // linking errors, much like LLVM's cmake setup does in that situation. - if builder.config.llvm_profile_generate && target.contains("msvc") { + if builder.config.llvm_profile_generate && target.is_msvc() { if let Some(clang_cl_path) = builder.config.llvm_clang_cl.as_ref() { // Find clang's runtime library directory and push that as a search path to the // cmake linker flags. @@ -1286,7 +1295,7 @@ impl Step for Libunwind { cfg.define("__LIBUNWIND_IS_NATIVE_ONLY", None); cfg.define("NDEBUG", None); } - if self.target.contains("windows") { + if self.target.is_windows() { cfg.define("_LIBUNWIND_HIDE_SYMBOLS", "1"); cfg.define("_LIBUNWIND_IS_NATIVE_ONLY", "1"); } diff --git a/src/bootstrap/src/core/build_steps/setup.rs b/src/bootstrap/src/core/build_steps/setup.rs index 486a1e20f..9c897ae1b 100644 --- a/src/bootstrap/src/core/build_steps/setup.rs +++ b/src/bootstrap/src/core/build_steps/setup.rs @@ -1,6 +1,8 @@ use crate::core::builder::{Builder, RunConfig, ShouldRun, Step}; +use crate::t; +use crate::utils::change_tracker::CONFIG_CHANGE_HISTORY; +use crate::utils::helpers::hex_encode; use crate::Config; -use crate::{t, CONFIG_CHANGE_HISTORY}; use sha2::Digest; use std::env::consts::EXE_SUFFIX; use std::fmt::Write as _; @@ -226,7 +228,7 @@ fn setup_config_toml(path: &PathBuf, profile: Profile, config: &Config) { return; } - let latest_change_id = CONFIG_CHANGE_HISTORY.last().unwrap(); + let latest_change_id = CONFIG_CHANGE_HISTORY.last().unwrap().change_id; let settings = format!( "# Includes one of the default files in src/bootstrap/defaults\n\ profile = \"{profile}\"\n\ @@ -548,12 +550,13 @@ impl Step for Vscode { if config.dry_run() { return; } - t!(create_vscode_settings_maybe(&config)); + while !t!(create_vscode_settings_maybe(&config)) {} } } /// Create a `.vscode/settings.json` file for rustc development, or just print it -fn create_vscode_settings_maybe(config: &Config) -> io::Result<()> { +/// If this method should be re-called, it returns `false`. +fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> { let (current_hash, historical_hashes) = SETTINGS_HASHES.split_last().unwrap(); let vscode_settings = config.src.join(".vscode").join("settings.json"); // If None, no settings.json exists @@ -564,9 +567,9 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result<()> { if let Ok(current) = fs::read_to_string(&vscode_settings) { let mut hasher = sha2::Sha256::new(); hasher.update(¤t); - let hash = hex::encode(hasher.finalize().as_slice()); + let hash = hex_encode(hasher.finalize().as_slice()); if hash == *current_hash { - return Ok(()); + return Ok(true); } else if historical_hashes.contains(&hash.as_str()) { mismatched_settings = Some(true); } else { @@ -586,13 +589,13 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result<()> { _ => (), } let should_create = match prompt_user( - "Would you like to create/update `settings.json`, or only print suggested settings?: [y/p/N]", + "Would you like to create/update settings.json? (Press 'p' to preview values): [y/N]", )? { Some(PromptResult::Yes) => true, Some(PromptResult::Print) => false, _ => { println!("Ok, skipping settings!"); - return Ok(()); + return Ok(true); } }; if should_create { @@ -619,5 +622,5 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result<()> { } else { println!("\n{RUST_ANALYZER_SETTINGS}"); } - Ok(()) + Ok(should_create) } diff --git a/src/bootstrap/src/core/build_steps/synthetic_targets.rs b/src/bootstrap/src/core/build_steps/synthetic_targets.rs index d2c65b740..9acdcaeb5 100644 --- a/src/bootstrap/src/core/build_steps/synthetic_targets.rs +++ b/src/bootstrap/src/core/build_steps/synthetic_targets.rs @@ -59,6 +59,11 @@ fn create_synthetic_target( let mut cmd = Command::new(builder.rustc(compiler)); cmd.arg("--target").arg(base.rustc_target_arg()); cmd.args(["-Zunstable-options", "--print", "target-spec-json"]); + + // If `rust.channel` is set to either beta or stable, rustc will complain that + // we cannot use nightly features. So `RUSTC_BOOTSTRAP` is needed here. + cmd.env("RUSTC_BOOTSTRAP", "1"); + cmd.stdout(Stdio::piped()); let output = cmd.spawn().unwrap().wait_with_output().unwrap(); diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index d2aa89dee..4eb776625 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -29,8 +29,9 @@ use crate::utils; use crate::utils::cache::{Interned, INTERNER}; use crate::utils::exec::BootstrapCommand; use crate::utils::helpers::{ - self, add_link_lib_path, dylib_path, dylib_path_var, output, t, - target_supports_cranelift_backend, up_to_date, + self, add_link_lib_path, add_rustdoc_cargo_linker_args, dylib_path, dylib_path_var, + linker_args, linker_flags, output, t, target_supports_cranelift_backend, up_to_date, + LldThreads, }; use crate::utils::render_tests::{add_flags_and_try_run_tests, try_run_tests}; use crate::{envify, CLang, DocTests, GitRepo, Mode}; @@ -49,9 +50,9 @@ const MIR_OPT_BLESS_TARGET_MAPPING: &[(&str, &str)] = &[ ("i686-unknown-linux-musl", "x86_64-unknown-linux-musl"), ("i686-pc-windows-msvc", "x86_64-pc-windows-msvc"), ("i686-pc-windows-gnu", "x86_64-pc-windows-gnu"), - ("i686-apple-darwin", "x86_64-apple-darwin"), // ARM Macs don't have a corresponding 32-bit target that they can (easily) // build for, so there is no entry for "aarch64-apple-darwin" here. + // Likewise, i686 for macOS is no longer possible to build. ]; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -271,13 +272,14 @@ impl Step for Cargotest { let _time = helpers::timeit(&builder); let mut cmd = builder.tool_cmd(Tool::CargoTest); - builder.run_delaying_failure( - cmd.arg(&cargo) - .arg(&out_dir) - .args(builder.config.test_args()) - .env("RUSTC", builder.rustc(compiler)) - .env("RUSTDOC", builder.rustdoc(compiler)), - ); + let mut cmd = cmd + .arg(&cargo) + .arg(&out_dir) + .args(builder.config.test_args()) + .env("RUSTC", builder.rustc(compiler)) + .env("RUSTDOC", builder.rustdoc(compiler)); + add_rustdoc_cargo_linker_args(&mut cmd, builder, compiler.host, LldThreads::No); + builder.run_delaying_failure(cmd); } } @@ -369,7 +371,7 @@ impl Step for RustAnalyzer { // We don't need to build the whole Rust Analyzer for the proc-macro-srv test suite, // but we do need the standard library to be present. - builder.ensure(compile::Std::new(compiler, host)); + builder.ensure(compile::Rustc::new(compiler, host)); let workspace_path = "src/tools/rust-analyzer"; // until the whole RA test suite runs on `i686`, we only run @@ -378,7 +380,7 @@ impl Step for RustAnalyzer { let mut cargo = tool::prepare_tool_cargo( builder, compiler, - Mode::ToolStd, + Mode::ToolRustc, host, "test", crate_path, @@ -862,15 +864,8 @@ impl Step for RustdocTheme { .env("CFG_RELEASE_CHANNEL", &builder.config.channel) .env("RUSTDOC_REAL", builder.rustdoc(self.compiler)) .env("RUSTC_BOOTSTRAP", "1"); - if let Some(linker) = builder.linker(self.compiler.host) { - cmd.env("RUSTDOC_LINKER", linker); - } - if builder.is_fuse_ld_lld(self.compiler.host) { - cmd.env( - "RUSTDOC_LLD_NO_THREADS", - helpers::lld_flag_no_threads(self.compiler.host.contains("windows")), - ); - } + cmd.args(linker_args(builder, self.compiler.host, LldThreads::No)); + builder.run_delaying_failure(&mut cmd); } } @@ -1005,6 +1000,7 @@ impl Step for RustdocGUI { let run = run.suite_path("tests/rustdoc-gui"); run.lazy_default_condition(Box::new(move || { builder.config.nodejs.is_some() + && builder.doc_tests != DocTests::Only && builder .config .npm @@ -1044,6 +1040,8 @@ impl Step for RustdocGUI { cmd.env("RUSTDOC", builder.rustdoc(self.compiler)) .env("RUSTC", builder.rustc(self.compiler)); + add_rustdoc_cargo_linker_args(&mut cmd, builder, self.compiler.host, LldThreads::No); + for path in &builder.paths { if let Some(p) = helpers::is_valid_test_suite_arg(path, "tests/rustdoc-gui", builder) { if !p.ends_with(".goml") { @@ -1162,7 +1160,8 @@ HELP: to skip test's attempt to check tidiness, pass `--skip src/tools/tidy` to } fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.path("src/tools/tidy") + let default = run.builder.doc_tests != DocTests::Only; + run.path("src/tools/tidy").default_condition(default) } fn make_run(run: RunConfig<'_>) { @@ -1564,6 +1563,10 @@ impl Step for Compiletest { /// compiletest `mode` and `suite` arguments. For example `mode` can be /// "run-pass" or `suite` can be something like `debuginfo`. fn run(self, builder: &Builder<'_>) { + if builder.doc_tests == DocTests::Only { + return; + } + if builder.top_stage == 0 && env::var("COMPILETEST_FORCE_STAGE0").is_err() { eprintln!("\ ERROR: `--stage 0` runs compiletest on the beta compiler, not your local changes, and will almost always cause tests to fail @@ -1594,8 +1597,13 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the // NOTE: Only stage 1 is special cased because we need the rustc_private artifacts to match the // running compiler in stage 2 when plugins run. let stage_id = if suite == "ui-fulldeps" && compiler.stage == 1 { - compiler = builder.compiler(compiler.stage - 1, target); - format!("stage{}-{}", compiler.stage + 1, target) + // At stage 0 (stage - 1) we are using the beta compiler. Using `self.target` can lead finding + // an incorrect compiler path on cross-targets, as the stage 0 beta compiler is always equal + // to `build.build` in the configuration. + let build = builder.build.build; + + compiler = builder.compiler(compiler.stage - 1, build); + format!("stage{}-{}", compiler.stage + 1, build) } else { format!("stage{}-{}", compiler.stage, target) }; @@ -1744,14 +1752,14 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the let mut hostflags = flags.clone(); hostflags.push(format!("-Lnative={}", builder.test_helpers_out(compiler.host).display())); - hostflags.extend(builder.lld_flags(compiler.host)); + hostflags.extend(linker_flags(builder, compiler.host, LldThreads::No)); for flag in hostflags { cmd.arg("--host-rustcflags").arg(flag); } let mut targetflags = flags; targetflags.push(format!("-Lnative={}", builder.test_helpers_out(target).display())); - targetflags.extend(builder.lld_flags(target)); + targetflags.extend(linker_flags(builder, compiler.host, LldThreads::No)); for flag in targetflags { cmd.arg("--target-rustcflags").arg(flag); } @@ -1825,6 +1833,10 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the cmd.arg("--json"); + if builder.config.rust_debug_assertions_std { + cmd.arg("--with-debug-assertions"); + }; + let mut llvm_components_passed = false; let mut copts_passed = false; if builder.config.llvm_enabled() { @@ -1925,13 +1937,18 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the // // Note that if we encounter `PATH` we make sure to append to our own `PATH` // rather than stomp over it. - if !builder.config.dry_run() && target.contains("msvc") { + if !builder.config.dry_run() && target.is_msvc() { for &(ref k, ref v) in builder.cc.borrow()[&target].env() { if k != "PATH" { cmd.env(k, v); } } } + + // Some UI tests trigger behavior in rustc where it reads $CARGO and changes behavior if it exists. + // To make the tests work that rely on it not being set, make sure it is not set. + cmd.env_remove("CARGO"); + cmd.env("RUSTC_BOOTSTRAP", "1"); // Override the rustc version used in symbol hashes to reduce the amount of normalization // needed when diffing test output. @@ -2322,6 +2339,8 @@ impl Step for CrateLibrustc { } fn run(self, builder: &Builder<'_>) { + builder.ensure(compile::Std::new(self.compiler, self.target)); + builder.ensure(Crate { compiler: self.compiler, target: self.target, @@ -3057,7 +3076,7 @@ impl Step for TestHelpers { // We may have found various cross-compilers a little differently due to our // extra configuration, so inform cc of these compilers. Note, though, that // on MSVC we still need cc's detection of env vars (ugh). - if !target.contains("msvc") { + if !target.is_msvc() { if let Some(ar) = builder.ar(target) { cfg.archiver(ar); } diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index d1bc05e51..9942f00a0 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -203,6 +203,16 @@ pub fn prepare_tool_cargo( if !features.is_empty() { cargo.arg("--features").arg(&features.join(", ")); } + + // Enable internal lints for clippy and rustdoc + // NOTE: this doesn't enable lints for any other tools unless they explicitly add `#![warn(rustc::internal)]` + // See https://github.com/rust-lang/rust/pull/80573#issuecomment-754010776 + // + // NOTE: We unconditionally set this here to avoid recompiling tools between `x check $tool` + // and `x test $tool` executions. + // See https://github.com/rust-lang/rust/issues/116538 + cargo.rustflag("-Zunstable-options"); + cargo } @@ -603,8 +613,7 @@ pub struct RustAnalyzer { } impl RustAnalyzer { - pub const ALLOW_FEATURES: &'static str = - "proc_macro_internals,proc_macro_diagnostic,proc_macro_span,proc_macro_span_shrink"; + pub const ALLOW_FEATURES: &'static str = "rustc_private,proc_macro_internals,proc_macro_diagnostic,proc_macro_span,proc_macro_span_shrink"; } impl Step for RustAnalyzer { @@ -636,7 +645,7 @@ impl Step for RustAnalyzer { compiler: self.compiler, target: self.target, tool: "rust-analyzer", - mode: Mode::ToolStd, + mode: Mode::ToolRustc, path: "src/tools/rust-analyzer", extra_features: vec!["rust-analyzer/in-rust-tree".to_owned()], is_optional_tool: false, @@ -824,7 +833,7 @@ impl<'a> Builder<'a> { // On MSVC a tool may invoke a C compiler (e.g., compiletest in run-make // mode) and that C compiler may need some extra PATH modification. Do // so here. - if compiler.host.contains("msvc") { + if compiler.host.is_msvc() { let curpaths = env::var_os("PATH").unwrap_or_default(); let curpaths = env::split_paths(&curpaths).collect::<Vec<_>>(); for &(ref k, ref v) in self.cc.borrow()[&compiler.host].env() { diff --git a/src/bootstrap/src/core/builder.rs b/src/bootstrap/src/core/builder.rs index cd276674d..e18096443 100644 --- a/src/bootstrap/src/core/builder.rs +++ b/src/bootstrap/src/core/builder.rs @@ -10,6 +10,7 @@ use std::io::{BufRead, BufReader}; use std::ops::Deref; use std::path::{Path, PathBuf}; use std::process::Command; +use std::sync::OnceLock; use std::time::{Duration, Instant}; use crate::core::build_steps::llvm; @@ -17,19 +18,18 @@ use crate::core::build_steps::tool::{self, SourceType}; use crate::core::build_steps::{check, clean, compile, dist, doc, install, run, setup, test}; use crate::core::config::flags::{Color, Subcommand}; use crate::core::config::{DryRun, SplitDebuginfo, TargetSelection}; +use crate::prepare_behaviour_dump_dir; use crate::utils::cache::{Cache, Interned, INTERNER}; -use crate::utils::helpers::{self, add_dylib_path, add_link_lib_path, exe, libdir, output, t}; -use crate::Crate; +use crate::utils::helpers::{self, add_dylib_path, add_link_lib_path, exe, linker_args}; +use crate::utils::helpers::{libdir, linker_flags, output, t, LldThreads}; use crate::EXTRA_CHECK_CFGS; -use crate::{Build, CLang, DocTests, GitRepo, Mode}; +use crate::{Build, CLang, Crate, DocTests, GitRepo, Mode}; pub use crate::Compiler; -// FIXME: -// - use std::lazy for `Lazy` -// - use std::cell for `OnceCell` -// Once they get stabilized and reach beta. + use clap::ValueEnum; -use once_cell::sync::{Lazy, OnceCell}; +// FIXME: replace with std::lazy after it gets stabilized and reaches beta +use once_cell::sync::Lazy; #[cfg(test)] #[path = "../tests/builder.rs"] @@ -495,7 +495,7 @@ impl<'a> ShouldRun<'a> { /// /// [`path`]: ShouldRun::path pub fn paths(mut self, paths: &[&str]) -> Self { - static SUBMODULES_PATHS: OnceCell<Vec<String>> = OnceCell::new(); + static SUBMODULES_PATHS: OnceLock<Vec<String>> = OnceLock::new(); let init_submodules_paths = |src: &PathBuf| { let file = File::open(src.join(".gitmodules")).unwrap(); @@ -810,6 +810,7 @@ impl<'a> Builder<'a> { doc::StyleGuide, doc::Tidy, doc::Bootstrap, + doc::Releases, ), Kind::Dist => describe!( dist::Docs, @@ -1151,6 +1152,44 @@ impl<'a> Builder<'a> { self.ensure(tool::Rustdoc { compiler }) } + pub fn cargo_clippy_cmd(&self, run_compiler: Compiler) -> Command { + let initial_sysroot_bin = self.initial_rustc.parent().unwrap(); + // Set PATH to include the sysroot bin dir so clippy can find cargo. + // FIXME: once rust-clippy#11944 lands on beta, set `CARGO` directly instead. + let path = t!(env::join_paths( + // The sysroot comes first in PATH to avoid using rustup's cargo. + std::iter::once(PathBuf::from(initial_sysroot_bin)) + .chain(env::split_paths(&t!(env::var("PATH")))) + )); + + if run_compiler.stage == 0 { + // `ensure(Clippy { stage: 0 })` *builds* clippy with stage0, it doesn't use the beta clippy. + let cargo_clippy = self.build.config.download_clippy(); + let mut cmd = Command::new(cargo_clippy); + cmd.env("PATH", &path); + return cmd; + } + + let build_compiler = self.compiler(run_compiler.stage - 1, self.build.build); + self.ensure(tool::Clippy { + compiler: build_compiler, + target: self.build.build, + extra_features: vec![], + }); + let cargo_clippy = self.ensure(tool::CargoClippy { + compiler: build_compiler, + target: self.build.build, + extra_features: vec![], + }); + let mut dylib_path = helpers::dylib_path(); + dylib_path.insert(0, self.sysroot(run_compiler).join("lib")); + + let mut cmd = Command::new(cargo_clippy.unwrap()); + cmd.env(helpers::dylib_path_var(), env::join_paths(&dylib_path).unwrap()); + cmd.env("PATH", path); + cmd + } + pub fn rustdoc_cmd(&self, compiler: Compiler) -> Command { let mut cmd = Command::new(&self.bootstrap_out.join("rustdoc")); cmd.env("RUSTC_STAGE", compiler.stage.to_string()) @@ -1173,9 +1212,7 @@ impl<'a> Builder<'a> { cmd.env_remove("MAKEFLAGS"); cmd.env_remove("MFLAGS"); - if let Some(linker) = self.linker(compiler.host) { - cmd.env("RUSTDOC_LINKER", linker); - } + cmd.args(linker_args(self, compiler.host, LldThreads::Yes)); cmd } @@ -1201,7 +1238,12 @@ impl<'a> Builder<'a> { target: TargetSelection, cmd: &str, ) -> Command { - let mut cargo = Command::new(&self.initial_cargo); + let mut cargo = if cmd == "clippy" { + self.cargo_clippy_cmd(compiler) + } else { + Command::new(&self.initial_cargo) + }; + // Run cargo from the source root so it can find .cargo/config. // This matters when using vendoring and the working directory is outside the repository. cargo.current_dir(&self.src); @@ -1325,6 +1367,23 @@ impl<'a> Builder<'a> { compiler.stage }; + // We synthetically interpret a stage0 compiler used to build tools as a + // "raw" compiler in that it's the exact snapshot we download. Normally + // the stage0 build means it uses libraries build by the stage0 + // compiler, but for tools we just use the precompiled libraries that + // we've downloaded + let use_snapshot = mode == Mode::ToolBootstrap; + assert!(!use_snapshot || stage == 0 || self.local_rebuild); + + let maybe_sysroot = self.sysroot(compiler); + let sysroot = if use_snapshot { self.rustc_snapshot_sysroot() } else { &maybe_sysroot }; + let libdir = self.rustc_libdir(compiler); + + let sysroot_str = sysroot.as_os_str().to_str().expect("sysroot should be UTF-8"); + if !matches!(self.config.dry_run, DryRun::SelfCheck) { + self.verbose_than(0, &format!("using sysroot {sysroot_str}")); + } + let mut rustflags = Rustflags::new(target); if stage != 0 { if let Ok(s) = env::var("CARGOFLAGS_NOT_BOOTSTRAP") { @@ -1336,41 +1395,16 @@ impl<'a> Builder<'a> { cargo.args(s.split_whitespace()); } rustflags.env("RUSTFLAGS_BOOTSTRAP"); - if cmd == "clippy" { - // clippy overwrites sysroot if we pass it to cargo. - // Pass it directly to clippy instead. - // NOTE: this can't be fixed in clippy because we explicitly don't set `RUSTC`, - // so it has no way of knowing the sysroot. - rustflags.arg("--sysroot"); - rustflags.arg( - self.sysroot(compiler) - .as_os_str() - .to_str() - .expect("sysroot must be valid UTF-8"), - ); - // Only run clippy on a very limited subset of crates (in particular, not build scripts). - cargo.arg("-Zunstable-options"); - // Explicitly does *not* set `--cfg=bootstrap`, since we're using a nightly clippy. - let host_version = Command::new("rustc").arg("--version").output().map_err(|_| ()); - let output = host_version.and_then(|output| { - if output.status.success() { - Ok(output) - } else { - Err(()) - } - }).unwrap_or_else(|_| { - eprintln!( - "ERROR: `x.py clippy` requires a host `rustc` toolchain with the `clippy` component" - ); - eprintln!("HELP: try `rustup component add clippy`"); - crate::exit!(1); - }); - if !t!(std::str::from_utf8(&output.stdout)).contains("nightly") { - rustflags.arg("--cfg=bootstrap"); - } - } else { - rustflags.arg("--cfg=bootstrap"); - } + rustflags.arg("--cfg=bootstrap"); + } + + if cmd == "clippy" { + // clippy overwrites sysroot if we pass it to cargo. + // Pass it directly to clippy instead. + // NOTE: this can't be fixed in clippy because we explicitly don't set `RUSTC`, + // so it has no way of knowing the sysroot. + rustflags.arg("--sysroot"); + rustflags.arg(sysroot_str); } let use_new_symbol_mangling = match self.config.rust_new_symbol_mangling { @@ -1404,9 +1438,6 @@ impl<'a> Builder<'a> { rustflags.arg("-Zunstable-options"); } - // #[cfg(bootstrap)] - let use_new_check_cfg_syntax = self.local_rebuild; - // Enable compile-time checking of `cfg` names, values and Cargo `features`. // // Note: `std`, `alloc` and `core` imports some dependencies by #[path] (like @@ -1414,17 +1445,9 @@ impl<'a> Builder<'a> { // features but cargo isn't involved in the #[path] process and so cannot pass the // complete list of features, so for that reason we don't enable checking of // features for std crates. - if use_new_check_cfg_syntax { - cargo.arg("-Zcheck-cfg"); - if mode == Mode::Std { - rustflags.arg("--check-cfg=cfg(feature,values(any()))"); - } - } else { - cargo.arg(if mode != Mode::Std { - "-Zcheck-cfg=names,values,output,features" - } else { - "-Zcheck-cfg=names,values,output" - }); + cargo.arg("-Zcheck-cfg"); + if mode == Mode::Std { + rustflags.arg("--check-cfg=cfg(feature,values(any()))"); } // Add extra cfg not defined in/by rustc @@ -1445,12 +1468,8 @@ impl<'a> Builder<'a> { .collect::<String>(), None => String::new(), }; - if use_new_check_cfg_syntax { - let values = values.strip_prefix(",").unwrap_or(&values); // remove the first `,` - rustflags.arg(&format!("--check-cfg=cfg({name},values({values}))")); - } else { - rustflags.arg(&format!("--check-cfg=values({name}{values})")); - } + let values = values.strip_prefix(",").unwrap_or(&values); // remove the first `,` + rustflags.arg(&format!("--check-cfg=cfg({name},values({values}))")); } } @@ -1466,11 +1485,7 @@ impl<'a> Builder<'a> { // We also declare that the flag is expected, which we need to do to not // get warnings about it being unexpected. hostflags.arg("-Zunstable-options"); - if use_new_check_cfg_syntax { - hostflags.arg("--check-cfg=cfg(bootstrap)"); - } else { - hostflags.arg("--check-cfg=values(bootstrap)"); - } + hostflags.arg("--check-cfg=cfg(bootstrap)"); // FIXME: It might be better to use the same value for both `RUSTFLAGS` and `RUSTDOCFLAGS`, // but this breaks CI. At the very least, stage0 `rustdoc` needs `--cfg bootstrap`. See @@ -1584,18 +1599,6 @@ impl<'a> Builder<'a> { let want_rustdoc = self.doc_tests != DocTests::No; - // We synthetically interpret a stage0 compiler used to build tools as a - // "raw" compiler in that it's the exact snapshot we download. Normally - // the stage0 build means it uses libraries build by the stage0 - // compiler, but for tools we just use the precompiled libraries that - // we've downloaded - let use_snapshot = mode == Mode::ToolBootstrap; - assert!(!use_snapshot || stage == 0 || self.local_rebuild); - - let maybe_sysroot = self.sysroot(compiler); - let sysroot = if use_snapshot { self.rustc_snapshot_sysroot() } else { &maybe_sysroot }; - let libdir = self.rustc_libdir(compiler); - // Clear the output directory if the real rustc we're using has changed; // Cargo cannot detect this as it thinks rustc is bootstrap/debug/rustc. // @@ -1631,10 +1634,19 @@ impl<'a> Builder<'a> { ) .env("RUSTC_ERROR_METADATA_DST", self.extended_error_dir()) .env("RUSTC_BREAK_ON_ICE", "1"); - // Clippy support is a hack and uses the default `cargo-clippy` in path. - // Don't override RUSTC so that the `cargo-clippy` in path will be run. - if cmd != "clippy" { - cargo.env("RUSTC", self.bootstrap_out.join("rustc")); + + // Set RUSTC_WRAPPER to the bootstrap shim, which switches between beta and in-tree + // sysroot depending on whether we're building build scripts. + // NOTE: we intentionally use RUSTC_WRAPPER so that we can support clippy - RUSTC is not + // respected by clippy-driver; RUSTC_WRAPPER happens earlier, before clippy runs. + cargo.env("RUSTC_WRAPPER", self.bootstrap_out.join("rustc")); + // NOTE: we also need to set RUSTC so cargo can run `rustc -vV`; apparently that ignores RUSTC_WRAPPER >:( + cargo.env("RUSTC", self.bootstrap_out.join("rustc")); + + // Someone might have set some previous rustc wrapper (e.g. + // sccache) before bootstrap overrode it. Respect that variable. + if let Some(existing_wrapper) = env::var_os("RUSTC_WRAPPER") { + cargo.env("RUSTC_WRAPPER_REAL", existing_wrapper); } // Dealing with rpath here is a little special, so let's go into some @@ -1673,10 +1685,7 @@ impl<'a> Builder<'a> { // flesh out rpath support more fully in the future. rustflags.arg("-Zosx-rpath-install-name"); Some(format!("-Wl,-rpath,@loader_path/../{libdir}")) - } else if !target.contains("windows") - && !target.contains("aix") - && !target.contains("xous") - { + } else if !target.is_windows() && !target.contains("aix") && !target.contains("xous") { rustflags.arg("-Clink-args=-Wl,-z,origin"); Some(format!("-Wl,-rpath,$ORIGIN/../{libdir}")) } else { @@ -1687,23 +1696,28 @@ impl<'a> Builder<'a> { } } - if let Some(host_linker) = self.linker(compiler.host) { - hostflags.arg(format!("-Clinker={}", host_linker.display())); + cargo.env(profile_var("STRIP"), self.config.rust_strip.to_string()); + + if let Some(stack_protector) = &self.config.rust_stack_protector { + rustflags.arg(&format!("-Zstack-protector={stack_protector}")); } - if self.is_fuse_ld_lld(compiler.host) { - hostflags.arg("-Clink-args=-fuse-ld=lld"); + + for arg in linker_args(self, compiler.host, LldThreads::Yes) { + hostflags.arg(&arg); } if let Some(target_linker) = self.linker(target) { let target = crate::envify(&target.triple); cargo.env(&format!("CARGO_TARGET_{target}_LINKER"), target_linker); } - if self.is_fuse_ld_lld(target) { - rustflags.arg("-Clink-args=-fuse-ld=lld"); + // We want to set -Clinker using Cargo, therefore we only call `linker_flags` and not + // `linker_args` here. + for flag in linker_flags(self, target, LldThreads::Yes) { + rustflags.arg(&flag); + } + for arg in linker_args(self, target, LldThreads::Yes) { + rustdocflags.arg(&arg); } - self.lld_flags(target).for_each(|flag| { - rustdocflags.arg(&flag); - }); if !(["build", "check", "clippy", "fix", "rustc"].contains(&cmd)) && want_rustdoc { cargo.env("RUSTDOC_LIBDIR", self.rustc_libdir(compiler)); @@ -1743,10 +1757,8 @@ impl<'a> Builder<'a> { let split_debuginfo_is_stable = target.contains("linux") || target.contains("apple") - || (target.contains("msvc") - && self.config.rust_split_debuginfo == SplitDebuginfo::Packed) - || (target.contains("windows") - && self.config.rust_split_debuginfo == SplitDebuginfo::Off); + || (target.is_msvc() && self.config.rust_split_debuginfo == SplitDebuginfo::Packed) + || (target.is_windows() && self.config.rust_split_debuginfo == SplitDebuginfo::Off); if !split_debuginfo_is_stable { rustflags.arg("-Zunstable-options"); @@ -1804,6 +1816,16 @@ impl<'a> Builder<'a> { // Enable usage of unstable features cargo.env("RUSTC_BOOTSTRAP", "1"); + + if self.config.dump_bootstrap_shims { + prepare_behaviour_dump_dir(&self.build); + + cargo + .env("DUMP_BOOTSTRAP_SHIMS", self.build.out.join("bootstrap-shims-dump")) + .env("BUILD_OUT", &self.build.out) + .env("CARGO_HOME", t!(home::cargo_home())); + }; + self.add_rust_test_threads(&mut cargo); // Almost all of the crates that we compile as part of the bootstrap may @@ -1892,7 +1914,6 @@ impl<'a> Builder<'a> { // some code doesn't go through this `rustc` wrapper. lint_flags.push("-Wrust_2018_idioms"); lint_flags.push("-Wunused_lifetimes"); - lint_flags.push("-Wsemicolon_in_expressions_from_macros"); if self.config.deny_warnings { lint_flags.push("-Dwarnings"); @@ -1924,7 +1945,7 @@ impl<'a> Builder<'a> { // the options through environment variables that are fetched and understood by both. // // FIXME: the guard against msvc shouldn't need to be here - if target.contains("msvc") { + if target.is_msvc() { if let Some(ref cl) = self.config.llvm_clang_cl { cargo.env("CC", cl).env("CXX", cl); } @@ -1982,6 +2003,16 @@ impl<'a> Builder<'a> { rustflags.arg("-Ccontrol-flow-guard"); } + // If EHCont Guard is enabled, pass the `-Zehcont-guard` flag to rustc when compiling the + // standard library, since this might be linked into the final outputs produced by rustc. + // Since this mitigation is only available on Windows, only enable it for the standard + // library in case the compiler is run on a non-Windows platform. + // This is not needed for stage 0 artifacts because these will only be used for building + // the stage 1 compiler. + if cfg!(windows) && mode == Mode::Std && self.config.ehcont_guard && compiler.stage >= 1 { + rustflags.arg("-Zehcont-guard"); + } + // For `cargo doc` invocations, make rustdoc print the Rust version into the docs // This replaces spaces with tabs because RUSTDOCFLAGS does not // support arguments with regular spaces. Hopefully someday Cargo will @@ -1992,7 +2023,11 @@ impl<'a> Builder<'a> { // Environment variables *required* throughout the build // // FIXME: should update code to not require this env var + + // The host this new compiler will *run* on. cargo.env("CFG_COMPILER_HOST_TRIPLE", target.triple); + // The host this new compiler is being *built* on. + cargo.env("CFG_COMPILER_BUILD_TRIPLE", compiler.host.triple); // Set this for all builds to make sure doc builds also get it. cargo.env("CFG_RELEASE_CHANNEL", &self.config.channel); diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 0a9175aa3..f1e1b89d9 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -17,6 +17,7 @@ use std::io::IsTerminal; use std::path::{Path, PathBuf}; use std::process::Command; use std::str::FromStr; +use std::sync::OnceLock; use crate::core::build_steps::compile::CODEGEN_BACKEND_PREFIX; use crate::core::build_steps::llvm; @@ -25,7 +26,6 @@ use crate::utils::cache::{Interned, INTERNER}; use crate::utils::channel::{self, GitInfo}; use crate::utils::helpers::{exe, output, t}; use build_helper::exit; -use once_cell::sync::OnceCell; use semver::Version; use serde::{Deserialize, Deserializer}; use serde_derive::Deserialize; @@ -105,6 +105,39 @@ impl Display for DebuginfoLevel { } } +/// LLD in bootstrap works like this: +/// - Self-contained lld: use `rust-lld` from the compiler's sysroot +/// - External: use an external `lld` binary +/// +/// It is configured depending on the target: +/// 1) Everything except MSVC +/// - Self-contained: `-Clinker-flavor=gnu-lld-cc -Clink-self-contained=+linker` +/// - External: `-Clinker-flavor=gnu-lld-cc` +/// 2) MSVC +/// - Self-contained: `-Clinker=<path to rust-lld>` +/// - External: `-Clinker=lld` +#[derive(Default, Copy, Clone)] +pub enum LldMode { + /// Do not use LLD + #[default] + Unused, + /// Use `rust-lld` from the compiler's sysroot + SelfContained, + /// Use an externally provided `lld` binary. + /// Note that the linker name cannot be overridden, the binary has to be named `lld` and it has + /// to be in $PATH. + External, +} + +impl LldMode { + pub fn is_used(&self) -> bool { + match self { + LldMode::SelfContained | LldMode::External => true, + LldMode::Unused => false, + } + } +} + /// Global configuration for the entire build and/or bootstrap. /// /// This structure is parsed from `config.toml`, and some of the fields are inferred from `git` or build-time parameters. @@ -117,6 +150,7 @@ impl Display for DebuginfoLevel { pub struct Config { pub changelog_seen: Option<usize>, // FIXME: Deprecated field. Remove it at 2024. pub change_id: Option<usize>, + pub bypass_bootstrap_lock: bool, pub ccache: Option<String>, /// Call Build::ninja() instead of this. pub ninja_in_file: bool, @@ -159,6 +193,7 @@ pub struct Config { pub cmd: Subcommand, pub incremental: bool, pub dry_run: DryRun, + pub dump_bootstrap_shims: bool, /// Arguments appearing after `--` to be forwarded to tools, /// e.g. `--fix-broken` or test arguments. pub free_args: Vec<String>, @@ -198,7 +233,7 @@ pub struct Config { pub llvm_from_ci: bool, pub llvm_build_config: HashMap<String, String>, - pub use_lld: bool, + pub lld_mode: LldMode, pub lld_enabled: bool, pub llvm_tools_enabled: bool, @@ -222,6 +257,8 @@ pub struct Config { pub rust_debuginfo_level_tests: DebuginfoLevel, pub rust_split_debuginfo: SplitDebuginfo, pub rust_rpath: bool, + pub rust_strip: bool, + pub rust_stack_protector: Option<String>, pub rustc_parallel: bool, pub rustc_default_linker: Option<String>, pub rust_optimize_tests: bool, @@ -248,6 +285,7 @@ pub struct Config { pub local_rebuild: bool, pub jemalloc: bool, pub control_flow_guard: bool, + pub ehcont_guard: bool, // dist misc pub dist_sign_folder: Option<PathBuf>, @@ -383,10 +421,10 @@ impl std::str::FromStr for SplitDebuginfo { impl SplitDebuginfo { /// Returns the default `-Csplit-debuginfo` value for the current target. See the comment for /// `rust.split-debuginfo` in `config.example.toml`. - fn default_for_platform(target: &str) -> Self { + fn default_for_platform(target: TargetSelection) -> Self { if target.contains("apple") { SplitDebuginfo::Unpacked - } else if target.contains("windows") { + } else if target.is_windows() { SplitDebuginfo::Packed } else { SplitDebuginfo::Off @@ -485,6 +523,14 @@ impl TargetSelection { pub fn is_synthetic(&self) -> bool { self.synthetic } + + pub fn is_msvc(&self) -> bool { + self.contains("msvc") + } + + pub fn is_windows(&self) -> bool { + self.contains("windows") + } } impl fmt::Display for TargetSelection { @@ -853,7 +899,6 @@ define_config! { define_config! { struct Dist { sign_folder: Option<String> = "sign-folder", - gpg_password_file: Option<String> = "gpg-password-file", upload_addr: Option<String> = "upload-addr", src_tarball: Option<bool> = "src-tarball", missing_tools: Option<bool> = "missing-tools", @@ -973,6 +1018,44 @@ enum StringOrInt<'a> { String(&'a str), Int(i64), } + +impl<'de> Deserialize<'de> for LldMode { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct LldModeVisitor; + + impl<'de> serde::de::Visitor<'de> for LldModeVisitor { + type Value = LldMode; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("one of true, 'self-contained' or 'external'") + } + + fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> + where + E: serde::de::Error, + { + Ok(if v { LldMode::External } else { LldMode::Unused }) + } + + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> + where + E: serde::de::Error, + { + match v { + "external" => Ok(LldMode::External), + "self-contained" => Ok(LldMode::SelfContained), + _ => Err(E::custom("unknown mode {v}")), + } + } + } + + deserializer.deserialize_any(LldModeVisitor) + } +} + define_config! { /// TOML representation of how the Rust build is configured. struct Rust { @@ -991,7 +1074,6 @@ define_config! { debuginfo_level_tools: Option<DebuginfoLevel> = "debuginfo-level-tools", debuginfo_level_tests: Option<DebuginfoLevel> = "debuginfo-level-tests", split_debuginfo: Option<String> = "split-debuginfo", - run_dsymutil: Option<bool> = "run-dsymutil", backtrace: Option<bool> = "backtrace", incremental: Option<bool> = "incremental", parallel_compiler: Option<bool> = "parallel-compiler", @@ -1000,6 +1082,8 @@ define_config! { description: Option<String> = "description", musl_root: Option<String> = "musl-root", rpath: Option<bool> = "rpath", + strip: Option<bool> = "strip", + stack_protector: Option<String> = "stack-protector", verbose_tests: Option<bool> = "verbose-tests", optimize_tests: Option<bool> = "optimize-tests", codegen_tests: Option<bool> = "codegen-tests", @@ -1008,7 +1092,7 @@ define_config! { save_toolstates: Option<String> = "save-toolstates", codegen_backends: Option<Vec<String>> = "codegen-backends", lld: Option<bool> = "lld", - use_lld: Option<bool> = "use-lld", + lld_mode: Option<LldMode> = "use-lld", llvm_tools: Option<bool> = "llvm-tools", deny_warnings: Option<bool> = "deny-warnings", backtrace_on_ice: Option<bool> = "backtrace-on-ice", @@ -1019,6 +1103,7 @@ define_config! { test_compare_mode: Option<bool> = "test-compare-mode", llvm_libunwind: Option<String> = "llvm-libunwind", control_flow_guard: Option<bool> = "control-flow-guard", + ehcont_guard: Option<bool> = "ehcont-guard", new_symbol_mangling: Option<bool> = "new-symbol-mangling", profile_generate: Option<String> = "profile-generate", profile_use: Option<String> = "profile-use", @@ -1057,6 +1142,7 @@ define_config! { impl Config { pub fn default_opts() -> Config { let mut config = Config::default(); + config.bypass_bootstrap_lock = false; config.llvm_optimize = true; config.ninja_in_file = true; config.llvm_static_stdcpp = false; @@ -1067,6 +1153,7 @@ impl Config { config.docs = true; config.docs_minification = true; config.rust_rpath = true; + config.rust_strip = false; config.channel = "dev".to_string(); config.codegen_tests = true; config.rust_dist_src = true; @@ -1128,6 +1215,7 @@ impl Config { config.cmd = flags.cmd; config.incremental = flags.incremental; config.dry_run = if flags.dry_run { DryRun::UserSelected } else { DryRun::Disabled }; + config.dump_bootstrap_shims = flags.dump_bootstrap_shims; config.keep_stage = flags.keep_stage; config.keep_stage_std = flags.keep_stage_std; config.color = flags.color; @@ -1135,6 +1223,7 @@ impl Config { config.llvm_profile_use = flags.llvm_profile_use; config.llvm_profile_generate = flags.llvm_profile_generate; config.enable_bolt_settings = flags.enable_bolt_settings; + config.bypass_bootstrap_lock = flags.bypass_bootstrap_lock; // Infer the rest of the configuration. @@ -1264,12 +1353,56 @@ impl Config { config.changelog_seen = toml.changelog_seen; config.change_id = toml.change_id; - let build = toml.build.unwrap_or_default(); - if let Some(file_build) = build.build { + let Build { + build, + host, + target, + build_dir, + cargo, + rustc, + rustfmt, + docs, + compiler_docs, + library_docs_private_items, + docs_minification, + submodules, + gdb, + nodejs, + npm, + python, + reuse, + locked_deps, + vendor, + full_bootstrap, + extended, + tools, + verbose, + sanitizers, + profiler, + cargo_native_static, + low_priority, + configure_args, + local_rebuild, + print_step_timings, + print_step_rusage, + check_stage, + doc_stage, + build_stage, + test_stage, + install_stage, + dist_stage, + bench_stage, + patch_binaries_for_nix, + // This field is only used by bootstrap.py + metrics: _, + android_ndk, + } = toml.build.unwrap_or_default(); + + if let Some(file_build) = build { config.build = TargetSelection::from_user(&file_build); }; - set(&mut config.out, flags.build_dir.or_else(|| build.build_dir.map(PathBuf::from))); + set(&mut config.out, flags.build_dir.or_else(|| build_dir.map(PathBuf::from))); // NOTE: Bootstrap spawns various commands with different working directories. // To avoid writing to random places on the file system, `config.out` needs to be an absolute path. if !config.out.is_absolute() { @@ -1277,7 +1410,7 @@ impl Config { config.out = crate::utils::helpers::absolute(&config.out); } - config.initial_rustc = if let Some(rustc) = build.rustc { + config.initial_rustc = if let Some(rustc) = rustc { if !flags.skip_stage0_validation { config.check_build_rustc_version(&rustc); } @@ -1287,8 +1420,7 @@ impl Config { config.out.join(config.build.triple).join("stage0/bin/rustc") }; - config.initial_cargo = build - .cargo + config.initial_cargo = cargo .map(|cargo| { t!(PathBuf::from(cargo).canonicalize(), "`initial_cargo` not found on disk") }) @@ -1303,14 +1435,14 @@ impl Config { config.hosts = if let Some(TargetSelectionList(arg_host)) = flags.host { arg_host - } else if let Some(file_host) = build.host { + } else if let Some(file_host) = host { file_host.iter().map(|h| TargetSelection::from_user(h)).collect() } else { vec![config.build] }; config.targets = if let Some(TargetSelectionList(arg_target)) = flags.target { arg_target - } else if let Some(file_target) = build.target { + } else if let Some(file_target) = target { file_target.iter().map(|h| TargetSelection::from_user(h)).collect() } else { // If target is *not* configured, then default to the host @@ -1318,43 +1450,44 @@ impl Config { config.hosts.clone() }; - config.nodejs = build.nodejs.map(PathBuf::from); - config.npm = build.npm.map(PathBuf::from); - config.gdb = build.gdb.map(PathBuf::from); - config.python = build.python.map(PathBuf::from); - config.reuse = build.reuse.map(PathBuf::from); - config.submodules = build.submodules; - config.android_ndk = build.android_ndk; - set(&mut config.low_priority, build.low_priority); - set(&mut config.compiler_docs, build.compiler_docs); - set(&mut config.library_docs_private_items, build.library_docs_private_items); - set(&mut config.docs_minification, build.docs_minification); - set(&mut config.docs, build.docs); - set(&mut config.locked_deps, build.locked_deps); - set(&mut config.vendor, build.vendor); - set(&mut config.full_bootstrap, build.full_bootstrap); - set(&mut config.extended, build.extended); - config.tools = build.tools; - set(&mut config.verbose, build.verbose); - set(&mut config.sanitizers, build.sanitizers); - set(&mut config.profiler, build.profiler); - set(&mut config.cargo_native_static, build.cargo_native_static); - set(&mut config.configure_args, build.configure_args); - set(&mut config.local_rebuild, build.local_rebuild); - set(&mut config.print_step_timings, build.print_step_timings); - set(&mut config.print_step_rusage, build.print_step_rusage); - config.patch_binaries_for_nix = build.patch_binaries_for_nix; + config.nodejs = nodejs.map(PathBuf::from); + config.npm = npm.map(PathBuf::from); + config.gdb = gdb.map(PathBuf::from); + config.python = python.map(PathBuf::from); + config.reuse = reuse.map(PathBuf::from); + config.submodules = submodules; + config.android_ndk = android_ndk; + set(&mut config.low_priority, low_priority); + set(&mut config.compiler_docs, compiler_docs); + set(&mut config.library_docs_private_items, library_docs_private_items); + set(&mut config.docs_minification, docs_minification); + set(&mut config.docs, docs); + set(&mut config.locked_deps, locked_deps); + set(&mut config.vendor, vendor); + set(&mut config.full_bootstrap, full_bootstrap); + set(&mut config.extended, extended); + config.tools = tools; + set(&mut config.verbose, verbose); + set(&mut config.sanitizers, sanitizers); + set(&mut config.profiler, profiler); + set(&mut config.cargo_native_static, cargo_native_static); + set(&mut config.configure_args, configure_args); + set(&mut config.local_rebuild, local_rebuild); + set(&mut config.print_step_timings, print_step_timings); + set(&mut config.print_step_rusage, print_step_rusage); + config.patch_binaries_for_nix = patch_binaries_for_nix; config.verbose = cmp::max(config.verbose, flags.verbose as usize); if let Some(install) = toml.install { - config.prefix = install.prefix.map(PathBuf::from); - config.sysconfdir = install.sysconfdir.map(PathBuf::from); - config.datadir = install.datadir.map(PathBuf::from); - config.docdir = install.docdir.map(PathBuf::from); - set(&mut config.bindir, install.bindir.map(PathBuf::from)); - config.libdir = install.libdir.map(PathBuf::from); - config.mandir = install.mandir.map(PathBuf::from); + let Install { prefix, sysconfdir, docdir, bindir, libdir, mandir, datadir } = install; + config.prefix = prefix.map(PathBuf::from); + config.sysconfdir = sysconfdir.map(PathBuf::from); + config.datadir = datadir.map(PathBuf::from); + config.docdir = docdir.map(PathBuf::from); + set(&mut config.bindir, bindir.map(PathBuf::from)); + config.libdir = libdir.map(PathBuf::from); + config.mandir = mandir.map(PathBuf::from); } // Store off these values as options because if they're not provided @@ -1377,9 +1510,63 @@ impl Config { let mut omit_git_hash = None; if let Some(rust) = toml.rust { - set(&mut config.channel, rust.channel); - - config.download_rustc_commit = config.download_ci_rustc_commit(rust.download_rustc); + let Rust { + optimize: optimize_toml, + debug: debug_toml, + codegen_units, + codegen_units_std, + debug_assertions: debug_assertions_toml, + debug_assertions_std: debug_assertions_std_toml, + overflow_checks: overflow_checks_toml, + overflow_checks_std: overflow_checks_std_toml, + debug_logging: debug_logging_toml, + debuginfo_level: debuginfo_level_toml, + debuginfo_level_rustc: debuginfo_level_rustc_toml, + debuginfo_level_std: debuginfo_level_std_toml, + debuginfo_level_tools: debuginfo_level_tools_toml, + debuginfo_level_tests: debuginfo_level_tests_toml, + split_debuginfo, + backtrace, + incremental, + parallel_compiler, + default_linker, + channel, + description, + musl_root, + rpath, + verbose_tests, + optimize_tests, + codegen_tests, + omit_git_hash: omit_git_hash_toml, + dist_src, + save_toolstates, + codegen_backends, + lld, + llvm_tools, + deny_warnings, + backtrace_on_ice, + verify_llvm_ir, + thin_lto_import_instr_limit, + remap_debuginfo, + jemalloc, + test_compare_mode, + llvm_libunwind, + control_flow_guard, + ehcont_guard, + new_symbol_mangling, + profile_generate, + profile_use, + download_rustc, + lto, + validate_mir_opts, + stack_protector, + strip, + lld_mode, + } = rust; + + set(&mut config.channel, channel); + + config.download_rustc_commit = config.download_ci_rustc_commit(download_rustc); // This list is incomplete, please help by expanding it! if config.download_rustc_commit.is_some() { // We need the channel used by the downloaded compiler to match the one we set for rustdoc; @@ -1396,67 +1583,77 @@ impl Config { } } - debug = rust.debug; - debug_assertions = rust.debug_assertions; - debug_assertions_std = rust.debug_assertions_std; - overflow_checks = rust.overflow_checks; - overflow_checks_std = rust.overflow_checks_std; - debug_logging = rust.debug_logging; - debuginfo_level = rust.debuginfo_level; - debuginfo_level_rustc = rust.debuginfo_level_rustc; - debuginfo_level_std = rust.debuginfo_level_std; - debuginfo_level_tools = rust.debuginfo_level_tools; - debuginfo_level_tests = rust.debuginfo_level_tests; - - config.rust_split_debuginfo = rust - .split_debuginfo + debug = debug_toml; + debug_assertions = debug_assertions_toml; + debug_assertions_std = debug_assertions_std_toml; + overflow_checks = overflow_checks_toml; + overflow_checks_std = overflow_checks_std_toml; + debug_logging = debug_logging_toml; + debuginfo_level = debuginfo_level_toml; + debuginfo_level_rustc = debuginfo_level_rustc_toml; + debuginfo_level_std = debuginfo_level_std_toml; + debuginfo_level_tools = debuginfo_level_tools_toml; + debuginfo_level_tests = debuginfo_level_tests_toml; + + config.rust_split_debuginfo = split_debuginfo .as_deref() .map(SplitDebuginfo::from_str) .map(|v| v.expect("invalid value for rust.split_debuginfo")) - .unwrap_or(SplitDebuginfo::default_for_platform(&config.build.triple)); - optimize = rust.optimize; - omit_git_hash = rust.omit_git_hash; - config.rust_new_symbol_mangling = rust.new_symbol_mangling; - set(&mut config.rust_optimize_tests, rust.optimize_tests); - set(&mut config.codegen_tests, rust.codegen_tests); - set(&mut config.rust_rpath, rust.rpath); - set(&mut config.jemalloc, rust.jemalloc); - set(&mut config.test_compare_mode, rust.test_compare_mode); - set(&mut config.backtrace, rust.backtrace); - config.description = rust.description; - set(&mut config.rust_dist_src, rust.dist_src); - set(&mut config.verbose_tests, rust.verbose_tests); + .unwrap_or(SplitDebuginfo::default_for_platform(config.build)); + optimize = optimize_toml; + omit_git_hash = omit_git_hash_toml; + config.rust_new_symbol_mangling = new_symbol_mangling; + set(&mut config.rust_optimize_tests, optimize_tests); + set(&mut config.codegen_tests, codegen_tests); + set(&mut config.rust_rpath, rpath); + set(&mut config.rust_strip, strip); + config.rust_stack_protector = stack_protector; + set(&mut config.jemalloc, jemalloc); + set(&mut config.test_compare_mode, test_compare_mode); + set(&mut config.backtrace, backtrace); + config.description = description; + set(&mut config.rust_dist_src, dist_src); + set(&mut config.verbose_tests, verbose_tests); // in the case "false" is set explicitly, do not overwrite the command line args - if let Some(true) = rust.incremental { + if let Some(true) = incremental { config.incremental = true; } - set(&mut config.use_lld, rust.use_lld); - set(&mut config.lld_enabled, rust.lld); - set(&mut config.llvm_tools_enabled, rust.llvm_tools); - config.rustc_parallel = rust - .parallel_compiler - .unwrap_or(config.channel == "dev" || config.channel == "nightly"); - config.rustc_default_linker = rust.default_linker; - config.musl_root = rust.musl_root.map(PathBuf::from); - config.save_toolstates = rust.save_toolstates.map(PathBuf::from); + set(&mut config.lld_mode, lld_mode); + set(&mut config.lld_enabled, lld); + + if matches!(config.lld_mode, LldMode::SelfContained) + && !config.lld_enabled + && flags.stage.unwrap_or(0) > 0 + { + panic!( + "Trying to use self-contained lld as a linker, but LLD is not being added to the sysroot. Enable it with rust.lld = true." + ); + } + + set(&mut config.llvm_tools_enabled, llvm_tools); + config.rustc_parallel = + parallel_compiler.unwrap_or(config.channel == "dev" || config.channel == "nightly"); + config.rustc_default_linker = default_linker; + config.musl_root = musl_root.map(PathBuf::from); + config.save_toolstates = save_toolstates.map(PathBuf::from); set( &mut config.deny_warnings, match flags.warnings { Warnings::Deny => Some(true), Warnings::Warn => Some(false), - Warnings::Default => rust.deny_warnings, + Warnings::Default => deny_warnings, }, ); - set(&mut config.backtrace_on_ice, rust.backtrace_on_ice); - set(&mut config.rust_verify_llvm_ir, rust.verify_llvm_ir); - config.rust_thin_lto_import_instr_limit = rust.thin_lto_import_instr_limit; - set(&mut config.rust_remap_debuginfo, rust.remap_debuginfo); - set(&mut config.control_flow_guard, rust.control_flow_guard); - config.llvm_libunwind_default = rust - .llvm_libunwind - .map(|v| v.parse().expect("failed to parse rust.llvm-libunwind")); - - if let Some(ref backends) = rust.codegen_backends { + set(&mut config.backtrace_on_ice, backtrace_on_ice); + set(&mut config.rust_verify_llvm_ir, verify_llvm_ir); + config.rust_thin_lto_import_instr_limit = thin_lto_import_instr_limit; + set(&mut config.rust_remap_debuginfo, remap_debuginfo); + set(&mut config.control_flow_guard, control_flow_guard); + set(&mut config.ehcont_guard, ehcont_guard); + config.llvm_libunwind_default = + llvm_libunwind.map(|v| v.parse().expect("failed to parse rust.llvm-libunwind")); + + if let Some(ref backends) = codegen_backends { let available_backends = vec!["llvm", "cranelift", "gcc"]; config.rust_codegen_backends = backends.iter().map(|s| { @@ -1474,16 +1671,13 @@ impl Config { }).collect(); } - config.rust_codegen_units = rust.codegen_units.map(threads_from_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.rust_lto = rust - .lto - .as_deref() - .map(|value| RustcLto::from_str(value).unwrap()) - .unwrap_or_default(); - config.rust_validate_mir_opts = rust.validate_mir_opts; + config.rust_codegen_units = codegen_units.map(threads_from_config); + config.rust_codegen_units_std = codegen_units_std.map(threads_from_config); + config.rust_profile_use = flags.rust_profile_use.or(profile_use); + config.rust_profile_generate = flags.rust_profile_generate.or(profile_generate); + config.rust_lto = + lto.as_deref().map(|value| RustcLto::from_str(value).unwrap()).unwrap_or_default(); + config.rust_validate_mir_opts = validate_mir_opts; } else { config.rust_profile_use = flags.rust_profile_use; config.rust_profile_generate = flags.rust_profile_generate; @@ -1497,43 +1691,71 @@ impl Config { config.rust_info = GitInfo::new(config.omit_git_hash, &config.src); if let Some(llvm) = toml.llvm { - match llvm.ccache { + let Llvm { + optimize: optimize_toml, + thin_lto, + release_debuginfo, + assertions, + tests, + plugins, + ccache, + static_libstdcpp, + ninja, + targets, + experimental_targets, + link_jobs, + link_shared, + version_suffix, + clang_cl, + cflags, + cxxflags, + ldflags, + use_libcxx, + use_linker, + allow_old_toolchain, + polly, + clang, + enable_warnings, + download_ci_llvm, + build_config, + } = llvm; + match ccache { Some(StringOrBool::String(ref s)) => config.ccache = Some(s.to_string()), Some(StringOrBool::Bool(true)) => { config.ccache = Some("ccache".to_string()); } Some(StringOrBool::Bool(false)) | None => {} } - set(&mut config.ninja_in_file, llvm.ninja); - llvm_assertions = llvm.assertions; - llvm_tests = llvm.tests; - llvm_plugins = llvm.plugins; - set(&mut config.llvm_optimize, llvm.optimize); - set(&mut config.llvm_thin_lto, llvm.thin_lto); - set(&mut config.llvm_release_debuginfo, llvm.release_debuginfo); - set(&mut config.llvm_static_stdcpp, llvm.static_libstdcpp); - if let Some(v) = llvm.link_shared { + set(&mut config.ninja_in_file, ninja); + llvm_assertions = assertions; + llvm_tests = tests; + llvm_plugins = plugins; + set(&mut config.llvm_optimize, optimize_toml); + set(&mut config.llvm_thin_lto, thin_lto); + set(&mut config.llvm_release_debuginfo, release_debuginfo); + set(&mut config.llvm_static_stdcpp, static_libstdcpp); + if let Some(v) = link_shared { config.llvm_link_shared.set(Some(v)); } - config.llvm_targets = llvm.targets.clone(); - config.llvm_experimental_targets = llvm.experimental_targets.clone(); - config.llvm_link_jobs = llvm.link_jobs; - config.llvm_version_suffix = llvm.version_suffix.clone(); - config.llvm_clang_cl = llvm.clang_cl.clone(); - - config.llvm_cflags = llvm.cflags.clone(); - config.llvm_cxxflags = llvm.cxxflags.clone(); - config.llvm_ldflags = llvm.ldflags.clone(); - set(&mut config.llvm_use_libcxx, llvm.use_libcxx); - config.llvm_use_linker = llvm.use_linker.clone(); - config.llvm_allow_old_toolchain = llvm.allow_old_toolchain.unwrap_or(false); - config.llvm_polly = llvm.polly.unwrap_or(false); - config.llvm_clang = llvm.clang.unwrap_or(false); - config.llvm_enable_warnings = llvm.enable_warnings.unwrap_or(false); - config.llvm_build_config = llvm.build_config.clone().unwrap_or(Default::default()); + config.llvm_targets = targets.clone(); + config.llvm_experimental_targets = experimental_targets.clone(); + config.llvm_link_jobs = link_jobs; + config.llvm_version_suffix = version_suffix.clone(); + config.llvm_clang_cl = clang_cl.clone(); + + config.llvm_cflags = cflags.clone(); + config.llvm_cxxflags = cxxflags.clone(); + config.llvm_ldflags = ldflags.clone(); + set(&mut config.llvm_use_libcxx, use_libcxx); + config.llvm_use_linker = use_linker.clone(); + config.llvm_allow_old_toolchain = allow_old_toolchain.unwrap_or(false); + config.llvm_polly = polly.unwrap_or(false); + config.llvm_clang = clang.unwrap_or(false); + config.llvm_enable_warnings = enable_warnings.unwrap_or(false); + config.llvm_build_config = build_config.clone().unwrap_or(Default::default()); let asserts = llvm_assertions.unwrap_or(false); - config.llvm_from_ci = config.parse_download_ci_llvm(llvm.download_ci_llvm, asserts); + config.llvm_from_ci = config.parse_download_ci_llvm(download_ci_llvm, asserts); if config.llvm_from_ci { // None of the LLVM options, except assertions, are supported @@ -1542,39 +1764,38 @@ impl Config { // explicitly set. The defaults and CI defaults don't // necessarily match but forcing people to match (somewhat // arbitrary) CI configuration locally seems bad/hard. - check_ci_llvm!(llvm.optimize); - check_ci_llvm!(llvm.thin_lto); - check_ci_llvm!(llvm.release_debuginfo); + check_ci_llvm!(optimize_toml); + check_ci_llvm!(thin_lto); + check_ci_llvm!(release_debuginfo); // CI-built LLVM can be either dynamic or static. We won't know until we download it. - check_ci_llvm!(llvm.link_shared); - check_ci_llvm!(llvm.static_libstdcpp); - check_ci_llvm!(llvm.targets); - check_ci_llvm!(llvm.experimental_targets); - check_ci_llvm!(llvm.link_jobs); - check_ci_llvm!(llvm.clang_cl); - check_ci_llvm!(llvm.version_suffix); - check_ci_llvm!(llvm.cflags); - check_ci_llvm!(llvm.cxxflags); - check_ci_llvm!(llvm.ldflags); - check_ci_llvm!(llvm.use_libcxx); - check_ci_llvm!(llvm.use_linker); - check_ci_llvm!(llvm.allow_old_toolchain); - check_ci_llvm!(llvm.polly); - check_ci_llvm!(llvm.clang); - check_ci_llvm!(llvm.build_config); - check_ci_llvm!(llvm.plugins); + check_ci_llvm!(link_shared); + check_ci_llvm!(static_libstdcpp); + check_ci_llvm!(targets); + check_ci_llvm!(experimental_targets); + check_ci_llvm!(link_jobs); + check_ci_llvm!(clang_cl); + check_ci_llvm!(version_suffix); + check_ci_llvm!(cflags); + check_ci_llvm!(cxxflags); + check_ci_llvm!(ldflags); + check_ci_llvm!(use_libcxx); + check_ci_llvm!(use_linker); + check_ci_llvm!(allow_old_toolchain); + check_ci_llvm!(polly); + check_ci_llvm!(clang); + check_ci_llvm!(build_config); + check_ci_llvm!(plugins); } // NOTE: can never be hit when downloading from CI, since we call `check_ci_llvm!(thin_lto)` above. - if config.llvm_thin_lto && llvm.link_shared.is_none() { + if config.llvm_thin_lto && link_shared.is_none() { // If we're building with ThinLTO on, by default we want to link // to LLVM shared, to avoid re-doing ThinLTO (which happens in // the link step) with each stage. config.llvm_link_shared.set(Some(true)); } } else { - config.llvm_from_ci = config.channel == "dev" - && crate::core::build_steps::llvm::is_ci_llvm_available(&config, false); + config.llvm_from_ci = config.parse_download_ci_llvm(None, false); } if let Some(t) = toml.target { @@ -1632,17 +1853,26 @@ impl Config { build_target.llvm_filecheck = Some(ci_llvm_bin.join(exe("FileCheck", config.build))); } - if let Some(t) = toml.dist { - config.dist_sign_folder = t.sign_folder.map(PathBuf::from); - config.dist_upload_addr = t.upload_addr; - config.dist_compression_formats = t.compression_formats; - set(&mut config.dist_compression_profile, t.compression_profile); - set(&mut config.rust_dist_src, t.src_tarball); - set(&mut config.missing_tools, t.missing_tools); - set(&mut config.dist_include_mingw_linker, t.include_mingw_linker) + if let Some(dist) = toml.dist { + let Dist { + sign_folder, + upload_addr, + src_tarball, + missing_tools, + compression_formats, + compression_profile, + include_mingw_linker, + } = dist; + config.dist_sign_folder = sign_folder.map(PathBuf::from); + config.dist_upload_addr = upload_addr; + config.dist_compression_formats = compression_formats; + set(&mut config.dist_compression_profile, compression_profile); + set(&mut config.rust_dist_src, src_tarball); + set(&mut config.missing_tools, missing_tools); + set(&mut config.dist_include_mingw_linker, include_mingw_linker) } - if let Some(r) = build.rustfmt { + if let Some(r) = rustfmt { *config.initial_rustfmt.borrow_mut() = if r.exists() { RustfmtState::SystemToolchain(r) } else { @@ -1683,20 +1913,20 @@ impl Config { 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), + Subcommand::Check { .. } => flags.stage.or(check_stage).unwrap_or(0), // `download-rustc` only has a speed-up for stage2 builds. Default to stage2 unless explicitly overridden. Subcommand::Doc { .. } => { - flags.stage.or(build.doc_stage).unwrap_or(if download_rustc { 2 } else { 0 }) + flags.stage.or(doc_stage).unwrap_or(if download_rustc { 2 } else { 0 }) } Subcommand::Build { .. } => { - flags.stage.or(build.build_stage).unwrap_or(if download_rustc { 2 } else { 1 }) + flags.stage.or(build_stage).unwrap_or(if download_rustc { 2 } else { 1 }) } Subcommand::Test { .. } => { - flags.stage.or(build.test_stage).unwrap_or(if download_rustc { 2 } else { 1 }) + flags.stage.or(test_stage).unwrap_or(if download_rustc { 2 } else { 1 }) } - Subcommand::Bench { .. } => flags.stage.or(build.bench_stage).unwrap_or(2), - Subcommand::Dist { .. } => flags.stage.or(build.dist_stage).unwrap_or(2), - Subcommand::Install { .. } => flags.stage.or(build.install_stage).unwrap_or(2), + Subcommand::Bench { .. } => flags.stage.or(bench_stage).unwrap_or(2), + Subcommand::Dist { .. } => flags.stage.or(dist_stage).unwrap_or(2), + Subcommand::Install { .. } => flags.stage.or(install_stage).unwrap_or(2), // These are all bootstrap tools, which don't depend on the compiler. // The stage we pass shouldn't matter, but use 0 just in case. Subcommand::Clean { .. } @@ -1738,7 +1968,7 @@ impl Config { config } - pub(crate) fn dry_run(&self) -> bool { + pub fn dry_run(&self) -> bool { match self.dry_run { DryRun::Disabled => false, DryRun::SelfCheck | DryRun::UserSelected => true, @@ -1904,7 +2134,7 @@ impl Config { } pub(crate) fn download_rustc_commit(&self) -> Option<&str> { - static DOWNLOAD_RUSTC: OnceCell<Option<String>> = OnceCell::new(); + static DOWNLOAD_RUSTC: OnceLock<Option<String>> = OnceLock::new(); if self.dry_run() && DOWNLOAD_RUSTC.get().is_none() { // avoid trying to actually download the commit return self.download_rustc_commit.as_deref(); @@ -2110,27 +2340,30 @@ impl Config { download_ci_llvm: Option<StringOrBool>, asserts: bool, ) -> bool { + let if_unchanged = || { + // Git is needed to track modifications here, but tarball source is not available. + // If not modified here or built through tarball source, we maintain consistency + // with '"if available"'. + if !self.rust_info.is_from_tarball() + && self + .last_modified_commit(&["src/llvm-project"], "download-ci-llvm", true) + .is_none() + { + // there are some untracked changes in the the given paths. + false + } else { + llvm::is_ci_llvm_available(&self, asserts) + } + }; match download_ci_llvm { - None => self.channel == "dev" && llvm::is_ci_llvm_available(&self, asserts), + None => self.channel == "dev" && if_unchanged(), Some(StringOrBool::Bool(b)) => b, + // FIXME: "if-available" is deprecated. Remove this block later (around mid 2024) + // to not break builds between the recent-to-old checkouts. Some(StringOrBool::String(s)) if s == "if-available" => { llvm::is_ci_llvm_available(&self, asserts) } - Some(StringOrBool::String(s)) if s == "if-unchanged" => { - // Git is needed to track modifications here, but tarball source is not available. - // If not modified here or built through tarball source, we maintain consistency - // with '"if available"'. - if !self.rust_info.is_from_tarball() - && self - .last_modified_commit(&["src/llvm-project"], "download-ci-llvm", true) - .is_none() - { - // there are some untracked changes in the the given paths. - false - } else { - llvm::is_ci_llvm_available(&self, asserts) - } - } + Some(StringOrBool::String(s)) if s == "if-unchanged" => if_unchanged(), Some(StringOrBool::String(other)) => { panic!("unrecognized option for download-ci-llvm: {:?}", other) } diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index 2a301007a..0b1372608 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -85,6 +85,9 @@ pub struct Flags { #[arg(global(true), long)] /// dry run; don't build anything pub dry_run: bool, + /// Indicates whether to dump the work done from bootstrap shims + #[arg(global(true), long)] + pub dump_bootstrap_shims: bool, #[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")] /// stage to build (indicates compiler to use/test, e.g., stage 0 uses the /// bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.) @@ -133,6 +136,13 @@ pub struct Flags { /// whether to use color in cargo and rustc output pub color: Color, + #[arg(global(true), long)] + /// Bootstrap uses this value to decide whether it should bypass locking the build process. + /// This is rarely needed (e.g., compiling the std library for different targets in parallel). + /// + /// Unless you know exactly what you are doing, you probably don't need this. + pub bypass_bootstrap_lock: bool, + /// whether rebuilding llvm should be skipped, overriding `skip-rebuld` in config.toml #[arg(global(true), long, value_name = "VALUE")] pub llvm_skip_rebuild: Option<bool>, diff --git a/src/bootstrap/src/core/download.rs b/src/bootstrap/src/core/download.rs index 3327aed96..ec404ab85 100644 --- a/src/bootstrap/src/core/download.rs +++ b/src/bootstrap/src/core/download.rs @@ -5,18 +5,18 @@ use std::{ io::{BufRead, BufReader, BufWriter, ErrorKind, Write}, path::{Path, PathBuf}, process::{Command, Stdio}, + sync::OnceLock, }; use build_helper::ci::CiEnv; -use once_cell::sync::OnceCell; use xz2::bufread::XzDecoder; -use crate::core::build_steps::llvm::detect_llvm_sha; use crate::core::config::RustfmtMetadata; use crate::utils::helpers::{check_run, exe, program_out_of_date}; +use crate::{core::build_steps::llvm::detect_llvm_sha, utils::helpers::hex_encode}; use crate::{t, Config}; -static SHOULD_FIX_BINS_AND_DYLIBS: OnceCell<bool> = OnceCell::new(); +static SHOULD_FIX_BINS_AND_DYLIBS: OnceLock<bool> = OnceLock::new(); /// `Config::try_run` wrapper for this module to avoid warnings on `try_run`, since we don't have access to a `builder` yet. fn try_run(config: &Config, cmd: &mut Command) -> Result<(), ()> { @@ -131,7 +131,7 @@ impl Config { println!("attempting to patch {}", fname.display()); // Only build `.nix-deps` once. - static NIX_DEPS_DIR: OnceCell<PathBuf> = OnceCell::new(); + static NIX_DEPS_DIR: OnceLock<PathBuf> = OnceLock::new(); let mut nix_build_succeeded = true; let nix_deps_dir = NIX_DEPS_DIR.get_or_init(|| { // Run `nix-build` to "build" each dependency (which will likely reuse @@ -208,7 +208,10 @@ impl Config { Some(other) => panic!("unsupported protocol {other} in {url}"), None => panic!("no protocol in {url}"), } - t!(std::fs::rename(&tempfile, dest_path)); + t!( + std::fs::rename(&tempfile, dest_path), + format!("failed to rename {tempfile:?} to {dest_path:?}") + ); } fn download_http_with_retries(&self, tempfile: &Path, url: &str, help_on_error: &str) { @@ -342,7 +345,7 @@ impl Config { reader.consume(l); } - let checksum = hex::encode(hasher.finalize().as_slice()); + let checksum = hex_encode(hasher.finalize().as_slice()); let verified = checksum == expected; if !verified { @@ -375,6 +378,32 @@ enum DownloadSource { /// Functions that are only ever called once, but named for clarify and to avoid thousand-line functions. impl Config { + pub(crate) fn download_clippy(&self) -> PathBuf { + self.verbose("downloading stage0 clippy artifacts"); + + let date = &self.stage0_metadata.compiler.date; + let version = &self.stage0_metadata.compiler.version; + let host = self.build; + + let bin_root = self.out.join(host.triple).join("stage0"); + let clippy_stamp = bin_root.join(".clippy-stamp"); + let cargo_clippy = bin_root.join("bin").join(exe("cargo-clippy", host)); + if cargo_clippy.exists() && !program_out_of_date(&clippy_stamp, &date) { + return cargo_clippy; + } + + let filename = format!("clippy-{version}-{host}.tar.xz"); + self.download_component(DownloadSource::Dist, filename, "clippy-preview", date, "stage0"); + if self.should_fix_bins_and_dylibs() { + self.fix_bin_or_dylib(&cargo_clippy); + self.fix_bin_or_dylib(&cargo_clippy.with_file_name(exe("clippy-driver", host))); + } + + cargo_clippy + } + + /// NOTE: rustfmt is a completely different toolchain than the bootstrap compiler, so it can't + /// reuse target directories or artifacts pub(crate) fn maybe_download_rustfmt(&self) -> Option<PathBuf> { let RustfmtMetadata { date, version } = self.stage0_metadata.rustfmt.as_ref()?; let channel = format!("{version}-{date}"); @@ -544,6 +573,10 @@ impl Config { key: &str, destination: &str, ) { + if self.dry_run() { + return; + } + let cache_dst = self.out.join("cache"); let cache_dir = cache_dst.join(key); if !cache_dir.exists() { diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index eec3be66a..9101d94ea 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -237,7 +237,7 @@ than building it. } } - if need_cmake && target.contains("msvc") { + if need_cmake && target.is_msvc() { // There are three builds of cmake on windows: MSVC, MinGW, and // Cygwin. The Cygwin build does not have generators for Visual // Studio, so detect that here and error. diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 33b8f1a7c..871318de5 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -25,18 +25,20 @@ use std::io; use std::path::{Path, PathBuf}; use std::process::{Command, Output, Stdio}; use std::str; +use std::sync::OnceLock; use build_helper::ci::{gha, CiEnv}; use build_helper::exit; use build_helper::util::fail; use filetime::FileTime; -use once_cell::sync::OnceCell; +use sha2::digest::Digest; use termcolor::{ColorChoice, StandardStream, WriteColor}; use utils::channel::GitInfo; +use utils::helpers::hex_encode; use crate::core::builder; use crate::core::builder::Kind; -use crate::core::config::flags; +use crate::core::config::{flags, LldMode}; use crate::core::config::{DryRun, Target}; use crate::core::config::{LlvmLibunwind, TargetSelection}; use crate::utils::cache::{Interned, INTERNER}; @@ -46,9 +48,10 @@ use crate::utils::helpers::{self, dir_is_empty, exe, libdir, mtime, output, syml mod core; mod utils; -pub use crate::core::builder::PathSet; -pub use crate::core::config::flags::Subcommand; -pub use crate::core::config::Config; +pub use core::builder::PathSet; +pub use core::config::flags::Subcommand; +pub use core::config::Config; +pub use utils::change_tracker::{find_recent_config_change_ids, CONFIG_CHANGE_HISTORY}; const LLVM_TOOLS: &[&str] = &[ "llvm-cov", // used to generate coverage report @@ -69,36 +72,25 @@ const LLVM_TOOLS: &[&str] = &[ /// LLD file names for all flavors. const LLD_FILE_NAMES: &[&str] = &["ld.lld", "ld64.lld", "lld-link", "wasm-ld"]; -/// Keeps track of major changes made to the bootstrap configuration. -/// -/// These values also represent the IDs of the PRs that caused major changes. -/// You can visit `https://github.com/rust-lang/rust/pull/{any-id-from-the-list}` to -/// check for more details regarding each change. -/// -/// If you make any major changes (such as adding new values or changing default values), -/// please ensure that the associated PR ID is added to the end of this list. -/// This is necessary because the list must be sorted by the merge date. -pub const CONFIG_CHANGE_HISTORY: &[usize] = &[115898, 116998, 117435, 116881]; - /// Extra --check-cfg to add when building /// (Mode restriction, config name, config values (if any)) const EXTRA_CHECK_CFGS: &[(Option<Mode>, &str, Option<&[&'static str]>)] = &[ (None, "bootstrap", None), (Some(Mode::Rustc), "parallel_compiler", None), (Some(Mode::ToolRustc), "parallel_compiler", None), + (Some(Mode::ToolRustc), "rust_analyzer", None), + (Some(Mode::ToolStd), "rust_analyzer", None), (Some(Mode::Codegen), "parallel_compiler", None), (Some(Mode::Std), "stdarch_intel_sde", None), (Some(Mode::Std), "no_fp_fmt_parse", None), (Some(Mode::Std), "no_global_oom_handling", None), (Some(Mode::Std), "no_rc", None), (Some(Mode::Std), "no_sync", None), - (Some(Mode::Std), "freebsd12", None), - (Some(Mode::Std), "freebsd13", None), (Some(Mode::Std), "backtrace_in_libstd", None), /* Extra values not defined in the built-in targets yet, but used in std */ (Some(Mode::Std), "target_env", Some(&["libnx"])), // (Some(Mode::Std), "target_os", Some(&[])), - (Some(Mode::Std), "target_arch", Some(&["asmjs", "spirv", "nvptx", "xtensa"])), + (Some(Mode::Std), "target_arch", Some(&["spirv", "nvptx", "xtensa"])), /* Extra names used by dependencies */ // FIXME: Used by serde_json, but we should not be triggering on external dependencies. (Some(Mode::Rustc), "no_btreemap_remove_entry", None), @@ -873,7 +865,7 @@ impl Build { } } else { let base = self.llvm_out(target).join("build"); - let base = if !self.ninja() && target.contains("msvc") { + let base = if !self.ninja() && target.is_msvc() { if self.config.llvm_optimize { if self.config.llvm_release_debuginfo { base.join("RelWithDebInfo") @@ -915,7 +907,7 @@ impl Build { /// Returns the sysroot of the snapshot compiler. fn rustc_snapshot_sysroot(&self) -> &Path { - static SYSROOT_CACHE: OnceCell<PathBuf> = once_cell::sync::OnceCell::new(); + static SYSROOT_CACHE: OnceLock<PathBuf> = OnceLock::new(); SYSROOT_CACHE.get_or_init(|| { let mut rustc = Command::new(&self.initial_rustc); rustc.args(&["--print", "sysroot"]); @@ -1266,35 +1258,27 @@ impl Build { Some(self.cxx.borrow()[&target].path().into()) } else if target != self.config.build && helpers::use_host_linker(target) - && !target.contains("msvc") + && !target.is_msvc() { Some(self.cc(target)) - } else if self.config.use_lld && !self.is_fuse_ld_lld(target) && self.build == target { - Some(self.initial_lld.clone()) + } else if self.config.lld_mode.is_used() + && self.is_lld_direct_linker(target) + && self.build == target + { + match self.config.lld_mode { + LldMode::SelfContained => Some(self.initial_lld.clone()), + LldMode::External => Some("lld".into()), + LldMode::Unused => None, + } } else { None } } - // LLD is used through `-fuse-ld=lld` rather than directly. + // Is LLD configured directly through `-Clinker`? // Only MSVC targets use LLD directly at the moment. - fn is_fuse_ld_lld(&self, target: TargetSelection) -> bool { - self.config.use_lld && !target.contains("msvc") - } - - fn lld_flags(&self, target: TargetSelection) -> impl Iterator<Item = String> { - let mut options = [None, None]; - - if self.config.use_lld { - if self.is_fuse_ld_lld(target) { - options[0] = Some("-Clink-arg=-fuse-ld=lld".to_string()); - } - - let no_threads = helpers::lld_flag_no_threads(target.contains("windows")); - options[1] = Some(format!("-Clink-arg=-Wl,{no_threads}")); - } - - IntoIterator::into_iter(options).flatten() + fn is_lld_direct_linker(&self, target: TargetSelection) -> bool { + target.is_msvc() } /// Returns if this target should statically link the C runtime, if specified @@ -1775,7 +1759,7 @@ to download LLVM rather than building it. // In these cases we automatically enable Ninja if we find it in the // environment. if !self.config.ninja_in_file - && self.config.build.contains("msvc") + && self.config.build.is_msvc() && cmd_finder.maybe_have("ninja").is_some() { return true; @@ -1849,26 +1833,63 @@ fn envify(s: &str) -> String { .collect() } -pub fn find_recent_config_change_ids(current_id: usize) -> Vec<usize> { - if !CONFIG_CHANGE_HISTORY.contains(¤t_id) { - // If the current change-id is greater than the most recent one, return - // an empty list (it may be due to switching from a recent branch to an - // older one); otherwise, return the full list (assuming the user provided - // the incorrect change-id by accident). - if let Some(max_id) = CONFIG_CHANGE_HISTORY.iter().max() { - if ¤t_id > max_id { - return Vec::new(); - } - } +/// Computes a hash representing the state of a repository/submodule and additional input. +/// +/// It uses `git diff` for the actual changes, and `git status` for including the untracked +/// files in the specified directory. The additional input is also incorporated into the +/// computation of the hash. +/// +/// # Parameters +/// +/// - `dir`: A reference to the directory path of the target repository/submodule. +/// - `additional_input`: An additional input to be included in the hash. +/// +/// # Panics +/// +/// In case of errors during `git` command execution (e.g., in tarball sources), default values +/// are used to prevent panics. +pub fn generate_smart_stamp_hash(dir: &Path, additional_input: &str) -> String { + let diff = Command::new("git") + .current_dir(dir) + .arg("diff") + .output() + .map(|o| String::from_utf8(o.stdout).unwrap_or_default()) + .unwrap_or_default(); + + let status = Command::new("git") + .current_dir(dir) + .arg("status") + .arg("--porcelain") + .arg("-z") + .arg("--untracked-files=normal") + .output() + .map(|o| String::from_utf8(o.stdout).unwrap_or_default()) + .unwrap_or_default(); + + let mut hasher = sha2::Sha256::new(); + + hasher.update(diff); + hasher.update(status); + hasher.update(additional_input); + + hex_encode(hasher.finalize().as_slice()) +} - return CONFIG_CHANGE_HISTORY.to_vec(); - } +/// Ensures that the behavior dump directory is properly initialized. +pub fn prepare_behaviour_dump_dir(build: &Build) { + static INITIALIZED: OnceLock<bool> = OnceLock::new(); - let index = CONFIG_CHANGE_HISTORY.iter().position(|&id| id == current_id).unwrap(); + let dump_path = build.out.join("bootstrap-shims-dump"); - CONFIG_CHANGE_HISTORY - .iter() - .skip(index + 1) // Skip the current_id and IDs before it - .cloned() - .collect() + let initialized = INITIALIZED.get().unwrap_or_else(|| &false); + if !initialized { + // clear old dumps + if dump_path.exists() { + t!(fs::remove_dir_all(&dump_path)); + } + + t!(fs::create_dir_all(&dump_path)); + + t!(INITIALIZED.set(true)); + } } diff --git a/src/bootstrap/src/tests/builder.rs b/src/bootstrap/src/tests/builder.rs index 96139f7b0..700ebcf5e 100644 --- a/src/bootstrap/src/tests/builder.rs +++ b/src/bootstrap/src/tests/builder.rs @@ -1,6 +1,6 @@ use super::*; -use crate::core::config::{Config, DryRun, TargetSelection}; use crate::core::build_steps::doc::DocumentationFormat; +use crate::core::config::{Config, DryRun, TargetSelection}; use std::thread; fn configure(cmd: &str, host: &[&str], target: &[&str]) -> Config { @@ -156,22 +156,6 @@ fn alias_and_path_for_library() { assert_eq!(first(cache.all::<doc::Std>()), &[doc_std!(A => A, stage = 0)]); } -#[test] -fn test_beta_rev_parsing() { - use crate::utils::helpers::extract_beta_rev; - - // single digit revision - assert_eq!(extract_beta_rev("1.99.9-beta.7 (xxxxxx)"), Some("7".to_string())); - // multiple digits - assert_eq!(extract_beta_rev("1.99.9-beta.777 (xxxxxx)"), Some("777".to_string())); - // nightly channel (no beta revision) - assert_eq!(extract_beta_rev("1.99.9-nightly (xxxxxx)"), None); - // stable channel (no beta revision) - assert_eq!(extract_beta_rev("1.99.9 (xxxxxxx)"), None); - // invalid string - assert_eq!(extract_beta_rev("invalid"), None); -} - mod defaults { use super::{configure, first, run_build}; use crate::core::builder::*; diff --git a/src/bootstrap/src/tests/config.rs b/src/bootstrap/src/tests/config.rs index 59bd52a94..6f4323438 100644 --- a/src/bootstrap/src/tests/config.rs +++ b/src/bootstrap/src/tests/config.rs @@ -1,5 +1,5 @@ -use crate::core::config::TomlConfig; use super::{Config, Flags}; +use crate::core::config::{LldMode, TomlConfig}; use clap::CommandFactory; use serde::Deserialize; @@ -24,19 +24,19 @@ fn download_ci_llvm() { } let parse_llvm = |s| parse(s).llvm_from_ci; - let if_available = parse_llvm("llvm.download-ci-llvm = \"if-available\""); + let if_unchanged = parse_llvm("llvm.download-ci-llvm = \"if-unchanged\""); assert!(parse_llvm("llvm.download-ci-llvm = true")); assert!(!parse_llvm("llvm.download-ci-llvm = false")); - assert_eq!(parse_llvm(""), if_available); - assert_eq!(parse_llvm("rust.channel = \"dev\""), if_available); + assert_eq!(parse_llvm(""), if_unchanged); + assert_eq!(parse_llvm("rust.channel = \"dev\""), if_unchanged); assert!(!parse_llvm("rust.channel = \"stable\"")); - assert!(parse_llvm("build.build = \"x86_64-unknown-linux-gnu\"")); - assert!(parse_llvm( - "llvm.assertions = true \r\n build.build = \"x86_64-unknown-linux-gnu\" \r\n llvm.download-ci-llvm = \"if-available\"" - )); + assert_eq!(parse_llvm("build.build = \"x86_64-unknown-linux-gnu\""), if_unchanged); + assert_eq!(parse_llvm( + "llvm.assertions = true \r\n build.build = \"x86_64-unknown-linux-gnu\" \r\n llvm.download-ci-llvm = \"if-unchanged\"" + ), if_unchanged); assert!(!parse_llvm( - "llvm.assertions = true \r\n build.build = \"aarch64-apple-darwin\" \r\n llvm.download-ci-llvm = \"if-available\"" + "llvm.assertions = true \r\n build.build = \"aarch64-apple-darwin\" \r\n llvm.download-ci-llvm = \"if-unchanged\"" )); } @@ -217,3 +217,12 @@ fn verify_file_integrity() { remove_file(tempfile).unwrap(); } + +#[test] +fn rust_lld() { + assert!(matches!(parse("").lld_mode, LldMode::Unused)); + assert!(matches!(parse("rust.use-lld = \"self-contained\"").lld_mode, LldMode::SelfContained)); + assert!(matches!(parse("rust.use-lld = \"external\"").lld_mode, LldMode::External)); + assert!(matches!(parse("rust.use-lld = true").lld_mode, LldMode::External)); + assert!(matches!(parse("rust.use-lld = false").lld_mode, LldMode::Unused)); +} diff --git a/src/bootstrap/src/tests/helpers.rs b/src/bootstrap/src/tests/helpers.rs new file mode 100644 index 000000000..afe18aeba --- /dev/null +++ b/src/bootstrap/src/tests/helpers.rs @@ -0,0 +1,59 @@ +use crate::utils::helpers::{extract_beta_rev, hex_encode, make}; +use std::path::PathBuf; + +#[test] +fn test_make() { + for (host, make_path) in vec![ + ("dragonfly", PathBuf::from("gmake")), + ("netbsd", PathBuf::from("gmake")), + ("freebsd", PathBuf::from("gmake")), + ("openbsd", PathBuf::from("gmake")), + ("linux", PathBuf::from("make")), + // for checking the default + ("_", PathBuf::from("make")), + ] { + assert_eq!(make(host), make_path); + } +} + +#[cfg(unix)] +#[test] +fn test_absolute_unix() { + use crate::utils::helpers::absolute_unix; + + // Test an absolute path + let path = PathBuf::from("/home/user/file.txt"); + assert_eq!(absolute_unix(&path).unwrap(), PathBuf::from("/home/user/file.txt")); + + // Test an absolute path with double leading slashes + let path = PathBuf::from("//root//file.txt"); + assert_eq!(absolute_unix(&path).unwrap(), PathBuf::from("//root/file.txt")); + + // Test a relative path + let path = PathBuf::from("relative/path"); + assert_eq!( + absolute_unix(&path).unwrap(), + std::env::current_dir().unwrap().join("relative/path") + ); +} + +#[test] +fn test_beta_rev_parsing() { + // single digit revision + assert_eq!(extract_beta_rev("1.99.9-beta.7 (xxxxxx)"), Some("7".to_string())); + // multiple digits + assert_eq!(extract_beta_rev("1.99.9-beta.777 (xxxxxx)"), Some("777".to_string())); + // nightly channel (no beta revision) + assert_eq!(extract_beta_rev("1.99.9-nightly (xxxxxx)"), None); + // stable channel (no beta revision) + assert_eq!(extract_beta_rev("1.99.9 (xxxxxxx)"), None); + // invalid string + assert_eq!(extract_beta_rev("invalid"), None); +} + +#[test] +fn test_string_to_hex_encode() { + let input_string = "Hello, World!"; + let hex_string = hex_encode(input_string); + assert_eq!(hex_string, "48656c6c6f2c20576f726c6421"); +} diff --git a/src/bootstrap/src/tests/setup.rs b/src/bootstrap/src/tests/setup.rs index 0fe6e4a46..3e4d66c74 100644 --- a/src/bootstrap/src/tests/setup.rs +++ b/src/bootstrap/src/tests/setup.rs @@ -1,11 +1,12 @@ use super::{RUST_ANALYZER_SETTINGS, SETTINGS_HASHES}; +use crate::utils::helpers::hex_encode; use sha2::Digest; #[test] fn check_matching_settings_hash() { let mut hasher = sha2::Sha256::new(); hasher.update(&RUST_ANALYZER_SETTINGS); - let hash = hex::encode(hasher.finalize().as_slice()); + let hash = hex_encode(hasher.finalize().as_slice()); assert_eq!( &hash, SETTINGS_HASHES.last().unwrap(), diff --git a/src/bootstrap/src/utils/bin_helpers.rs b/src/bootstrap/src/utils/bin_helpers.rs index c90fd2805..9c4e039ea 100644 --- a/src/bootstrap/src/utils/bin_helpers.rs +++ b/src/bootstrap/src/utils/bin_helpers.rs @@ -2,14 +2,18 @@ //! dependency on the bootstrap library. This reduces the binary size and //! improves compilation time by reducing the linking time. +use std::env; +use std::fs::OpenOptions; +use std::io::Write; +use std::process::Command; +use std::str::FromStr; + /// Parses the value of the "RUSTC_VERBOSE" environment variable and returns it as a `usize`. /// If it was not defined, returns 0 by default. /// /// Panics if "RUSTC_VERBOSE" is defined with the value that is not an unsigned integer. pub(crate) fn parse_rustc_verbose() -> usize { - use std::str::FromStr; - - match std::env::var("RUSTC_VERBOSE") { + match env::var("RUSTC_VERBOSE") { Ok(s) => usize::from_str(&s).expect("RUSTC_VERBOSE should be an integer"), Err(_) => 0, } @@ -19,10 +23,29 @@ pub(crate) fn parse_rustc_verbose() -> usize { /// /// If "RUSTC_STAGE" was not set, the program will be terminated with 101. pub(crate) fn parse_rustc_stage() -> String { - std::env::var("RUSTC_STAGE").unwrap_or_else(|_| { + env::var("RUSTC_STAGE").unwrap_or_else(|_| { // Don't panic here; it's reasonable to try and run these shims directly. Give a helpful error instead. eprintln!("rustc shim: FATAL: RUSTC_STAGE was not set"); eprintln!("rustc shim: NOTE: use `x.py build -vvv` to see all environment variables set by bootstrap"); std::process::exit(101); }) } + +/// Writes the command invocation to a file if `DUMP_BOOTSTRAP_SHIMS` is set during bootstrap. +/// +/// Before writing it, replaces user-specific values to create generic dumps for cross-environment +/// comparisons. +pub(crate) fn maybe_dump(dump_name: String, cmd: &Command) { + if let Ok(dump_dir) = env::var("DUMP_BOOTSTRAP_SHIMS") { + let dump_file = format!("{dump_dir}/{dump_name}"); + + let mut file = + OpenOptions::new().create(true).write(true).append(true).open(&dump_file).unwrap(); + + let cmd_dump = format!("{:?}\n", cmd); + let cmd_dump = cmd_dump.replace(&env::var("BUILD_OUT").unwrap(), "${BUILD_OUT}"); + let cmd_dump = cmd_dump.replace(&env::var("CARGO_HOME").unwrap(), "${CARGO_HOME}"); + + file.write_all(cmd_dump.as_bytes()).expect("Unable to write file"); + } +} diff --git a/src/bootstrap/src/utils/cc_detect.rs b/src/bootstrap/src/utils/cc_detect.rs index 52b36ce75..fb5b9d8c8 100644 --- a/src/bootstrap/src/utils/cc_detect.rs +++ b/src/bootstrap/src/utils/cc_detect.rs @@ -39,7 +39,7 @@ fn cc2ar(cc: &Path, target: TargetSelection) -> Option<PathBuf> { Some(PathBuf::from(ar)) } else if let Some(ar) = env::var_os("AR") { Some(PathBuf::from(ar)) - } else if target.contains("msvc") { + } else if target.is_msvc() { None } else if target.contains("musl") { Some(PathBuf::from("ar")) @@ -78,7 +78,7 @@ fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build { cfg.static_crt(a); } None => { - if target.contains("msvc") { + if target.is_msvc() { cfg.static_crt(true); } if target.contains("musl") { diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs new file mode 100644 index 000000000..8b53a6154 --- /dev/null +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -0,0 +1,99 @@ +//! This module facilitates the tracking system for major changes made to the bootstrap, +//! with the goal of keeping developers synchronized with important modifications in +//! the bootstrap. + +#[derive(Clone, Debug)] +pub struct ChangeInfo { + /// Represents the ID of PR caused major change on bootstrap. + pub change_id: usize, + pub severity: ChangeSeverity, + /// Provides a short summary of the change that will guide developers + /// on "how to handle/behave" in response to the changes. + pub summary: &'static str, +} + +#[derive(Clone, Debug)] +pub enum ChangeSeverity { + /// Used when build configurations continue working as before. + Info, + /// Used when the default value of an option changes, or support for an option is removed entirely, + /// potentially requiring developers to update their build configurations. + Warning, +} + +impl ToString for ChangeSeverity { + fn to_string(&self) -> String { + match self { + ChangeSeverity::Info => "INFO".to_string(), + ChangeSeverity::Warning => "WARNING".to_string(), + } + } +} + +pub fn find_recent_config_change_ids(current_id: usize) -> Vec<ChangeInfo> { + if !CONFIG_CHANGE_HISTORY.iter().any(|config| config.change_id == current_id) { + // If the current change-id is greater than the most recent one, return + // an empty list (it may be due to switching from a recent branch to an + // older one); otherwise, return the full list (assuming the user provided + // the incorrect change-id by accident). + if let Some(config) = CONFIG_CHANGE_HISTORY.iter().max_by_key(|config| config.change_id) { + if ¤t_id > &config.change_id { + return Vec::new(); + } + } + + return CONFIG_CHANGE_HISTORY.to_vec(); + } + + let index = + CONFIG_CHANGE_HISTORY.iter().position(|config| config.change_id == current_id).unwrap(); + + CONFIG_CHANGE_HISTORY + .iter() + .skip(index + 1) // Skip the current_id and IDs before it + .cloned() + .collect() +} + +/// Keeps track of major changes made to the bootstrap configuration. +/// +/// If you make any major changes (such as adding new values or changing default values), +/// please ensure adding `ChangeInfo` to the end(because the list must be sorted by the merge date) +/// of this list. +pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ + ChangeInfo { + change_id: 115898, + severity: ChangeSeverity::Info, + summary: "Implementation of this change-tracking system. Ignore this.", + }, + ChangeInfo { + change_id: 116998, + severity: ChangeSeverity::Info, + summary: "Removed android-ndk r15 support in favor of android-ndk r25b.", + }, + ChangeInfo { + change_id: 117435, + severity: ChangeSeverity::Info, + summary: "New option `rust.parallel-compiler` added to config.toml.", + }, + ChangeInfo { + change_id: 116881, + severity: ChangeSeverity::Warning, + summary: "Default value of `download-ci-llvm` was changed for `codegen` profile.", + }, + ChangeInfo { + change_id: 117813, + severity: ChangeSeverity::Info, + summary: "Use of the `if-available` value for `download-ci-llvm` is deprecated; prefer using the new `if-unchanged` value.", + }, + ChangeInfo { + change_id: 116278, + severity: ChangeSeverity::Info, + summary: "The `rust.use-lld` configuration now has different options ('external'/true or 'self-contained'), and its behaviour has changed.", + }, + ChangeInfo { + change_id: 118703, + severity: ChangeSeverity::Info, + summary: "Removed rust.run_dsymutil and dist.gpg_password_file config options, as they were unused.", + }, +]; diff --git a/src/bootstrap/src/utils/dylib.rs b/src/bootstrap/src/utils/dylib.rs index 279a6a010..b6e7aec17 100644 --- a/src/bootstrap/src/utils/dylib.rs +++ b/src/bootstrap/src/utils/dylib.rs @@ -25,3 +25,16 @@ pub fn dylib_path() -> Vec<std::path::PathBuf> { }; std::env::split_paths(&var).collect() } + +/// Given an executable called `name`, return the filename for the +/// executable for a particular target. +#[allow(dead_code)] +pub fn exe(name: &str, target: &str) -> String { + if target.contains("windows") { + format!("{name}.exe") + } else if target.contains("uefi") { + format!("{name}.efi") + } else { + name.to_string() + } +} diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs index 5bc81f2d9..0c4297db6 100644 --- a/src/bootstrap/src/utils/helpers.rs +++ b/src/bootstrap/src/utils/helpers.rs @@ -5,19 +5,25 @@ use build_helper::util::fail; use std::env; +use std::ffi::OsStr; use std::fs; use std::io; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::str; +use std::sync::OnceLock; use std::time::{Instant, SystemTime, UNIX_EPOCH}; use crate::core::builder::Builder; use crate::core::config::{Config, TargetSelection}; -use crate::OnceCell; +use crate::LldMode; pub use crate::utils::dylib::{dylib_path, dylib_path_var}; +#[cfg(test)] +#[path = "../tests/helpers.rs"] +mod tests; + /// A helper macro to `unwrap` a result except also print out details like: /// /// * The file/line of the panic @@ -44,16 +50,8 @@ macro_rules! t { } pub use t; -/// Given an executable called `name`, return the filename for the -/// executable for a particular target. pub fn exe(name: &str, target: TargetSelection) -> String { - if target.contains("windows") { - format!("{name}.exe") - } else if target.contains("uefi") { - format!("{name}.efi") - } else { - name.to_string() - } + crate::utils::dylib::exe(name, &target.triple) } /// Returns `true` if the file name given looks like a dynamic library. @@ -70,7 +68,7 @@ pub fn is_debug_info(name: &str) -> bool { /// Returns the corresponding relative library directory that the compiler's /// dylibs will be found in. pub fn libdir(target: TargetSelection) -> &'static str { - if target.contains("windows") { "bin" } else { "lib" } + if target.is_windows() { "bin" } else { "lib" } } /// Adds a list of lookup paths to `cmd`'s dynamic library lookup path. @@ -189,7 +187,7 @@ pub fn target_supports_cranelift_backend(target: TargetSelection) -> bool { || target.contains("aarch64") || target.contains("s390x") || target.contains("riscv64gc") - } else if target.contains("darwin") || target.contains("windows") { + } else if target.contains("darwin") || target.is_windows() { target.contains("x86_64") } else { false @@ -443,17 +441,29 @@ pub fn get_clang_cl_resource_dir(clang_cl_path: &str) -> PathBuf { clang_rt_dir.to_path_buf() } -pub fn lld_flag_no_threads(is_windows: bool) -> &'static str { - static LLD_NO_THREADS: OnceCell<(&'static str, &'static str)> = OnceCell::new(); - let (windows, other) = LLD_NO_THREADS.get_or_init(|| { - let out = output(Command::new("lld").arg("-flavor").arg("ld").arg("--version")); - let newer = match (out.find(char::is_numeric), out.find('.')) { - (Some(b), Some(e)) => out.as_str()[b..e].parse::<i32>().ok().unwrap_or(14) > 10, +/// Returns a flag that configures LLD to use only a single thread. +/// If we use an external LLD, we need to find out which version is it to know which flag should we +/// pass to it (LLD older than version 10 had a different flag). +fn lld_flag_no_threads(lld_mode: LldMode, is_windows: bool) -> &'static str { + static LLD_NO_THREADS: OnceLock<(&'static str, &'static str)> = OnceLock::new(); + + let new_flags = ("/threads:1", "--threads=1"); + let old_flags = ("/no-threads", "--no-threads"); + + let (windows_flag, other_flag) = LLD_NO_THREADS.get_or_init(|| { + let newer_version = match lld_mode { + LldMode::External => { + let out = output(Command::new("lld").arg("-flavor").arg("ld").arg("--version")); + match (out.find(char::is_numeric), out.find('.')) { + (Some(b), Some(e)) => out.as_str()[b..e].parse::<i32>().ok().unwrap_or(14) > 10, + _ => true, + } + } _ => true, }; - if newer { ("/threads:1", "--threads=1") } else { ("/no-threads", "--no-threads") } + if newer_version { new_flags } else { old_flags } }); - if is_windows { windows } else { other } + if is_windows { windows_flag } else { other_flag } } pub fn dir_is_empty(dir: &Path) -> bool { @@ -470,3 +480,75 @@ pub fn extract_beta_rev(version: &str) -> Option<String> { count } + +pub enum LldThreads { + Yes, + No, +} + +/// Returns the linker arguments for rustc/rustdoc for the given builder and target. +pub fn linker_args( + builder: &Builder<'_>, + target: TargetSelection, + lld_threads: LldThreads, +) -> Vec<String> { + let mut args = linker_flags(builder, target, lld_threads); + + if let Some(linker) = builder.linker(target) { + args.push(format!("-Clinker={}", linker.display())); + } + + args +} + +/// Returns the linker arguments for rustc/rustdoc for the given builder and target, without the +/// -Clinker flag. +pub fn linker_flags( + builder: &Builder<'_>, + target: TargetSelection, + lld_threads: LldThreads, +) -> Vec<String> { + let mut args = vec![]; + if !builder.is_lld_direct_linker(target) && builder.config.lld_mode.is_used() { + args.push(String::from("-Clink-arg=-fuse-ld=lld")); + + if matches!(lld_threads, LldThreads::No) { + args.push(format!( + "-Clink-arg=-Wl,{}", + lld_flag_no_threads(builder.config.lld_mode, target.is_windows()) + )); + } + } + args +} + +pub fn add_rustdoc_cargo_linker_args( + cmd: &mut Command, + builder: &Builder<'_>, + target: TargetSelection, + lld_threads: LldThreads, +) { + let args = linker_args(builder, target, lld_threads); + let mut flags = cmd + .get_envs() + .find_map(|(k, v)| if k == OsStr::new("RUSTDOCFLAGS") { v } else { None }) + .unwrap_or_default() + .to_os_string(); + for arg in args { + if !flags.is_empty() { + flags.push(" "); + } + flags.push(arg); + } + if !flags.is_empty() { + cmd.env("RUSTDOCFLAGS", flags); + } +} + +/// Converts `T` into a hexadecimal `String`. +pub fn hex_encode<T>(input: T) -> String +where + T: AsRef<[u8]>, +{ + input.as_ref().iter().map(|x| format!("{:02x}", x)).collect() +} diff --git a/src/bootstrap/src/utils/mod.rs b/src/bootstrap/src/utils/mod.rs index 8ca22d008..cb535f0e1 100644 --- a/src/bootstrap/src/utils/mod.rs +++ b/src/bootstrap/src/utils/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod cache; pub(crate) mod cc_detect; +pub(crate) mod change_tracker; pub(crate) mod channel; pub(crate) mod dylib; pub(crate) mod exec; |