@@ -5,39 +5,113 @@ use std::io;
55
66use rustc_session:: EarlyDiagCtxt ;
77
8- fn arg_expand ( arg : String ) -> Result < Vec < String > , Error > {
9- if let Some ( path) = arg. strip_prefix ( '@' ) {
10- let file = match fs:: read_to_string ( path) {
11- Ok ( file) => file,
12- Err ( ref err) if err. kind ( ) == io:: ErrorKind :: InvalidData => {
13- return Err ( Error :: Utf8Error ( Some ( path. to_string ( ) ) ) ) ;
8+ /// Expands argfiles in command line arguments.
9+ #[ derive( Default ) ]
10+ struct Expander {
11+ shell_argfiles : bool ,
12+ next_is_unstable_option : bool ,
13+ expanded : Vec < String > ,
14+ }
15+
16+ impl Expander {
17+ /// Handles the next argument. If the argument is an argfile, it is expanded
18+ /// inline.
19+ fn arg ( & mut self , arg : & str ) -> Result < ( ) , Error > {
20+ if let Some ( argfile) = arg. strip_prefix ( '@' ) {
21+ match argfile. split_once ( ':' ) {
22+ Some ( ( "shell" , path) ) if self . shell_argfiles => {
23+ shlex:: split ( & Self :: read_file ( path) ?)
24+ . ok_or_else ( || Error :: ShellParseError ( path. to_string ( ) ) ) ?
25+ . into_iter ( )
26+ . for_each ( |arg| self . push ( arg) ) ;
27+ }
28+ _ => {
29+ let contents = Self :: read_file ( argfile) ?;
30+ contents. lines ( ) . for_each ( |arg| self . push ( arg. to_string ( ) ) ) ;
31+ }
32+ }
33+ } else {
34+ self . push ( arg. to_string ( ) ) ;
35+ }
36+
37+ Ok ( ( ) )
38+ }
39+
40+ /// Adds a command line argument verbatim with no argfile expansion.
41+ fn push ( & mut self , arg : String ) {
42+ // Unfortunately, we have to do some eager argparsing to handle unstable
43+ // options which change the behavior of argfile arguments.
44+ //
45+ // Normally, all of the argfile arguments (e.g. `@args.txt`) are
46+ // expanded into our arguments list *and then* the whole list of
47+ // arguments are passed on to be parsed. However, argfile parsing
48+ // options like `-Zshell_argfiles` need to change the behavior of that
49+ // argument expansion. So we have to do a little parsing on our own here
50+ // instead of leaning on the existing logic.
51+ //
52+ // All we care about are unstable options, so we parse those out and
53+ // look for any that affect how we expand argfiles. This argument
54+ // inspection is very conservative; we only change behavior when we see
55+ // exactly the options we're looking for and everything gets passed
56+ // through.
57+
58+ if self . next_is_unstable_option {
59+ self . inspect_unstable_option ( & arg) ;
60+ self . next_is_unstable_option = false ;
61+ } else if let Some ( unstable_option) = arg. strip_prefix ( "-Z" ) {
62+ if unstable_option. is_empty ( ) {
63+ self . next_is_unstable_option = true ;
64+ } else {
65+ self . inspect_unstable_option ( unstable_option) ;
66+ }
67+ }
68+
69+ self . expanded . push ( arg) ;
70+ }
71+
72+ /// Consumes the `Expander`, returning the expanded arguments.
73+ fn finish ( self ) -> Vec < String > {
74+ self . expanded
75+ }
76+
77+ /// Parses any relevant unstable flags specified on the command line.
78+ fn inspect_unstable_option ( & mut self , option : & str ) {
79+ match option {
80+ "shell-argfiles" => self . shell_argfiles = true ,
81+ _ => ( ) ,
82+ }
83+ }
84+
85+ /// Reads the contents of a file as UTF-8.
86+ fn read_file ( path : & str ) -> Result < String , Error > {
87+ fs:: read_to_string ( path) . map_err ( |e| {
88+ if e. kind ( ) == io:: ErrorKind :: InvalidData {
89+ Error :: Utf8Error ( Some ( path. to_string ( ) ) )
90+ } else {
91+ Error :: IOError ( path. to_string ( ) , e)
1492 }
15- Err ( err) => return Err ( Error :: IOError ( path. to_string ( ) , err) ) ,
16- } ;
17- Ok ( file. lines ( ) . map ( ToString :: to_string) . collect ( ) )
18- } else {
19- Ok ( vec ! [ arg] )
93+ } )
2094 }
2195}
2296
2397/// **Note:** This function doesn't interpret argument 0 in any special way.
2498/// If this function is intended to be used with command line arguments,
2599/// `argv[0]` must be removed prior to calling it manually.
26100pub fn arg_expand_all ( early_dcx : & EarlyDiagCtxt , at_args : & [ String ] ) -> Vec < String > {
27- let mut args = Vec :: new ( ) ;
101+ let mut expander = Expander :: default ( ) ;
28102 for arg in at_args {
29- match arg_expand ( arg. clone ( ) ) {
30- Ok ( arg) => args. extend ( arg) ,
31- Err ( err) => early_dcx. early_fatal ( format ! ( "Failed to load argument file: {err}" ) ) ,
103+ if let Err ( err) = expander. arg ( arg) {
104+ early_dcx. early_fatal ( format ! ( "Failed to load argument file: {err}" ) ) ;
32105 }
33106 }
34- args
107+ expander . finish ( )
35108}
36109
37110#[ derive( Debug ) ]
38111pub enum Error {
39112 Utf8Error ( Option < String > ) ,
40113 IOError ( String , io:: Error ) ,
114+ ShellParseError ( String ) ,
41115}
42116
43117impl fmt:: Display for Error {
@@ -46,6 +120,7 @@ impl fmt::Display for Error {
46120 Error :: Utf8Error ( None ) => write ! ( fmt, "Utf8 error" ) ,
47121 Error :: Utf8Error ( Some ( path) ) => write ! ( fmt, "Utf8 error in {path}" ) ,
48122 Error :: IOError ( path, err) => write ! ( fmt, "IO Error: {path}: {err}" ) ,
123+ Error :: ShellParseError ( path) => write ! ( fmt, "Invalid shell-style arguments in {path}" ) ,
49124 }
50125 }
51126}
0 commit comments