@@ -6,7 +6,7 @@ use std::io::{self, BufRead, BufReader, BufWriter, Read, Write};
66use std:: iter:: TakeWhile ;
77use std:: ops:: Not ;
88use std:: path:: { Path , PathBuf } ;
9- use std:: process:: Command ;
9+ use std:: process:: { self , Command } ;
1010
1111use 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)`. (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 > ,
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 > > ArgSplitFlagValue < ' 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 ArgSplitFlagValue < ' _ , 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 > ( 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) ;
149169 }
150170 }
151171 }
@@ -213,7 +233,7 @@ fn exec(mut cmd: Command) {
213233/// If it fails, fail this process with the same exit code.
214234/// Otherwise, continue.
215235fn exec_with_pipe ( mut cmd : Command , input : & [ u8 ] ) {
216- cmd. stdin ( std :: process:: Stdio :: piped ( ) ) ;
236+ cmd. stdin ( process:: Stdio :: piped ( ) ) ;
217237 let mut child = cmd. spawn ( ) . expect ( "failed to spawn process" ) ;
218238 {
219239 let stdin = child. stdin . as_mut ( ) . expect ( "failed to open stdin" ) ;
@@ -452,6 +472,43 @@ path = "lib.rs"
452472 }
453473}
454474
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+
455512fn phase_cargo_miri ( mut args : env:: Args ) {
456513 // Check for version and help flags even when invoked as `cargo-miri`.
457514 if has_arg_flag ( "--help" ) || has_arg_flag ( "-h" ) {
@@ -510,8 +567,32 @@ fn phase_cargo_miri(mut args: env::Args) {
510567 & host
511568 } ;
512569
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) ;
515596
516597 // Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation,
517598 // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish
0 commit comments