wasm_component_ld/
lib.rs

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