Skip to content

Commit f7bfbe7

Browse files
committed
Prototype: make cargo-miri run doc-tests
1 parent e3ca994 commit f7bfbe7

File tree

1 file changed

+91
-17
lines changed

1 file changed

+91
-17
lines changed

cargo-miri/bin.rs

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

@@ -750,6 +801,30 @@ fn main() {
750801
return;
751802
}
752803

804+
// The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc
805+
// by the arguments alone, and we can't take from the args iterator in this case.
806+
// phase_cargo_rustdoc sets this environment variable to let us disambiguate here
807+
let invoked_as_rustc_from_rustdoc = env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some();
808+
if invoked_as_rustc_from_rustdoc {
809+
// ...however, we then also see this variable when rustdoc invokes us as the testrunner!
810+
// The runner is invoked as `$runtool ($runtool-arg)* output_file;
811+
// since we don't specify any runtool-args, and rustdoc supplies multiple arguments to
812+
// the test-builder unconditionally, we can just check the number of remaining arguments:
813+
if args.len() == 1 {
814+
let arg = args.next().unwrap();
815+
let binary = Path::new(&arg);
816+
if binary.exists() {
817+
phase_cargo_runner(binary, args);
818+
} else {
819+
show_error(format!("`cargo-miri` called with non-existing path argument `{}`; please invoke this binary through `cargo miri`", arg));
820+
}
821+
} else {
822+
phase_cargo_rustc(args);
823+
}
824+
825+
return;
826+
}
827+
753828
// Dispatch to `cargo-miri` phase. There are three phases:
754829
// - When we are called via `cargo miri`, we run as the frontend and invoke the underlying
755830
// cargo. We set RUSTC_WRAPPER and CARGO_TARGET_RUNNER to ourselves.
@@ -762,16 +837,15 @@ fn main() {
762837
Some("miri") => phase_cargo_miri(args),
763838
Some("rustc") => phase_cargo_rustc(args),
764839
Some(arg) => {
765-
// We have to distinguish the "runner" and "rustfmt" cases.
840+
// We have to distinguish the "runner" and "rustdoc" cases.
766841
// As runner, the first argument is the binary (a file that should exist, with an absolute path);
767-
// as rustfmt, the first argument is a flag (`--something`).
842+
// as rustdoc, the first argument is a flag (`--something`).
768843
let binary = Path::new(arg);
769844
if binary.exists() {
770845
assert!(!arg.starts_with("--")); // not a flag
771846
phase_cargo_runner(binary, args);
772847
} else if arg.starts_with("--") {
773-
// We are rustdoc.
774-
eprintln!("Running doctests is not currently supported by Miri.")
848+
phase_cargo_rustdoc(arg, args);
775849
} else {
776850
show_error(format!("`cargo-miri` called with unexpected first argument `{}`; please only invoke this binary through `cargo miri`", arg));
777851
}

0 commit comments

Comments
 (0)