blob: 77cecddcb8b14f4a686e926d9ad65c1384aed04c [file] [log] [blame]
//! 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
}