@@ -6,7 +6,7 @@ use std::io::{self, BufRead, BufReader, BufWriter, Read, Write};
6
6
use std:: iter:: TakeWhile ;
7
7
use std:: ops:: Not ;
8
8
use std:: path:: { Path , PathBuf } ;
9
- use std:: process:: Command ;
9
+ use std:: process:: { self , Command } ;
10
10
11
11
use serde:: { Deserialize , Serialize } ;
12
12
@@ -112,40 +112,60 @@ fn has_arg_flag(name: &str) -> bool {
112
112
args. any ( |val| val == name)
113
113
}
114
114
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)`. (The flag `name` itself is not yielded at all, only its values are.)
117
+ struct ArgSplitFlagValue < ' a , I > {
118
+ args : TakeWhile < I , fn ( & String ) -> bool > ,
118
119
name : & ' a str ,
119
120
}
120
121
121
- impl < ' a > ArgFlagValueIter < ' a > {
122
- fn new ( name : & ' a str ) -> Self {
122
+ impl < ' a , I : Iterator < Item = String > > ArgSplitFlagValue < ' a , I > {
123
+ fn new ( args : I , name : & ' a str ) -> Self {
123
124
Self {
124
125
// Stop searching at `--`.
125
- args : env :: args ( ) . take_while ( |val| val != "--" ) ,
126
+ args : args. take_while ( |val| val != "--" ) ,
126
127
name,
127
128
}
128
129
}
129
130
}
130
131
131
- impl Iterator for ArgFlagValueIter < ' _ > {
132
- type Item = String ;
132
+ impl < I : Iterator < Item = String > > Iterator for ArgSplitFlagValue < ' _ , I > {
133
+ type Item = Result < String , String > ;
133
134
134
135
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 ) {
140
138
// Strip leading `name`.
141
139
let suffix = & arg[ self . name . len ( ) ..] ;
142
140
if suffix. is_empty ( ) {
143
141
// This argument is exactly `name`; the next one is the value.
144
- return self . args . next ( ) ;
142
+ return self . args . next ( ) . map ( Ok ) ;
145
143
} else if suffix. starts_with ( '=' ) {
146
144
// This argument is `name=value`; get the value.
147
145
// 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 > ( ArgSplitFlagValue < ' a , env:: Args > ) ;
155
+
156
+ impl < ' a > ArgFlagValueIter < ' a > {
157
+ fn new ( name : & ' a str ) -> Self {
158
+ Self ( ArgSplitFlagValue :: 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) ;
149
169
}
150
170
}
151
171
}
@@ -213,7 +233,7 @@ fn exec(mut cmd: Command) {
213
233
/// If it fails, fail this process with the same exit code.
214
234
/// Otherwise, continue.
215
235
fn exec_with_pipe ( mut cmd : Command , input : & [ u8 ] ) {
216
- cmd. stdin ( std :: process:: Stdio :: piped ( ) ) ;
236
+ cmd. stdin ( process:: Stdio :: piped ( ) ) ;
217
237
let mut child = cmd. spawn ( ) . expect ( "failed to spawn process" ) ;
218
238
{
219
239
let stdin = child. stdin . as_mut ( ) . expect ( "failed to open stdin" ) ;
@@ -452,6 +472,43 @@ path = "lib.rs"
452
472
}
453
473
}
454
474
475
+ /// Detect the target directory by calling `cargo metadata`.
476
+ fn detect_target_dir ( ) -> PathBuf {
477
+ #[ derive( Deserialize ) ]
478
+ struct Metadata {
479
+ target_directory : PathBuf ,
480
+ }
481
+ let mut cmd = cargo ( ) ;
482
+ // `-Zunstable-options` is required by `--config`.
483
+ cmd. args ( [ "metadata" , "--no-deps" , "--format-version=1" , "-Zunstable-options" ] ) ;
484
+ // The `build.target-dir` config can be passed by `--config` flags, so forward them to
485
+ // `cargo metadata`.
486
+ let config_flag = "--config" ;
487
+ for arg in ArgSplitFlagValue :: new (
488
+ env:: args ( ) . skip ( 3 ) , // skip the program name, "miri" and "run" / "test"
489
+ config_flag,
490
+ ) {
491
+ if let Ok ( config) = arg {
492
+ cmd. arg ( config_flag) . arg ( config) ;
493
+ }
494
+ }
495
+ let mut child = cmd
496
+ . stdin ( process:: Stdio :: null ( ) )
497
+ . stdout ( process:: Stdio :: piped ( ) )
498
+ . spawn ( )
499
+ . expect ( "failed ro run `cargo metadata`" ) ;
500
+ // Check this `Result` after `status.success()` is checked, so we don't print the error
501
+ // to stderr if `cargo metadata` is also printing to stderr.
502
+ let metadata: Result < Metadata , _ > = serde_json:: from_reader ( child. stdout . take ( ) . unwrap ( ) ) ;
503
+ let status = child. wait ( ) . expect ( "failed to wait for `cargo metadata` to exit" ) ;
504
+ if !status. success ( ) {
505
+ std:: process:: exit ( status. code ( ) . unwrap_or ( -1 ) ) ;
506
+ }
507
+ metadata
508
+ . unwrap_or_else ( |e| show_error ( format ! ( "invalid `cargo metadata` output: {}" , e) ) )
509
+ . target_directory
510
+ }
511
+
455
512
fn phase_cargo_miri ( mut args : env:: Args ) {
456
513
// Check for version and help flags even when invoked as `cargo-miri`.
457
514
if has_arg_flag ( "--help" ) || has_arg_flag ( "-h" ) {
@@ -510,8 +567,32 @@ fn phase_cargo_miri(mut args: env::Args) {
510
567
& host
511
568
} ;
512
569
513
- // Forward all further arguments to cargo.
514
- cmd. args ( args) ;
570
+ let mut target_dir = None ;
571
+
572
+ // Forward all arguments before `--` other than `--target-dir` and its value to Cargo.
573
+ for arg in ArgSplitFlagValue :: new ( & mut args, "--target-dir" ) {
574
+ match arg {
575
+ Ok ( value) => {
576
+ if target_dir. is_some ( ) {
577
+ show_error ( format ! ( "`--target-dir` is provided more than once" ) ) ;
578
+ }
579
+ target_dir = Some ( value. into ( ) ) ;
580
+ }
581
+ Err ( arg) => {
582
+ cmd. arg ( arg) ;
583
+ }
584
+ }
585
+ }
586
+
587
+ // Detect the target directory if it's not specified via `--target-dir`.
588
+ let target_dir = target_dir. get_or_insert_with ( detect_target_dir) ;
589
+
590
+ // Set `--target-dir` to `miri` inside the original target directory.
591
+ target_dir. push ( "miri" ) ;
592
+ cmd. arg ( "--target-dir" ) . arg ( target_dir) ;
593
+
594
+ // Forward all further arguments after `--` to cargo.
595
+ cmd. arg ( "--" ) . args ( args) ;
515
596
516
597
// Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation,
517
598
// i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish
0 commit comments