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