1mod add;
3mod app;
4mod auth;
5#[cfg(target_os = "linux")]
6mod binfmt;
7mod cache;
8#[cfg(feature = "compiler")]
9mod compile;
10mod config;
11mod connect;
12mod container;
13#[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
14mod create_exe;
15#[cfg(feature = "static-artifact-create")]
16mod create_obj;
17pub(crate) mod domain;
18#[cfg(feature = "static-artifact-create")]
19mod gen_c_header;
20mod gen_completions;
21mod gen_manpage;
22mod init;
23mod inspect;
24#[cfg(feature = "journal")]
25mod journal;
26pub(crate) mod namespace;
27mod package;
28mod run;
29mod self_update;
30pub mod ssh;
31mod validate;
32#[cfg(feature = "wast")]
33mod wast;
34use itertools::Itertools;
35use std::io::IsTerminal as _;
36use tokio::task::JoinHandle;
37
38#[cfg(target_os = "linux")]
39pub use binfmt::*;
40use clap::{CommandFactory, Parser};
41#[cfg(feature = "compiler")]
42pub use compile::*;
43#[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
44pub use create_exe::*;
45#[cfg(feature = "wast")]
46pub use wast::*;
47#[cfg(feature = "static-artifact-create")]
48#[allow(unused_imports)]
49pub use {create_obj::*, gen_c_header::*};
50
51#[cfg(feature = "journal")]
52pub use self::journal::*;
53pub use self::{
54 add::*, auth::*, cache::*, config::*, container::*, init::*, inspect::*, package::*,
55 publish::*, run::Run, self_update::*, validate::*,
56};
57use crate::error::PrettyError;
58use git_version::git_version;
59
60pub(crate) trait CliCommand {
62 type Output;
63
64 fn run(self) -> Result<(), anyhow::Error>;
65}
66
67#[async_trait::async_trait]
72pub(crate) trait AsyncCliCommand: Send + Sync {
73 type Output: Send + Sync;
74
75 async fn run_async(self) -> Result<Self::Output, anyhow::Error>;
76
77 fn setup(
78 &self,
79 done: tokio::sync::oneshot::Receiver<()>,
80 ) -> Option<JoinHandle<anyhow::Result<()>>> {
81 if std::io::stdin().is_terminal() {
82 return Some(tokio::task::spawn(async move {
83 tokio::select! {
84 _ = done => {}
85
86 _ = tokio::signal::ctrl_c() => {
87 let term = console::Term::stdout();
88 let _ = term.show_cursor();
89 #[cfg(target_os = "windows")]
91 std::process::exit(3);
92
93 #[cfg(not(target_os = "windows"))]
95 std::process::exit(130);
96 }
97 }
98
99 Ok::<(), anyhow::Error>(())
100 }));
101 }
102
103 None
104 }
105}
106
107impl<O: Send + Sync, C: AsyncCliCommand<Output = O>> CliCommand for C {
108 type Output = O;
109
110 fn run(self) -> Result<(), anyhow::Error> {
111 tokio::runtime::Runtime::new()?.block_on(async {
112 let (snd, rcv) = tokio::sync::oneshot::channel();
113 let handle = self.setup(rcv);
114
115 if let Err(e) = AsyncCliCommand::run_async(self).await {
116 if let Some(handle) = handle {
117 handle.abort();
118 }
119 return Err(e);
120 }
121
122 if let Some(handle) = handle {
123 if snd.send(()).is_err() {
124 tracing::warn!("Failed to send 'done' signal to setup thread!");
125 handle.abort();
126 } else {
127 handle.await??;
128 }
129 }
130
131 Ok::<(), anyhow::Error>(())
132 })?;
133
134 Ok(())
135 }
136}
137
138#[derive(clap::Parser, Debug)]
140#[clap(author, version)]
141#[clap(disable_version_flag = true)] #[cfg_attr(feature = "headless", clap(
143 name = "wasmer-headless",
144 about = concat!("wasmer-headless ", env!("CARGO_PKG_VERSION")),
145))]
146#[cfg_attr(not(feature = "headless"), clap(
147 name = "wasmer",
148 about = concat!("wasmer ", env!("CARGO_PKG_VERSION")),
149))]
150pub struct WasmerCmd {
151 #[clap(short = 'V', long)]
153 version: bool,
154 #[clap(flatten)]
155 output: crate::logging::Output,
156 #[clap(subcommand)]
157 cmd: Option<Cmd>,
158}
159
160impl WasmerCmd {
161 fn execute(self) -> Result<(), anyhow::Error> {
162 let WasmerCmd {
163 cmd,
164 version,
165 output,
166 } = self;
167
168 output.initialize_logging();
169
170 if version {
171 return print_version(output.is_verbose());
172 }
173
174 match cmd {
175 Some(Cmd::GenManPage(cmd)) => cmd.execute(),
176 Some(Cmd::GenCompletions(cmd)) => cmd.execute(),
177 Some(Cmd::Run(options)) => options.execute(output),
178 Some(Cmd::SelfUpdate(options)) => options.execute(),
179 Some(Cmd::Cache(cache)) => cache.execute(),
180 Some(Cmd::Validate(validate)) => validate.execute(),
181 #[cfg(feature = "compiler")]
182 Some(Cmd::Compile(compile)) => compile.execute(),
183 Some(Cmd::Config(config)) => config.run(),
189 Some(Cmd::Inspect(inspect)) => inspect.execute(),
190 Some(Cmd::Init(init)) => init.run(),
191 Some(Cmd::Login(login)) => login.run(),
192 Some(Cmd::Auth(auth)) => auth.run(),
193 Some(Cmd::Publish(publish)) => publish.run().map(|_| ()),
194 Some(Cmd::Package(cmd)) => match cmd {
195 Package::Download(cmd) => cmd.execute(),
196 Package::Build(cmd) => cmd.execute().map(|_| ()),
197 Package::Tag(cmd) => cmd.run(),
198 Package::Push(cmd) => cmd.run(),
199 Package::Publish(cmd) => cmd.run().map(|_| ()),
200 Package::Tree(cmd) => cmd.run(),
201 Package::Unpack(cmd) => cmd.execute(),
202 Package::Search(cmd) => cmd.run(),
203 Package::Get(cmd) => cmd.run(),
204 },
205 Some(Cmd::Container(cmd)) => match cmd {
206 crate::commands::Container::Unpack(cmd) => cmd.execute(),
207 },
208 #[cfg(feature = "static-artifact-create")]
209 Some(Cmd::GenCHeader(gen_header)) => gen_header.execute(),
210 #[cfg(feature = "wast")]
211 Some(Cmd::Wast(wast)) => wast.execute(),
212 #[cfg(target_os = "linux")]
213 Some(Cmd::Binfmt(binfmt)) => binfmt.execute(),
214 Some(Cmd::Whoami(whoami)) => whoami.run(),
215 Some(Cmd::Add(add)) => add.run(),
216
217 Some(Cmd::Deploy(c)) => c.run(),
219 Some(Cmd::App(apps)) => apps.run(),
220 #[cfg(feature = "journal")]
221 Some(Cmd::Journal(journal)) => journal.run(),
222 Some(Cmd::Ssh(ssh)) => ssh.run(),
223 Some(Cmd::Namespace(namespace)) => namespace.run(),
224 Some(Cmd::Domain(namespace)) => namespace.run(),
225 None => {
226 WasmerCmd::command().print_long_help()?;
227 std::process::exit(2);
229 }
230 }
231 }
232
233 pub fn run() {
235 #[cfg(windows)]
237 colored::control::set_virtual_terminal(true).unwrap();
238
239 PrettyError::report(Self::run_inner())
240 }
241
242 fn run_inner() -> Result<(), anyhow::Error> {
243 let mut args_os = std::env::args_os();
244
245 let args = args_os.next().into_iter();
246
247 let mut binfmt_args = Vec::new();
248 if is_binfmt_interpreter() {
249 let current_dir = std::env::current_dir().unwrap();
256 let mut mount_paths = ["/home", "/etc", "/tmp", "/var", "/nix", "/opt", "/root"]
257 .into_iter()
258 .map(std::path::PathBuf::from)
259 .filter(|path| {
260 if !path.is_dir() {
261 return false;
263 }
264 if std::fs::read_dir(path).is_err() {
265 return false;
267 }
268 true
269 })
270 .collect_vec();
271 if mount_paths
272 .iter()
273 .all(|path| !current_dir.starts_with(path))
274 {
275 mount_paths.push(current_dir.clone());
277 }
278
279 binfmt_args.push("run".into());
280 binfmt_args.push("--net".into());
281 binfmt_args.push("--forward-host-env".into());
283 for mount_path in mount_paths {
284 if let Some(mount_path_str) = mount_path.to_str() {
285 binfmt_args.push(format!("--volume={mount_path_str}:{mount_path_str}").into());
286 }
287 }
288 if let Some(current_dir_str) = current_dir.to_str() {
289 binfmt_args.push(format!("--cwd={current_dir_str}").into());
290 }
291 binfmt_args.push("--quiet".into());
292 binfmt_args.push("--".into());
293 binfmt_args.push(args_os.next().unwrap());
294 args_os.next().unwrap();
295 };
296 let args_vec = args.chain(binfmt_args).chain(args_os).collect_vec();
297
298 match WasmerCmd::try_parse_from(args_vec.iter()) {
299 Ok(args) => args.execute(),
300 Err(e) => {
301 let first_arg_is_subcommand = if let Some(first_arg) = args_vec.get(1) {
302 let mut ret = false;
303 let cmd = WasmerCmd::command();
304
305 for cmd in cmd.get_subcommands() {
306 if cmd.get_name() == first_arg {
307 ret = true;
308 break;
309 }
310 }
311
312 ret
313 } else {
314 false
315 };
316
317 let might_be_wasmer_run = matches!(
318 e.kind(),
319 clap::error::ErrorKind::InvalidSubcommand
320 | clap::error::ErrorKind::UnknownArgument
321 ) && !first_arg_is_subcommand;
322
323 if might_be_wasmer_run && let Ok(run) = Run::try_parse_from(args_vec.iter()) {
324 let output = crate::logging::Output::default();
329 output.initialize_logging();
330 run.execute(output);
331 }
332
333 e.exit();
334 }
335 }
336 }
337}
338
339#[derive(clap::Parser, Debug)]
340#[allow(clippy::large_enum_variant)]
341enum Cmd {
343 Login(Login),
345
346 #[clap(subcommand)]
347 Auth(CmdAuth),
348
349 #[clap(name = "publish")]
351 Publish(PackagePublish),
352
353 Cache(Cache),
355
356 Validate(Validate),
358
359 #[cfg(feature = "compiler")]
361 Compile(Compile),
362
363 #[cfg(feature = "static-artifact-create")]
431 GenCHeader(GenCHeader),
432
433 Config(Config),
436
437 #[clap(name = "self-update")]
439 SelfUpdate(SelfUpdate),
440
441 Inspect(Inspect),
443
444 #[clap(name = "init")]
446 Init(Init),
447
448 #[cfg(feature = "wast")]
450 Wast(Wast),
451
452 #[cfg(target_os = "linux")]
454 Binfmt(Binfmt),
455
456 Whoami(Whoami),
458
459 Add(CmdAdd),
461
462 #[clap(alias = "run-unstable")]
464 Run(Run),
465
466 #[cfg(feature = "journal")]
468 #[clap(subcommand)]
469 Journal(CmdJournal),
470
471 #[clap(subcommand)]
472 Package(crate::commands::Package),
473
474 #[clap(subcommand)]
475 Container(crate::commands::Container),
476
477 Deploy(crate::commands::app::deploy::CmdAppDeploy),
480
481 #[clap(subcommand, alias = "apps")]
483 App(crate::commands::app::CmdApp),
484
485 Ssh(crate::commands::ssh::CmdSsh),
487
488 #[clap(subcommand, alias = "namespaces")]
490 Namespace(crate::commands::namespace::CmdNamespace),
491
492 #[clap(subcommand, alias = "domains")]
494 Domain(crate::commands::domain::CmdDomain),
495
496 #[clap(name = "gen-completions")]
498 GenCompletions(crate::commands::gen_completions::CmdGenCompletions),
499
500 #[clap(name = "gen-man", hide = true)]
502 GenManPage(crate::commands::gen_manpage::CmdGenManPage),
503}
504
505fn is_binfmt_interpreter() -> bool {
506 cfg_if::cfg_if! {
507 if #[cfg(target_os = "linux")] {
508 let binary_path = match std::env::args_os().next() {
510 Some(path) => std::path::PathBuf::from(path),
511 None => return false,
512 };
513 binary_path.file_name().and_then(|f| f.to_str()) == Some(Binfmt::FILENAME)
514 } else {
515 false
516 }
517 }
518}
519
520fn print_version(verbose: bool) -> Result<(), anyhow::Error> {
521 if !verbose {
522 println!("wasmer {}", env!("CARGO_PKG_VERSION"));
523 return Ok(());
524 }
525
526 println!("wasmer {}", env!("CARGO_PKG_VERSION"));
527 println!("binary: {}", env!("CARGO_PKG_NAME"));
528 let git_hash = git_version!(
529 args = [
530 "--abbrev=40",
531 "--always",
532 "--dirty=-modified",
533 "--exclude=*"
534 ],
535 fallback = "",
536 )
537 .to_string();
538 if !git_hash.is_empty() {
539 println!("commit-hash: {git_hash}",);
540 }
541 if !env!("WASMER_REPRODUCIBLE_BUILD")
542 .parse::<bool>()
543 .expect("build-time variable expected")
544 {
545 println!("commit-date: {}", env!("WASMER_BUILD_DATE"));
546 }
547 println!("host: {}", target_lexicon::HOST);
548
549 let cpu_features = wasmer_types::target::CpuFeature::for_host()
550 .iter()
551 .map(|f| f.to_string())
552 .join(" ");
553 println!("CPU flags: {cpu_features}");
554
555 let mut runtimes = Vec::new();
556 if cfg!(feature = "singlepass") {
557 runtimes.push("Singlepass");
558 }
559 if cfg!(feature = "cranelift") {
560 runtimes.push("Cranelift");
561 }
562 if cfg!(feature = "llvm") {
563 runtimes.push("LLVM");
564 }
565 if cfg!(feature = "v8") {
566 runtimes.push("V8");
567 }
568 println!("runtimes: {}", runtimes.join(", "));
569
570 #[allow(clippy::useless_vec)]
571 #[allow(unused_mut)]
572 let mut features = vec!["wasix".to_string()];
573 #[cfg(feature = "napi-v8")]
574 {
575 for napi_version in enum_iterator::all::<wasmer_napi::NapiVersion>() {
576 if !matches!(napi_version, wasmer_napi::NapiVersion::Unknown) {
577 features.push(napi_version.to_string());
578 }
579 }
580 features.push(wasmer_napi::NAPI_EXTENSION_WASMER_MODULE_NAME.to_string());
581 }
582 println!("features: {}", features.join(", "));
583
584 Ok(())
585}