about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/miri/miri-script/Cargo.lock122
-rw-r--r--src/tools/miri/miri-script/Cargo.toml1
-rw-r--r--src/tools/miri/miri-script/src/args.rs135
-rw-r--r--src/tools/miri/miri-script/src/main.rs331
4 files changed, 254 insertions, 335 deletions
diff --git a/src/tools/miri/miri-script/Cargo.lock b/src/tools/miri/miri-script/Cargo.lock
index 0c0fe477cdd..0208327a8dd 100644
--- a/src/tools/miri/miri-script/Cargo.lock
+++ b/src/tools/miri/miri-script/Cargo.lock
@@ -3,6 +3,55 @@
 version = 4
 
 [[package]]
+name = "anstream"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
 name = "anyhow"
 version = "1.0.80"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -21,6 +70,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
+name = "clap"
+version = "4.5.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+
+[[package]]
 name = "directories"
 version = "5.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -81,6 +176,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
 name = "home"
 version = "0.5.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -90,6 +191,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
 name = "itertools"
 version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -137,6 +244,7 @@ name = "miri-script"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "clap",
  "directories",
  "dunce",
  "itertools",
@@ -279,6 +387,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
 
 [[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
 name = "syn"
 version = "2.0.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -329,6 +443,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
 name = "walkdir"
 version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -362,7 +482,7 @@ version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
 dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
diff --git a/src/tools/miri/miri-script/Cargo.toml b/src/tools/miri/miri-script/Cargo.toml
index 5b31d5a6ff9..0ab49bbacfc 100644
--- a/src/tools/miri/miri-script/Cargo.toml
+++ b/src/tools/miri/miri-script/Cargo.toml
@@ -25,3 +25,4 @@ dunce = "1.0.4"
 directories = "5"
 serde_json = "1"
 tempfile = "3.13.0"
+clap = { version = "4.5.21", features = ["derive"] }
diff --git a/src/tools/miri/miri-script/src/args.rs b/src/tools/miri/miri-script/src/args.rs
deleted file mode 100644
index 55d9de4233d..00000000000
--- a/src/tools/miri/miri-script/src/args.rs
+++ /dev/null
@@ -1,135 +0,0 @@
-use std::{env, iter};
-
-use anyhow::{Result, bail};
-
-pub struct Args {
-    args: iter::Peekable<env::Args>,
-    /// Set to `true` once we saw a `--`.
-    terminated: bool,
-}
-
-impl Args {
-    pub fn new() -> Self {
-        let mut args = Args { args: env::args().peekable(), terminated: false };
-        args.args.next().unwrap(); // skip program name
-        args
-    }
-
-    /// Get the next argument without any interpretation.
-    pub fn next_raw(&mut self) -> Option<String> {
-        self.args.next()
-    }
-
-    /// Consume a `-$f` flag if present.
-    pub fn get_short_flag(&mut self, flag: char) -> Result<bool> {
-        if self.terminated {
-            return Ok(false);
-        }
-        if let Some(next) = self.args.peek() {
-            if let Some(next) = next.strip_prefix("-") {
-                if let Some(next) = next.strip_prefix(flag) {
-                    if next.is_empty() {
-                        self.args.next().unwrap(); // consume this argument
-                        return Ok(true);
-                    } else {
-                        bail!("`-{flag}` followed by value");
-                    }
-                }
-            }
-        }
-        Ok(false)
-    }
-
-    /// Consume a `--$name` flag if present.
-    pub fn get_long_flag(&mut self, name: &str) -> Result<bool> {
-        if self.terminated {
-            return Ok(false);
-        }
-        if let Some(next) = self.args.peek() {
-            if let Some(next) = next.strip_prefix("--") {
-                if next == name {
-                    self.args.next().unwrap(); // consume this argument
-                    return Ok(true);
-                }
-            }
-        }
-        Ok(false)
-    }
-
-    /// Consume a `--$name val` or `--$name=val` option if present.
-    pub fn get_long_opt(&mut self, name: &str) -> Result<Option<String>> {
-        assert!(!name.is_empty());
-        if self.terminated {
-            return Ok(None);
-        }
-        let Some(next) = self.args.peek() else { return Ok(None) };
-        let Some(next) = next.strip_prefix("--") else { return Ok(None) };
-        let Some(next) = next.strip_prefix(name) else { return Ok(None) };
-        // Starts with `--flag`.
-        Ok(if let Some(val) = next.strip_prefix("=") {
-            // `--flag=val` form
-            let val = val.into();
-            self.args.next().unwrap(); // consume this argument
-            Some(val)
-        } else if next.is_empty() {
-            // `--flag val` form
-            self.args.next().unwrap(); // consume this argument
-            let Some(val) = self.args.next() else { bail!("`--{name}` not followed by value") };
-            Some(val)
-        } else {
-            // Some unrelated flag, like `--flag-more` or so.
-            None
-        })
-    }
-
-    /// Consume a `--$name=val` or `--$name` option if present; the latter
-    /// produces a default value. (`--$name val` is *not* accepted for this form
-    /// of argument, it understands `val` already as the next argument!)
-    pub fn get_long_opt_with_default(
-        &mut self,
-        name: &str,
-        default: &str,
-    ) -> Result<Option<String>> {
-        assert!(!name.is_empty());
-        if self.terminated {
-            return Ok(None);
-        }
-        let Some(next) = self.args.peek() else { return Ok(None) };
-        let Some(next) = next.strip_prefix("--") else { return Ok(None) };
-        let Some(next) = next.strip_prefix(name) else { return Ok(None) };
-        // Starts with `--flag`.
-        Ok(if let Some(val) = next.strip_prefix("=") {
-            // `--flag=val` form
-            let val = val.into();
-            self.args.next().unwrap(); // consume this argument
-            Some(val)
-        } else if next.is_empty() {
-            // `--flag` form
-            self.args.next().unwrap(); // consume this argument
-            Some(default.into())
-        } else {
-            // Some unrelated flag, like `--flag-more` or so.
-            None
-        })
-    }
-
-    /// Returns the next free argument or uninterpreted flag, or `None` if there are no more
-    /// arguments left. `--` is returned as well, but it is interpreted in the sense that no more
-    /// flags will be parsed after this.
-    pub fn get_other(&mut self) -> Option<String> {
-        if self.terminated {
-            return self.args.next();
-        }
-        let next = self.args.next()?;
-        if next == "--" {
-            self.terminated = true; // don't parse any more flags
-            // This is where our parser is special, we do yield the `--`.
-        }
-        Some(next)
-    }
-
-    /// Return the rest of the aguments entirely unparsed.
-    pub fn remainder(self) -> Vec<String> {
-        self.args.collect()
-    }
-}
diff --git a/src/tools/miri/miri-script/src/main.rs b/src/tools/miri/miri-script/src/main.rs
index e1bf3c18629..7592e56cfcf 100644
--- a/src/tools/miri/miri-script/src/main.rs
+++ b/src/tools/miri/miri-script/src/main.rs
@@ -1,6 +1,5 @@
 #![allow(clippy::needless_question_mark)]
 
-mod args;
 mod commands;
 mod coverage;
 mod util;
@@ -8,257 +7,191 @@ mod util;
 use std::ops::Range;
 
 use anyhow::{Context, Result, anyhow, bail};
+use clap::{Parser, Subcommand};
+
+/// Parses a seed range
+///
+/// This function is used for the `--many-seeds` flag. It expects the range in the form
+/// `<from>..<to>`. `<from>` is inclusive, `<to>` is exclusive. `<from>` can be omitted,
+/// in which case it is assumed to be `0`.
+fn parse_range(val: &str) -> anyhow::Result<Range<u32>> {
+    let (from, to) = val
+        .split_once("..")
+        .ok_or_else(|| anyhow!("invalid format for `--many-seeds`: expected `from..to`"))?;
+    let from: u32 = if from.is_empty() {
+        0
+    } else {
+        from.parse().context("invalid `from` in `--many-seeds=from..to")?
+    };
+    let to: u32 = to.parse().context("invalid `to` in `--many-seeds=from..to")?;
+    Ok(from..to)
+}
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Subcommand)]
 pub enum Command {
-    /// Installs the miri driver and cargo-miri.
+    /// Installs the miri driver and cargo-miri to the sysroot of the active toolchain.
+    ///
     /// 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.
+    /// working directory.
     Install {
         /// Flags that are passed through to `cargo install`.
+        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
         flags: Vec<String>,
     },
-    /// Just build miri.
+    /// Build Miri.
     Build {
         /// Flags that are passed through to `cargo build`.
+        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
         flags: Vec<String>,
     },
-    /// Just check miri.
+    /// Check Miri.
     Check {
         /// Flags that are passed through to `cargo check`.
+        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
+        flags: Vec<String>,
+    },
+    /// Check Miri with Clippy.
+    Clippy {
+        /// Flags that are passed through to `cargo clippy`.
+        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
         flags: Vec<String>,
     },
-    /// Build miri, set up a sysroot and then run the test suite.
+    /// Run the Miri test suite.
     Test {
+        /// Update stdout/stderr reference files.
+        #[arg(long)]
         bless: bool,
         /// The cross-interpretation target.
-        /// If none then the host is the target.
+        #[arg(long)]
         target: Option<String>,
-        /// Produce coverage report if set.
+        /// Produce coverage report.
+        #[arg(long)]
         coverage: bool,
         /// Flags that are passed through to the test harness.
+        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
         flags: Vec<String>,
     },
-    /// Build miri, set up a sysroot and then run the driver with the given <flags>.
-    /// (Also respects MIRIFLAGS environment variable.)
+    /// Run the Miri driver.
+    ///
+    /// Also respects MIRIFLAGS environment variable.
     Run {
+        /// Build the program with the dependencies declared in `test_dependencies/Cargo.toml`.
+        #[arg(long)]
         dep: bool,
+        /// Show build progress.
+        #[arg(long, short)]
         verbose: bool,
+        /// Run the driver with the seeds in the given range (`..to` or `from..to`, default: `0..64`).
+        #[arg(long, value_parser = parse_range)]
         many_seeds: Option<Range<u32>>,
+        /// The cross-interpretation target.
+        #[arg(long)]
         target: Option<String>,
+        /// The Rust edition.
+        #[arg(long)]
         edition: Option<String>,
         /// Flags that are passed through to `miri`.
+        ///
+        /// The flags set in `MIRIFLAGS` are added in front of these flags.
+        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
         flags: Vec<String>,
     },
-    /// Build documentation
+    /// Build documentation.
     Doc {
         /// Flags that are passed through to `cargo doc`.
+        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
         flags: Vec<String>,
     },
     /// Format all sources and tests.
     Fmt {
         /// Flags that are passed through to `rustfmt`.
+        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
         flags: Vec<String>,
     },
-    /// Runs clippy on all sources.
-    Clippy {
-        /// Flags that are passed through to `cargo clippy`.
-        flags: Vec<String>,
-    },
-    /// Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
+    /// Runs the benchmarks from bench-cargo-miri in hyperfine.
+    ///
+    /// hyperfine needs to be installed.
     Bench {
+        #[arg(long)]
         target: Option<String>,
         /// When `true`, skip the `./miri install` step.
+        #[arg(long)]
         no_install: bool,
-        /// List of benchmarks to run. By default all benchmarks are run.
+        /// List of benchmarks to run (default: run all benchmarks).
         benches: Vec<String>,
     },
-    /// 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 { flags: Vec<String> },
-    /// 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 },
+    /// Update and activate the rustup toolchain 'miri'.
+    ///
+    /// The `rust-version` file is used to determine the commit that will be intsalled.
+    /// `rustup-toolchain-install-master` must be installed for this to work.
+    Toolchain {
+        /// Flags that are passed through to `rustup-toolchain-install-master`.
+        flags: Vec<String>,
+    },
+    /// Pull and merge Miri changes from the rustc repo.
+    ///
+    /// The fetched commit is stored in the `rust-version` file, so the next `./miri toolchain` will
+    /// install the rustc that just got pulled.
+    RustcPull {
+        /// The commit to fetch (default: latest rustc commit).
+        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 {
+        /// The Github user that owns the rustc fork to which we should push.
+        github_user: String,
+        /// The branch to push to.
+        #[arg(default_value = "miri-sync")]
+        branch: String,
+    },
 }
 
-const HELP: &str = r#"  COMMANDS
-
-./miri build <flags>:
-Just build miri. <flags> are passed to `cargo build`.
-
-./miri check <flags>:
-Just check miri. <flags> are passed to `cargo check`.
-
-./miri test [--bless] [--target <target>] <flags>:
-Build miri, set up a sysroot and then run the test suite.
-<flags> are passed to the test harness.
-
-./miri run [--dep] [-v|--verbose] [--many-seeds|--many-seeds=..to|--many-seeds=from..to] <flags>:
-Build miri, set up a sysroot and then run the driver with the given <flags>.
-(Also respects MIRIFLAGS environment variable.)
-If `--many-seeds` is present, Miri is run many times in parallel with different seeds.
-The range defaults to `0..64`.
-
-./miri fmt <flags>:
-Format all sources and tests. <flags> are passed to `rustfmt`.
-
-./miri clippy <flags>:
-Runs clippy on all sources. <flags> are passed to `cargo clippy`.
-
-./miri cargo <flags>:
-Runs just `cargo <flags>` with the Miri-specific environment variables.
-Mainly meant to be invoked by rust-analyzer.
-
-./miri install <flags>:
-Installs the miri driver and cargo-miri. <flags> are passed to `cargo
-install`. 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.
-
-./miri bench [--target <target>] [--no-install] <benches>:
-Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
-<benches> can explicitly list the benchmarks to run; by default, all of them are run.
-By default, this runs `./miri install` to ensure the latest local Miri is being benchmarked;
-`--no-install` can be used to skip that step.
-
-./miri toolchain <flags>:
-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`.
-
-./miri rustc-pull <commit>:
-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.
-
-./miri rustc-push <github user> [<branch>]:
-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. The branch defaults to `miri-sync`.
-
-  ENVIRONMENT VARIABLES
+impl Command {
+    fn add_remainder(&mut self, remainder: Vec<String>) -> Result<()> {
+        if remainder.is_empty() {
+            return Ok(());
+        }
 
-MIRI_SYSROOT:
-If already set, the "sysroot setup" step is skipped.
+        match self {
+            Self::Install { flags }
+            | Self::Build { flags }
+            | Self::Check { flags }
+            | Self::Doc { flags }
+            | Self::Fmt { flags }
+            | Self::Toolchain { flags }
+            | Self::Clippy { flags }
+            | Self::Run { flags, .. }
+            | Self::Test { flags, .. } => {
+                flags.extend(remainder);
+                Ok(())
+            }
+            Self::Bench { .. } | Self::RustcPull { .. } | Self::RustcPush { .. } =>
+                bail!("unexpected \"--\" found in arguments"),
+        }
+    }
+}
 
-CARGO_EXTRA_FLAGS:
-Pass extra flags to all cargo invocations. (Ignored by `./miri cargo`.)"#;
+#[derive(Parser)]
+#[command(after_help = "Environment variables:
+  MIRI_SYSROOT: If already set, the \"sysroot setup\" step is skipped
+  CARGO_EXTRA_FLAGS: Pass extra flags to all cargo invocations")]
+pub struct Cli {
+    #[command(subcommand)]
+    pub command: Command,
+}
 
 fn main() -> Result<()> {
-    // We are hand-rolling our own argument parser, since `clap` can't express what we need
-    // (https://github.com/clap-rs/clap/issues/5055).
-    let mut args = args::Args::new();
-    let command = match args.next_raw().as_deref() {
-        Some("build") => Command::Build { flags: args.remainder() },
-        Some("check") => Command::Check { flags: args.remainder() },
-        Some("doc") => Command::Doc { flags: args.remainder() },
-        Some("test") => {
-            let mut target = None;
-            let mut bless = false;
-            let mut flags = Vec::new();
-            let mut coverage = false;
-            loop {
-                if args.get_long_flag("bless")? {
-                    bless = true;
-                } else if args.get_long_flag("coverage")? {
-                    coverage = true;
-                } else if let Some(val) = args.get_long_opt("target")? {
-                    target = Some(val);
-                } else if let Some(flag) = args.get_other() {
-                    flags.push(flag);
-                } else {
-                    break;
-                }
-            }
-            Command::Test { bless, flags, target, coverage }
-        }
-        Some("run") => {
-            let mut dep = false;
-            let mut verbose = false;
-            let mut many_seeds = None;
-            let mut target = None;
-            let mut edition = None;
-            let mut flags = Vec::new();
-            loop {
-                if args.get_long_flag("dep")? {
-                    dep = true;
-                } else if args.get_long_flag("verbose")? || args.get_short_flag('v')? {
-                    verbose = true;
-                } else if let Some(val) = args.get_long_opt_with_default("many-seeds", "0..64")? {
-                    let (from, to) = val.split_once("..").ok_or_else(|| {
-                        anyhow!("invalid format for `--many-seeds`: expected `from..to`")
-                    })?;
-                    let from: u32 = if from.is_empty() {
-                        0
-                    } else {
-                        from.parse().context("invalid `from` in `--many-seeds=from..to")?
-                    };
-                    let to: u32 = to.parse().context("invalid `to` in `--many-seeds=from..to")?;
-                    many_seeds = Some(from..to);
-                } else if let Some(val) = args.get_long_opt("target")? {
-                    target = Some(val);
-                } else if let Some(val) = args.get_long_opt("edition")? {
-                    edition = Some(val);
-                } else if let Some(flag) = args.get_other() {
-                    flags.push(flag);
-                } else {
-                    break;
-                }
-            }
-            Command::Run { dep, verbose, many_seeds, target, edition, flags }
-        }
-        Some("fmt") => Command::Fmt { flags: args.remainder() },
-        Some("clippy") => Command::Clippy { flags: args.remainder() },
-        Some("install") => Command::Install { flags: args.remainder() },
-        Some("bench") => {
-            let mut target = None;
-            let mut benches = Vec::new();
-            let mut no_install = false;
-            loop {
-                if let Some(val) = args.get_long_opt("target")? {
-                    target = Some(val);
-                } else if args.get_long_flag("no-install")? {
-                    no_install = true;
-                } else if let Some(flag) = args.get_other() {
-                    benches.push(flag);
-                } else {
-                    break;
-                }
-            }
-            Command::Bench { target, benches, no_install }
-        }
-        Some("toolchain") => Command::Toolchain { flags: args.remainder() },
-        Some("rustc-pull") => {
-            let commit = args.next_raw();
-            if args.next_raw().is_some() {
-                bail!("Too many arguments for `./miri rustc-pull`");
-            }
-            Command::RustcPull { commit }
-        }
-        Some("rustc-push") => {
-            let github_user = args.next_raw().ok_or_else(|| {
-                anyhow!("Missing first argument for `./miri rustc-push GITHUB_USER [BRANCH]`")
-            })?;
-            let branch = args.next_raw().unwrap_or_else(|| "miri-sync".into());
-            if args.next_raw().is_some() {
-                bail!("Too many arguments for `./miri rustc-push GITHUB_USER BRANCH`");
-            }
-            Command::RustcPush { github_user, branch }
-        }
-        _ => {
-            eprintln!("Unknown or missing command. Usage:\n\n{HELP}");
-            std::process::exit(1);
-        }
-    };
+    // Split the arguments into the part before the `--` and the part after.
+    // The `--` itself ends up in the second part.
+    let miri_args: Vec<_> = std::env::args().take_while(|x| *x != "--").collect();
+    let remainder: Vec<_> = std::env::args().skip_while(|x| *x != "--").collect();
+
+    let args = Cli::parse_from(miri_args);
+    let mut command = args.command;
+    command.add_remainder(remainder)?;
     command.exec()?;
     Ok(())
 }