Skip to content

Commit 2d17b5a

Browse files
author
hyd-dev
committed
Use miri inside the target directory used by rustc as Miri's target directory
1 parent 5b7f1f9 commit 2d17b5a

File tree

5 files changed

+119
-18
lines changed

5 files changed

+119
-18
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ jobs:
8383
--host ${{ matrix.host_target }}
8484
rustup default master
8585
86+
# We need a nightly Cargo to run tests that depend on unstable Cargo features.
87+
- name: Install latest nightly
88+
uses: actions-rs/toolchain@v1
89+
with:
90+
toolchain: nightly
91+
8692
- name: Show Rust version
8793
run: |
8894
rustup show

cargo-miri/bin.rs

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::io::{self, BufRead, BufReader, BufWriter, Read, Write};
66
use std::iter::TakeWhile;
77
use std::ops::Not;
88
use std::path::{Path, PathBuf};
9-
use std::process::Command;
9+
use std::process::{Command, Stdio};
1010

1111
use serde::{Deserialize, Serialize};
1212

@@ -112,40 +112,60 @@ fn has_arg_flag(name: &str) -> bool {
112112
args.any(|val| val == name)
113113
}
114114

115-
/// Yields all values of command line flag `name`.
116-
struct ArgFlagValueIter<'a> {
117-
args: TakeWhile<env::Args, fn(&String) -> bool>,
115+
/// Yields all values of command line flag `name` as `Ok(arg)`, and all other arguments except
116+
/// the flag as `Err(arg)`.
117+
struct ArgFlagValueWithOtherArgsIter<'a, I> {
118+
args: TakeWhile<I, fn(&String) -> bool>,
118119
name: &'a str,
119120
}
120121

121-
impl<'a> ArgFlagValueIter<'a> {
122-
fn new(name: &'a str) -> Self {
122+
impl<'a, I: Iterator<Item = String>> ArgFlagValueWithOtherArgsIter<'a, I> {
123+
fn new(args: I, name: &'a str) -> Self {
123124
Self {
124125
// Stop searching at `--`.
125-
args: env::args().take_while(|val| val != "--"),
126+
args: args.take_while(|val| val != "--"),
126127
name,
127128
}
128129
}
129130
}
130131

131-
impl Iterator for ArgFlagValueIter<'_> {
132-
type Item = String;
132+
impl<I: Iterator<Item = String>> Iterator for ArgFlagValueWithOtherArgsIter<'_, I> {
133+
type Item = Result<String, String>;
133134

134135
fn next(&mut self) -> Option<Self::Item> {
135-
loop {
136-
let arg = self.args.next()?;
137-
if !arg.starts_with(self.name) {
138-
continue;
139-
}
136+
let arg = self.args.next()?;
137+
if arg.starts_with(self.name) {
140138
// Strip leading `name`.
141139
let suffix = &arg[self.name.len()..];
142140
if suffix.is_empty() {
143141
// This argument is exactly `name`; the next one is the value.
144-
return self.args.next();
142+
return self.args.next().map(Ok);
145143
} else if suffix.starts_with('=') {
146144
// This argument is `name=value`; get the value.
147145
// Strip leading `=`.
148-
return Some(suffix[1..].to_owned());
146+
return Some(Ok(suffix[1..].to_owned()));
147+
}
148+
}
149+
Some(Err(arg))
150+
}
151+
}
152+
153+
/// Yields all values of command line flag `name`.
154+
struct ArgFlagValueIter<'a>(ArgFlagValueWithOtherArgsIter<'a, env::Args>);
155+
156+
impl<'a> ArgFlagValueIter<'a> {
157+
fn new(name: &'a str) -> Self {
158+
Self(ArgFlagValueWithOtherArgsIter::new(env::args(), name))
159+
}
160+
}
161+
162+
impl Iterator for ArgFlagValueIter<'_> {
163+
type Item = String;
164+
165+
fn next(&mut self) -> Option<Self::Item> {
166+
loop {
167+
if let Ok(value) = self.0.next()? {
168+
return Some(value);
149169
}
150170
}
151171
}
@@ -510,8 +530,59 @@ fn phase_cargo_miri(mut args: env::Args) {
510530
&host
511531
};
512532

513-
// Forward all further arguments to cargo.
514-
cmd.args(args);
533+
let mut target_dir = None;
534+
535+
// Forward all arguments before `--` other than `--target-dir` and its value to Cargo.
536+
for arg in ArgFlagValueWithOtherArgsIter::new(&mut args, "--target-dir") {
537+
match arg {
538+
Ok(value) => target_dir = Some(value.into()),
539+
Err(arg) => drop(cmd.arg(arg)),
540+
}
541+
}
542+
543+
// Detect the target directory if it's not specified via `--target-dir`.
544+
let target_dir = target_dir.get_or_insert_with(|| {
545+
#[derive(Deserialize)]
546+
struct Metadata {
547+
target_directory: PathBuf,
548+
}
549+
let mut cmd = cargo();
550+
// `-Zunstable-options` is required by `--config`.
551+
cmd.args(["metadata", "--no-deps", "--format-version=1", "-Zunstable-options"]);
552+
// The `build.target-dir` config can by passed by `--config` flags, so forward them to
553+
// `cargo metadata`.
554+
let config_flag = "--config";
555+
for arg in ArgFlagValueWithOtherArgsIter::new(
556+
env::args().skip(3), // skip the program name, "miri" and "run" / "test"
557+
config_flag,
558+
) {
559+
if let Ok(config) = arg {
560+
cmd.arg(config_flag).arg(config);
561+
}
562+
}
563+
let mut child = cmd
564+
.stdin(Stdio::null())
565+
.stdout(Stdio::piped())
566+
.spawn()
567+
.expect("failed ro run `cargo metadata`");
568+
// Check this `Result` after `status.success()` is checked, so we don't print the error
569+
// to stderr if `cargo metadata` is also printing to stderr.
570+
let metadata: Result<Metadata, _> = serde_json::from_reader(child.stdout.take().unwrap());
571+
let status = child.wait().expect("failed to wait `cargo metadata` to exit");
572+
if !status.success() {
573+
std::process::exit(status.code().unwrap_or(-1));
574+
}
575+
metadata
576+
.unwrap_or_else(|e| show_error(format!("invalid `cargo metadata` output: {}", e)))
577+
.target_directory
578+
});
579+
580+
// Set `--target-dir` to `miri` inside the original target directory.
581+
target_dir.push("miri");
582+
cmd.arg("--target-dir").arg(target_dir);
583+
584+
// Forward all further arguments after `--` to cargo.
585+
cmd.arg("--").args(args);
515586

516587
// Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation,
517588
// i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish

test-cargo-miri/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
*.real
2+
custom-run
3+
custom-test
4+
config-cli

test-cargo-miri/run-test.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ def test_cargo_miri_run():
101101
"run.subcrate.stdout.ref", "run.subcrate.stderr.ref",
102102
env={'MIRIFLAGS': "-Zmiri-disable-isolation"},
103103
)
104+
test("`cargo miri run` (custom target dir)",
105+
# Attempt to confuse the argument parser.
106+
cargo_miri("run") + ["--target-dir=custom-run", "--", "--target-dir=target/custom-run"],
107+
"run.args.stdout.ref", "run.custom-target-dir.stderr.ref",
108+
)
104109

105110
def test_cargo_miri_test():
106111
# rustdoc is not run on foreign targets
@@ -144,8 +149,18 @@ def test_cargo_miri_test():
144149
cargo_miri("test") + ["-p", "subcrate", "--doc"],
145150
"test.stdout-empty.ref", "test.stderr-proc-macro-doctest.ref",
146151
)
152+
test("`cargo miri test` (custom target dir)",
153+
cargo_miri("test") + ["--target-dir=custom-test"],
154+
default_ref, "test.stderr-empty.ref",
155+
)
156+
del os.environ["CARGO_TARGET_DIR"] # this overrides `build.target-dir` passed by `--config`, so unset it
157+
test("`cargo miri test` (config-cli)",
158+
cargo_miri("test") + ["--config=build.target-dir=\"config-cli\"", "-Zunstable-options"],
159+
default_ref, "test.stderr-empty.ref",
160+
)
147161

148162
os.chdir(os.path.dirname(os.path.realpath(__file__)))
163+
os.environ["CARGO_TARGET_DIR"] = "target" # this affects the location of the target directory that we need to check
149164
os.environ["RUST_TEST_NOCAPTURE"] = "0" # this affects test output, so make sure it is not set
150165
os.environ["RUST_TEST_THREADS"] = "1" # avoid non-deterministic output due to concurrent test runs
151166

@@ -158,6 +173,10 @@ def test_cargo_miri_test():
158173
subprocess.run(cargo_miri("setup"), check=True)
159174
test_cargo_miri_run()
160175
test_cargo_miri_test()
176+
for target_dir in ["target", "custom-run", "custom-test", "config-cli"]:
177+
if os.listdir(target_dir) != ["miri"]:
178+
fail(f"`{target_dir}` contains unexpected files")
179+
os.access(os.path.join(target_dir, "miri", "debug", "deps"), os.F_OK)
161180

162181
print("\nTEST SUCCESSFUL!")
163182
sys.exit(0)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
main
2+
--target-dir=target/custom-run

0 commit comments

Comments
 (0)