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 std::ffi::OsString;
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")]
48pub use {create_obj::*, gen_c_header::*};
49
50#[cfg(feature = "journal")]
51pub use self::journal::*;
52pub use self::{
53 add::*, auth::*, cache::*, config::*, container::*, init::*, inspect::*, package::*,
54 publish::*, run::Run, self_update::*, validate::*,
55};
56use crate::error::PrettyError;
57use git_version::git_version;
58
59pub(crate) trait CliCommand {
61 type Output;
62
63 fn run(self) -> Result<(), anyhow::Error>;
64}
65
66#[async_trait::async_trait]
71pub(crate) trait AsyncCliCommand: Send + Sync {
72 type Output: Send + Sync;
73
74 async fn run_async(self) -> Result<Self::Output, anyhow::Error>;
75
76 fn setup(
77 &self,
78 done: tokio::sync::oneshot::Receiver<()>,
79 ) -> Option<JoinHandle<anyhow::Result<()>>> {
80 if std::io::stdin().is_terminal() {
81 return Some(tokio::task::spawn(async move {
82 tokio::select! {
83 _ = done => {}
84
85 _ = tokio::signal::ctrl_c() => {
86 let term = console::Term::stdout();
87 let _ = term.show_cursor();
88 #[cfg(target_os = "windows")]
90 std::process::exit(3);
91
92 #[cfg(not(target_os = "windows"))]
94 std::process::exit(130);
95 }
96 }
97
98 Ok::<(), anyhow::Error>(())
99 }));
100 }
101
102 None
103 }
104}
105
106impl<O: Send + Sync, C: AsyncCliCommand<Output = O>> CliCommand for C {
107 type Output = O;
108
109 fn run(self) -> Result<(), anyhow::Error> {
110 tokio::runtime::Runtime::new()?.block_on(async {
111 let (snd, rcv) = tokio::sync::oneshot::channel();
112 let handle = self.setup(rcv);
113
114 if let Err(e) = AsyncCliCommand::run_async(self).await {
115 if let Some(handle) = handle {
116 handle.abort();
117 }
118 return Err(e);
119 }
120
121 if let Some(handle) = handle {
122 if snd.send(()).is_err() {
123 tracing::warn!("Failed to send 'done' signal to setup thread!");
124 handle.abort();
125 } else {
126 handle.await??;
127 }
128 }
129
130 Ok::<(), anyhow::Error>(())
131 })?;
132
133 Ok(())
134 }
135}
136
137#[derive(clap::Parser, Debug)]
139#[clap(author, version)]
140#[clap(disable_version_flag = true)] #[cfg_attr(feature = "headless", clap(
142 name = "wasmer-headless",
143 about = concat!("wasmer-headless ", env!("CARGO_PKG_VERSION")),
144))]
145#[cfg_attr(not(feature = "headless"), clap(
146 name = "wasmer",
147 about = concat!("wasmer ", env!("CARGO_PKG_VERSION")),
148))]
149pub struct WasmerCmd {
150 #[clap(short = 'V', long)]
152 version: bool,
153 #[clap(flatten)]
154 output: crate::logging::Output,
155 #[clap(subcommand)]
156 cmd: Option<Cmd>,
157}
158
159impl WasmerCmd {
160 fn execute(self) -> Result<(), anyhow::Error> {
161 let WasmerCmd {
162 cmd,
163 version,
164 output,
165 } = self;
166
167 output.initialize_logging();
168
169 if version {
170 return print_version(output.is_verbose());
171 }
172
173 match cmd {
174 Some(Cmd::GenManPage(cmd)) => cmd.execute(),
175 Some(Cmd::GenCompletions(cmd)) => cmd.execute(),
176 Some(Cmd::Run(options)) => options.execute(output),
177 Some(Cmd::SelfUpdate(options)) => options.execute(),
178 Some(Cmd::Cache(cache)) => cache.execute(),
179 Some(Cmd::Validate(validate)) => validate.execute(),
180 #[cfg(feature = "compiler")]
181 Some(Cmd::Compile(compile)) => compile.execute(),
182 #[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
183 Some(Cmd::CreateExe(create_exe)) => create_exe.run(),
184 #[cfg(feature = "static-artifact-create")]
185 Some(Cmd::CreateObj(create_obj)) => create_obj.execute(),
186 Some(Cmd::Config(config)) => config.run(),
187 Some(Cmd::Inspect(inspect)) => inspect.execute(),
188 Some(Cmd::Init(init)) => init.run(),
189 Some(Cmd::Login(login)) => login.run(),
190 Some(Cmd::Auth(auth)) => auth.run(),
191 Some(Cmd::Publish(publish)) => publish.run().map(|_| ()),
192 Some(Cmd::Package(cmd)) => match cmd {
193 Package::Download(cmd) => cmd.execute(),
194 Package::Build(cmd) => cmd.execute().map(|_| ()),
195 Package::Tag(cmd) => cmd.run(),
196 Package::Push(cmd) => cmd.run(),
197 Package::Publish(cmd) => cmd.run().map(|_| ()),
198 Package::Unpack(cmd) => cmd.execute(),
199 },
200 Some(Cmd::Container(cmd)) => match cmd {
201 crate::commands::Container::Unpack(cmd) => cmd.execute(),
202 },
203 #[cfg(feature = "static-artifact-create")]
204 Some(Cmd::GenCHeader(gen_heder)) => gen_heder.execute(),
205 #[cfg(feature = "wast")]
206 Some(Cmd::Wast(wast)) => wast.execute(),
207 #[cfg(target_os = "linux")]
208 Some(Cmd::Binfmt(binfmt)) => binfmt.execute(),
209 Some(Cmd::Whoami(whoami)) => whoami.run(),
210 Some(Cmd::Add(add)) => add.run(),
211
212 Some(Cmd::Deploy(c)) => c.run(),
214 Some(Cmd::App(apps)) => apps.run(),
215 #[cfg(feature = "journal")]
216 Some(Cmd::Journal(journal)) => journal.run(),
217 Some(Cmd::Ssh(ssh)) => ssh.run(),
218 Some(Cmd::Namespace(namespace)) => namespace.run(),
219 Some(Cmd::Domain(namespace)) => namespace.run(),
220 None => {
221 WasmerCmd::command().print_long_help()?;
222 std::process::exit(2);
224 }
225 }
226 }
227
228 pub fn run() {
230 #[cfg(windows)]
232 colored::control::set_virtual_terminal(true).unwrap();
233
234 PrettyError::report(Self::run_inner())
235 }
236
237 fn run_inner() -> Result<(), anyhow::Error> {
238 let mut args_os = std::env::args_os();
239
240 let args = args_os.next().into_iter();
241
242 let mut binfmt_args: Vec<OsString> = Vec::new();
243 if is_binfmt_interpreter() {
244 let current_dir = std::env::current_dir()
251 .unwrap()
252 .into_os_string()
253 .into_string()
254 .unwrap();
255 let mut mount_paths = vec!["/home", "/etc", "/tmp", "/var", "/nix", "/opt", "/root"]
256 .into_iter()
257 .filter(|path| {
258 let path = std::path::Path::new(path);
259 if !path.is_dir() {
260 return false;
262 }
263 if std::fs::read_dir(path).is_err() {
264 return false;
266 }
267 true
268 })
269 .collect::<Vec<_>>();
270 if !current_dir.starts_with("/home") {
271 mount_paths.push(current_dir.as_str());
272 }
273
274 binfmt_args.push("run".into());
275 binfmt_args.push("--net".into());
276 binfmt_args.push("--forward-host-env".into());
278 for mount_path in mount_paths {
279 binfmt_args.push(format!("--mapdir={mount_path}:{mount_path}").into());
280 }
281 binfmt_args.push(format!("--cwd={current_dir}").into());
282 binfmt_args.push("--quiet".into());
283 binfmt_args.push("--".into());
284 binfmt_args.push(args_os.next().unwrap());
285 args_os.next().unwrap();
286 };
287 let args_vec = args.chain(binfmt_args).chain(args_os).collect::<Vec<_>>();
288
289 match WasmerCmd::try_parse_from(args_vec.iter()) {
290 Ok(args) => args.execute(),
291 Err(e) => {
292 let first_arg_is_subcommand = if let Some(first_arg) = args_vec.get(1) {
293 let mut ret = false;
294 let cmd = WasmerCmd::command();
295
296 for cmd in cmd.get_subcommands() {
297 if cmd.get_name() == first_arg {
298 ret = true;
299 break;
300 }
301 }
302
303 ret
304 } else {
305 false
306 };
307
308 let might_be_wasmer_run = matches!(
309 e.kind(),
310 clap::error::ErrorKind::InvalidSubcommand
311 | clap::error::ErrorKind::UnknownArgument
312 ) && !first_arg_is_subcommand;
313
314 if might_be_wasmer_run && let Ok(run) = Run::try_parse_from(args_vec.iter()) {
315 let output = crate::logging::Output::default();
320 output.initialize_logging();
321 run.execute(output);
322 }
323
324 e.exit();
325 }
326 }
327 }
328}
329
330#[derive(clap::Parser, Debug)]
331#[allow(clippy::large_enum_variant)]
332enum Cmd {
334 Login(Login),
336
337 #[clap(subcommand)]
338 Auth(CmdAuth),
339
340 #[clap(name = "publish")]
342 Publish(PackagePublish),
343
344 Cache(Cache),
346
347 Validate(Validate),
349
350 #[cfg(feature = "compiler")]
352 Compile(Compile),
353
354 #[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
385 #[clap(name = "create-exe", verbatim_doc_comment)]
386 CreateExe(CreateExe),
387
388 #[cfg(feature = "static-artifact-create")]
418 #[structopt(name = "create-obj", verbatim_doc_comment)]
419 CreateObj(CreateObj),
420
421 #[cfg(feature = "static-artifact-create")]
423 GenCHeader(GenCHeader),
424
425 Config(Config),
428
429 #[clap(name = "self-update")]
431 SelfUpdate(SelfUpdate),
432
433 Inspect(Inspect),
435
436 #[clap(name = "init")]
438 Init(Init),
439
440 #[cfg(feature = "wast")]
442 Wast(Wast),
443
444 #[cfg(target_os = "linux")]
446 Binfmt(Binfmt),
447
448 Whoami(Whoami),
450
451 Add(CmdAdd),
453
454 #[clap(alias = "run-unstable")]
456 Run(Run),
457
458 #[cfg(feature = "journal")]
460 #[clap(subcommand)]
461 Journal(CmdJournal),
462
463 #[clap(subcommand)]
464 Package(crate::commands::Package),
465
466 #[clap(subcommand)]
467 Container(crate::commands::Container),
468
469 Deploy(crate::commands::app::deploy::CmdAppDeploy),
472
473 #[clap(subcommand, alias = "apps")]
475 App(crate::commands::app::CmdApp),
476
477 Ssh(crate::commands::ssh::CmdSsh),
479
480 #[clap(subcommand, alias = "namespaces")]
482 Namespace(crate::commands::namespace::CmdNamespace),
483
484 #[clap(subcommand, alias = "domains")]
486 Domain(crate::commands::domain::CmdDomain),
487
488 #[clap(name = "gen-completions")]
490 GenCompletions(crate::commands::gen_completions::CmdGenCompletions),
491
492 #[clap(name = "gen-man", hide = true)]
494 GenManPage(crate::commands::gen_manpage::CmdGenManPage),
495}
496
497fn is_binfmt_interpreter() -> bool {
498 cfg_if::cfg_if! {
499 if #[cfg(target_os = "linux")] {
500 let binary_path = match std::env::args_os().next() {
502 Some(path) => std::path::PathBuf::from(path),
503 None => return false,
504 };
505 binary_path.file_name().and_then(|f| f.to_str()) == Some(Binfmt::FILENAME)
506 } else {
507 false
508 }
509 }
510}
511
512fn print_version(verbose: bool) -> Result<(), anyhow::Error> {
513 if !verbose {
514 println!("wasmer {}", env!("CARGO_PKG_VERSION"));
515 return Ok(());
516 }
517
518 println!(
519 "wasmer {} ({} {})",
520 env!("CARGO_PKG_VERSION"),
521 git_version!(
522 args = ["--abbrev=8", "--always", "--dirty=-modified", "--exclude=*"],
523 fallback = ""
524 ),
525 env!("WASMER_BUILD_DATE")
526 );
527 println!("binary: {}", env!("CARGO_PKG_NAME"));
528 println!(
529 "commit-hash: {}",
530 git_version!(
531 args = [
532 "--abbrev=40",
533 "--always",
534 "--dirty=-modified",
535 "--exclude=*"
536 ],
537 fallback = "",
538 ),
539 );
540 println!("commit-date: {}", env!("WASMER_BUILD_DATE"));
541 println!("host: {}", target_lexicon::HOST);
542
543 let cpu_features = {
544 let feats = wasmer_types::target::CpuFeature::for_host();
545 let all = wasmer_types::target::CpuFeature::all();
546 all.iter()
547 .map(|f| {
548 let available = feats.contains(f);
549 format!("{}={}", f, if available { "true" } else { "false" })
550 })
551 .collect::<Vec<_>>()
552 .join(",")
553 };
554 println!("cpu: {cpu_features}");
555
556 let mut runtimes = Vec::<&'static str>::new();
557 if cfg!(feature = "singlepass") {
558 runtimes.push("singlepass");
559 }
560 if cfg!(feature = "cranelift") {
561 runtimes.push("cranelift");
562 }
563 if cfg!(feature = "llvm") {
564 runtimes.push("llvm");
565 }
566
567 if cfg!(feature = "wamr") {
568 runtimes.push("wamr");
569 }
570
571 if cfg!(feature = "wasmi") {
572 runtimes.push("wasmi");
573 }
574
575 if cfg!(feature = "v8") {
576 runtimes.push("v8");
577 }
578
579 println!("runtimes: {}", runtimes.join(", "));
580 Ok(())
581}