Skip to content

Commit d1b7b0c

Browse files
committed
Prototype: make cargo-miri run doc-tests
1 parent a09f8b0 commit d1b7b0c

File tree

1 file changed

+91
-17
lines changed

1 file changed

+91
-17
lines changed

cargo-miri/bin.rs

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::env;
22
use std::ffi::OsString;
33
use std::fs::{self, File};
4-
use std::io::{self, BufRead, BufReader, BufWriter, Write};
4+
use std::io::{self, BufRead, BufReader, BufWriter, Read, Write};
55
use std::ops::Not;
66
use std::path::{Path, PathBuf};
77
use std::process::Command;
@@ -45,6 +45,8 @@ struct CrateRunInfo {
4545
env: Vec<(OsString, OsString)>,
4646
/// The current working directory.
4747
current_dir: OsString,
48+
/// The contents passed via standard input.
49+
stdin: Vec<u8>,
4850
}
4951

5052
impl CrateRunInfo {
@@ -53,7 +55,13 @@ impl CrateRunInfo {
5355
let args = args.collect();
5456
let env = env::vars_os().collect();
5557
let current_dir = env::current_dir().unwrap().into_os_string();
56-
CrateRunInfo { args, env, current_dir }
58+
59+
let mut stdin = Vec::new();
60+
if env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some() {
61+
std::io::stdin().lock().read_to_end(&mut stdin).expect("cannot read stdin");
62+
}
63+
64+
CrateRunInfo { args, env, current_dir, stdin }
5765
}
5866

5967
fn store(&self, filename: &Path) {
@@ -539,17 +547,22 @@ fn phase_cargo_rustc(args: env::Args) {
539547
}
540548

541549
fn out_filename(prefix: &str, suffix: &str) -> PathBuf {
542-
let mut path = PathBuf::from(get_arg_flag_value("--out-dir").unwrap());
543-
path.push(format!(
544-
"{}{}{}{}",
545-
prefix,
546-
get_arg_flag_value("--crate-name").unwrap(),
547-
// This is technically a `-C` flag but the prefix seems unique enough...
548-
// (and cargo passes this before the filename so it should be unique)
549-
get_arg_flag_value("extra-filename").unwrap_or(String::new()),
550-
suffix,
551-
));
552-
path
550+
if let Some(out_dir) = get_arg_flag_value("--out-dir") {
551+
let mut path = PathBuf::from(out_dir);
552+
path.push(format!(
553+
"{}{}{}{}",
554+
prefix,
555+
get_arg_flag_value("--crate-name").unwrap(),
556+
// This is technically a `-C` flag but the prefix seems unique enough...
557+
// (and cargo passes this before the filename so it should be unique)
558+
get_arg_flag_value("extra-filename").unwrap_or(String::new()),
559+
suffix,
560+
));
561+
path
562+
} else {
563+
let out_file = get_arg_flag_value("-o").unwrap();
564+
PathBuf::from(out_file)
565+
}
553566
}
554567

555568
let verbose = std::env::var_os("MIRI_VERBOSE").is_some();
@@ -729,6 +742,44 @@ fn phase_cargo_runner(binary: &Path, binary_args: env::Args) {
729742
if verbose {
730743
eprintln!("[cargo-miri runner] {:?}", cmd);
731744
}
745+
746+
cmd.stdin(std::process::Stdio::piped());
747+
let mut child = cmd.spawn().expect("failed to spawn miri process");
748+
{
749+
let stdin = child.stdin.as_mut().expect("failed to open stdin");
750+
stdin.write_all(&info.stdin).expect("failed to write out test source");
751+
}
752+
let exit_status = child.wait().expect("failed to run command");
753+
if exit_status.success().not() {
754+
std::process::exit(exit_status.code().unwrap_or(-1))
755+
}
756+
}
757+
758+
fn phase_cargo_rustdoc(fst_arg: &str, args: env::Args) {
759+
let verbose = std::env::var_os("MIRI_VERBOSE").is_some();
760+
761+
// phase_cargo_miri sets the RUSTDOC env var to ourselves, so we can't use that here;
762+
// just default to a straight-forward invocation for now:
763+
let mut cmd = Command::new(OsString::from("rustdoc"));
764+
765+
// just pass everything through until we find a reason not to do that:
766+
cmd.arg(fst_arg);
767+
cmd.args(args);
768+
769+
cmd.arg("-Z").arg("unstable-options");
770+
771+
let cargo_miri_path = std::env::current_exe().expect("current executable path invalid");
772+
cmd.arg("--test-builder").arg(&cargo_miri_path);
773+
cmd.arg("--runtool").arg(&cargo_miri_path);
774+
775+
// rustdoc passes generated code to rustc via stdin, rather than a temporary file,
776+
// so we need to let the coming invocations know to expect that
777+
cmd.env("MIRI_CALLED_FROM_RUSTDOC", "1");
778+
779+
if verbose {
780+
eprintln!("[cargo-miri rustdoc] {:?}", cmd);
781+
}
782+
732783
exec(cmd)
733784
}
734785

@@ -745,6 +796,30 @@ fn main() {
745796
return;
746797
}
747798

799+
// The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc
800+
// by the arguments alone, and we can't take from the args iterator in this case.
801+
// phase_cargo_rustdoc sets this environment variable to let us disambiguate here
802+
let invoked_as_rustc_from_rustdoc = env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some();
803+
if invoked_as_rustc_from_rustdoc {
804+
// ...however, we then also see this variable when rustdoc invokes us as the testrunner!
805+
// The runner is invoked as `$runtool ($runtool-arg)* output_file;
806+
// since we don't specify any runtool-args, and rustdoc supplies multiple arguments to
807+
// the test-builder unconditionally, we can just check the number of remaining arguments:
808+
if args.len() == 1 {
809+
let arg = args.next().unwrap();
810+
let binary = Path::new(&arg);
811+
if binary.exists() {
812+
phase_cargo_runner(binary, args);
813+
} else {
814+
show_error(format!("`cargo-miri` called with non-existing path argument `{}`; please invoke this binary through `cargo miri`", arg));
815+
}
816+
} else {
817+
phase_cargo_rustc(args);
818+
}
819+
820+
return;
821+
}
822+
748823
// Dispatch to `cargo-miri` phase. There are three phases:
749824
// - When we are called via `cargo miri`, we run as the frontend and invoke the underlying
750825
// cargo. We set RUSTC_WRAPPER and CARGO_TARGET_RUNNER to ourselves.
@@ -757,16 +832,15 @@ fn main() {
757832
Some("miri") => phase_cargo_miri(args),
758833
Some("rustc") => phase_cargo_rustc(args),
759834
Some(arg) => {
760-
// We have to distinguish the "runner" and "rustfmt" cases.
835+
// We have to distinguish the "runner" and "rustdoc" cases.
761836
// As runner, the first argument is the binary (a file that should exist, with an absolute path);
762-
// as rustfmt, the first argument is a flag (`--something`).
837+
// as rustdoc, the first argument is a flag (`--something`).
763838
let binary = Path::new(arg);
764839
if binary.exists() {
765840
assert!(!arg.starts_with("--")); // not a flag
766841
phase_cargo_runner(binary, args);
767842
} else if arg.starts_with("--") {
768-
// We are rustdoc.
769-
eprintln!("Running doctests is not currently supported by Miri.")
843+
phase_cargo_rustdoc(arg, args);
770844
} else {
771845
show_error(format!("`cargo-miri` called with unexpected first argument `{}`; please only invoke this binary through `cargo miri`", arg));
772846
}

0 commit comments

Comments
 (0)