@@ -5,39 +5,113 @@ use std::io;
5
5
6
6
use rustc_session:: EarlyDiagCtxt ;
7
7
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)
14
92
}
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
+ } )
20
94
}
21
95
}
22
96
23
97
/// **Note:** This function doesn't interpret argument 0 in any special way.
24
98
/// If this function is intended to be used with command line arguments,
25
99
/// `argv[0]` must be removed prior to calling it manually.
26
100
pub fn arg_expand_all ( early_dcx : & EarlyDiagCtxt , at_args : & [ String ] ) -> Vec < String > {
27
- let mut args = Vec :: new ( ) ;
101
+ let mut expander = Expander :: default ( ) ;
28
102
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}" ) ) ;
32
105
}
33
106
}
34
- args
107
+ expander . finish ( )
35
108
}
36
109
37
110
#[ derive( Debug ) ]
38
111
pub enum Error {
39
112
Utf8Error ( Option < String > ) ,
40
113
IOError ( String , io:: Error ) ,
114
+ ShellParseError ( String ) ,
41
115
}
42
116
43
117
impl fmt:: Display for Error {
@@ -46,6 +120,7 @@ impl fmt::Display for Error {
46
120
Error :: Utf8Error ( None ) => write ! ( fmt, "Utf8 error" ) ,
47
121
Error :: Utf8Error ( Some ( path) ) => write ! ( fmt, "Utf8 error in {path}" ) ,
48
122
Error :: IOError ( path, err) => write ! ( fmt, "IO Error: {path}: {err}" ) ,
123
+ Error :: ShellParseError ( path) => write ! ( fmt, "Invalid shell-style arguments in {path}" ) ,
49
124
}
50
125
}
51
126
}
0 commit comments