miri-script refactor
This commit is contained in:
parent
7f2eca6a34
commit
3f952f4508
10 changed files with 589 additions and 650 deletions
2
src/tools/miri/.github/workflows/ci.yml
vendored
2
src/tools/miri/.github/workflows/ci.yml
vendored
|
|
@ -130,7 +130,7 @@ jobs:
|
|||
- name: clippy
|
||||
run: ./miri clippy -- -D warnings
|
||||
- name: rustdoc
|
||||
run: RUSTDOCFLAGS="-Dwarnings" cargo doc --document-private-items
|
||||
run: RUSTDOCFLAGS="-Dwarnings" ./miri cargo doc --document-private-items
|
||||
|
||||
# These jobs doesn't actually test anything, but they're only used to tell
|
||||
# bors the build completed, as there is no practical way to detect when a
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ evaluation error was originally raised.
|
|||
### UI testing
|
||||
|
||||
We use ui-testing in Miri, meaning we generate `.stderr` and `.stdout` files for the output
|
||||
produced by Miri. You can use `./miri bless` to automatically (re)generate these files when
|
||||
produced by Miri. You can use `./miri test --bless` to automatically (re)generate these files when
|
||||
you add new tests or change how Miri presents certain output.
|
||||
|
||||
Note that when you also use `MIRIFLAGS` to change optimizations and similar, the ui output
|
||||
|
|
|
|||
|
|
@ -538,8 +538,7 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
|
|||
}
|
||||
// Respect `MIRIFLAGS`.
|
||||
if let Ok(a) = env::var("MIRIFLAGS") {
|
||||
// This code is taken from `RUSTFLAGS` handling in cargo.
|
||||
let args = a.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string);
|
||||
let args = flagsplit(&a);
|
||||
cmd.args(args);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,11 @@ pub fn cargo() -> Command {
|
|||
Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")))
|
||||
}
|
||||
|
||||
pub fn flagsplit(flags: &str) -> Vec<String> {
|
||||
// This code is taken from `RUSTFLAGS` handling in cargo.
|
||||
flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect()
|
||||
}
|
||||
|
||||
/// Execute the `Command`, where possible by replacing the current process with a new process
|
||||
/// described by the `Command`. Then exit this process with the exit code of the new process.
|
||||
pub fn exec(mut cmd: Command) -> ! {
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@
|
|||
set -e
|
||||
# Instead of doing just `cargo run --manifest-path .. $@`, we invoke miri-script binary directly. Invoking `cargo run` goes through
|
||||
# rustup (that sets it's own environmental variables), which is undesirable.
|
||||
cargo build --manifest-path "$(dirname "$0")"/miri-script/Cargo.toml
|
||||
"$(dirname "$0")"/miri-script/target/debug/miri-script $@
|
||||
cargo build -q --manifest-path "$(dirname "$0")"/miri-script/Cargo.toml
|
||||
"$(dirname "$0")"/miri-script/target/debug/miri-script "$@"
|
||||
|
|
|
|||
4
src/tools/miri/miri-script/miri
Executable file
4
src/tools/miri/miri-script/miri
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
# RA invokes `./miri cargo ...` for each workspace, so we need to forward that to the main `miri`
|
||||
# script. See <https://github.com/rust-analyzer/rust-analyzer/issues/10793>.
|
||||
exec "$(dirname "$0")"/../miri "$@"
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
use clap::{Parser, Subcommand};
|
||||
use std::ffi::OsString;
|
||||
|
||||
#[derive(Parser, Clone, Debug)]
|
||||
#[command(author, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub commands: Subcommands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Clone, Debug)]
|
||||
pub enum Subcommands {
|
||||
/// Installs the miri driver and cargo-miri.
|
||||
/// Sets up the rpath such that the installed binary should work in any
|
||||
/// working directory. Note that the binaries are placed in the `miri` toolchain
|
||||
/// sysroot, to prevent conflicts with other toolchains.
|
||||
Install {
|
||||
/// Flags that are passed through to `cargo install`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Just build miri.
|
||||
Build {
|
||||
/// Flags that are passed through to `cargo build`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Just check miri.
|
||||
Check {
|
||||
/// Flags that are passed through to `cargo check`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Build miri, set up a sysroot and then run the test suite.
|
||||
Test {
|
||||
#[arg(long, default_value_t = false)]
|
||||
bless: bool,
|
||||
/// Flags that are passed through to `cargo test`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Build miri, set up a sysroot and then run the driver with the given <flags>.
|
||||
/// (Also respects MIRIFLAGS environment variable.)
|
||||
Run {
|
||||
#[arg(long, default_value_t = false)]
|
||||
dep: bool,
|
||||
/// Flags that are passed through to `miri`
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Format all sources and tests.
|
||||
Fmt {
|
||||
/// Flags that are passed through to `rustfmt`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Runs clippy on all sources.
|
||||
Clippy {
|
||||
/// Flags that are passed through to `cargo clippy`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Runs just `cargo <flags>` with the Miri-specific environment variables.
|
||||
/// Mainly meant to be invoked by rust-analyzer.
|
||||
Cargo {
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Runs <command> over and over again with different seeds for Miri. The MIRIFLAGS
|
||||
/// variable is set to its original value appended with ` -Zmiri-seed=$SEED` for
|
||||
/// many different seeds.
|
||||
ManySeeds {
|
||||
/// Starting seed.
|
||||
#[clap(long, env("MIRI_SEED_START"), default_value_t = 0)]
|
||||
seed_start: u64,
|
||||
#[clap(long, env("MIRI_SEEDS"), default_value_t = 256)]
|
||||
/// Amount of seeds to try.
|
||||
seeds: u64,
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
command: Vec<OsString>,
|
||||
},
|
||||
/// Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
|
||||
Bench {
|
||||
/// List of benchmarks to run. By default all benchmarks are run.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
benches: Vec<OsString>,
|
||||
},
|
||||
/// Update and activate the rustup toolchain 'miri' to the commit given in the
|
||||
/// `rust-version` file.
|
||||
/// `rustup-toolchain-install-master` must be installed for this to work. Any extra
|
||||
/// flags are passed to `rustup-toolchain-install-master`.
|
||||
Toolchain {
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest
|
||||
/// rustc commit. The fetched commit is stored in the `rust-version` file, so the
|
||||
/// next `./miri toolchain` will install the rustc that just got pulled.
|
||||
RustcPull { commit: Option<String> },
|
||||
/// Push Miri changes back to the rustc repo. This will pull a copy of the rustc
|
||||
/// history into the Miri repo, unless you set the RUSTC_GIT env var to an existing
|
||||
/// clone of the rustc repo.
|
||||
RustcPush { github_user: String, branch: String },
|
||||
}
|
||||
|
|
@ -1,307 +1,100 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::io::Write;
|
||||
use std::ops::Not;
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use dunce::canonicalize;
|
||||
use path_macro::path;
|
||||
use xshell::{cmd, Shell};
|
||||
|
||||
use walkdir::WalkDir;
|
||||
use xshell::cmd;
|
||||
|
||||
use crate::arg::Subcommands;
|
||||
use crate::util::*;
|
||||
use crate::Command;
|
||||
|
||||
/// Used for rustc syncs.
|
||||
const JOSH_FILTER: &str =
|
||||
":rev(75dd959a3a40eb5b4574f8d2e23aa6efbeb33573:prefix=src/tools/miri):/src/tools/miri";
|
||||
|
||||
fn detect_miri_dir() -> std::io::Result<PathBuf> {
|
||||
const MIRI_SCRIPT_ROOT_DIR: &str = env!("CARGO_MANIFEST_DIR");
|
||||
Ok(canonicalize(MIRI_SCRIPT_ROOT_DIR)?.parent().unwrap().into())
|
||||
}
|
||||
|
||||
/// Queries an active toolchain for `dir` via `rustup`.
|
||||
fn get_active_toolchain(dir: &Path) -> Result<String> {
|
||||
let sh = Shell::new()?;
|
||||
sh.change_dir(dir);
|
||||
let stdout = cmd!(sh, "rustup show active-toolchain").read()?;
|
||||
Ok(stdout.split_whitespace().next().context("Could not obtain active Rust toolchain")?.into())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct MiriRunner<'a> {
|
||||
/// miri_dir is the root of the miri repository checkout we are working in.
|
||||
miri_dir: PathBuf,
|
||||
/// active_toolchain is passed as `+toolchain` argument to cargo/rustc invocations.
|
||||
active_toolchain: String,
|
||||
cargo_extra_flags: Vec<String>,
|
||||
command: &'a super::Subcommands,
|
||||
/// Environment variables passed to child processes.
|
||||
env: BTreeMap<OsString, OsString>,
|
||||
/// Additional variables used by environment-altering commands.
|
||||
/// These should be accessed by corresponding methods (e.g. `sysroot()`) and not directly.
|
||||
sysroot: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn shell_with_parent_env() -> Result<Shell> {
|
||||
let sh = Shell::new()?;
|
||||
// xshell does not propagate parent's env variables by default.
|
||||
for (k, v) in std::env::vars_os() {
|
||||
sh.set_var(k, v);
|
||||
}
|
||||
Ok(sh)
|
||||
}
|
||||
|
||||
impl MiriRunner<'_> {
|
||||
pub(super) fn exec(command: &super::Subcommands) -> Result<()> {
|
||||
Self::exec_inner(command, true)
|
||||
}
|
||||
fn exec_inner(command: &super::Subcommands, run_auto_things: bool) -> Result<()> {
|
||||
let miri_dir = detect_miri_dir()?;
|
||||
let active_toolchain = get_active_toolchain(&miri_dir)?;
|
||||
let config = command.get_config(&miri_dir);
|
||||
// CARGO_EXTRA_FLAGS do not have to be a valid UTF-8, but that's what shell_words' expects.
|
||||
let cargo_extra_flags = std::env::var("CARGO_EXTRA_FLAGS").unwrap_or_default();
|
||||
let cargo_extra_flags = shell_words::split(&cargo_extra_flags)?;
|
||||
let env = BTreeMap::new();
|
||||
|
||||
let mut runner = MiriRunner {
|
||||
miri_dir,
|
||||
active_toolchain,
|
||||
command,
|
||||
env,
|
||||
cargo_extra_flags,
|
||||
sysroot: None,
|
||||
};
|
||||
if let Some(config) = config {
|
||||
// Run the auto-things.
|
||||
if run_auto_things {
|
||||
if config.toolchain {
|
||||
// Run this first, so that the toolchain doesn't change after
|
||||
// other code has run.
|
||||
let command = Subcommands::Toolchain { flags: vec![] };
|
||||
Self::exec_inner(&command, false)?;
|
||||
// Let's make sure to actually use that toolchain, too.
|
||||
runner.active_toolchain = "miri".to_owned();
|
||||
}
|
||||
if config.fmt {
|
||||
let command = Subcommands::Fmt { flags: vec![] };
|
||||
Self::exec_inner(&command, false)?;
|
||||
}
|
||||
if config.clippy {
|
||||
let command = Subcommands::Clippy {
|
||||
flags: ["--", "-D", "warnings"].into_iter().map(OsString::from).collect(),
|
||||
};
|
||||
Self::exec_inner(&command, false)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the environment
|
||||
// Determine some toolchain properties
|
||||
let libdir = runner.libdir()?;
|
||||
if !libdir.exists() {
|
||||
println!("Something went wrong determining the library dir.");
|
||||
println!("I got {} but that does not exist.", libdir.display());
|
||||
println!("Please report a bug at https://github.com/rust-lang/miri/issues.");
|
||||
std::process::exit(2);
|
||||
}
|
||||
// Share target dir between `miri` and `cargo-miri`.
|
||||
let target_dir = std::env::var_os("CARGO_TARGET_DIR")
|
||||
.filter(|val| !val.is_empty())
|
||||
.unwrap_or_else(|| {
|
||||
let target_dir = path!(runner.miri_dir / "target");
|
||||
target_dir.into()
|
||||
});
|
||||
runner.set_env("CARGO_TARGET_DIR", target_dir);
|
||||
|
||||
// We configure dev builds to not be unusably slow.
|
||||
let devel_opt_level = std::env::var_os("CARGO_PROFILE_DEV_OPT_LEVEL")
|
||||
.filter(|val| !val.is_empty())
|
||||
.unwrap_or_else(|| "2".into());
|
||||
runner.set_env("CARGO_PROFILE_DEV_OPT_LEVEL", devel_opt_level);
|
||||
let rustflags = {
|
||||
let env = std::env::var_os("RUSTFLAGS");
|
||||
let mut flags_with_warnings = OsString::from(
|
||||
"-Zunstable-options -Wrustc::internal -Wrust_2018_idioms -Wunused_lifetimes -Wsemicolon_in_expressions_from_macros ",
|
||||
);
|
||||
if let Some(value) = env {
|
||||
flags_with_warnings.push(value);
|
||||
}
|
||||
// We set the rpath so that Miri finds the private rustc libraries it needs.
|
||||
let mut flags_with_compiler_settings = OsString::from("-C link-args=-Wl,-rpath,");
|
||||
flags_with_compiler_settings.push(&libdir);
|
||||
flags_with_compiler_settings.push(flags_with_warnings);
|
||||
flags_with_compiler_settings
|
||||
};
|
||||
runner.set_env("RUSTFLAGS", rustflags);
|
||||
}
|
||||
runner.execute()
|
||||
}
|
||||
fn execute(&mut self) -> Result<()> {
|
||||
// Run command.
|
||||
match self.command {
|
||||
Subcommands::Install { flags } => self.install(flags),
|
||||
Subcommands::Build { flags } => self.build(flags),
|
||||
Subcommands::Check { flags } => self.check(flags),
|
||||
Subcommands::Test { bless, flags } => self.test(*bless, flags),
|
||||
Subcommands::Run { dep, flags } => self.run(*dep, flags),
|
||||
Subcommands::Fmt { flags } => self.fmt(flags),
|
||||
Subcommands::Clippy { flags } => self.clippy(flags),
|
||||
Subcommands::Cargo { flags } => self.cargo(flags),
|
||||
Subcommands::ManySeeds { command, seed_start, seeds } =>
|
||||
self.many_seeds(command, *seed_start, *seeds),
|
||||
Subcommands::Bench { benches } => self.bench(benches),
|
||||
Subcommands::Toolchain { flags } => self.toolchain(flags),
|
||||
Subcommands::RustcPull { commit } => self.rustc_pull(commit.clone()),
|
||||
Subcommands::RustcPush { github_user, branch } => self.rustc_push(github_user, branch),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_env(
|
||||
&mut self,
|
||||
key: impl Into<OsString>,
|
||||
value: impl Into<OsString>,
|
||||
) -> Option<OsString> {
|
||||
self.env.insert(key.into(), value.into())
|
||||
}
|
||||
|
||||
/// Prepare and set MIRI_SYSROOT. Respects `MIRI_TEST_TARGET` and takes into account
|
||||
/// locally built vs. distributed rustc.
|
||||
fn find_miri_sysroot(&mut self) -> Result<()> {
|
||||
let current_sysroot = std::env::var_os("MIRI_SYSROOT").unwrap_or_default();
|
||||
|
||||
if !current_sysroot.is_empty() {
|
||||
impl MiriEnv {
|
||||
fn build_miri_sysroot(&mut self) -> Result<()> {
|
||||
if self.sh.var("MIRI_SYSROOT").is_ok() {
|
||||
// Sysroot already set, use that.
|
||||
let current_value = self.set_env("MIRI_SYSROOT", ¤t_sysroot);
|
||||
assert!(current_value.is_none() || current_value.unwrap() == current_sysroot);
|
||||
return Ok(());
|
||||
}
|
||||
// We need to build a sysroot.
|
||||
let target = std::env::var_os("MIRI_TEST_TARGET").filter(|target| !target.is_empty());
|
||||
let sysroot = self.build_miri_sysroot(target.as_deref())?;
|
||||
self.set_env("MIRI_SYSROOT", sysroot);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build a sysroot and set MIRI_SYSROOT to use it. Arguments are passed to `cargo miri setup`.
|
||||
fn build_miri_sysroot(&self, target: Option<&OsStr>) -> Result<String> {
|
||||
let manifest_path = path!(self.miri_dir / "cargo-miri" / "Cargo.toml");
|
||||
let Self { active_toolchain, cargo_extra_flags, .. } = &self;
|
||||
let target_prefix: Option<&OsStr> = target.map(|_| "--target".as_ref());
|
||||
let sh = self.shell()?;
|
||||
let output = cmd!(sh, "cargo +{active_toolchain} --quiet run {cargo_extra_flags...} --manifest-path {manifest_path} -- miri setup --print-sysroot {target_prefix...} {target...}").read();
|
||||
if output.is_err() {
|
||||
// Run it again (without `--print-sysroot`) so the user can see the error.
|
||||
cmd!(sh, "cargo +{active_toolchain} --quiet run {cargo_extra_flags...} --manifest-path {manifest_path} -- miri setup {target_prefix...} {target...}").run().with_context(|| "`cargo miri setup` failed")?;
|
||||
}
|
||||
|
||||
Ok(output?)
|
||||
}
|
||||
fn build_package(
|
||||
// Path to Cargo.toml file of a package to build.
|
||||
path: &OsStr,
|
||||
toolchain: impl AsRef<OsStr>,
|
||||
extra_flags: &[String],
|
||||
args: impl IntoIterator<Item = impl AsRef<OsStr>>,
|
||||
) -> Result<()> {
|
||||
let sh = Shell::new()?;
|
||||
cmd!(sh, "cargo +{toolchain} build {extra_flags...} --manifest-path {path} {args...}")
|
||||
.run()?;
|
||||
Ok(())
|
||||
}
|
||||
fn shell(&self) -> Result<Shell> {
|
||||
let sh = shell_with_parent_env()?;
|
||||
for (k, v) in &self.env {
|
||||
sh.set_var(k, v);
|
||||
}
|
||||
|
||||
Ok(sh)
|
||||
}
|
||||
|
||||
fn libdir(&self) -> Result<PathBuf> {
|
||||
let sh = shell_with_parent_env()?;
|
||||
let toolchain = &self.active_toolchain;
|
||||
let target_output = cmd!(sh, "rustc +{toolchain} --version --verbose").read()?;
|
||||
let rustc_meta = rustc_version::version_meta_for(&target_output)?;
|
||||
let target = rustc_meta.host;
|
||||
|
||||
let sysroot = cmd!(sh, "rustc +{toolchain} --print sysroot").read()?;
|
||||
|
||||
let sysroot = PathBuf::from(sysroot);
|
||||
let libdir = path!(sysroot / "lib" / "rustlib" / target / "lib");
|
||||
Ok(libdir)
|
||||
}
|
||||
fn sysroot(&mut self) -> Result<PathBuf> {
|
||||
if let Some(sysroot) = self.sysroot.as_ref() {
|
||||
Ok(sysroot.clone())
|
||||
} else {
|
||||
let sh = shell_with_parent_env()?;
|
||||
let toolchain = &self.active_toolchain;
|
||||
|
||||
let sysroot: PathBuf = cmd!(sh, "rustc +{toolchain} --print sysroot").read()?.into();
|
||||
self.sysroot = Some(sysroot.clone());
|
||||
Ok(sysroot)
|
||||
}
|
||||
}
|
||||
fn install_to_dir(
|
||||
&mut self,
|
||||
sh: &Shell,
|
||||
path: PathBuf,
|
||||
args: impl IntoIterator<Item = impl AsRef<OsStr>>,
|
||||
) -> Result<()> {
|
||||
let sysroot = self.sysroot()?;
|
||||
let toolchain = &self.active_toolchain;
|
||||
let extra_flags = &self.cargo_extra_flags;
|
||||
// "--locked" to respect the Cargo.lock file if it exists.
|
||||
// Install binaries to the miri toolchain's sysroot so they do not interact with other toolchains.
|
||||
cmd!(sh, "cargo +{toolchain} install {extra_flags...} --path {path} --force --root {sysroot} {args...}").run()?;
|
||||
let Self { toolchain, cargo_extra_flags, .. } = &self;
|
||||
let target = &match self.sh.var("MIRI_TEST_TARGET") {
|
||||
Ok(target) => vec!["--target".into(), target],
|
||||
Err(_) => vec![],
|
||||
};
|
||||
let output = cmd!(self.sh,
|
||||
"cargo +{toolchain} --quiet run {cargo_extra_flags...} --manifest-path {manifest_path} --
|
||||
miri setup --print-sysroot {target...}"
|
||||
).read();
|
||||
let Ok(output) = output else {
|
||||
// Run it again (without `--print-sysroot` or `--quiet`) so the user can see the error.
|
||||
cmd!(
|
||||
self.sh,
|
||||
"cargo +{toolchain} run {cargo_extra_flags...} --manifest-path {manifest_path} --
|
||||
miri setup {target...}"
|
||||
)
|
||||
.run()
|
||||
.with_context(|| "`cargo miri setup` failed")?;
|
||||
panic!("`cargo miri setup` didn't fail again the 2nd time?");
|
||||
};
|
||||
self.sh.set_var("MIRI_SYSROOT", output);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MiriRunner<'_> {
|
||||
fn bench(&self, benches: &[OsString]) -> Result<()> {
|
||||
// The hyperfine to use
|
||||
let hyperfine = std::env::var("HYPERFINE");
|
||||
let hyperfine = hyperfine.as_deref().unwrap_or("hyperfine -w 1 -m 5 --shell=none");
|
||||
let hyperfine = shell_words::split(hyperfine).unwrap();
|
||||
let Some((program_name, args)) = hyperfine.split_first() else {
|
||||
bail!("Expected HYPERFINE environment variable to be non-empty");
|
||||
};
|
||||
// Make sure we have an up-to-date Miri installed
|
||||
Self::exec_inner(&Subcommands::Install { flags: vec![] }, false)?;
|
||||
let benches_dir = path!(self.miri_dir / "bench-cargo-miri");
|
||||
let benches = if benches.is_empty() {
|
||||
std::fs::read_dir(&benches_dir)?
|
||||
.filter_map(|path| {
|
||||
path.ok()
|
||||
.filter(|dir| dir.file_type().map(|t| t.is_dir()).unwrap_or(false))
|
||||
.map(|p| p.file_name())
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
benches.to_owned()
|
||||
};
|
||||
let sh = shell_with_parent_env()?;
|
||||
let toolchain = &self.active_toolchain;
|
||||
// Run the requested benchmarks
|
||||
for bench in benches {
|
||||
let current_bench_dir = path!(benches_dir / bench / "Cargo.toml");
|
||||
cmd!(
|
||||
sh,
|
||||
"{program_name} {args...} 'cargo +'{toolchain}' miri run --manifest-path \"'{current_bench_dir}'\"'"
|
||||
)
|
||||
.run()?;
|
||||
impl Command {
|
||||
fn auto_actions() -> Result<()> {
|
||||
let miri_dir = miri_dir()?;
|
||||
let auto_everything = path!(miri_dir / ".auto_everything").exists();
|
||||
let auto_toolchain = auto_everything || path!(miri_dir / ".auto-toolchain").exists();
|
||||
let auto_fmt = auto_everything || path!(miri_dir / ".auto-fmt").exists();
|
||||
let auto_clippy = auto_everything || path!(miri_dir / ".auto-clippy").exists();
|
||||
|
||||
// `toolchain` goes first as it could affect the others
|
||||
if auto_toolchain {
|
||||
Self::toolchain(vec![])?;
|
||||
}
|
||||
if auto_fmt {
|
||||
Self::fmt(vec![])?;
|
||||
}
|
||||
if auto_clippy {
|
||||
Self::clippy(vec![])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn toolchain(&self, flags: &[OsString]) -> Result<()> {
|
||||
pub fn exec(self) -> Result<()> {
|
||||
match self {
|
||||
Command::Install { flags } => Self::install(flags),
|
||||
Command::Build { flags } => Self::build(flags),
|
||||
Command::Check { flags } => Self::check(flags),
|
||||
Command::Test { bless, flags } => Self::test(bless, flags),
|
||||
Command::Run { dep, flags } => Self::run(dep, flags),
|
||||
Command::Fmt { flags } => Self::fmt(flags),
|
||||
Command::Clippy { flags } => Self::clippy(flags),
|
||||
Command::Cargo { flags } => Self::cargo(flags),
|
||||
Command::ManySeeds { command, seed_start, seeds } =>
|
||||
Self::many_seeds(command, seed_start, seeds),
|
||||
Command::Bench { benches } => Self::bench(benches),
|
||||
Command::Toolchain { flags } => Self::toolchain(flags),
|
||||
Command::RustcPull { commit } => Self::rustc_pull(commit.clone()),
|
||||
Command::RustcPush { rustc_git, github_user, branch } =>
|
||||
Self::rustc_push(rustc_git, github_user, branch),
|
||||
}
|
||||
}
|
||||
|
||||
fn toolchain(flags: Vec<OsString>) -> Result<()> {
|
||||
// Make sure rustup-toolchain-install-master is installed.
|
||||
which::which("rustup-toolchain-install-master").context("Please install rustup-toolchain-install-master by running 'cargo install rustup-toolchain-install-master'")?;
|
||||
let sh = shell_with_parent_env()?;
|
||||
sh.change_dir(&self.miri_dir);
|
||||
which::which("rustup-toolchain-install-master")
|
||||
.context("Please install rustup-toolchain-install-master by running 'cargo install rustup-toolchain-install-master'")?;
|
||||
let sh = shell()?;
|
||||
sh.change_dir(miri_dir()?);
|
||||
let new_commit = Some(sh.read_file("rust-version")?.trim().to_owned());
|
||||
let current_commit = {
|
||||
let rustc_info = cmd!(sh, "rustc +miri --version -v").read();
|
||||
|
|
@ -318,8 +111,9 @@ impl MiriRunner<'_> {
|
|||
};
|
||||
// Check if we already are at that commit.
|
||||
if current_commit == new_commit {
|
||||
println!("miri toolchain is already at commit {}.", current_commit.unwrap());
|
||||
cmd!(sh, "rustup override set miri").run()?;
|
||||
if active_toolchain()? != "miri" {
|
||||
cmd!(sh, "rustup override set miri").run()?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
// Install and setup new toolchain.
|
||||
|
|
@ -337,10 +131,10 @@ impl MiriRunner<'_> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn rustc_pull(&self, commit: Option<String>) -> Result<()> {
|
||||
let sh = shell_with_parent_env()?;
|
||||
sh.change_dir(&self.miri_dir);
|
||||
let commit: String = commit.map(Result::Ok).unwrap_or_else(|| {
|
||||
fn rustc_pull(commit: Option<String>) -> Result<()> {
|
||||
let sh = shell()?;
|
||||
sh.change_dir(miri_dir()?);
|
||||
let commit = commit.map(Result::Ok).unwrap_or_else(|| {
|
||||
let rust_repo_head =
|
||||
cmd!(sh, "git ls-remote https://github.com/rust-lang/rust/ HEAD").read()?;
|
||||
rust_repo_head
|
||||
|
|
@ -349,85 +143,109 @@ impl MiriRunner<'_> {
|
|||
.map(|front| front.trim().to_owned())
|
||||
.ok_or_else(|| anyhow!("Could not obtain Rust repo HEAD from remote."))
|
||||
})?;
|
||||
// Make sure the repo is clean.
|
||||
if cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty().not() {
|
||||
bail!("working directory must be clean before running `./miri rustc-pull`");
|
||||
}
|
||||
|
||||
// Update rust-version file. As a separate commit, since making it part of
|
||||
// the merge has confused the heck out of josh in the past.
|
||||
sh.write_file(path!(self.miri_dir / "rust-version"), &commit)?;
|
||||
// We pass `--no-verify` to avoid running git hooks like `./miri fmt` that could in turn
|
||||
// trigger auto-actions.
|
||||
sh.write_file("rust-version", &commit)?;
|
||||
const PREPARING_COMMIT_MESSAGE: &str = "Preparing for merge from rustc";
|
||||
cmd!(sh, "git commit rust-version -m {PREPARING_COMMIT_MESSAGE}")
|
||||
cmd!(sh, "git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}")
|
||||
.run()
|
||||
.context("FAILED to commit rust-version file, something went wrong")?;
|
||||
// Fetch given rustc commit and note down which one that was
|
||||
|
||||
// Fetch given rustc commit.
|
||||
cmd!(sh, "git fetch http://localhost:8000/rust-lang/rust.git@{commit}{JOSH_FILTER}.git")
|
||||
.run()
|
||||
.context("FAILED to fetch new commits, something went wrong")?;
|
||||
.map_err(|e| {
|
||||
// Try to un-do the previous `git commit`, to leave the repo in the state we found it it.
|
||||
cmd!(sh, "git reset --hard HEAD^")
|
||||
.run()
|
||||
.expect("FAILED to clean up again after failed `git fetch`, sorry for that");
|
||||
e
|
||||
})
|
||||
.context("FAILED to fetch new commits, something went wrong (committing the rust-version file has been undone)")?;
|
||||
|
||||
// Merge the fetched commit.
|
||||
const MERGE_COMMIT_MESSAGE: &str = "Merge from rustc";
|
||||
cmd!(sh, "git merge FETCH_HEAD --no-ff -m {MERGE_COMMIT_MESSAGE}")
|
||||
cmd!(sh, "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}")
|
||||
.run()
|
||||
.context("FAILED to merge new commits, something went wrong")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rustc_push(&self, github_user: &str, branch: &str) -> Result<()> {
|
||||
let rustc_git = std::env::var_os("RUSTC_GIT");
|
||||
let working_directory = if let Some(rustc_git) = rustc_git {
|
||||
rustc_git
|
||||
} else {
|
||||
fn rustc_push(rustc_git: Option<String>, github_user: String, branch: String) -> Result<()> {
|
||||
let sh = shell()?;
|
||||
sh.change_dir(miri_dir()?);
|
||||
let base = sh.read_file("rust-version")?.trim().to_owned();
|
||||
// Make sure the repo is clean.
|
||||
if cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty().not() {
|
||||
bail!("working directory must be clean before running `./miri rustc-push`");
|
||||
}
|
||||
|
||||
// Find a repo we can do our preparation in.
|
||||
if let Some(rustc_git) = rustc_git {
|
||||
// If rustc_git is `Some`, we'll use an existing fork for the branch updates.
|
||||
sh.change_dir(rustc_git);
|
||||
} else {
|
||||
// Otherwise, do this in the local Miri repo.
|
||||
println!(
|
||||
"This will pull a copy of the rust-lang/rust history into this Miri checkout, growing it by about 1GB."
|
||||
);
|
||||
println!(
|
||||
"To avoid that, abort now and set the RUSTC_GIT environment variable to an existing rustc checkout. Proceed? [y/N] "
|
||||
print!(
|
||||
"To avoid that, abort now and set the `--rustc-git` flag to an existing rustc checkout. Proceed? [y/N] "
|
||||
);
|
||||
std::io::stdout().flush()?;
|
||||
let mut answer = String::new();
|
||||
std::io::stdin().read_line(&mut answer)?;
|
||||
if answer.trim().to_lowercase() != "y" {
|
||||
std::process::exit(1);
|
||||
}
|
||||
self.miri_dir.clone().into()
|
||||
};
|
||||
// Prepare the branch. Pushing works much better if we use as base exactly
|
||||
// the commit that we pulled from last time, so we use the `rust-version`
|
||||
// file as a good approximation of that.
|
||||
let rust_version_path = path!(self.miri_dir / "rust-version");
|
||||
let base = std::fs::read_to_string(rust_version_path)?.trim().to_owned();
|
||||
println!("Preparing {github_user}/rust (base: {base})...)");
|
||||
let sh = shell_with_parent_env()?;
|
||||
sh.change_dir(working_directory);
|
||||
|
||||
if cmd!(sh, "git fetch https://github.com/{github_user}").read().is_ok() {
|
||||
println!("Preparing {github_user}/rust (base: {base})...");
|
||||
if cmd!(sh, "git fetch https://github.com/{github_user}/rust {branch}")
|
||||
.ignore_stderr()
|
||||
.read()
|
||||
.is_ok()
|
||||
{
|
||||
println!(
|
||||
"The branch '{branch}' seems to already exist in 'https://github.com/{github_user}'. Please delete it and try again."
|
||||
"The branch '{branch}' seems to already exist in 'https://github.com/{github_user}/rust'. Please delete it and try again."
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
cmd!(sh, "git fetch https://github.com/rust-lang/rust {base}").run()?;
|
||||
|
||||
cmd!(sh, "git push https://github.com/{github_user}/rust {base}:refs/heads/{branch}")
|
||||
.run()?;
|
||||
println!();
|
||||
|
||||
// Do the actual push.
|
||||
sh.change_dir(&self.miri_dir);
|
||||
sh.change_dir(miri_dir()?);
|
||||
println!("Pushing miri changes...");
|
||||
cmd!(
|
||||
sh,
|
||||
"git push http://localhost:8000/{github_user}/rust.git{JOSH_FILTER}.git HEAD:{branch}"
|
||||
)
|
||||
.run()?;
|
||||
// Do a round-trip check to make sure the push worked as expected.
|
||||
println!();
|
||||
|
||||
// Do a round-trip check to make sure the push worked as expected.
|
||||
cmd!(
|
||||
sh,
|
||||
"git fetch http://localhost:8000/{github_user}/rust.git{JOSH_FILTER}.git {branch}"
|
||||
)
|
||||
.ignore_stderr()
|
||||
.read()?;
|
||||
let head = cmd!(sh, "git rev-parse HEAD").read()?;
|
||||
let fetch_head = cmd!(sh, "git rev-parse FETCH_HEAD").read()?;
|
||||
if head != fetch_head {
|
||||
println!("ERROR: Josh created a non-roundtrip push! Do NOT merge this into rustc!");
|
||||
std::process::exit(1);
|
||||
bail!("Josh created a non-roundtrip push! Do NOT merge this into rustc!");
|
||||
}
|
||||
println!(
|
||||
"Confirmed that the push round-trips back to Miri properly. Please create a rustc PR:"
|
||||
|
|
@ -436,189 +254,20 @@ impl MiriRunner<'_> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn install(&mut self, flags: &[OsString]) -> Result<()> {
|
||||
let sh = self.shell()?;
|
||||
self.install_to_dir(&sh, self.miri_dir.clone(), flags)?;
|
||||
let cargo_miri_dir = path!(self.miri_dir / "cargo-miri");
|
||||
self.install_to_dir(&sh, cargo_miri_dir, flags)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build(&self, flags: &[OsString]) -> Result<()> {
|
||||
// Build, and let caller control flags.
|
||||
let miri_manifest = path!(self.miri_dir / "Cargo.toml");
|
||||
let cargo_miri_manifest = path!(self.miri_dir / "cargo-miri" / "Cargo.toml");
|
||||
Self::build_package(
|
||||
miri_manifest.as_ref(),
|
||||
&self.active_toolchain,
|
||||
&self.cargo_extra_flags,
|
||||
flags,
|
||||
)?;
|
||||
Self::build_package(
|
||||
cargo_miri_manifest.as_ref(),
|
||||
&self.active_toolchain,
|
||||
&self.cargo_extra_flags,
|
||||
flags,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check(&self, flags: &[OsString]) -> Result<()> {
|
||||
fn check_package(
|
||||
// Path to Cargo.toml file of a package to check.
|
||||
path: &OsStr,
|
||||
toolchain: impl AsRef<OsStr>,
|
||||
extra_flags: &[String],
|
||||
all_targets: bool,
|
||||
args: impl IntoIterator<Item = impl AsRef<OsStr>>,
|
||||
) -> Result<()> {
|
||||
let all_targets: Option<&OsStr> = all_targets.then_some("--all-targets".as_ref());
|
||||
let sh = Shell::new()?;
|
||||
cmd!(sh, "cargo +{toolchain} check {extra_flags...} --manifest-path {path} {all_targets...} {args...}").run()?;
|
||||
Ok(())
|
||||
}
|
||||
// Check, and let caller control flags.
|
||||
let miri_manifest = path!(self.miri_dir / "Cargo.toml");
|
||||
let cargo_miri_manifest = path!(self.miri_dir / "cargo-miri" / "Cargo.toml");
|
||||
check_package(
|
||||
miri_manifest.as_ref(),
|
||||
&self.active_toolchain,
|
||||
&self.cargo_extra_flags,
|
||||
true,
|
||||
flags,
|
||||
)?;
|
||||
check_package(
|
||||
cargo_miri_manifest.as_ref(),
|
||||
&self.active_toolchain,
|
||||
&self.cargo_extra_flags,
|
||||
false,
|
||||
flags,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test(&mut self, bless: bool, flags: &[OsString]) -> Result<()> {
|
||||
let miri_manifest = path!(self.miri_dir / "Cargo.toml");
|
||||
// First build and get a sysroot.
|
||||
Self::build_package(
|
||||
miri_manifest.as_ref(),
|
||||
&self.active_toolchain,
|
||||
&self.cargo_extra_flags,
|
||||
std::iter::empty::<OsString>(),
|
||||
)?;
|
||||
self.find_miri_sysroot()?;
|
||||
let extra_flags = &self.cargo_extra_flags;
|
||||
// Then test, and let caller control flags.
|
||||
// Only in root project as `cargo-miri` has no tests.
|
||||
let sh = self.shell()?;
|
||||
if bless {
|
||||
sh.set_var("MIRI_BLESS", "Gesundheit");
|
||||
}
|
||||
let toolchain: &OsStr = self.active_toolchain.as_ref();
|
||||
cmd!(
|
||||
sh,
|
||||
"cargo +{toolchain} test {extra_flags...} --manifest-path {miri_manifest} -- {flags...}"
|
||||
)
|
||||
.run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&mut self, dep: bool, flags: &[OsString]) -> Result<()> {
|
||||
use itertools::Itertools;
|
||||
// Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so
|
||||
// that we set the MIRI_SYSROOT up the right way.
|
||||
let target = flags.iter().tuple_windows().find(|(first, _)| first == &"--target");
|
||||
if let Some((_, target)) = target {
|
||||
// Found it!
|
||||
self.set_env("MIRI_TEST_TARGET", target);
|
||||
} else if let Some(var) =
|
||||
std::env::var_os("MIRI_TEST_TARGET").filter(|target| !target.is_empty())
|
||||
{
|
||||
// Make sure miri actually uses this target.
|
||||
let entry = self.env.entry("MIRIFLAGS".into()).or_default();
|
||||
entry.push(" --target ");
|
||||
entry.push(var);
|
||||
}
|
||||
// First build and get a sysroot.
|
||||
let miri_manifest = path!(self.miri_dir / "Cargo.toml");
|
||||
Self::build_package(
|
||||
miri_manifest.as_ref(),
|
||||
&self.active_toolchain,
|
||||
&self.cargo_extra_flags,
|
||||
std::iter::empty::<OsString>(),
|
||||
)?;
|
||||
self.find_miri_sysroot()?;
|
||||
// Then run the actual command.
|
||||
let miri_flags = self.env.get(&OsString::from("MIRIFLAGS")).cloned().unwrap_or_default();
|
||||
let miri_flags: &OsStr = miri_flags.as_ref();
|
||||
let extra_flags = &self.cargo_extra_flags;
|
||||
let sh = self.shell()?;
|
||||
let toolchain: &OsStr = self.active_toolchain.as_ref();
|
||||
if dep {
|
||||
cmd!(
|
||||
sh,
|
||||
"cargo +{toolchain} --quiet test --test compiletest {extra_flags...} --manifest-path {miri_manifest} -- --miri-run-dep-mode {miri_flags} {flags...}"
|
||||
).run()?;
|
||||
} else {
|
||||
cmd!(
|
||||
sh,
|
||||
"cargo +{toolchain} --quiet run {extra_flags...} --manifest-path {miri_manifest} -- {miri_flags} {flags...}"
|
||||
).run()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fmt(&self, flags: &[OsString]) -> Result<()> {
|
||||
let toolchain = &self.active_toolchain;
|
||||
let config_path = path!(self.miri_dir / "rustfmt.toml");
|
||||
let sh = self.shell()?;
|
||||
for item in WalkDir::new(&self.miri_dir).into_iter().filter_entry(|entry| {
|
||||
let name: String = entry.file_name().to_string_lossy().into();
|
||||
let ty = entry.file_type();
|
||||
if ty.is_file() {
|
||||
name.ends_with(".rs")
|
||||
} else {
|
||||
// dir or symlink
|
||||
&name != "target"
|
||||
}
|
||||
}) {
|
||||
let item = item.unwrap(); // Should never panic as we've already filtered out failed entries.
|
||||
if item.file_type().is_file() {
|
||||
let path = item.path();
|
||||
cmd!(sh, "rustfmt +{toolchain} --edition=2021 --config-path {config_path} {flags...} {path}").quiet().run()?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clippy(&self, flags: &[OsString]) -> Result<()> {
|
||||
let toolchain_modifier = &self.active_toolchain;
|
||||
let extra_flags = &self.cargo_extra_flags;
|
||||
let miri_manifest = path!(self.miri_dir / "Cargo.toml");
|
||||
let sh = self.shell()?;
|
||||
cmd!(sh, "cargo +{toolchain_modifier} clippy {extra_flags...} --manifest-path {miri_manifest} --all-targets -- {flags...}").run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cargo(&self, flags: &[OsString]) -> Result<()> {
|
||||
// We carefully kept the working dir intact, so this will run cargo *on the workspace in the
|
||||
// current working dir*, not on the main Miri workspace. That is exactly what RA needs.
|
||||
let toolchain_modifier = &self.active_toolchain;
|
||||
let sh = self.shell()?;
|
||||
cmd!(sh, "cargo +{toolchain_modifier} {flags...}").run()?;
|
||||
Ok(())
|
||||
}
|
||||
fn many_seeds(&self, command: &[OsString], seed_start: u64, seed_count: u64) -> Result<()> {
|
||||
fn many_seeds(command: Vec<OsString>, seed_start: u64, seed_count: u64) -> Result<()> {
|
||||
let seed_end = seed_start + seed_count;
|
||||
assert!(!command.is_empty());
|
||||
let (command_name, trailing_args) = command.split_first().unwrap();
|
||||
let sh = shell_with_parent_env()?;
|
||||
let Some((command_name, trailing_args)) = command.split_first() else {
|
||||
bail!("expected many-seeds command to be non-empty");
|
||||
};
|
||||
let sh = shell()?;
|
||||
for seed in seed_start..seed_end {
|
||||
println!("Trying seed: {seed}");
|
||||
let mut miriflags = std::env::var_os("MIRIFLAGS").unwrap_or_default();
|
||||
let mut miriflags = env::var_os("MIRIFLAGS").unwrap_or_default();
|
||||
miriflags.push(format!(" -Zlayout-seed={seed} -Zmiri-seed={seed}"));
|
||||
let status =
|
||||
cmd!(sh, "{command_name} {trailing_args...}").env("MIRIFLAGS", miriflags).run();
|
||||
let status = cmd!(sh, "{command_name} {trailing_args...}")
|
||||
.env("MIRIFLAGS", miriflags)
|
||||
.quiet()
|
||||
.run();
|
||||
if status.is_err() {
|
||||
println!("Failing seed: {seed}");
|
||||
break;
|
||||
|
|
@ -626,4 +275,175 @@ impl MiriRunner<'_> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bench(benches: Vec<OsString>) -> Result<()> {
|
||||
// The hyperfine to use
|
||||
let hyperfine = env::var("HYPERFINE");
|
||||
let hyperfine = hyperfine.as_deref().unwrap_or("hyperfine -w 1 -m 5 --shell=none");
|
||||
let hyperfine = shell_words::split(hyperfine)?;
|
||||
let Some((program_name, args)) = hyperfine.split_first() else {
|
||||
bail!("expected HYPERFINE environment variable to be non-empty");
|
||||
};
|
||||
// Make sure we have an up-to-date Miri installed and selected the right toolchain.
|
||||
Self::install(vec![])?;
|
||||
|
||||
let sh = shell()?;
|
||||
sh.change_dir(miri_dir()?);
|
||||
let benches_dir = "bench-cargo-miri";
|
||||
let benches = if benches.is_empty() {
|
||||
sh.read_dir(benches_dir)?
|
||||
.into_iter()
|
||||
.filter(|path| path.is_dir())
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
} else {
|
||||
benches.to_owned()
|
||||
};
|
||||
// Run the requested benchmarks
|
||||
for bench in benches {
|
||||
let current_bench = path!(benches_dir / bench / "Cargo.toml");
|
||||
// We don't attempt to escape `current_bench`, but we wrap it in quotes.
|
||||
// That seems to make Windows CI happy.
|
||||
cmd!(
|
||||
sh,
|
||||
"{program_name} {args...} 'cargo miri run --manifest-path \"'{current_bench}'\"'"
|
||||
)
|
||||
.run()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install(flags: Vec<OsString>) -> Result<()> {
|
||||
Self::auto_actions()?;
|
||||
let e = MiriEnv::new()?;
|
||||
e.install_to_sysroot(e.miri_dir.clone(), &flags)?;
|
||||
e.install_to_sysroot(path!(e.miri_dir / "cargo-miri"), &flags)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build(flags: Vec<OsString>) -> Result<()> {
|
||||
Self::auto_actions()?;
|
||||
let e = MiriEnv::new()?;
|
||||
e.build(path!(e.miri_dir / "Cargo.toml"), &flags, /* quiet */ false)?;
|
||||
e.build(path!(e.miri_dir / "cargo-miri" / "Cargo.toml"), &flags, /* quiet */ false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check(flags: Vec<OsString>) -> Result<()> {
|
||||
Self::auto_actions()?;
|
||||
let e = MiriEnv::new()?;
|
||||
e.check(path!(e.miri_dir / "Cargo.toml"), &flags)?;
|
||||
e.check(path!(e.miri_dir / "cargo-miri" / "Cargo.toml"), &flags)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clippy(flags: Vec<OsString>) -> Result<()> {
|
||||
Self::auto_actions()?;
|
||||
let e = MiriEnv::new()?;
|
||||
e.clippy(path!(e.miri_dir / "Cargo.toml"), &flags)?;
|
||||
e.clippy(path!(e.miri_dir / "cargo-miri" / "Cargo.toml"), &flags)?;
|
||||
e.clippy(path!(e.miri_dir / "miri-script" / "Cargo.toml"), &flags)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cargo(flags: Vec<OsString>) -> Result<()> {
|
||||
Self::auto_actions()?;
|
||||
let e = MiriEnv::new()?;
|
||||
let toolchain = &e.toolchain;
|
||||
// We carefully kept the working dir intact, so this will run cargo *on the workspace in the
|
||||
// current working dir*, not on the main Miri workspace. That is exactly what RA needs.
|
||||
cmd!(e.sh, "cargo +{toolchain} {flags...}").run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test(bless: bool, flags: Vec<OsString>) -> Result<()> {
|
||||
Self::auto_actions()?;
|
||||
let mut e = MiriEnv::new()?;
|
||||
// First build, and get a sysroot.
|
||||
e.build(path!(e.miri_dir / "Cargo.toml"), &[], /* quiet */ true)?;
|
||||
e.build_miri_sysroot()?;
|
||||
|
||||
// Then test, and let caller control flags.
|
||||
// Only in root project as `cargo-miri` has no tests.
|
||||
if bless {
|
||||
e.sh.set_var("RUSTC_BLESS", "Gesundheit");
|
||||
}
|
||||
e.test(path!(e.miri_dir / "Cargo.toml"), &flags)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(dep: bool, flags: Vec<OsString>) -> Result<()> {
|
||||
Self::auto_actions()?;
|
||||
let mut e = MiriEnv::new()?;
|
||||
// Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so
|
||||
// that we set the MIRI_SYSROOT up the right way.
|
||||
use itertools::Itertools;
|
||||
let target = flags.iter().tuple_windows().find(|(first, _)| first == &"--target");
|
||||
if let Some((_, target)) = target {
|
||||
// Found it!
|
||||
e.sh.set_var("MIRI_TEST_TARGET", target);
|
||||
} else if let Ok(target) = std::env::var("MIRI_TEST_TARGET") {
|
||||
// Make sure miri actually uses this target.
|
||||
let miriflags = e.sh.var("MIRIFLAGS").unwrap_or_default();
|
||||
e.sh.set_var("MIRIFLAGS", format!("{miriflags} --target {target}"));
|
||||
}
|
||||
// First build, and get a sysroot.
|
||||
let miri_manifest = path!(e.miri_dir / "Cargo.toml");
|
||||
e.build(&miri_manifest, &[], /* quiet */ true)?;
|
||||
e.build_miri_sysroot()?;
|
||||
|
||||
// Then run the actual command.
|
||||
let miri_flags = e.sh.var("MIRIFLAGS").unwrap_or_default();
|
||||
let miri_flags = flagsplit(&miri_flags);
|
||||
let toolchain = &e.toolchain;
|
||||
let extra_flags = &e.cargo_extra_flags;
|
||||
if dep {
|
||||
cmd!(
|
||||
e.sh,
|
||||
"cargo +{toolchain} --quiet test --test compiletest {extra_flags...} --manifest-path {miri_manifest} -- --miri-run-dep-mode {miri_flags...} {flags...}"
|
||||
).quiet().run()?;
|
||||
} else {
|
||||
cmd!(
|
||||
e.sh,
|
||||
"cargo +{toolchain} --quiet run {extra_flags...} --manifest-path {miri_manifest} -- {miri_flags...} {flags...}"
|
||||
).quiet().run()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fmt(flags: Vec<OsString>) -> Result<()> {
|
||||
Self::auto_actions()?;
|
||||
let e = MiriEnv::new()?;
|
||||
let toolchain = &e.toolchain;
|
||||
let config_path = path!(e.miri_dir / "rustfmt.toml");
|
||||
|
||||
let mut cmd = cmd!(
|
||||
e.sh,
|
||||
"rustfmt +{toolchain} --edition=2021 --config-path {config_path} {flags...}"
|
||||
);
|
||||
eprintln!("$ {cmd} ...");
|
||||
|
||||
// Add all the filenames to the command.
|
||||
// FIXME: `rustfmt` will follow the `mod` statements in these files, so we get a bunch of
|
||||
// duplicate diffs.
|
||||
for item in WalkDir::new(&e.miri_dir).into_iter().filter_entry(|entry| {
|
||||
let name = entry.file_name().to_string_lossy();
|
||||
let ty = entry.file_type();
|
||||
if ty.is_file() {
|
||||
name.ends_with(".rs")
|
||||
} else {
|
||||
// dir or symlink. skip `target` and `.git`.
|
||||
&name != "target" && &name != ".git"
|
||||
}
|
||||
}) {
|
||||
let item = item?;
|
||||
if item.file_type().is_file() {
|
||||
cmd = cmd.arg(item.into_path());
|
||||
}
|
||||
}
|
||||
|
||||
// We want our own error message, repeating the command is too much.
|
||||
cmd.quiet().run().map_err(|_| anyhow!("`rustfmt` failed"))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,120 @@
|
|||
pub(crate) mod arg;
|
||||
mod commands;
|
||||
mod util;
|
||||
|
||||
use std::path::Path;
|
||||
use std::ffi::OsString;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use path_macro::path;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use arg::Subcommands;
|
||||
|
||||
struct AutoConfig {
|
||||
toolchain: bool,
|
||||
fmt: bool,
|
||||
clippy: bool,
|
||||
#[derive(Parser, Clone, Debug)]
|
||||
#[command(author, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
impl Subcommands {
|
||||
fn run_auto_things(&self) -> bool {
|
||||
use Subcommands::*;
|
||||
match self {
|
||||
// Early commands, that don't do auto-things and don't want the environment-altering things happening below.
|
||||
Toolchain { .. }
|
||||
| RustcPull { .. }
|
||||
| RustcPush { .. }
|
||||
| ManySeeds { .. }
|
||||
| Bench { .. } => false,
|
||||
Install { .. }
|
||||
| Check { .. }
|
||||
| Build { .. }
|
||||
| Test { .. }
|
||||
| Run { .. }
|
||||
| Fmt { .. }
|
||||
| Clippy { .. }
|
||||
| Cargo { .. } => true,
|
||||
}
|
||||
}
|
||||
fn get_config(&self, miri_dir: &Path) -> Option<AutoConfig> {
|
||||
let skip_auto_ops = std::env::var_os("MIRI_AUTO_OPS").is_some();
|
||||
if !self.run_auto_things() {
|
||||
return None;
|
||||
}
|
||||
if skip_auto_ops {
|
||||
return Some(AutoConfig { toolchain: false, fmt: false, clippy: false });
|
||||
}
|
||||
|
||||
let auto_everything = path!(miri_dir / ".auto_everything").exists();
|
||||
let toolchain = auto_everything || path!(miri_dir / ".auto-toolchain").exists();
|
||||
let fmt = auto_everything || path!(miri_dir / ".auto-fmt").exists();
|
||||
let clippy = auto_everything || path!(miri_dir / ".auto-clippy").exists();
|
||||
Some(AutoConfig { toolchain, fmt, clippy })
|
||||
}
|
||||
#[derive(Subcommand, Clone, Debug)]
|
||||
pub enum Command {
|
||||
/// Installs the miri driver and cargo-miri.
|
||||
/// Sets up the rpath such that the installed binary should work in any
|
||||
/// working directory. Note that the binaries are placed in the `miri` toolchain
|
||||
/// sysroot, to prevent conflicts with other toolchains.
|
||||
Install {
|
||||
/// Flags that are passed through to `cargo install`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Just build miri.
|
||||
Build {
|
||||
/// Flags that are passed through to `cargo build`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Just check miri.
|
||||
Check {
|
||||
/// Flags that are passed through to `cargo check`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Build miri, set up a sysroot and then run the test suite.
|
||||
Test {
|
||||
#[arg(long, default_value_t = false)]
|
||||
bless: bool,
|
||||
/// Flags that are passed through to `cargo test`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Build miri, set up a sysroot and then run the driver with the given <flags>.
|
||||
/// (Also respects MIRIFLAGS environment variable.)
|
||||
Run {
|
||||
#[arg(long, default_value_t = false)]
|
||||
dep: bool,
|
||||
/// Flags that are passed through to `miri`
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Format all sources and tests.
|
||||
Fmt {
|
||||
/// Flags that are passed through to `rustfmt`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Runs clippy on all sources.
|
||||
Clippy {
|
||||
/// Flags that are passed through to `cargo clippy`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Runs just `cargo <flags>` with the Miri-specific environment variables.
|
||||
/// Mainly meant to be invoked by rust-analyzer.
|
||||
Cargo {
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Runs <command> over and over again with different seeds for Miri. The MIRIFLAGS
|
||||
/// variable is set to its original value appended with ` -Zmiri-seed=$SEED` for
|
||||
/// many different seeds.
|
||||
ManySeeds {
|
||||
/// Starting seed.
|
||||
#[arg(long, env = "MIRI_SEED_START", default_value_t = 0)]
|
||||
seed_start: u64,
|
||||
/// Amount of seeds to try.
|
||||
#[arg(long, env = "MIRI_SEEDS", default_value_t = 256)]
|
||||
seeds: u64,
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
command: Vec<OsString>,
|
||||
},
|
||||
/// Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
|
||||
Bench {
|
||||
/// List of benchmarks to run. By default all benchmarks are run.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
benches: Vec<OsString>,
|
||||
},
|
||||
/// Update and activate the rustup toolchain 'miri' to the commit given in the
|
||||
/// `rust-version` file.
|
||||
/// `rustup-toolchain-install-master` must be installed for this to work. Any extra
|
||||
/// flags are passed to `rustup-toolchain-install-master`.
|
||||
Toolchain {
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<OsString>,
|
||||
},
|
||||
/// Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest
|
||||
/// rustc commit. The fetched commit is stored in the `rust-version` file, so the
|
||||
/// next `./miri toolchain` will install the rustc that just got pulled.
|
||||
RustcPull { commit: Option<String> },
|
||||
/// Push Miri changes back to the rustc repo. This will pull a copy of the rustc
|
||||
/// history into the Miri repo, unless you set the RUSTC_GIT env var to an existing
|
||||
/// clone of the rustc repo.
|
||||
RustcPush {
|
||||
#[arg(long, env = "RUSTC_GIT")]
|
||||
rustc_git: Option<String>,
|
||||
github_user: String,
|
||||
branch: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = arg::Cli::parse();
|
||||
commands::MiriRunner::exec(&args.commands)?;
|
||||
let args = Cli::parse();
|
||||
args.command.exec()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
153
src/tools/miri/miri-script/src/util.rs
Normal file
153
src/tools/miri/miri-script/src/util.rs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use dunce::canonicalize;
|
||||
use path_macro::path;
|
||||
use xshell::{cmd, Shell};
|
||||
|
||||
pub fn miri_dir() -> std::io::Result<PathBuf> {
|
||||
const MIRI_SCRIPT_ROOT_DIR: &str = env!("CARGO_MANIFEST_DIR");
|
||||
Ok(canonicalize(MIRI_SCRIPT_ROOT_DIR)?.parent().unwrap().into())
|
||||
}
|
||||
|
||||
/// Queries the active toolchain for the Miri dir.
|
||||
pub fn active_toolchain() -> Result<String> {
|
||||
let sh = shell()?;
|
||||
sh.change_dir(miri_dir()?);
|
||||
let stdout = cmd!(sh, "rustup show active-toolchain").read()?;
|
||||
Ok(stdout.split_whitespace().next().context("Could not obtain active Rust toolchain")?.into())
|
||||
}
|
||||
|
||||
pub fn shell() -> Result<Shell> {
|
||||
let sh = Shell::new()?;
|
||||
// xshell does not propagate parent's env variables by default.
|
||||
for (k, v) in std::env::vars_os() {
|
||||
sh.set_var(k, v);
|
||||
}
|
||||
Ok(sh)
|
||||
}
|
||||
|
||||
pub fn flagsplit(flags: &str) -> Vec<String> {
|
||||
// This code is taken from `RUSTFLAGS` handling in cargo.
|
||||
flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect()
|
||||
}
|
||||
|
||||
/// Some extra state we track for building Miri, such as the right RUSTFLAGS.
|
||||
pub struct MiriEnv {
|
||||
/// miri_dir is the root of the miri repository checkout we are working in.
|
||||
pub miri_dir: PathBuf,
|
||||
/// active_toolchain is passed as `+toolchain` argument to cargo/rustc invocations.
|
||||
pub toolchain: String,
|
||||
/// Extra flags to pass to cargo.
|
||||
pub cargo_extra_flags: Vec<String>,
|
||||
/// The rustc sysroot
|
||||
pub sysroot: PathBuf,
|
||||
/// The shell we use.
|
||||
pub sh: Shell,
|
||||
}
|
||||
|
||||
impl MiriEnv {
|
||||
pub fn new() -> Result<Self> {
|
||||
let sh = shell()?;
|
||||
let toolchain = active_toolchain()?;
|
||||
let miri_dir = miri_dir()?;
|
||||
|
||||
let sysroot = cmd!(sh, "rustc +{toolchain} --print sysroot").read()?.into();
|
||||
let target_output = cmd!(sh, "rustc +{toolchain} --version --verbose").read()?;
|
||||
let rustc_meta = rustc_version::version_meta_for(&target_output)?;
|
||||
let libdir = path!(sysroot / "lib" / "rustlib" / rustc_meta.host / "lib");
|
||||
|
||||
// Determine some toolchain properties
|
||||
if !libdir.exists() {
|
||||
println!("Something went wrong determining the library dir.");
|
||||
println!("I got {} but that does not exist.", libdir.display());
|
||||
println!("Please report a bug at https://github.com/rust-lang/miri/issues.");
|
||||
std::process::exit(2);
|
||||
}
|
||||
// Share target dir between `miri` and `cargo-miri`.
|
||||
let target_dir = std::env::var_os("CARGO_TARGET_DIR")
|
||||
.unwrap_or_else(|| path!(miri_dir / "target").into());
|
||||
sh.set_var("CARGO_TARGET_DIR", target_dir);
|
||||
|
||||
// We configure dev builds to not be unusably slow.
|
||||
let devel_opt_level =
|
||||
std::env::var_os("CARGO_PROFILE_DEV_OPT_LEVEL").unwrap_or_else(|| "2".into());
|
||||
sh.set_var("CARGO_PROFILE_DEV_OPT_LEVEL", devel_opt_level);
|
||||
|
||||
// Compute rustflags.
|
||||
let rustflags = {
|
||||
let env = std::env::var_os("RUSTFLAGS");
|
||||
let mut flags_with_warnings = OsString::from(
|
||||
"-Zunstable-options -Wrustc::internal -Wrust_2018_idioms -Wunused_lifetimes -Wsemicolon_in_expressions_from_macros ",
|
||||
);
|
||||
if let Some(value) = env {
|
||||
flags_with_warnings.push(value);
|
||||
}
|
||||
// We set the rpath so that Miri finds the private rustc libraries it needs.
|
||||
let mut flags_with_compiler_settings = OsString::from("-C link-args=-Wl,-rpath,");
|
||||
flags_with_compiler_settings.push(&libdir);
|
||||
flags_with_compiler_settings.push(flags_with_warnings);
|
||||
flags_with_compiler_settings
|
||||
};
|
||||
sh.set_var("RUSTFLAGS", rustflags);
|
||||
|
||||
// Get extra flags for cargo.
|
||||
let cargo_extra_flags = std::env::var("CARGO_EXTRA_FLAGS").unwrap_or_default();
|
||||
let cargo_extra_flags = flagsplit(&cargo_extra_flags);
|
||||
|
||||
Ok(MiriEnv { miri_dir, toolchain, sh, sysroot, cargo_extra_flags })
|
||||
}
|
||||
|
||||
pub fn install_to_sysroot(
|
||||
&self,
|
||||
path: impl AsRef<OsStr>,
|
||||
args: impl IntoIterator<Item = impl AsRef<OsStr>>,
|
||||
) -> Result<()> {
|
||||
let MiriEnv { sysroot, toolchain, cargo_extra_flags, .. } = self;
|
||||
// Install binaries to the miri toolchain's `sysroot` so they do not interact with other toolchains.
|
||||
cmd!(self.sh, "cargo +{toolchain} install {cargo_extra_flags...} --path {path} --force --root {sysroot} {args...}").run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn build(
|
||||
&self,
|
||||
manifest_path: impl AsRef<OsStr>,
|
||||
args: &[OsString],
|
||||
quiet: bool,
|
||||
) -> Result<()> {
|
||||
let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
|
||||
let quiet_flag = if quiet { Some("--quiet") } else { None };
|
||||
let mut cmd = cmd!(
|
||||
self.sh,
|
||||
"cargo +{toolchain} build {cargo_extra_flags...} --manifest-path {manifest_path} {quiet_flag...} {args...}"
|
||||
);
|
||||
cmd.set_quiet(quiet);
|
||||
cmd.run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check(&self, manifest_path: impl AsRef<OsStr>, args: &[OsString]) -> Result<()> {
|
||||
let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
|
||||
cmd!(self.sh, "cargo +{toolchain} check {cargo_extra_flags...} --manifest-path {manifest_path} --all-targets {args...}")
|
||||
.run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clippy(&self, manifest_path: impl AsRef<OsStr>, args: &[OsString]) -> Result<()> {
|
||||
let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
|
||||
cmd!(self.sh, "cargo +{toolchain} clippy {cargo_extra_flags...} --manifest-path {manifest_path} --all-targets {args...}")
|
||||
.run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test(&self, manifest_path: impl AsRef<OsStr>, args: &[OsString]) -> Result<()> {
|
||||
let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
|
||||
cmd!(
|
||||
self.sh,
|
||||
"cargo +{toolchain} test {cargo_extra_flags...} --manifest-path {manifest_path} {args...}"
|
||||
)
|
||||
.run()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue