@@ -37,6 +37,7 @@ pub struct ProcessBuilder {
37
37
/// `true` to include environment variable in display.
38
38
display_env_vars : bool ,
39
39
/// `true` to retry with an argfile if hitting "command line too big" error.
40
+ /// See [`ProcessBuilder::retry_with_argfile`] for more information.
40
41
retry_with_argfile : bool ,
41
42
}
42
43
@@ -185,6 +186,22 @@ impl ProcessBuilder {
185
186
}
186
187
187
188
/// Enables retrying with an argfile if hitting "command line too big" error
189
+ ///
190
+ /// This is primarily for the `@path` arg of rustc and rustdoc, which treat
191
+ /// each line as an command-line argument, so `LF` and `CRLF` bytes are not
192
+ /// valid as an argument for argfile at this moment.
193
+ /// For example, `RUSTDOCFLAGS="--crate-version foo\nbar" cargo doc` is
194
+ /// valid when invoking from command-line but not from argfile.
195
+ ///
196
+ /// To sum up, the limitations of the argfile are:
197
+ ///
198
+ /// - Must be valid UTF-8 encoded.
199
+ /// - Must not contain any newlines in each argument.
200
+ ///
201
+ /// Ref:
202
+ ///
203
+ /// - https://doc.rust-lang.org/rustdoc/command-line-arguments.html#path-load-command-line-flags-from-a-path
204
+ /// - https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path>
188
205
pub fn retry_with_argfile ( & mut self , enabled : bool ) -> & mut Self {
189
206
self . retry_with_argfile = enabled;
190
207
self
@@ -393,11 +410,6 @@ impl ProcessBuilder {
393
410
394
411
/// Builds the command with an `@<path>` argfile that contains all the
395
412
/// arguments. This is primarily served for rustc/rustdoc command family.
396
- ///
397
- /// Ref:
398
- ///
399
- /// - https://doc.rust-lang.org/rustdoc/command-line-arguments.html#path-load-command-line-flags-from-a-path
400
- /// - https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path>
401
413
fn build_command_with_argfile ( & self ) -> io:: Result < ( Command , NamedTempFile ) > {
402
414
use std:: io:: Write as _;
403
415
@@ -411,21 +423,26 @@ impl ProcessBuilder {
411
423
log:: debug!( "created argfile at {path} for `{self}`" ) ;
412
424
413
425
let cap = self . get_args ( ) . map ( |arg| arg. len ( ) + 1 ) . sum :: < usize > ( ) ;
414
- let mut buf = String :: with_capacity ( cap) ;
426
+ let mut buf = Vec :: with_capacity ( cap) ;
415
427
for arg in & self . args {
416
- let arg = arg
417
- . to_str ( )
418
- . ok_or_else ( || {
419
- io:: Error :: new (
420
- io:: ErrorKind :: Other ,
421
- "argument contains invalid UTF-8 characters" ,
422
- )
423
- } ) ?;
424
- // TODO: Shall we escape line feed?
425
- buf. push_str ( arg) ;
426
- buf. push ( '\n' ) ;
428
+ let arg = arg. to_str ( ) . ok_or_else ( || {
429
+ io:: Error :: new (
430
+ io:: ErrorKind :: Other ,
431
+ format ! (
432
+ "argument for argfile contains invalid UTF-8 characters: `{}`" ,
433
+ arg. to_string_lossy( )
434
+ ) ,
435
+ )
436
+ } ) ?;
437
+ if arg. contains ( '\n' ) {
438
+ return Err ( io:: Error :: new (
439
+ io:: ErrorKind :: Other ,
440
+ format ! ( "argument for argfile contains newlines: `{arg}`" ) ,
441
+ ) ) ;
442
+ }
443
+ writeln ! ( buf, "{arg}" ) ?;
427
444
}
428
- tmp. write_all ( buf. as_bytes ( ) ) ?;
445
+ tmp. write_all ( & mut buf) ?;
429
446
Ok ( ( cmd, tmp) )
430
447
}
431
448
@@ -552,10 +569,11 @@ mod imp {
552
569
553
570
#[ cfg( test) ]
554
571
mod tests {
555
- use super :: * ;
572
+ use super :: ProcessBuilder ;
556
573
use std:: fs;
574
+
557
575
#[ test]
558
- fn test_argfile ( ) {
576
+ fn argfile_build_succeeds ( ) {
559
577
let mut cmd = ProcessBuilder :: new ( "echo" ) ;
560
578
cmd. args ( [ "foo" , "bar" ] . as_slice ( ) ) ;
561
579
let ( cmd, argfile) = cmd. build_command_with_argfile ( ) . unwrap ( ) ;
@@ -569,4 +587,39 @@ mod tests {
569
587
let buf = fs:: read_to_string ( argfile. path ( ) ) . unwrap ( ) ;
570
588
assert_eq ! ( buf, "foo\n bar\n " ) ;
571
589
}
590
+
591
+ #[ test]
592
+ fn argfile_build_fails_if_arg_contains_newline ( ) {
593
+ let mut cmd = ProcessBuilder :: new ( "echo" ) ;
594
+ cmd. arg ( "foo\n " ) ;
595
+ let err = cmd. build_command_with_argfile ( ) . unwrap_err ( ) ;
596
+ assert_eq ! (
597
+ err. to_string( ) ,
598
+ "argument for argfile contains newlines: `foo\n `"
599
+ ) ;
600
+ }
601
+
602
+ #[ test]
603
+ fn argfile_build_fails_if_arg_contains_invalid_utf8 ( ) {
604
+ let mut cmd = ProcessBuilder :: new ( "echo" ) ;
605
+
606
+ #[ cfg( windows) ]
607
+ let invalid_arg = {
608
+ use std:: os:: windows:: prelude:: * ;
609
+ std:: ffi:: OsString :: from_wide ( & [ 0x0066 , 0x006f , 0xD800 , 0x006f ] )
610
+ } ;
611
+
612
+ #[ cfg( unix) ]
613
+ let invalid_arg = {
614
+ use std:: os:: unix:: ffi:: OsStrExt ;
615
+ std:: ffi:: OsStr :: from_bytes ( & [ 0x66 , 0x6f , 0x80 , 0x6f ] ) . to_os_string ( )
616
+ } ;
617
+
618
+ cmd. arg ( invalid_arg) ;
619
+ let err = cmd. build_command_with_argfile ( ) . unwrap_err ( ) ;
620
+ assert_eq ! (
621
+ err. to_string( ) ,
622
+ "argument for argfile contains invalid UTF-8 characters: `fo�o`"
623
+ ) ;
624
+ }
572
625
}
0 commit comments