1use anyhow::{bail, Context, Result};
2use clap::{ArgAction, CommandFactory, FromArgMatches};
3use lexopt::Arg;
4use std::env;
5use std::ffi::OsString;
6use std::path::{Path, PathBuf};
7use std::process::{Command, ExitStatus};
8use std::str::FromStr;
9use wasmparser::Payload;
10use wit_component::StringEncoding;
11use wit_parser::{Resolve, WorldId};
12
13mod argfile;
14
15/// Representation of a flag passed to `wasm-ld`
16///
17/// Note that the parsing of flags in `wasm-ld` is not as uniform as parsing
18/// arguments via `clap`. For example if `--foo bar` is supported that doesn't
19/// mean that `--foo=bar` is supported. Similarly some options such as `--foo`
20/// support optional values as `--foo=bar` but can't be specified as
21/// `--foo bar`.
22///
23/// Finally there's currently only one "weird" flag which is `-shared` which has
24/// a single dash but a long name. That's specially handled elsewhere.
25///
26/// The general goal here is that we want to inherit `wasm-ld`'s CLI but also
27/// want to be able to reserve CLI flags for this linker itself, so `wasm-ld`'s
28/// arguments are parsed where our own are intermixed.
29struct LldFlag {
30 clap_name: &'static str,
31 long: Option<&'static str>,
32 short: Option<char>,
33 value: FlagValue,
34}
35
36enum FlagValue {
37 /// This option has no value, e.g. `-f` or `--foo`
38 None,
39
40 /// This option's value must be specified with `=`, for example `--foo=bar`
41 RequiredEqual(&'static str),
42
43 /// This option's value must be specified with ` `, for example `--foo bar`.
44 ///
45 /// I think that `wasm-ld` supports both `-f foo` and `-ffoo` for
46 /// single-character flags, but I haven't tested as putting a space seems to
47 /// work.
48 RequiredSpace(&'static str),
49
50 /// This option's value is optional but if specified it must use an `=` for
51 /// example `--foo=bar` or `--foo`.
52 Optional(&'static str),
53}
54
55/// This is a large macro which is intended to take CLI-looking syntax and turn
56/// each individual flag into a `LldFlag` specified above.
57macro_rules! flag {
58 // Long options specified as:
59 //
60 // -f / --foo
61 //
62 // or just
63 //
64 // --foo
65 //
66 // Options can look like `--foo`, `--foo=bar`, `--foo[=bar]`, or
67 // `--foo bar` to match the kinds of flags that LLD supports.
68 ($(-$short:ident /)? --$($flag:tt)*) => {
69 LldFlag {
70 clap_name: concat!("long_", $(stringify!($flag),)*),
71 long: Some(flag!(@name [] $($flag)*)),
72 short: flag!(@short $($short)?),
73 value: flag!(@value $($flag)*),
74 }
75 };
76
77 // Short options specified as `-f` or `-f foo`.
78 (-$flag:tt $($val:tt)*) => {
79 LldFlag {
80 clap_name: concat!("short_", stringify!($flag)),
81 long: None,
82 short: Some(flag!(@char $flag)),
83 value: flag!(@value $flag $($val)*),
84 }
85 };
86
87 // Generates the long name of a flag, collected within the `[]` argument to
88 // this macro. This will iterate over the flag given as the rest of the
89 // macro arguments and collect values into `[...]` and recurse.
90 //
91 // The first recursion case handles `foo-bar-baz=..` where Rust tokenizes
92 // this as `foo` then `-` then `bar` then ... If this is found then `foo-`
93 // is added to the name and then the macro recurses.
94 (@name [$($name:tt)*] $n:ident-$($rest:tt)*) => (flag!(@name [$($name)* $n-] $($rest)*));
95 // These are the ways options are represented, either `--foo bar`,
96 // `--foo=bar`, `--foo=bar`, or `--foo`. In all these cases discard the
97 // value itself and then recurse.
98 (@name [$($name:tt)*] $n:ident $_value:ident) => (flag!(@name [$($name)* $n]));
99 (@name [$($name:tt)*] $n:ident=$_value:ident) => (flag!(@name [$($name)* $n]));
100 (@name [$($name:tt)*] $n:ident[=$_value:ident]) => (flag!(@name [$($name)* $n]));
101 (@name [$($name:tt)*] $n:ident) => (flag!(@name [$($name)* $n]));
102 // If there's nothing left then the `$name` has collected everything so
103 // it's stringifyied and caoncatenated.
104 (@name [$($name:tt)*]) => (concat!($(stringify!($name),)*));
105
106 // This parses the value-style of the flag given. The recursion here looks
107 // similar to `@name` above. except that the four terminal cases all
108 // correspond to different variants of `FlagValue`.
109 (@value $n:ident - $($rest:tt)*) => (flag!(@value $($rest)*));
110 (@value $_flag:ident = $name:ident) => (FlagValue::RequiredEqual(stringify!($name)));
111 (@value $_flag:ident $name:ident) => (FlagValue::RequiredSpace(stringify!($name)));
112 (@value $_flag:ident [= $name:ident]) => (FlagValue::Optional(stringify!($name)));
113 (@value $_flag:ident) => (FlagValue::None);
114
115 // Helper for flags that have both a long and a short form to parse whether
116 // a short form was provided.
117 (@short) => (None);
118 (@short $name:ident) => (Some(flag!(@char $name)));
119
120 // Helper for getting the `char` of a short flag.
121 (@char $name:ident) => ({
122 let name = stringify!($name);
123 assert!(name.len() == 1);
124 name.as_bytes()[0] as char
125 });
126}
127
128const LLD_FLAGS: &[LldFlag] = &[
129 flag! { --allow-undefined-file=PATH },
130 flag! { --allow-undefined },
131 flag! { --Bdynamic },
132 flag! { --Bstatic },
133 flag! { --Bsymbolic },
134 flag! { --build-id[=VAL] },
135 flag! { --call_shared },
136 flag! { --check-features },
137 flag! { --color-diagnostics[=VALUE] },
138 flag! { --compress-relocations },
139 flag! { --demangle },
140 flag! { --dn },
141 flag! { --dy },
142 flag! { --emit-relocs },
143 flag! { --end-lib },
144 flag! { --entry SYM },
145 flag! { --error-limit=N },
146 flag! { --error-unresolved-symbols },
147 flag! { --experimental-pic },
148 flag! { --export-all },
149 flag! { -E / --export-dynamic },
150 flag! { --export-if-defined=SYM },
151 flag! { --export-memory[=NAME] },
152 flag! { --export-table },
153 flag! { --export=SYM },
154 flag! { --extra-features=LIST },
155 flag! { --fatal-warnings },
156 flag! { --features=LIST },
157 flag! { --gc-sections },
158 flag! { --global-base=VALUE },
159 flag! { --growable-table },
160 flag! { --import-memory[=NAME] },
161 flag! { --import-table },
162 flag! { --import-undefined },
163 flag! { --initial-heap=SIZE },
164 flag! { --initial-memory=SIZE },
165 flag! { --keep-section=NAME },
166 flag! { --lto-CGO=LEVEL },
167 flag! { --lto-debug-pass-manager },
168 flag! { --lto-O=LEVEL },
169 flag! { --lto-partitions=NUM },
170 flag! { -L PATH },
171 flag! { -l LIB },
172 flag! { --Map=FILE },
173 flag! { --max-memory=SIZE },
174 flag! { --merge-data-segments },
175 flag! { --mllvm=FLAG },
176 flag! { -m ARCH },
177 flag! { --no-check-features },
178 flag! { --no-color-diagnostics },
179 flag! { --no-demangle },
180 flag! { --no-entry },
181 flag! { --no-export-dynamic },
182 flag! { --no-gc-sections },
183 flag! { --no-merge-data-segments },
184 flag! { --no-pie },
185 flag! { --no-print-gc-sections },
186 flag! { --no-whole-archive },
187 flag! { --non_shared },
188 flag! { -O LEVEL },
189 flag! { --pie },
190 flag! { --print-gc-sections },
191 flag! { -M / --print-map },
192 flag! { --relocatable },
193 flag! { --save-temps },
194 flag! { --shared-memory },
195 flag! { --shared },
196 flag! { --soname=VALUE },
197 flag! { --stack-first },
198 flag! { --start-lib },
199 flag! { --static },
200 flag! { -s / --strip-all },
201 flag! { -S / --strip-debug },
202 flag! { --table-base=VALUE },
203 flag! { --thinlto-cache-dir=PATH },
204 flag! { --thinlto-cache-policy=VALUE },
205 flag! { --thinlto-jobs=N },
206 flag! { --threads=N },
207 flag! { -y / --trace-symbol=SYM },
208 flag! { -t / --trace },
209 flag! { --undefined=SYM },
210 flag! { --unresolved-symbols=VALUE },
211 flag! { --warn-unresolved-symbols },
212 flag! { --whole-archive },
213 flag! { --why-extract=MEMBER },
214 flag! { --wrap=VALUE },
215 flag! { -z OPT },
216];
217
218const LLD_LONG_FLAGS_NONSTANDARD: &[&str] = &["-shared"];
219
220#[derive(Default)]
221struct App {
222 component: ComponentLdArgs,
223 lld_args: Vec<OsString>,
224 shared: bool,
225}
226
227/// A linker to create a Component from input object files and libraries.
228///
229/// This application is an equivalent of `wasm-ld` except that it produces a
230/// component instead of a core wasm module. This application behaves very
231/// similarly to `wasm-ld` in that it takes the same inputs and flags, and it
232/// will internally invoke `wasm-ld`. After `wasm-ld` has been invoked the core
233/// wasm module will be turned into a component using component tooling and
234/// embedded information in the core wasm module.
235#[derive(clap::Parser, Default)]
236#[command(version)]
237struct ComponentLdArgs {
238 /// Which default WASI adapter, if any, to use when creating the output
239 /// component.
240 #[clap(long, name = "command|reactor|proxy|none")]
241 wasi_adapter: Option<WasiAdapter>,
242
243 /// Location of where to find `wasm-ld`.
244 ///
245 /// If not specified this is automatically detected.
246 #[clap(long, name = "PATH")]
247 wasm_ld_path: Option<PathBuf>,
248
249 /// Quoting syntax for response files.
250 #[clap(long, name = "STYLE")]
251 rsp_quoting: Option<String>,
252
253 /// Where to place the component output.
254 #[clap(short, long)]
255 output: PathBuf,
256
257 /// Print verbose output.
258 #[clap(long)]
259 verbose: bool,
260
261 /// Whether or not the output component is validated.
262 ///
263 /// This defaults to `true`.
264 #[clap(long)]
265 validate_component: Option<bool>,
266
267 /// Whether or not imports are deduplicated based on semver in the final
268 /// component.
269 ///
270 /// This defaults to `true`.
271 #[clap(long)]
272 merge_imports_based_on_semver: Option<bool>,
273
274 /// Adapters to use when creating the final component.
275 #[clap(long = "adapt", value_name = "[NAME=]MODULE", value_parser = parse_adapter)]
276 adapters: Vec<(String, Vec<u8>)>,
277
278 /// WIT file representing additional component type information to use.
279 ///
280 /// May be specified more than once.
281 ///
282 /// See also the `--string-encoding` option.
283 #[clap(long = "component-type", value_name = "WIT_FILE")]
284 component_types: Vec<PathBuf>,
285
286 /// String encoding to use when creating the final component.
287 ///
288 /// This may be either "utf8", "utf16", or "compact-utf16". This value is
289 /// only used when one or more `--component-type` options are specified.
290 #[clap(long, value_parser = parse_encoding, default_value = "utf8")]
291 string_encoding: StringEncoding,
292
293 /// Skip the `wit-component`-based process to generate a component.
294 #[clap(long)]
295 skip_wit_component: bool,
296}
297
298fn parse_adapter(s: &str) -> Result<(String, Vec<u8>)> {
299 let (name: &str, path: &str) = parse_optionally_name_file(s);
300 let wasm: Vec = wat::parse_file(path)?;
301 Ok((name.to_string(), wasm))
302}
303
304fn parse_encoding(s: &str) -> Result<StringEncoding> {
305 Ok(match s {
306 "utf8" => StringEncoding::UTF8,
307 "utf16" => StringEncoding::UTF16,
308 "compact-utf16" => StringEncoding::CompactUTF16,
309 _ => bail!("unknown string encoding: {s:?}"),
310 })
311}
312
313fn parse_optionally_name_file(s: &str) -> (&str, &str) {
314 let mut parts: SplitN<'_, char> = s.splitn(n:2, pat:'=');
315 let name_or_path: &str = parts.next().unwrap();
316 match parts.next() {
317 Some(path: &str) => (name_or_path, path),
318 None => {
319 let name: &str = PathOption<&str>::new(name_or_path)
320 .file_name()
321 .unwrap()
322 .to_str()
323 .unwrap();
324 let name: &str = match name.find('.') {
325 Some(i: usize) => &name[..i],
326 None => name,
327 };
328 (name, name_or_path)
329 }
330 }
331}
332
333#[derive(Debug, Copy, Clone)]
334enum WasiAdapter {
335 Command,
336 Reactor,
337 Proxy,
338 None,
339}
340
341impl FromStr for WasiAdapter {
342 type Err = anyhow::Error;
343
344 fn from_str(s: &str) -> Result<Self, Self::Err> {
345 match s {
346 "none" => Ok(WasiAdapter::None),
347 "command" => Ok(WasiAdapter::Command),
348 "reactor" => Ok(WasiAdapter::Reactor),
349 "proxy" => Ok(WasiAdapter::Proxy),
350 _ => bail!("unknown wasi adapter {s}, must be one of: none, command, reactor, proxy"),
351 }
352 }
353}
354
355pub fn main() {
356 let err: Error = match run() {
357 Ok(()) => return,
358 Err(e: Error) => e,
359 };
360 eprintln!("error: {err}");
361 if err.chain().len() > 1 {
362 eprintln!("\nCaused by:");
363 for (i: usize, err: &dyn Error) in err.chain().skip(1).enumerate() {
364 eprintln!("{i:>5}: {}", err.to_string().replace("\n", "\n "));
365 }
366 }
367
368 std::process::exit(code:1);
369}
370
371fn run() -> Result<()> {
372 App::parse()?.run()
373}
374
375impl App {
376 /// Parse the CLI arguments into an `App` to run the linker.
377 ///
378 /// This is unfortunately nontrivial because the way `wasm-ld` takes
379 /// arguments is not compatible with `clap`. Namely flags like
380 /// `--whole-archive` are positional are processed in a stateful manner.
381 /// This means that the relative ordering of flags to `wasm-ld` needs to be
382 /// preserved. Additionally there are flags like `-shared` which clap does
383 /// not support.
384 ///
385 /// To handle this the `lexopt` crate is used to perform low-level argument
386 /// parsing. That's then used to determine whether the argument is intended
387 /// for `wasm-component-ld` or `wasm-ld`, so arguments are filtered into two
388 /// lists. Using these lists the arguments to `wasm-component-ld` are then
389 /// parsed. On failure a help message is presented with all `wasm-ld`
390 /// arguments added as well.
391 ///
392 /// This means that functionally it looks like `clap` parses everything when
393 /// in fact `lexopt` is used to filter out `wasm-ld` arguments and `clap`
394 /// only parses arguments specific to `wasm-component-ld`.
395 fn parse() -> Result<App> {
396 let mut args = argfile::expand().context("failed to expand @-response files")?;
397
398 // First remove `-flavor wasm` in case this is invoked as a generic LLD
399 // driver. We can safely ignore that going forward.
400 if let Some([flavor, wasm]) = args.get(1..3) {
401 if flavor == "-flavor" && wasm == "wasm" {
402 args.remove(1);
403 args.remove(1);
404 }
405 }
406
407 let mut command = ComponentLdArgs::command();
408 let mut lld_args = Vec::new();
409 let mut component_ld_args = vec![std::env::args_os().nth(0).unwrap()];
410 let mut shared = false;
411 let mut parser = lexopt::Parser::from_iter(args);
412
413 fn handle_lld_arg(
414 lld: &LldFlag,
415 parser: &mut lexopt::Parser,
416 lld_args: &mut Vec<OsString>,
417 ) -> Result<()> {
418 let mut arg = OsString::new();
419 match (lld.short, lld.long) {
420 (_, Some(long)) => {
421 arg.push("--");
422 arg.push(long);
423 }
424 (Some(short), _) => {
425 arg.push("-");
426 arg.push(short.encode_utf8(&mut [0; 5]));
427 }
428 (None, None) => unreachable!(),
429 }
430 match lld.value {
431 FlagValue::None => {
432 lld_args.push(arg);
433 }
434
435 FlagValue::RequiredSpace(_) => {
436 lld_args.push(arg);
437 lld_args.push(parser.value()?);
438 }
439
440 FlagValue::RequiredEqual(_) => {
441 arg.push("=");
442 arg.push(&parser.value()?);
443 lld_args.push(arg);
444 }
445
446 // If the value is optional then the argument must have an `=`
447 // in the argument itself.
448 FlagValue::Optional(_) => {
449 match parser.optional_value() {
450 Some(val) => {
451 arg.push("=");
452 arg.push(&val);
453 }
454 None => {}
455 }
456 lld_args.push(arg);
457 }
458 }
459 Ok(())
460 }
461
462 loop {
463 if let Some(mut args) = parser.try_raw_args() {
464 if let Some(arg) = args.peek() {
465 let for_lld = LLD_LONG_FLAGS_NONSTANDARD.iter().any(|s| arg == *s);
466 if for_lld {
467 lld_args.push(arg.to_owned());
468 if arg == "-shared" {
469 shared = true;
470 }
471 args.next();
472 continue;
473 }
474 }
475 }
476
477 match parser.next()? {
478 Some(Arg::Value(obj)) => {
479 lld_args.push(obj);
480 }
481 Some(Arg::Short(c)) => match LLD_FLAGS.iter().find(|f| f.short == Some(c)) {
482 Some(lld) => {
483 handle_lld_arg(lld, &mut parser, &mut lld_args)?;
484 }
485 None => {
486 component_ld_args.push(format!("-{c}").into());
487 if let Some(arg) =
488 command.get_arguments().find(|a| a.get_short() == Some(c))
489 {
490 if let ArgAction::Set = arg.get_action() {
491 component_ld_args.push(parser.value()?);
492 }
493 }
494 }
495 },
496 Some(Arg::Long(c)) => match LLD_FLAGS.iter().find(|f| f.long == Some(c)) {
497 Some(lld) => {
498 handle_lld_arg(lld, &mut parser, &mut lld_args)?;
499 }
500 None => {
501 component_ld_args.push(format!("--{c}").into());
502 if let Some(arg) = command.get_arguments().find(|a| a.get_long() == Some(c))
503 {
504 match arg.get_action() {
505 ArgAction::Set | ArgAction::Append => {
506 component_ld_args.push(parser.value()?)
507 }
508 _ => (),
509 }
510 }
511 }
512 },
513 None => break,
514 }
515 }
516
517 match command.try_get_matches_from_mut(component_ld_args.clone()) {
518 Ok(matches) => Ok(App {
519 component: ComponentLdArgs::from_arg_matches(&matches)?,
520 lld_args,
521 shared,
522 }),
523 Err(_) => {
524 add_wasm_ld_options(ComponentLdArgs::command()).get_matches_from(component_ld_args);
525 unreachable!();
526 }
527 }
528 }
529
530 fn run(&mut self) -> Result<()> {
531 let mut lld = self.lld();
532
533 // If a temporary output is needed make sure it has the same file name
534 // as the output of our command itself since LLD will embed this file
535 // name in the name section of the output.
536 let temp_dir = match self.component.output.parent() {
537 Some(parent) => tempfile::TempDir::new_in(parent)?,
538 None => tempfile::TempDir::new()?,
539 };
540 let temp_output = match self.component.output.file_name() {
541 Some(name) => temp_dir.path().join(name),
542 None => bail!(
543 "output of {:?} does not have a file name",
544 self.component.output
545 ),
546 };
547
548 // Shared libraries don't get wit-component run below so place the
549 // output directly at the desired output location. Otherwise output to a
550 // temporary location for wit-component to read and then the real output
551 // is created after wit-component runs.
552 if self.skip_wit_component() {
553 lld.output(&self.component.output);
554 } else {
555 lld.output(&temp_output);
556 }
557
558 let linker = &lld.exe;
559 let status = lld
560 .status(&temp_dir, &self.lld_args)
561 .with_context(|| format!("failed to spawn {linker:?}"))?;
562 if !status.success() {
563 bail!("failed to invoke LLD: {status}");
564 }
565
566 if self.skip_wit_component() {
567 return Ok(());
568 }
569
570 let reactor_adapter =
571 wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER;
572 let command_adapter =
573 wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_COMMAND_ADAPTER;
574 let proxy_adapter =
575 wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_PROXY_ADAPTER;
576 let mut core_module = std::fs::read(&temp_output)
577 .with_context(|| format!("failed to read {linker:?} output: {temp_output:?}"))?;
578
579 // Inspect the output module to see if it's a command or reactor.
580 let mut exports_start = false;
581 for payload in wasmparser::Parser::new(0).parse_all(&core_module) {
582 match payload {
583 Ok(Payload::ExportSection(e)) => {
584 for export in e {
585 if let Ok(e) = export {
586 if e.name == "_start" {
587 exports_start = true;
588 break;
589 }
590 }
591 }
592 }
593 _ => {}
594 }
595 }
596
597 if !self.component.component_types.is_empty() {
598 let mut merged = None::<(Resolve, WorldId)>;
599 for wit_file in &self.component.component_types {
600 let mut resolve = Resolve::default();
601 let (package, _) = resolve
602 .push_path(wit_file)
603 .with_context(|| format!("unable to add component type {wit_file:?}"))?;
604
605 let world = resolve.select_world(package, None)?;
606
607 if let Some((merged_resolve, merged_world)) = &mut merged {
608 let world = merged_resolve.merge(resolve)?.map_world(world, None)?;
609 merged_resolve.merge_worlds(world, *merged_world)?;
610 } else {
611 merged = Some((resolve, world));
612 }
613 }
614
615 let Some((resolve, world)) = merged else {
616 unreachable!()
617 };
618
619 wit_component::embed_component_metadata(
620 &mut core_module,
621 &resolve,
622 world,
623 self.component.string_encoding,
624 )?;
625 }
626
627 let mut encoder = wit_component::ComponentEncoder::default();
628 if let Some(validate) = self.component.validate_component {
629 encoder = encoder.validate(validate);
630 }
631 if let Some(merge) = self.component.merge_imports_based_on_semver {
632 encoder = encoder.merge_imports_based_on_semver(merge);
633 }
634 encoder = encoder
635 .module(&core_module)
636 .context("failed to parse core wasm for componentization")?;
637 let adapter = self.component.wasi_adapter.unwrap_or(if exports_start {
638 WasiAdapter::Command
639 } else {
640 WasiAdapter::Reactor
641 });
642 let adapter = match adapter {
643 WasiAdapter::Command => Some(&command_adapter[..]),
644 WasiAdapter::Reactor => Some(&reactor_adapter[..]),
645 WasiAdapter::Proxy => Some(&proxy_adapter[..]),
646 WasiAdapter::None => None,
647 };
648
649 if let Some(adapter) = adapter {
650 encoder = encoder
651 .adapter("wasi_snapshot_preview1", adapter)
652 .context("failed to inject adapter")?;
653 }
654
655 for (name, adapter) in self.component.adapters.iter() {
656 encoder = encoder
657 .adapter(name, adapter)
658 .with_context(|| format!("failed to inject adapter {name:?}"))?;
659 }
660
661 let component = encoder.encode().context("failed to encode component")?;
662
663 std::fs::write(&self.component.output, &component).context(format!(
664 "failed to write output file: {:?}",
665 self.component.output
666 ))?;
667
668 Ok(())
669 }
670
671 fn skip_wit_component(&self) -> bool {
672 self.component.skip_wit_component
673 // Skip componentization with `--shared` since that's creating a
674 // shared library that's not a component yet.
675 || self.shared
676 }
677
678 fn lld(&self) -> Lld {
679 let mut lld = self.find_lld();
680 if self.component.verbose {
681 lld.verbose = true
682 }
683 lld
684 }
685
686 fn find_lld(&self) -> Lld {
687 if let Some(path) = &self.component.wasm_ld_path {
688 return Lld::new(path);
689 }
690
691 // Search for the first of `wasm-ld` or `rust-lld` in `$PATH`
692 let wasm_ld = format!("wasm-ld{}", env::consts::EXE_SUFFIX);
693 let rust_lld = format!("rust-lld{}", env::consts::EXE_SUFFIX);
694 for entry in env::split_paths(&env::var_os("PATH").unwrap_or_default()) {
695 if entry.join(&wasm_ld).is_file() {
696 return Lld::new(wasm_ld);
697 }
698 if entry.join(&rust_lld).is_file() {
699 let mut lld = Lld::new(rust_lld);
700 lld.needs_flavor = true;
701 return lld;
702 }
703 }
704
705 // Fall back to `wasm-ld` if the search failed to get an error message
706 // that indicates that `wasm-ld` was attempted to be found but couldn't
707 // be found.
708 Lld::new("wasm-ld")
709 }
710}
711
712/// Helper structure representing an `lld` invocation.
713struct Lld {
714 exe: PathBuf,
715 needs_flavor: bool,
716 verbose: bool,
717 output: Option<PathBuf>,
718}
719
720impl 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()
819 }
820}
821
822fn add_wasm_ld_options(mut command: clap::Command) -> clap::Command {
823 use clap::Arg;
824
825 command = command.arg(
826 Arg::new("objects")
827 .action(ArgAction::Append)
828 .help("objects to pass to `wasm-ld`"),
829 );
830
831 for flag in LLD_FLAGS {
832 let mut arg = Arg::new(flag.clap_name).help("forwarded to `wasm-ld`");
833 if let Some(short) = flag.short {
834 arg = arg.short(short);
835 }
836 if let Some(long) = flag.long {
837 arg = arg.long(long);
838 }
839 arg = match flag.value {
840 FlagValue::RequiredEqual(name) | FlagValue::RequiredSpace(name) => {
841 arg.action(ArgAction::Set).value_name(name)
842 }
843 FlagValue::Optional(name) => arg
844 .action(ArgAction::Set)
845 .value_name(name)
846 .num_args(0..=1)
847 .require_equals(true),
848 FlagValue::None => arg.action(ArgAction::SetTrue),
849 };
850 arg = arg.help_heading("Options forwarded to `wasm-ld`");
851 command = command.arg(arg);
852 }
853
854 command
855}
856
857#[test]
858fn verify_app() {
859 ComponentLdArgs::command().debug_assert();
860 add_wasm_ld_options(ComponentLdArgs::command()).debug_assert();
861}
862