wasm_component_ld/
lib.rs

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-multiple-definition },
130    flag! { --allow-undefined-file=PATH },
131    flag! { --allow-undefined },
132    flag! { --Bdynamic },
133    flag! { --Bstatic },
134    flag! { --Bsymbolic },
135    flag! { --build-id[=VAL] },
136    flag! { --call_shared },
137    flag! { --check-features },
138    flag! { --color-diagnostics[=VALUE] },
139    flag! { --compress-relocations },
140    flag! { --demangle },
141    flag! { --dn },
142    flag! { --dy },
143    flag! { --emit-relocs },
144    flag! { --end-lib },
145    flag! { --entry SYM },
146    flag! { --error-limit=N },
147    flag! { --error-unresolved-symbols },
148    flag! { --experimental-pic },
149    flag! { --export-all },
150    flag! { -E / --export-dynamic },
151    flag! { --export-if-defined=SYM },
152    flag! { --export-memory[=NAME] },
153    flag! { --export-table },
154    flag! { --export=SYM },
155    flag! { --extra-features=LIST },
156    flag! { --fatal-warnings },
157    flag! { --features=LIST },
158    flag! { --gc-sections },
159    flag! { --global-base=VALUE },
160    flag! { --growable-table },
161    flag! { --import-memory[=NAME] },
162    flag! { --import-table },
163    flag! { --import-undefined },
164    flag! { --initial-heap=SIZE },
165    flag! { --initial-memory=SIZE },
166    flag! { --keep-section=NAME },
167    flag! { --lto-CGO=LEVEL },
168    flag! { --lto-debug-pass-manager },
169    flag! { --lto-O=LEVEL },
170    flag! { --lto-partitions=NUM },
171    flag! { -L PATH },
172    flag! { -l LIB },
173    flag! { --Map=FILE },
174    flag! { --max-memory=SIZE },
175    flag! { --merge-data-segments },
176    flag! { --mllvm=FLAG },
177    flag! { -m ARCH },
178    flag! { --no-allow-multiple-definition },
179    flag! { --no-check-features },
180    flag! { --no-color-diagnostics },
181    flag! { --no-demangle },
182    flag! { --no-entry },
183    flag! { --no-export-dynamic },
184    flag! { --no-fatal-warnings },
185    flag! { --no-gc-sections },
186    flag! { --no-growable-memory },
187    flag! { --no-merge-data-segments },
188    flag! { --no-pie },
189    flag! { --no-print-gc-sections },
190    flag! { --no-shlib-sigcheck },
191    flag! { --no-whole-archive },
192    flag! { --noinhibit-exec },
193    flag! { --non_shared },
194    flag! { -O LEVEL },
195    flag! { --page-size=VALUE },
196    flag! { --pie },
197    flag! { --print-gc-sections },
198    flag! { -M / --print-map },
199    flag! { --relocatable },
200    flag! { --reproduce=VALUE },
201    flag! { --rpath=VALUE },
202    flag! { --save-temps },
203    flag! { --shared-memory },
204    flag! { --shared },
205    flag! { --soname=VALUE },
206    flag! { --stack-first },
207    flag! { --start-lib },
208    flag! { --static },
209    flag! { -s / --strip-all },
210    flag! { -S / --strip-debug },
211    flag! { --table-base=VALUE },
212    flag! { --thinlto-cache-dir=PATH },
213    flag! { --thinlto-cache-policy=VALUE },
214    flag! { --thinlto-jobs=N },
215    flag! { --threads=N },
216    flag! { -y / --trace-symbol=SYM },
217    flag! { -t / --trace },
218    flag! { --undefined=SYM },
219    flag! { --unresolved-symbols=VALUE },
220    flag! { --warn-unresolved-symbols },
221    flag! { --whole-archive },
222    flag! { --why-extract=MEMBER },
223    flag! { --wrap=VALUE },
224    flag! { -z OPT },
225];
226
227const LLD_LONG_FLAGS_NONSTANDARD: &[&str] = &["-shared"];
228
229#[derive(Default)]
230struct App {
231    component: ComponentLdArgs,
232    lld_args: Vec<OsString>,
233    shared: bool,
234}
235
236/// A linker to create a Component from input object files and libraries.
237///
238/// This application is an equivalent of `wasm-ld` except that it produces a
239/// component instead of a core wasm module. This application behaves very
240/// similarly to `wasm-ld` in that it takes the same inputs and flags, and it
241/// will internally invoke `wasm-ld`. After `wasm-ld` has been invoked the core
242/// wasm module will be turned into a component using component tooling and
243/// embedded information in the core wasm module.
244#[derive(clap::Parser, Default)]
245#[command(version)]
246struct ComponentLdArgs {
247    /// Which default WASI adapter, if any, to use when creating the output
248    /// component.
249    #[clap(long, name = "command|reactor|proxy|none")]
250    wasi_adapter: Option<WasiAdapter>,
251
252    /// Location of where to find `wasm-ld`.
253    ///
254    /// If not specified this is automatically detected.
255    #[clap(long, name = "PATH")]
256    wasm_ld_path: Option<PathBuf>,
257
258    /// Quoting syntax for response files.
259    #[clap(long, name = "STYLE")]
260    rsp_quoting: Option<String>,
261
262    /// Where to place the component output.
263    #[clap(short, long)]
264    output: PathBuf,
265
266    /// Print verbose output.
267    #[clap(short, long)]
268    verbose: bool,
269
270    /// Whether or not the output component is validated.
271    ///
272    /// This defaults to `true`.
273    #[clap(long)]
274    validate_component: Option<bool>,
275
276    /// Whether or not imports are deduplicated based on semver in the final
277    /// component.
278    ///
279    /// This defaults to `true`.
280    #[clap(long)]
281    merge_imports_based_on_semver: Option<bool>,
282
283    /// Adapters to use when creating the final component.
284    #[clap(long = "adapt", value_name = "[NAME=]MODULE", value_parser = parse_adapter)]
285    adapters: Vec<(String, Vec<u8>)>,
286
287    /// Whether or not "legacy" names are rejected during componentization.
288    ///
289    /// This option can be used to require the naming scheme outlined in
290    /// <https://github.com/WebAssembly/component-model/pull/378> to be used
291    /// and rejects all modules using the previous ad-hoc naming scheme.
292    ///
293    /// This defaults to `false`.
294    #[clap(long)]
295    reject_legacy_names: bool,
296
297    /// Whether or not the `cabi_realloc` function used by the adapter is backed
298    /// by `memory.grow`.
299    ///
300    /// By default the adapter will import `cabi_realloc` from the main module
301    /// and use that, but this can be used to instead back memory allocation
302    /// requests with `memory.grow` instead.
303    ///
304    /// This defaults to `false`.
305    #[clap(long)]
306    realloc_via_memory_grow: bool,
307
308    /// WIT file representing additional component type information to use.
309    ///
310    /// May be specified more than once.
311    ///
312    /// See also the `--string-encoding` option.
313    #[clap(long = "component-type", value_name = "WIT_FILE")]
314    component_types: Vec<PathBuf>,
315
316    /// String encoding to use when creating the final component.
317    ///
318    /// This may be either "utf8", "utf16", or "compact-utf16".  This value is
319    /// only used when one or more `--component-type` options are specified.
320    #[clap(long, value_parser = parse_encoding, default_value = "utf8")]
321    string_encoding: StringEncoding,
322
323    /// Skip the `wit-component`-based process to generate a component.
324    #[clap(long)]
325    skip_wit_component: bool,
326
327    /// Raw flags to pass to the end of the `lld` invocation in case they're
328    /// not already recognized by this wrapper executable.
329    #[clap(long)]
330    append_lld_flag: Vec<OsString>,
331}
332
333fn parse_adapter(s: &str) -> Result<(String, Vec<u8>)> {
334    let (name, path) = parse_optionally_name_file(s);
335    let wasm = wat::parse_file(path)?;
336    Ok((name.to_string(), wasm))
337}
338
339fn parse_encoding(s: &str) -> Result<StringEncoding> {
340    Ok(match s {
341        "utf8" => StringEncoding::UTF8,
342        "utf16" => StringEncoding::UTF16,
343        "compact-utf16" => StringEncoding::CompactUTF16,
344        _ => bail!("unknown string encoding: {s:?}"),
345    })
346}
347
348fn parse_optionally_name_file(s: &str) -> (&str, &str) {
349    let mut parts = s.splitn(2, '=');
350    let name_or_path = parts.next().unwrap();
351    match parts.next() {
352        Some(path) => (name_or_path, path),
353        None => {
354            let name = Path::new(name_or_path)
355                .file_name()
356                .unwrap()
357                .to_str()
358                .unwrap();
359            let name = match name.find('.') {
360                Some(i) => &name[..i],
361                None => name,
362            };
363            (name, name_or_path)
364        }
365    }
366}
367
368#[derive(Debug, Copy, Clone)]
369enum WasiAdapter {
370    Command,
371    Reactor,
372    Proxy,
373    None,
374}
375
376impl FromStr for WasiAdapter {
377    type Err = anyhow::Error;
378
379    fn from_str(s: &str) -> Result<Self, Self::Err> {
380        match s {
381            "none" => Ok(WasiAdapter::None),
382            "command" => Ok(WasiAdapter::Command),
383            "reactor" => Ok(WasiAdapter::Reactor),
384            "proxy" => Ok(WasiAdapter::Proxy),
385            _ => bail!("unknown wasi adapter {s}, must be one of: none, command, reactor, proxy"),
386        }
387    }
388}
389
390pub fn main() {
391    let err = match run() {
392        Ok(()) => return,
393        Err(e) => e,
394    };
395    eprintln!("error: {err}");
396    if err.chain().len() > 1 {
397        eprintln!("\nCaused by:");
398        for (i, err) in err.chain().skip(1).enumerate() {
399            eprintln!("{i:>5}: {}", err.to_string().replace("\n", "\n       "));
400        }
401    }
402
403    std::process::exit(1);
404}
405
406fn run() -> Result<()> {
407    App::parse()?.run()
408}
409
410impl App {
411    /// Parse the CLI arguments into an `App` to run the linker.
412    ///
413    /// This is unfortunately nontrivial because the way `wasm-ld` takes
414    /// arguments is not compatible with `clap`. Namely flags like
415    /// `--whole-archive` are positional are processed in a stateful manner.
416    /// This means that the relative ordering of flags to `wasm-ld` needs to be
417    /// preserved. Additionally there are flags like `-shared` which clap does
418    /// not support.
419    ///
420    /// To handle this the `lexopt` crate is used to perform low-level argument
421    /// parsing. That's then used to determine whether the argument is intended
422    /// for `wasm-component-ld` or `wasm-ld`, so arguments are filtered into two
423    /// lists. Using these lists the arguments to `wasm-component-ld` are then
424    /// parsed. On failure a help message is presented with all `wasm-ld`
425    /// arguments added as well.
426    ///
427    /// This means that functionally it looks like `clap` parses everything when
428    /// in fact `lexopt` is used to filter out `wasm-ld` arguments and `clap`
429    /// only parses arguments specific to `wasm-component-ld`.
430    fn parse() -> Result<App> {
431        let mut args = argfile::expand().context("failed to expand @-response files")?;
432
433        // First remove `-flavor wasm` in case this is invoked as a generic LLD
434        // driver. We can safely ignore that going forward.
435        if let Some([flavor, wasm]) = args.get(1..3) {
436            if flavor == "-flavor" && wasm == "wasm" {
437                args.remove(1);
438                args.remove(1);
439            }
440        }
441
442        let mut command = ComponentLdArgs::command();
443        let mut lld_args = Vec::new();
444        let mut component_ld_args = vec![std::env::args_os().nth(0).unwrap()];
445        let mut shared = false;
446        let mut parser = lexopt::Parser::from_iter(args);
447
448        fn handle_lld_arg(
449            lld: &LldFlag,
450            parser: &mut lexopt::Parser,
451            lld_args: &mut Vec<OsString>,
452        ) -> Result<()> {
453            let mut arg = OsString::new();
454            match (lld.short, lld.long) {
455                (_, Some(long)) => {
456                    arg.push("--");
457                    arg.push(long);
458                }
459                (Some(short), _) => {
460                    arg.push("-");
461                    arg.push(short.encode_utf8(&mut [0; 5]));
462                }
463                (None, None) => unreachable!(),
464            }
465            match lld.value {
466                FlagValue::None => {
467                    lld_args.push(arg);
468                }
469
470                FlagValue::RequiredSpace(_) => {
471                    lld_args.push(arg);
472                    lld_args.push(parser.value()?);
473                }
474
475                FlagValue::RequiredEqual(_) => {
476                    arg.push("=");
477                    arg.push(&parser.value()?);
478                    lld_args.push(arg);
479                }
480
481                // If the value is optional then the argument must have an `=`
482                // in the argument itself.
483                FlagValue::Optional(_) => {
484                    match parser.optional_value() {
485                        Some(val) => {
486                            arg.push("=");
487                            arg.push(&val);
488                        }
489                        None => {}
490                    }
491                    lld_args.push(arg);
492                }
493            }
494            Ok(())
495        }
496
497        loop {
498            if let Some(mut args) = parser.try_raw_args() {
499                if let Some(arg) = args.peek() {
500                    let for_lld = LLD_LONG_FLAGS_NONSTANDARD.iter().any(|s| arg == *s);
501                    if for_lld {
502                        lld_args.push(arg.to_owned());
503                        if arg == "-shared" {
504                            shared = true;
505                        }
506                        args.next();
507                        continue;
508                    }
509                }
510            }
511
512            match parser.next()? {
513                Some(Arg::Value(obj)) => {
514                    lld_args.push(obj);
515                }
516                Some(Arg::Short(c)) => match LLD_FLAGS.iter().find(|f| f.short == Some(c)) {
517                    Some(lld) => {
518                        handle_lld_arg(lld, &mut parser, &mut lld_args)?;
519                    }
520                    None => {
521                        component_ld_args.push(format!("-{c}").into());
522                        if let Some(arg) =
523                            command.get_arguments().find(|a| a.get_short() == Some(c))
524                        {
525                            if let ArgAction::Set = arg.get_action() {
526                                component_ld_args.push(parser.value()?);
527                            }
528                        }
529                    }
530                },
531                Some(Arg::Long(c)) => match LLD_FLAGS.iter().find(|f| f.long == Some(c)) {
532                    Some(lld) => {
533                        handle_lld_arg(lld, &mut parser, &mut lld_args)?;
534                    }
535                    None => {
536                        let mut flag = OsString::from(format!("--{c}"));
537                        if let Some(arg) = command.get_arguments().find(|a| a.get_long() == Some(c))
538                        {
539                            match arg.get_action() {
540                                ArgAction::Set | ArgAction::Append => {
541                                    flag.push("=");
542                                    flag.push(parser.value()?);
543                                }
544                                _ => (),
545                            }
546                        }
547                        component_ld_args.push(flag);
548                    }
549                },
550                None => break,
551            }
552        }
553
554        match command.try_get_matches_from_mut(component_ld_args.clone()) {
555            Ok(matches) => Ok(App {
556                component: ComponentLdArgs::from_arg_matches(&matches)?,
557                lld_args,
558                shared,
559            }),
560            Err(_) => {
561                add_wasm_ld_options(ComponentLdArgs::command()).get_matches_from(component_ld_args);
562                unreachable!();
563            }
564        }
565    }
566
567    fn run(&mut self) -> Result<()> {
568        let mut lld = self.lld();
569
570        // If a temporary output is needed make sure it has the same file name
571        // as the output of our command itself since LLD will embed this file
572        // name in the name section of the output.
573        let temp_dir = match self.component.output.parent() {
574            Some(parent) => tempfile::TempDir::new_in(parent)?,
575            None => tempfile::TempDir::new()?,
576        };
577        let temp_output = match self.component.output.file_name() {
578            Some(name) => temp_dir.path().join(name),
579            None => bail!(
580                "output of {:?} does not have a file name",
581                self.component.output
582            ),
583        };
584
585        // Shared libraries don't get wit-component run below so place the
586        // output directly at the desired output location. Otherwise output to a
587        // temporary location for wit-component to read and then the real output
588        // is created after wit-component runs.
589        if self.skip_wit_component() {
590            lld.output(&self.component.output);
591        } else {
592            lld.output(&temp_output);
593        }
594
595        let linker = &lld.exe;
596        let lld_flags = self
597            .lld_args
598            .iter()
599            .chain(&self.component.append_lld_flag)
600            .collect::<Vec<_>>();
601        let status = lld
602            .status(&temp_dir, &lld_flags)
603            .with_context(|| format!("failed to spawn {linker:?}"))?;
604        if !status.success() {
605            bail!("failed to invoke LLD: {status}");
606        }
607
608        if self.skip_wit_component() {
609            return Ok(());
610        }
611
612        let reactor_adapter =
613            wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER;
614        let command_adapter =
615            wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_COMMAND_ADAPTER;
616        let proxy_adapter =
617            wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_PROXY_ADAPTER;
618        let mut core_module = std::fs::read(&temp_output)
619            .with_context(|| format!("failed to read {linker:?} output: {temp_output:?}"))?;
620
621        // Inspect the output module to see if it's a command or reactor.
622        let mut exports_start = false;
623        for payload in wasmparser::Parser::new(0).parse_all(&core_module) {
624            match payload {
625                Ok(Payload::ExportSection(e)) => {
626                    for export in e {
627                        if let Ok(e) = export {
628                            if e.name == "_start" {
629                                exports_start = true;
630                                break;
631                            }
632                        }
633                    }
634                }
635                _ => {}
636            }
637        }
638
639        if !self.component.component_types.is_empty() {
640            let mut merged = None::<(Resolve, WorldId)>;
641            for wit_file in &self.component.component_types {
642                let mut resolve = Resolve::default();
643                let (package, _) = resolve
644                    .push_path(wit_file)
645                    .with_context(|| format!("unable to add component type {wit_file:?}"))?;
646
647                let world = resolve.select_world(&[package], None)?;
648
649                if let Some((merged_resolve, merged_world)) = &mut merged {
650                    let world = merged_resolve.merge(resolve)?.map_world(world, None)?;
651                    merged_resolve.merge_worlds(world, *merged_world)?;
652                } else {
653                    merged = Some((resolve, world));
654                }
655            }
656
657            let Some((resolve, world)) = merged else {
658                unreachable!()
659            };
660
661            wit_component::embed_component_metadata(
662                &mut core_module,
663                &resolve,
664                world,
665                self.component.string_encoding,
666            )?;
667        }
668
669        let mut encoder = wit_component::ComponentEncoder::default()
670            .reject_legacy_names(self.component.reject_legacy_names)
671            .realloc_via_memory_grow(self.component.realloc_via_memory_grow);
672        if let Some(validate) = self.component.validate_component {
673            encoder = encoder.validate(validate);
674        }
675        if let Some(merge) = self.component.merge_imports_based_on_semver {
676            encoder = encoder.merge_imports_based_on_semver(merge);
677        }
678        encoder = encoder
679            .module(&core_module)
680            .context("failed to parse core wasm for componentization")?;
681        let adapter = self.component.wasi_adapter.unwrap_or(if exports_start {
682            WasiAdapter::Command
683        } else {
684            WasiAdapter::Reactor
685        });
686        let adapter = match adapter {
687            WasiAdapter::Command => Some(&command_adapter[..]),
688            WasiAdapter::Reactor => Some(&reactor_adapter[..]),
689            WasiAdapter::Proxy => Some(&proxy_adapter[..]),
690            WasiAdapter::None => None,
691        };
692
693        if let Some(adapter) = adapter {
694            encoder = encoder
695                .adapter("wasi_snapshot_preview1", adapter)
696                .context("failed to inject adapter")?;
697        }
698
699        for (name, adapter) in self.component.adapters.iter() {
700            encoder = encoder
701                .adapter(name, adapter)
702                .with_context(|| format!("failed to inject adapter {name:?}"))?;
703        }
704
705        let component = encoder.encode().context("failed to encode component")?;
706
707        std::fs::write(&self.component.output, &component).context(format!(
708            "failed to write output file: {:?}",
709            self.component.output
710        ))?;
711
712        Ok(())
713    }
714
715    fn skip_wit_component(&self) -> bool {
716        self.component.skip_wit_component
717            // Skip componentization with `--shared` since that's creating a
718            // shared library that's not a component yet.
719            || self.shared
720    }
721
722    fn lld(&self) -> Lld {
723        let mut lld = self.find_lld();
724        if self.component.verbose {
725            lld.verbose = true
726        }
727        lld
728    }
729
730    fn find_lld(&self) -> Lld {
731        if let Some(path) = &self.component.wasm_ld_path {
732            return Lld::new(path);
733        }
734
735        // Search for the first of `wasm-ld` or `rust-lld` in `$PATH`
736        let wasm_ld = format!("wasm-ld{}", env::consts::EXE_SUFFIX);
737        let rust_lld = format!("rust-lld{}", env::consts::EXE_SUFFIX);
738        for entry in env::split_paths(&env::var_os("PATH").unwrap_or_default()) {
739            if entry.join(&wasm_ld).is_file() {
740                return Lld::new(wasm_ld);
741            }
742            if entry.join(&rust_lld).is_file() {
743                let mut lld = Lld::new(rust_lld);
744                lld.needs_flavor = true;
745                return lld;
746            }
747        }
748
749        // Fall back to `wasm-ld` if the search failed to get an error message
750        // that indicates that `wasm-ld` was attempted to be found but couldn't
751        // be found.
752        Lld::new("wasm-ld")
753    }
754}
755
756/// Helper structure representing an `lld` invocation.
757struct Lld {
758    exe: PathBuf,
759    needs_flavor: bool,
760    verbose: bool,
761    output: Option<PathBuf>,
762}
763
764impl Lld {
765    fn new(exe: impl Into<PathBuf>) -> Lld {
766        Lld {
767            exe: exe.into(),
768            needs_flavor: false,
769            verbose: false,
770            output: None,
771        }
772    }
773
774    fn output(&mut self, dst: impl Into<PathBuf>) {
775        self.output = Some(dst.into());
776    }
777
778    fn status(&self, tmpdir: &tempfile::TempDir, args: &[&OsString]) -> Result<ExitStatus> {
779        // If we can probably pass `args` natively, try to do so. In some cases
780        // though just skip this entirely and go straight to below.
781        if !self.probably_too_big(args) {
782            match self.run(args) {
783                // If this subprocess failed to spawn because the arguments
784                // were too large, fall through to below.
785                Err(ref e) if self.command_line_too_big(e) => {
786                    if self.verbose {
787                        eprintln!("command line was too large, trying again...");
788                    }
789                }
790                other => return Ok(other?),
791            }
792        } else if self.verbose {
793            eprintln!("arguments probably too large {args:?}");
794        }
795
796        // The `args` are too big to be passed via the command line itself so
797        // encode the mall using "posix quoting" into an "argfile". This gets
798        // passed as `@foo` to lld and we also pass `--rsp-quoting=posix` to
799        // ensure that LLD always uses posix quoting. That means that we don't
800        // have to implement the dual nature of both posix and windows encoding
801        // here.
802        let mut argfile = Vec::new();
803        for arg in args {
804            for byte in arg.as_encoded_bytes() {
805                if *byte == b'\\' || *byte == b' ' {
806                    argfile.push(b'\\');
807                }
808                argfile.push(*byte);
809            }
810            argfile.push(b'\n');
811        }
812        let path = tmpdir.path().join("argfile_tmp");
813        std::fs::write(&path, &argfile).with_context(|| format!("failed to write {path:?}"))?;
814        let mut argfile_arg = OsString::from("@");
815        argfile_arg.push(&path);
816        let status = self.run(&[&"--rsp-quoting=posix".into(), &argfile_arg])?;
817        Ok(status)
818    }
819
820    /// Tests whether the `args` array is too large to execute natively.
821    ///
822    /// Windows `cmd.exe` has a very small limit of around 8k so perform a
823    /// guess up to 6k. This isn't 100% accurate.
824    fn probably_too_big(&self, args: &[&OsString]) -> bool {
825        let args_size = args
826            .iter()
827            .map(|s| s.as_encoded_bytes().len())
828            .sum::<usize>();
829        cfg!(windows) && args_size > 6 * 1024
830    }
831
832    /// Test if the OS failed to spawn a process because the arguments were too
833    /// long.
834    fn command_line_too_big(&self, err: &std::io::Error) -> bool {
835        #[cfg(unix)]
836        return err.raw_os_error() == Some(libc::E2BIG);
837        #[cfg(windows)]
838        return err.raw_os_error()
839            == Some(windows_sys::Win32::Foundation::ERROR_FILENAME_EXCED_RANGE as i32);
840        #[cfg(not(any(unix, windows)))]
841        {
842            let _ = err;
843            return false;
844        }
845    }
846
847    fn run(&self, args: &[&OsString]) -> std::io::Result<ExitStatus> {
848        let mut cmd = Command::new(&self.exe);
849        if self.needs_flavor {
850            cmd.arg("-flavor").arg("wasm");
851        }
852        cmd.args(args);
853        if self.verbose {
854            cmd.arg("--verbose");
855        }
856        if let Some(output) = &self.output {
857            cmd.arg("-o").arg(output);
858        }
859        if self.verbose {
860            eprintln!("running {cmd:?}");
861        }
862        cmd.status()
863    }
864}
865
866fn add_wasm_ld_options(mut command: clap::Command) -> clap::Command {
867    use clap::Arg;
868
869    command = command.arg(
870        Arg::new("objects")
871            .action(ArgAction::Append)
872            .help("objects to pass to `wasm-ld`"),
873    );
874
875    for flag in LLD_FLAGS {
876        let mut arg = Arg::new(flag.clap_name).help("forwarded to `wasm-ld`");
877        if let Some(short) = flag.short {
878            arg = arg.short(short);
879        }
880        if let Some(long) = flag.long {
881            arg = arg.long(long);
882        }
883        arg = match flag.value {
884            FlagValue::RequiredEqual(name) | FlagValue::RequiredSpace(name) => {
885                arg.action(ArgAction::Set).value_name(name)
886            }
887            FlagValue::Optional(name) => arg
888                .action(ArgAction::Set)
889                .value_name(name)
890                .num_args(0..=1)
891                .require_equals(true),
892            FlagValue::None => arg.action(ArgAction::SetTrue),
893        };
894        arg = arg.help_heading("Options forwarded to `wasm-ld`");
895        command = command.arg(arg);
896    }
897
898    command
899}
900
901#[test]
902fn verify_app() {
903    ComponentLdArgs::command().debug_assert();
904    add_wasm_ld_options(ComponentLdArgs::command()).debug_assert();
905}