| //! Implements `cargo miri setup`. |
| |
| use std::env; |
| use std::ffi::OsStr; |
| use std::path::PathBuf; |
| use std::process::{self, Command}; |
| |
| use rustc_build_sysroot::{BuildMode, SysrootBuilder, SysrootConfig}; |
| use rustc_version::VersionMeta; |
| |
| use crate::util::*; |
| |
| /// Performs the setup required to make `cargo miri` work: Getting a custom-built libstd. Then sets |
| /// `MIRI_SYSROOT`. Skipped if `MIRI_SYSROOT` is already set, in which case we expect the user has |
| /// done all this already. |
| pub fn setup( |
| subcommand: &MiriCommand, |
| target: &str, |
| rustc_version: &VersionMeta, |
| verbose: usize, |
| ) -> PathBuf { |
| let only_setup = matches!(subcommand, MiriCommand::Setup); |
| let ask_user = !only_setup; |
| let print_sysroot = only_setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path |
| if !only_setup { |
| if let Some(sysroot) = std::env::var_os("MIRI_SYSROOT") { |
| // Skip setup step if MIRI_SYSROOT is explicitly set, *unless* we are `cargo miri setup`. |
| return sysroot.into(); |
| } |
| } |
| |
| // Determine where the rust sources are located. The env var trumps auto-detection. |
| let rust_src_env_var = std::env::var_os("MIRI_LIB_SRC"); |
| let rust_src = match rust_src_env_var { |
| Some(path) => { |
| let path = PathBuf::from(path); |
| // Make path absolute if possible. |
| path.canonicalize().unwrap_or(path) |
| } |
| None => { |
| // Check for `rust-src` rustup component. |
| let rustup_src = rustc_build_sysroot::rustc_sysroot_src(miri_for_host()) |
| .expect("could not determine sysroot source directory"); |
| if !rustup_src.exists() { |
| // Ask the user to install the `rust-src` component, and use that. |
| let mut cmd = Command::new("rustup"); |
| cmd.args(["component", "add", "rust-src"]); |
| ask_to_run( |
| cmd, |
| ask_user, |
| "install the `rust-src` component for the selected toolchain", |
| ); |
| } |
| rustup_src |
| } |
| }; |
| if !rust_src.exists() { |
| show_error!("given Rust source directory `{}` does not exist.", rust_src.display()); |
| } |
| if rust_src.file_name().and_then(OsStr::to_str) != Some("library") { |
| show_error!( |
| "given Rust source directory `{}` does not seem to be the `library` subdirectory of \ |
| a Rust source checkout.", |
| rust_src.display() |
| ); |
| } |
| |
| // Determine where to put the sysroot. |
| let sysroot_dir = match std::env::var_os("MIRI_SYSROOT") { |
| Some(dir) => PathBuf::from(dir), |
| None => { |
| let user_dirs = directories::ProjectDirs::from("org", "rust-lang", "miri").unwrap(); |
| user_dirs.cache_dir().to_owned() |
| } |
| }; |
| // Sysroot configuration and build details. |
| let sysroot_config = if std::env::var_os("MIRI_NO_STD").is_some() { |
| SysrootConfig::NoStd |
| } else { |
| SysrootConfig::WithStd { |
| std_features: ["panic_unwind", "backtrace"].into_iter().map(Into::into).collect(), |
| } |
| }; |
| let cargo_cmd = { |
| let mut command = cargo(); |
| // Use Miri as rustc to build a libstd compatible with us (and use the right flags). |
| // However, when we are running in bootstrap, we cannot just overwrite `RUSTC`, |
| // because we still need bootstrap to distinguish between host and target crates. |
| // In that case we overwrite `RUSTC_REAL` instead which determines the rustc used |
| // for target crates. |
| // We set ourselves (`cargo-miri`) instead of Miri directly to be able to patch the flags |
| // for `libpanic_abort` (usually this is done by bootstrap but we have to do it ourselves). |
| // The `MIRI_CALLED_FROM_SETUP` will mean we dispatch to `phase_setup_rustc`. |
| let cargo_miri_path = std::env::current_exe().expect("current executable path invalid"); |
| if env::var_os("RUSTC_STAGE").is_some() { |
| assert!(env::var_os("RUSTC").is_some()); |
| command.env("RUSTC_REAL", &cargo_miri_path); |
| } else { |
| command.env("RUSTC", &cargo_miri_path); |
| } |
| command.env("MIRI_CALLED_FROM_SETUP", "1"); |
| // Miri expects `MIRI_SYSROOT` to be set when invoked in target mode. Even if that directory is empty. |
| command.env("MIRI_SYSROOT", &sysroot_dir); |
| // Make sure there are no other wrappers getting in our way (Cc |
| // https://github.com/rust-lang/miri/issues/1421, |
| // https://github.com/rust-lang/miri/issues/2429). Looks like setting |
| // `RUSTC_WRAPPER` to the empty string overwrites `build.rustc-wrapper` set via |
| // `config.toml`. |
| command.env("RUSTC_WRAPPER", ""); |
| |
| if only_setup && !print_sysroot { |
| // Forward output. Even make it verbose, if requested. |
| for _ in 0..verbose { |
| command.arg("-v"); |
| } |
| } else { |
| // Suppress output. |
| command.stdout(process::Stdio::null()); |
| command.stderr(process::Stdio::null()); |
| } |
| |
| command |
| }; |
| // Disable debug assertions in the standard library -- Miri is already slow enough. |
| // But keep the overflow checks, they are cheap. This completely overwrites flags |
| // the user might have set, which is consistent with normal `cargo build` that does |
| // not apply `RUSTFLAGS` to the sysroot either. |
| let rustflags = &["-Cdebug-assertions=off", "-Coverflow-checks=on"]; |
| |
| // Do the build. |
| if print_sysroot { |
| // Be silent. |
| } else if only_setup { |
| // We want to be explicit. |
| eprintln!("Preparing a sysroot for Miri (target: {target})..."); |
| } else { |
| // We want to be quiet, but still let the user know that something is happening. |
| eprint!("Preparing a sysroot for Miri (target: {target})... "); |
| } |
| SysrootBuilder::new(&sysroot_dir, target) |
| .build_mode(BuildMode::Check) |
| .rustc_version(rustc_version.clone()) |
| .sysroot_config(sysroot_config) |
| .rustflags(rustflags) |
| .cargo(cargo_cmd) |
| .build_from_source(&rust_src) |
| .unwrap_or_else(|err| { |
| if print_sysroot { |
| show_error!("failed to build sysroot") |
| } else if only_setup { |
| show_error!("failed to build sysroot: {err:?}") |
| } else { |
| show_error!( |
| "failed to build sysroot; run `cargo miri setup` to see the error details" |
| ) |
| } |
| }); |
| if print_sysroot { |
| // Be silent. |
| } else if only_setup { |
| eprintln!("A sysroot for Miri is now available in `{}`.", sysroot_dir.display()); |
| } else { |
| eprintln!("done"); |
| } |
| if print_sysroot { |
| // Print just the sysroot and nothing else to stdout; this way we do not need any escaping. |
| println!("{}", sysroot_dir.display()); |
| } |
| |
| sysroot_dir |
| } |