]> git.lizzy.rs Git - rust.git/blob - cargo-miri/bin.rs
make sure subcrate tests have the right cwd
[rust.git] / cargo-miri / bin.rs
1 use std::env;
2 use std::ffi::OsString;
3 use std::fs::{self, File};
4 use std::io::{self, BufRead, BufReader, BufWriter, Write};
5 use std::ops::Not;
6 use std::path::{Path, PathBuf};
7 use std::process::Command;
8
9 use serde::{Deserialize, Serialize};
10
11 use rustc_version::VersionMeta;
12
13 const XARGO_MIN_VERSION: (u32, u32, u32) = (0, 3, 22);
14
15 const CARGO_MIRI_HELP: &str = r#"Runs binary crates and tests in Miri
16
17 Usage:
18     cargo miri [subcommand] [<cargo options>...] [--] [<program/test suite options>...]
19
20 Subcommands:
21     run                      Run binaries
22     test                     Run tests
23     setup                    Only perform automatic setup, but without asking questions (for getting a proper libstd)
24
25 The cargo options are exactly the same as for `cargo run` and `cargo test`, respectively.
26
27 Examples:
28     cargo miri run
29     cargo miri test -- test-suite-filter
30 "#;
31
32 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
33 enum MiriCommand {
34     Run,
35     Test,
36     Setup,
37 }
38
39 /// The inforamtion Miri needs to run a crate. Stored as JSON when the crate is "compiled".
40 #[derive(Serialize, Deserialize)]
41 struct CrateRunInfo {
42     /// The command-line arguments.
43     args: Vec<String>,
44     /// The environment.
45     env: Vec<(OsString, OsString)>,
46     /// The current working directory.
47     current_dir: OsString,
48 }
49
50 impl CrateRunInfo {
51     /// Gather all the information we need.
52     fn collect(args: env::Args) -> Self {
53         let args = args.collect();
54         let env = env::vars_os().collect();
55         let current_dir = env::current_dir().unwrap().into_os_string();
56         CrateRunInfo { args, env, current_dir }
57     }
58
59     fn store(&self, filename: &Path) {
60         let file = File::create(filename)
61             .unwrap_or_else(|_| show_error(format!("Cannot create `{}`", filename.display())));
62         let file = BufWriter::new(file);
63         serde_json::ser::to_writer(file, self)
64             .unwrap_or_else(|_| show_error(format!("Cannot write to `{}`", filename.display())));
65     }
66 }
67
68 fn show_help() {
69     println!("{}", CARGO_MIRI_HELP);
70 }
71
72 fn show_version() {
73     println!(
74         "miri {} ({} {})",
75         env!("CARGO_PKG_VERSION"),
76         env!("VERGEN_SHA_SHORT"),
77         env!("VERGEN_COMMIT_DATE")
78     );
79 }
80
81 fn show_error(msg: String) -> ! {
82     eprintln!("fatal error: {}", msg);
83     std::process::exit(1)
84 }
85
86 // Determines whether a `--flag` is present.
87 fn has_arg_flag(name: &str) -> bool {
88     let mut args = std::env::args().take_while(|val| val != "--");
89     args.any(|val| val == name)
90 }
91
92 /// Gets the value of a `--flag`.
93 fn get_arg_flag_value(name: &str) -> Option<String> {
94     // Stop searching at `--`.
95     let mut args = std::env::args().take_while(|val| val != "--");
96     loop {
97         let arg = match args.next() {
98             Some(arg) => arg,
99             None => return None,
100         };
101         if !arg.starts_with(name) {
102             continue;
103         }
104         // Strip leading `name`.
105         let suffix = &arg[name.len()..];
106         if suffix.is_empty() {
107             // This argument is exactly `name`; the next one is the value.
108             return args.next();
109         } else if suffix.starts_with('=') {
110             // This argument is `name=value`; get the value.
111             // Strip leading `=`.
112             return Some(suffix[1..].to_owned());
113         }
114     }
115 }
116
117 /// Returns the path to the `miri` binary
118 fn find_miri() -> PathBuf {
119     if let Some(path) = env::var_os("MIRI") {
120         return path.into();
121     }
122     let mut path = std::env::current_exe().expect("current executable path invalid");
123     path.set_file_name("miri");
124     path
125 }
126
127 fn miri() -> Command {
128     Command::new(find_miri())
129 }
130
131 fn version_info() -> VersionMeta {
132     VersionMeta::for_command(miri()).expect("failed to determine underlying rustc version of Miri")
133 }
134
135 fn cargo() -> Command {
136     Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")))
137 }
138
139 fn xargo_check() -> Command {
140     Command::new(env::var_os("XARGO_CHECK").unwrap_or_else(|| OsString::from("xargo-check")))
141 }
142
143 /// Execute the command if it fails, fail this process with the same exit code.
144 /// Otherwise, continue.
145 fn exec(mut cmd: Command) {
146     let exit_status = cmd.status().expect("failed to run command");
147     if exit_status.success().not() {
148         std::process::exit(exit_status.code().unwrap_or(-1))
149     }
150 }
151
152 fn xargo_version() -> Option<(u32, u32, u32)> {
153     let out = xargo_check().arg("--version").output().ok()?;
154     if !out.status.success() {
155         return None;
156     }
157     // Parse output. The first line looks like "xargo 0.3.12 (b004f1c 2018-12-13)".
158     let line = out
159         .stderr
160         .lines()
161         .nth(0)
162         .expect("malformed `xargo --version` output: not at least one line")
163         .expect("malformed `xargo --version` output: error reading first line");
164     let (name, version) = {
165         let mut split = line.split(' ');
166         (
167             split.next().expect("malformed `xargo --version` output: empty"),
168             split.next().expect("malformed `xargo --version` output: not at least two words"),
169         )
170     };
171     if name != "xargo" {
172         // This is some fork of xargo
173         return None;
174     }
175     let mut version_pieces = version.split('.');
176     let major = version_pieces
177         .next()
178         .expect("malformed `xargo --version` output: not a major version piece")
179         .parse()
180         .expect("malformed `xargo --version` output: major version is not an integer");
181     let minor = version_pieces
182         .next()
183         .expect("malformed `xargo --version` output: not a minor version piece")
184         .parse()
185         .expect("malformed `xargo --version` output: minor version is not an integer");
186     let patch = version_pieces
187         .next()
188         .expect("malformed `xargo --version` output: not a patch version piece")
189         .parse()
190         .expect("malformed `xargo --version` output: patch version is not an integer");
191     if !version_pieces.next().is_none() {
192         panic!("malformed `xargo --version` output: more than three pieces in version");
193     }
194     Some((major, minor, patch))
195 }
196
197 fn ask_to_run(mut cmd: Command, ask: bool, text: &str) {
198     // Disable interactive prompts in CI (GitHub Actions, Travis, AppVeyor, etc).
199     // Azure doesn't set `CI` though (nothing to see here, just Microsoft being Microsoft),
200     // so we also check their `TF_BUILD`.
201     let is_ci = env::var_os("CI").is_some() || env::var_os("TF_BUILD").is_some();
202     if ask && !is_ci {
203         let mut buf = String::new();
204         print!("I will run `{:?}` to {}. Proceed? [Y/n] ", cmd, text);
205         io::stdout().flush().unwrap();
206         io::stdin().read_line(&mut buf).unwrap();
207         match buf.trim().to_lowercase().as_ref() {
208             // Proceed.
209             "" | "y" | "yes" => {}
210             "n" | "no" => show_error(format!("Aborting as per your request")),
211             a => show_error(format!("I do not understand `{}`", a)),
212         };
213     } else {
214         println!("Running `{:?}` to {}.", cmd, text);
215     }
216
217     if cmd.status().expect(&format!("failed to execute {:?}", cmd)).success().not() {
218         show_error(format!("Failed to {}", text));
219     }
220 }
221
222 /// Performs the setup required to make `cargo miri` work: Getting a custom-built libstd. Then sets
223 /// `MIRI_SYSROOT`. Skipped if `MIRI_SYSROOT` is already set, in which case we expect the user has
224 /// done all this already.
225 fn setup(subcommand: MiriCommand) {
226     if std::env::var_os("MIRI_SYSROOT").is_some() {
227         if subcommand == MiriCommand::Setup {
228             println!("WARNING: MIRI_SYSROOT already set, not doing anything.")
229         }
230         return;
231     }
232
233     // Subcommands other than `setup` will do a setup if necessary, but
234     // interactively confirm first.
235     let ask_user = subcommand != MiriCommand::Setup;
236
237     // First, we need xargo.
238     if xargo_version().map_or(true, |v| v < XARGO_MIN_VERSION) {
239         if std::env::var_os("XARGO_CHECK").is_some() {
240             // The user manually gave us a xargo binary; don't do anything automatically.
241             show_error(format!("Your xargo is too old; please upgrade to the latest version"))
242         }
243         let mut cmd = cargo();
244         cmd.args(&["install", "xargo", "-f"]);
245         ask_to_run(cmd, ask_user, "install a recent enough xargo");
246     }
247
248     // Determine where the rust sources are located.  `XARGO_RUST_SRC` env var trumps everything.
249     let rust_src = match std::env::var_os("XARGO_RUST_SRC") {
250         Some(path) => {
251             let path = PathBuf::from(path);
252             // Make path absolute if possible.
253             path.canonicalize().unwrap_or(path)
254         }
255         None => {
256             // Check for `rust-src` rustup component.
257             let sysroot = miri()
258                 .args(&["--print", "sysroot"])
259                 .output()
260                 .expect("failed to determine sysroot")
261                 .stdout;
262             let sysroot = std::str::from_utf8(&sysroot).unwrap();
263             let sysroot = Path::new(sysroot.trim_end_matches('\n'));
264             // Check for `$SYSROOT/lib/rustlib/src/rust/library`; test if that contains `std/Cargo.toml`.
265             let rustup_src =
266                 sysroot.join("lib").join("rustlib").join("src").join("rust").join("library");
267             if !rustup_src.join("std").join("Cargo.toml").exists() {
268                 // Ask the user to install the `rust-src` component, and use that.
269                 let mut cmd = Command::new("rustup");
270                 cmd.args(&["component", "add", "rust-src"]);
271                 ask_to_run(
272                     cmd,
273                     ask_user,
274                     "install the `rust-src` component for the selected toolchain",
275                 );
276             }
277             rustup_src
278         }
279     };
280     if !rust_src.exists() {
281         show_error(format!("Given Rust source directory `{}` does not exist.", rust_src.display()));
282     }
283
284     // Next, we need our own libstd. Prepare a xargo project for that purpose.
285     // We will do this work in whatever is a good cache dir for this platform.
286     let dirs = directories::ProjectDirs::from("org", "rust-lang", "miri").unwrap();
287     let dir = dirs.cache_dir();
288     if !dir.exists() {
289         fs::create_dir_all(&dir).unwrap();
290     }
291     // The interesting bit: Xargo.toml
292     File::create(dir.join("Xargo.toml"))
293         .unwrap()
294         .write_all(
295             br#"
296 [dependencies.std]
297 default_features = false
298 # We need the `panic_unwind` feature because we use the `unwind` panic strategy.
299 # Using `abort` works for libstd, but then libtest will not compile.
300 features = ["panic_unwind"]
301
302 [dependencies.test]
303 "#,
304         )
305         .unwrap();
306     // The boring bits: a dummy project for xargo.
307     // FIXME: With xargo-check, can we avoid doing this?
308     File::create(dir.join("Cargo.toml"))
309         .unwrap()
310         .write_all(
311             br#"
312 [package]
313 name = "miri-xargo"
314 description = "A dummy project for building libstd with xargo."
315 version = "0.0.0"
316
317 [lib]
318 path = "lib.rs"
319 "#,
320         )
321         .unwrap();
322     File::create(dir.join("lib.rs")).unwrap();
323
324     // Determine architectures.
325     // We always need to set a target so rustc bootstrap can tell apart host from target crates.
326     let host = version_info().host;
327     let target = get_arg_flag_value("--target");
328     let target = target.as_ref().unwrap_or(&host);
329     // Now invoke xargo.
330     let mut command = xargo_check();
331     command.arg("check").arg("-q");
332     command.arg("--target").arg(target);
333     command.current_dir(&dir);
334     command.env("XARGO_HOME", &dir);
335     command.env("XARGO_RUST_SRC", &rust_src);
336     // Use Miri as rustc to build a libstd compatible with us (and use the right flags).
337     // However, when we are running in bootstrap, we cannot just overwrite `RUSTC`,
338     // because we still need bootstrap to distinguish between host and target crates.
339     // In that case we overwrite `RUSTC_REAL` instead which determines the rustc used
340     // for target crates.
341     if env::var_os("RUSTC_STAGE").is_some() {
342         command.env("RUSTC_REAL", find_miri());
343     } else {
344         command.env("RUSTC", find_miri());
345     }
346     command.env("MIRI_BE_RUSTC", "1");
347     // Make sure there are no other wrappers or flags getting in our way
348     // (Cc https://github.com/rust-lang/miri/issues/1421).
349     // This is consistent with normal `cargo build` that does not apply `RUSTFLAGS`
350     // to the sysroot either.
351     command.env_remove("RUSTC_WRAPPER");
352     command.env_remove("RUSTFLAGS");
353     // Finally run it!
354     if command.status().expect("failed to run xargo").success().not() {
355         show_error(format!("Failed to run xargo"));
356     }
357
358     // That should be it! But we need to figure out where xargo built stuff.
359     // Unfortunately, it puts things into a different directory when the
360     // architecture matches the host.
361     let sysroot = if target == &host { dir.join("HOST") } else { PathBuf::from(dir) };
362     std::env::set_var("MIRI_SYSROOT", &sysroot); // pass the env var to the processes we spawn, which will turn it into "--sysroot" flags
363     // Figure out what to print.
364     let print_sysroot = subcommand == MiriCommand::Setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path
365     if print_sysroot {
366         // Print just the sysroot and nothing else; this way we do not need any escaping.
367         println!("{}", sysroot.display());
368     } else if subcommand == MiriCommand::Setup {
369         println!("A libstd for Miri is now available in `{}`.", sysroot.display());
370     }
371 }
372
373 fn phase_cargo_miri(mut args: env::Args) {
374     // Check for version and help flags even when invoked as `cargo-miri`.
375     if has_arg_flag("--help") || has_arg_flag("-h") {
376         show_help();
377         return;
378     }
379     if has_arg_flag("--version") || has_arg_flag("-V") {
380         show_version();
381         return;
382     }
383
384     // Require a subcommand before any flags.
385     // We cannot know which of those flags take arguments and which do not,
386     // so we cannot detect subcommands later.
387     let subcommand = match args.next().as_deref() {
388         Some("test") => MiriCommand::Test,
389         Some("run") => MiriCommand::Run,
390         Some("setup") => MiriCommand::Setup,
391         // Invalid command.
392         _ => show_error(format!("`cargo miri` supports the following subcommands: `run`, `test`, and `setup`.")),
393     };
394     let verbose = has_arg_flag("-v");
395
396     // We always setup.
397     setup(subcommand);
398
399     // Invoke actual cargo for the job, but with different flags.
400     let miri_path = std::env::current_exe().expect("current executable path invalid");
401     let cargo_cmd = match subcommand {
402         MiriCommand::Test => "test",
403         MiriCommand::Run => "run",
404         MiriCommand::Setup => return, // `cargo miri setup` stops here.
405     };
406     let mut cmd = cargo();
407     cmd.arg(cargo_cmd);
408
409     // Make sure we know the build target, and cargo does, too.
410     // This is needed to make the `CARGO_TARGET_*_RUNNER` env var do something,
411     // and it later helps us detect which crates are proc-macro/build-script
412     // (host crates) and which crates are needed for the program itself.
413     let target = if let Some(target) = get_arg_flag_value("--target") {
414         target
415     } else {
416         // No target given. Pick default and tell cargo about it.
417         let host = version_info().host;
418         cmd.arg("--target");
419         cmd.arg(&host);
420         host
421     };
422
423     // Forward all further arguments. We do some processing here because we want to
424     // detect people still using the old way of passing flags to Miri
425     // (`cargo miri -- -Zmiri-foo`).
426     while let Some(arg) = args.next() {
427         cmd.arg(&arg);
428         if arg == "--" {
429             // Check if the next argument starts with `-Zmiri`. If yes, we assume
430             // this is an old-style invocation.
431             if let Some(next_arg) = args.next() {
432                 if next_arg.starts_with("-Zmiri") {
433                     eprintln!(
434                         "WARNING: it seems like you are setting Miri's flags in `cargo miri` the old way,\n\
435                         i.e., by passing them after the first `--`. This style is deprecated; please set\n\
436                         the MIRIFLAGS environment variable instead. `cargo miri run/test` now interprets\n\
437                         arguments the exact same way as `cargo run/test`."
438                     );
439                     // Old-style invocation. Turn these into MIRIFLAGS.
440                     let mut miriflags = env::var("MIRIFLAGS").unwrap_or_default();
441                     miriflags.push(' ');
442                     miriflags.push_str(&next_arg);
443                     while let Some(further_arg) = args.next() {
444                         if further_arg == "--" {
445                             // End of the Miri flags!
446                             break;
447                         }
448                         miriflags.push(' ');
449                         miriflags.push_str(&further_arg);
450                     }
451                     env::set_var("MIRIFLAGS", miriflags);
452                     // Pass the remaining flags to cargo.
453                     cmd.args(args);
454                     break;
455                 }
456                 // Not a Miri argument after all, make sure we pass it to cargo.
457                 cmd.arg(next_arg);
458             }
459         }
460     }
461
462     // Set `RUSTC_WRAPPER` to ourselves.  Cargo will prepend that binary to its usual invocation,
463     // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish
464     // the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.)
465     if env::var_os("RUSTC_WRAPPER").is_some() {
466         println!("WARNING: Ignoring `RUSTC_WRAPPER` environment variable, Miri does not support wrapping.");
467     }
468     cmd.env("RUSTC_WRAPPER", &miri_path);
469     if verbose {
470         eprintln!("+ RUSTC_WRAPPER={:?}", miri_path);
471     }
472
473     // Set the runner for the current target to us as well, so we can interpret the binaries.
474     let runner_env_name = format!("CARGO_TARGET_{}_RUNNER", target.to_uppercase().replace('-', "_"));
475     cmd.env(runner_env_name, &miri_path);
476
477     // Set rustdoc to us as well, so we can make it do nothing (see issue #584).
478     cmd.env("RUSTDOC", &miri_path);
479
480     // Run cargo.
481     if verbose {
482         cmd.env("MIRI_VERBOSE", ""); // This makes the other phases verbose.
483         eprintln!("[cargo-miri miri] {:?}", cmd);
484     }
485     exec(cmd)
486 }
487
488 fn phase_cargo_rustc(args: env::Args) {
489     /// Determines if we are being invoked (as rustc) to build a crate for
490     /// the "target" architecture, in contrast to the "host" architecture.
491     /// Host crates are for build scripts and proc macros and still need to
492     /// be built like normal; target crates need to be built for or interpreted
493     /// by Miri.
494     ///
495     /// Currently, we detect this by checking for "--target=", which is
496     /// never set for host crates. This matches what rustc bootstrap does,
497     /// which hopefully makes it "reliable enough". This relies on us always
498     /// invoking cargo itself with `--target`, which `in_cargo_miri` ensures.
499     fn is_target_crate() -> bool {
500         get_arg_flag_value("--target").is_some()
501     }
502
503     /// Returns whether or not Cargo invoked the wrapper (this binary) to compile
504     /// the final, binary crate (either a test for 'cargo test', or a binary for 'cargo run')
505     /// Cargo does not give us this information directly, so we need to check
506     /// various command-line flags.
507     fn is_runnable_crate() -> bool {
508         let is_bin = get_arg_flag_value("--crate-type").as_deref().unwrap_or("bin") == "bin";
509         let is_test = has_arg_flag("--test");
510         let print = get_arg_flag_value("--print").is_some();
511         (is_bin || is_test) && !print
512     }
513
514     fn out_filename(prefix: &str, suffix: &str) -> PathBuf {
515         let mut path = PathBuf::from(get_arg_flag_value("--out-dir").unwrap());
516         path.push(format!(
517             "{}{}{}{}",
518             prefix,
519             get_arg_flag_value("--crate-name").unwrap(),
520             // This is technically a `-C` flag but the prefix seems unique enough...
521             // (and cargo passes this before the filename so it should be unique)
522             get_arg_flag_value("extra-filename").unwrap_or(String::new()),
523             suffix,
524         ));
525         path
526     }
527
528     let verbose = std::env::var_os("MIRI_VERBOSE").is_some();
529     let target_crate = is_target_crate();
530
531     if target_crate && is_runnable_crate() {
532         // This is the binary or test crate that we want to interpret under Miri.
533         // But we cannot run it here, as cargo invoked us as a compiler -- our stdin and stdout are not
534         // like we want them.
535         // Instead of compiling, we write JSON into the output file with all the relevant command-line flags
536         // and environment variables; this is used when cargo calls us again in the CARGO_TARGET_RUNNER phase.
537         let info = CrateRunInfo::collect(args);
538         let filename = out_filename("", "");
539         if verbose {
540             eprintln!("[cargo-miri rustc] writing run info to `{}`", filename.display());
541         }
542
543         info.store(&filename);
544         // For Windows, do the same thing again with `.exe` appended to the filename.
545         // (Need to do this here as cargo moves that "binary" to a different place before running it.)
546         info.store(&out_filename("", ".exe"));
547
548         return;
549     }
550
551     let mut cmd = miri();
552     let mut emit_link_hack = false;
553     // Arguments are treated very differently depending on whether this crate is
554     // for interpretation by Miri, or for use by a build script / proc macro.
555     if target_crate {
556         // Forward arguments, but remove "link" from "--emit" to make this a check-only build.
557         let emit_flag = "--emit";
558         for arg in args {
559             if arg.starts_with(emit_flag) {
560                 // Patch this argument. First, extract its value.
561                 let val = &arg[emit_flag.len()..];
562                 assert!(val.starts_with("="), "`cargo` should pass `--emit=X` as one argument");
563                 let val = &val[1..];
564                 let mut val: Vec<_> = val.split(',').collect();
565                 // Now make sure "link" is not in there, but "metadata" is.
566                 if let Some(i) = val.iter().position(|&s| s == "link") {
567                     emit_link_hack = true;
568                     val.remove(i);
569                     if !val.iter().any(|&s| s == "metadata") {
570                         val.push("metadata");
571                     }
572                 }
573                 cmd.arg(format!("{}={}", emit_flag, val.join(",")));
574             } else {
575                 cmd.arg(arg);
576             }
577         }
578
579         // Use our custom sysroot.
580         let sysroot =
581             env::var_os("MIRI_SYSROOT").expect("The wrapper should have set MIRI_SYSROOT");
582         cmd.arg("--sysroot");
583         cmd.arg(sysroot);
584     } else {
585         // For host crates, just forward everything.
586         cmd.args(args);
587     }
588
589     // We want to compile, not interpret. We still use Miri to make sure the compiler version etc
590     // are the exact same as what is used for interpretation.
591     cmd.env("MIRI_BE_RUSTC", "1");
592
593     // Run it.
594     if verbose {
595         eprintln!("[cargo-miri rustc] {:?}", cmd);
596     }
597     exec(cmd);
598
599     // Create a stub .rlib file if "link" was requested by cargo.
600     if emit_link_hack {
601         // Some platforms prepend "lib", some do not... let's just create both files.
602         let filename = out_filename("lib", ".rlib");
603         File::create(filename).expect("Failed to create rlib file");
604         let filename = out_filename("", ".rlib");
605         File::create(filename).expect("Failed to create rlib file");
606     }
607 }
608
609 fn phase_cargo_runner(binary: &Path, binary_args: env::Args) {
610     let verbose = std::env::var_os("MIRI_VERBOSE").is_some();
611
612     let file = File::open(&binary)
613         .unwrap_or_else(|_| show_error(format!("File {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`", binary)));
614     let file = BufReader::new(file);
615     let info: CrateRunInfo = serde_json::from_reader(file)
616         .unwrap_or_else(|_| show_error(format!("File {:?} does not contain valid JSON", binary)));
617
618     // Set missing env vars. Looks like `build.rs` vars are still set at run-time, but
619     // `CARGO_BIN_EXE_*` are not. This means we can give the run-time environment precedence,
620     // to rather do too little than too much.
621     for (name, val) in info.env {
622         if env::var_os(&name).is_none() {
623             env::set_var(name, val);
624         }
625     }
626
627     let mut cmd = miri();
628     // Forward rustc arguments.
629     // We need to patch "--extern" filenames because we forced a check-only
630     // build without cargo knowing about that: replace `.rlib` suffix by
631     // `.rmeta`.
632     // We also need to remove `--error-format` as cargo specifies that to be JSON,
633     // but when we run here, cargo does not interpret the JSON any more. `--json`
634     // then also nees to be dropped.
635     let mut args = info.args.into_iter();
636     let extern_flag = "--extern";
637     let error_format_flag = "--error-format";
638     let json_flag = "--json";
639     while let Some(arg) = args.next() {
640         if arg == extern_flag {
641             // `--extern` is always passed as a separate argument by cargo.
642             let next_arg = args.next().expect("`--extern` should be followed by a filename");
643             let next_arg = next_arg.strip_suffix(".rlib").expect("all extern filenames should end in `.rlib`");
644             cmd.arg(extern_flag);
645             cmd.arg(format!("{}.rmeta", next_arg));
646         } else if arg.starts_with(error_format_flag) {
647             let suffix = &arg[error_format_flag.len()..];
648             assert!(suffix.starts_with('='));
649             // Drop this argument.
650         } else if arg.starts_with(json_flag) {
651             let suffix = &arg[json_flag.len()..];
652             assert!(suffix.starts_with('='));
653             // Drop this argument.
654         } else {
655             cmd.arg(arg);
656         }
657     }
658     // Set sysroot.
659     let sysroot =
660         env::var_os("MIRI_SYSROOT").expect("The wrapper should have set MIRI_SYSROOT");
661     cmd.arg("--sysroot");
662     cmd.arg(sysroot);
663     // Respect `MIRIFLAGS`.
664     if let Ok(a) = env::var("MIRIFLAGS") {
665         // This code is taken from `RUSTFLAGS` handling in cargo.
666         let args = a
667             .split(' ')
668             .map(str::trim)
669             .filter(|s| !s.is_empty())
670             .map(str::to_string);
671         cmd.args(args);
672     }
673
674     // Then pass binary arguments.
675     cmd.arg("--");
676     cmd.args(binary_args);
677
678     // Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
679     // But then we need to switch to the run-time one, which we instruct Miri do do by setting `MIRI_CWD`.
680     cmd.current_dir(info.current_dir);
681     cmd.env("MIRI_CWD", env::current_dir().unwrap());
682
683     // Run it.
684     if verbose {
685         eprintln!("[cargo-miri runner] {:?}", cmd);
686     }
687     exec(cmd)
688 }
689
690 fn main() {
691     // Rustc does not support non-UTF-8 arguments so we make no attempt either.
692     // (We do support non-UTF-8 environment variables though.)
693     let mut args = std::env::args();
694     // Skip binary name.
695     args.next().unwrap();
696
697     // Dispatch to `cargo-miri` phase. There are three phases:
698     // - When we are called via `cargo miri`, we run as the frontend and invoke the underlying
699     //   cargo. We set RUSTC_WRAPPER and CARGO_TARGET_RUNNER to ourselves.
700     // - When we are executed due to RUSTC_WRAPPER, we build crates or store the flags of
701     //   binary crates for later interpretation.
702     // - When we are executed due to CARGO_TARGET_RUNNER, we start interpretation based on the
703     //   flags that were stored earlier.
704     // On top of that, we are also called as RUSTDOC, but that is just a stub currently.
705     match args.next().as_deref() {
706         Some("miri") => phase_cargo_miri(args),
707         Some("rustc") => phase_cargo_rustc(args),
708         Some(arg) => {
709             // We have to distinguish the "runner" and "rustfmt" cases.
710             // As runner, the first argument is the binary (a file that should exist, with an absolute path);
711             // as rustfmt, the first argument is a flag (`--something`).
712             let binary = Path::new(arg);
713             if binary.exists() {
714                 assert!(!arg.starts_with("--")); // not a flag
715                 phase_cargo_runner(binary, args);
716             } else if arg.starts_with("--") {
717                 // We are rustdoc.
718                 eprintln!("Running doctests is not currently supported by Miri.")
719             } else {
720                 show_error(format!("`cargo-miri` called with unexpected first argument `{}`; please only invoke this binary through `cargo miri`", arg));
721             }
722         }
723         _ => show_error(format!("`cargo-miri` called without first argument; please only invoke this binary through `cargo miri`")),
724     }
725 }