@@ -4,12 +4,14 @@ use lexopt::Arg;
4
4
use std:: env;
5
5
use std:: ffi:: OsString ;
6
6
use std:: path:: { Path , PathBuf } ;
7
- use std:: process:: Command ;
7
+ use std:: process:: { Command , ExitStatus } ;
8
8
use std:: str:: FromStr ;
9
9
use wasmparser:: Payload ;
10
10
use wit_component:: StringEncoding ;
11
11
use wit_parser:: { Resolve , WorldId } ;
12
12
13
+ mod argfile;
14
+
13
15
/// Representation of a flag passed to `wasm-ld`
14
16
///
15
17
/// Note that the parsing of flags in `wasm-ld` is not as uniform as parsing
@@ -391,7 +393,7 @@ impl App {
391
393
/// in fact `lexopt` is used to filter out `wasm-ld` arguments and `clap`
392
394
/// only parses arguments specific to `wasm-component-ld`.
393
395
fn parse ( ) -> Result < App > {
394
- let mut args = env :: args_os ( ) . collect :: < Vec < _ > > ( ) ;
396
+ let mut args = argfile :: expand ( ) . context ( "failed to expand @-response files" ) ? ;
395
397
396
398
// First remove `-flavor wasm` in case this is invoked as a generic LLD
397
399
// driver. We can safely ignore that going forward.
@@ -526,8 +528,7 @@ impl App {
526
528
}
527
529
528
530
fn run ( & mut self ) -> Result < ( ) > {
529
- let mut cmd = self . lld ( ) ;
530
- let linker = cmd. get_program ( ) . to_owned ( ) ;
531
+ let mut lld = self . lld ( ) ;
531
532
532
533
// If a temporary output is needed make sure it has the same file name
533
534
// as the output of our command itself since LLD will embed this file
@@ -549,16 +550,14 @@ impl App {
549
550
// temporary location for wit-component to read and then the real output
550
551
// is created after wit-component runs.
551
552
if self . skip_wit_component ( ) {
552
- cmd . arg ( "-o" ) . arg ( & self . component . output ) ;
553
+ lld . output ( & self . component . output ) ;
553
554
} else {
554
- cmd . arg ( "-o" ) . arg ( & temp_output) ;
555
+ lld . output ( & temp_output) ;
555
556
}
556
557
557
- if self . component . verbose {
558
- eprintln ! ( "running LLD: {cmd:?}" ) ;
559
- }
560
- let status = cmd
561
- . status ( )
558
+ let linker = & lld. exe ;
559
+ let status = lld
560
+ . status ( & temp_dir, & self . lld_args )
562
561
. with_context ( || format ! ( "failed to spawn {linker:?}" ) ) ?;
563
562
if !status. success ( ) {
564
563
bail ! ( "failed to invoke LLD: {status}" ) ;
@@ -676,38 +675,147 @@ impl App {
676
675
|| self . shared
677
676
}
678
677
679
- fn lld ( & self ) -> Command {
678
+ fn lld ( & self ) -> Lld {
680
679
let mut lld = self . find_lld ( ) ;
681
- lld. args ( & self . lld_args ) ;
682
680
if self . component . verbose {
683
- lld. arg ( "-- verbose" ) ;
681
+ lld. verbose = true
684
682
}
685
683
lld
686
684
}
687
685
688
- fn find_lld ( & self ) -> Command {
686
+ fn find_lld ( & self ) -> Lld {
689
687
if let Some ( path) = & self . component . wasm_ld_path {
690
- return Command :: new ( path) ;
688
+ return Lld :: new ( path) ;
691
689
}
692
690
693
691
// Search for the first of `wasm-ld` or `rust-lld` in `$PATH`
694
692
let wasm_ld = format ! ( "wasm-ld{}" , env:: consts:: EXE_SUFFIX ) ;
695
693
let rust_lld = format ! ( "rust-lld{}" , env:: consts:: EXE_SUFFIX ) ;
696
694
for entry in env:: split_paths ( & env:: var_os ( "PATH" ) . unwrap_or_default ( ) ) {
697
695
if entry. join ( & wasm_ld) . is_file ( ) {
698
- return Command :: new ( wasm_ld) ;
696
+ return Lld :: new ( wasm_ld) ;
699
697
}
700
698
if entry. join ( & rust_lld) . is_file ( ) {
701
- let mut ret = Command :: new ( rust_lld) ;
702
- ret . arg ( "-flavor" ) . arg ( "wasm" ) ;
703
- return ret ;
699
+ let mut lld = Lld :: new ( rust_lld) ;
700
+ lld . needs_flavor = true ;
701
+ return lld ;
704
702
}
705
703
}
706
704
707
705
// Fall back to `wasm-ld` if the search failed to get an error message
708
706
// that indicates that `wasm-ld` was attempted to be found but couldn't
709
707
// be found.
710
- Command :: new ( "wasm-ld" )
708
+ Lld :: new ( "wasm-ld" )
709
+ }
710
+ }
711
+
712
+ /// Helper structure representing an `lld` invocation.
713
+ struct Lld {
714
+ exe : PathBuf ,
715
+ needs_flavor : bool ,
716
+ verbose : bool ,
717
+ output : Option < PathBuf > ,
718
+ }
719
+
720
+ impl Lld {
721
+ fn new ( exe : impl Into < PathBuf > ) -> Lld {
722
+ Lld {
723
+ exe : exe. into ( ) ,
724
+ needs_flavor : false ,
725
+ verbose : false ,
726
+ output : None ,
727
+ }
728
+ }
729
+
730
+ fn output ( & mut self , dst : impl Into < PathBuf > ) {
731
+ self . output = Some ( dst. into ( ) ) ;
732
+ }
733
+
734
+ fn status ( & self , tmpdir : & tempfile:: TempDir , args : & [ OsString ] ) -> Result < ExitStatus > {
735
+ // If we can probably pass `args` natively, try to do so. In some cases
736
+ // though just skip this entirely and go straight to below.
737
+ if !self . probably_too_big ( args) {
738
+ match self . run ( args) {
739
+ // If this subprocess failed to spawn because the arguments
740
+ // were too large, fall through to below.
741
+ Err ( ref e) if self . command_line_too_big ( e) => {
742
+ if self . verbose {
743
+ eprintln ! ( "command line was too large, trying again..." ) ;
744
+ }
745
+ }
746
+ other => return Ok ( other?) ,
747
+ }
748
+ } else if self . verbose {
749
+ eprintln ! ( "arguments probably too large {args:?}" ) ;
750
+ }
751
+
752
+ // The `args` are too big to be passed via the command line itself so
753
+ // encode the mall using "posix quoting" into an "argfile". This gets
754
+ // passed as `@foo` to lld and we also pass `--rsp-quoting=posix` to
755
+ // ensure that LLD always uses posix quoting. That means that we don't
756
+ // have to implement the dual nature of both posix and windows encoding
757
+ // here.
758
+ let mut argfile = Vec :: new ( ) ;
759
+ for arg in args {
760
+ for byte in arg. as_encoded_bytes ( ) {
761
+ if * byte == b'\\' || * byte == b' ' {
762
+ argfile. push ( b'\\' ) ;
763
+ }
764
+ argfile. push ( * byte) ;
765
+ }
766
+ argfile. push ( b'\n' ) ;
767
+ }
768
+ let path = tmpdir. path ( ) . join ( "argfile_tmp" ) ;
769
+ std:: fs:: write ( & path, & argfile) . with_context ( || format ! ( "failed to write {path:?}" ) ) ?;
770
+ let mut argfile_arg = OsString :: from ( "@" ) ;
771
+ argfile_arg. push ( & path) ;
772
+ let status = self . run ( & [ "--rsp-quoting=posix" . into ( ) , argfile_arg. into ( ) ] ) ?;
773
+ Ok ( status)
774
+ }
775
+
776
+ /// Tests whether the `args` array is too large to execute natively.
777
+ ///
778
+ /// Windows `cmd.exe` has a very small limit of around 8k so perform a
779
+ /// guess up to 6k. This isn't 100% accurate.
780
+ fn probably_too_big ( & self , args : & [ OsString ] ) -> bool {
781
+ let args_size = args
782
+ . iter ( )
783
+ . map ( |s| s. as_encoded_bytes ( ) . len ( ) )
784
+ . sum :: < usize > ( ) ;
785
+ cfg ! ( windows) && args_size > 6 * 1024
786
+ }
787
+
788
+ /// Test if the OS failed to spawn a process because the arguments were too
789
+ /// long.
790
+ fn command_line_too_big ( & self , err : & std:: io:: Error ) -> bool {
791
+ #[ cfg( unix) ]
792
+ return err. raw_os_error ( ) == Some ( libc:: E2BIG ) ;
793
+ #[ cfg( windows) ]
794
+ return err. raw_os_error ( )
795
+ == Some ( windows_sys:: Win32 :: Foundation :: ERROR_FILENAME_EXCED_RANGE as i32 ) ;
796
+ #[ cfg( not( any( unix, windows) ) ) ]
797
+ {
798
+ let _ = err;
799
+ return false ;
800
+ }
801
+ }
802
+
803
+ fn run ( & self , args : & [ OsString ] ) -> std:: io:: Result < ExitStatus > {
804
+ let mut cmd = Command :: new ( & self . exe ) ;
805
+ if self . needs_flavor {
806
+ cmd. arg ( "-flavor" ) . arg ( "wasm" ) ;
807
+ }
808
+ cmd. args ( args) ;
809
+ if self . verbose {
810
+ cmd. arg ( "--verbose" ) ;
811
+ }
812
+ if let Some ( output) = & self . output {
813
+ cmd. arg ( "-o" ) . arg ( output) ;
814
+ }
815
+ if self . verbose {
816
+ eprintln ! ( "running {cmd:?}" ) ;
817
+ }
818
+ cmd. status ( )
711
819
}
712
820
}
713
821
0 commit comments