Skip to main content

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