wasmer_cli/commands/
mod.rs

1//! The commands available in the Wasmer binary.
2mod 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 tokio::task::JoinHandle;
36
37#[cfg(target_os = "linux")]
38pub use binfmt::*;
39use clap::{CommandFactory, Parser};
40#[cfg(feature = "compiler")]
41pub use compile::*;
42#[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
43pub use create_exe::*;
44#[cfg(feature = "wast")]
45pub use wast::*;
46#[cfg(feature = "static-artifact-create")]
47pub use {create_obj::*, gen_c_header::*};
48
49#[cfg(feature = "journal")]
50pub use self::journal::*;
51pub use self::{
52    add::*, auth::*, cache::*, config::*, container::*, init::*, inspect::*, package::*,
53    publish::*, run::Run, self_update::*, validate::*,
54};
55use crate::error::PrettyError;
56
57/// An executable CLI command.
58pub(crate) trait CliCommand {
59    type Output;
60
61    fn run(self) -> Result<(), anyhow::Error>;
62}
63
64/// An executable CLI command that runs in an async context.
65///
66/// An [`AsyncCliCommand`] automatically implements [`CliCommand`] by creating
67/// a new tokio runtime and blocking.
68#[async_trait::async_trait]
69pub(crate) trait AsyncCliCommand: Send + Sync {
70    type Output: Send + Sync;
71
72    async fn run_async(self) -> Result<Self::Output, anyhow::Error>;
73
74    fn setup(
75        &self,
76        done: tokio::sync::oneshot::Receiver<()>,
77    ) -> Option<JoinHandle<anyhow::Result<()>>> {
78        if is_terminal::IsTerminal::is_terminal(&std::io::stdin()) {
79            return Some(tokio::task::spawn(async move {
80                tokio::select! {
81                    _ = done => {}
82
83                    _ = tokio::signal::ctrl_c() => {
84                        let term = console::Term::stdout();
85                        let _ = term.show_cursor();
86                        // https://learn.microsoft.com/en-us/cpp/c-runtime-library/signal-constants
87                        #[cfg(target_os = "windows")]
88                        std::process::exit(3);
89
90                        // POSIX compliant OSs: 128 + SIGINT (2)
91                        #[cfg(not(target_os = "windows"))]
92                        std::process::exit(130);
93                    }
94                }
95
96                Ok::<(), anyhow::Error>(())
97            }));
98        }
99
100        None
101    }
102}
103
104impl<O: Send + Sync, C: AsyncCliCommand<Output = O>> CliCommand for C {
105    type Output = O;
106
107    fn run(self) -> Result<(), anyhow::Error> {
108        tokio::runtime::Runtime::new()?.block_on(async {
109            let (snd, rcv) = tokio::sync::oneshot::channel();
110            let handle = self.setup(rcv);
111
112            if let Err(e) = AsyncCliCommand::run_async(self).await {
113                if let Some(handle) = handle {
114                    handle.abort();
115                }
116                return Err(e);
117            }
118
119            if let Some(handle) = handle {
120                if snd.send(()).is_err() {
121                    tracing::warn!("Failed to send 'done' signal to setup thread!");
122                    handle.abort();
123                } else {
124                    handle.await??;
125                }
126            }
127
128            Ok::<(), anyhow::Error>(())
129        })?;
130
131        Ok(())
132    }
133}
134
135/// Command-line arguments for the Wasmer CLI.
136#[derive(clap::Parser, Debug)]
137#[clap(author, version)]
138#[clap(disable_version_flag = true)] // handled manually
139#[cfg_attr(feature = "headless", clap(
140    name = "wasmer-headless",
141    about = concat!("wasmer-headless ", env!("CARGO_PKG_VERSION")),
142))]
143#[cfg_attr(not(feature = "headless"), clap(
144    name = "wasmer",
145    about = concat!("wasmer ", env!("CARGO_PKG_VERSION")),
146))]
147pub struct WasmerCmd {
148    /// Print version info and exit.
149    #[clap(short = 'V', long)]
150    version: bool,
151    #[clap(flatten)]
152    output: crate::logging::Output,
153    #[clap(subcommand)]
154    cmd: Option<Cmd>,
155}
156
157impl WasmerCmd {
158    fn execute(self) -> Result<(), anyhow::Error> {
159        let WasmerCmd {
160            cmd,
161            version,
162            output,
163        } = self;
164
165        output.initialize_logging();
166
167        if version {
168            return print_version(output.is_verbose());
169        }
170
171        match cmd {
172            Some(Cmd::GenManPage(cmd)) => cmd.execute(),
173            Some(Cmd::GenCompletions(cmd)) => cmd.execute(),
174            Some(Cmd::Run(options)) => options.execute(output),
175            Some(Cmd::SelfUpdate(options)) => options.execute(),
176            Some(Cmd::Cache(cache)) => cache.execute(),
177            Some(Cmd::Validate(validate)) => validate.execute(),
178            #[cfg(feature = "compiler")]
179            Some(Cmd::Compile(compile)) => compile.execute(),
180            #[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
181            Some(Cmd::CreateExe(create_exe)) => create_exe.run(),
182            #[cfg(feature = "static-artifact-create")]
183            Some(Cmd::CreateObj(create_obj)) => create_obj.execute(),
184            Some(Cmd::Config(config)) => config.run(),
185            Some(Cmd::Inspect(inspect)) => inspect.execute(),
186            Some(Cmd::Init(init)) => init.run(),
187            Some(Cmd::Login(login)) => login.run(),
188            Some(Cmd::Auth(auth)) => auth.run(),
189            Some(Cmd::Publish(publish)) => publish.run().map(|_| ()),
190            Some(Cmd::Package(cmd)) => match cmd {
191                Package::Download(cmd) => cmd.execute(),
192                Package::Build(cmd) => cmd.execute().map(|_| ()),
193                Package::Tag(cmd) => cmd.run(),
194                Package::Push(cmd) => cmd.run(),
195                Package::Publish(cmd) => cmd.run().map(|_| ()),
196                Package::Unpack(cmd) => cmd.execute(),
197            },
198            Some(Cmd::Container(cmd)) => match cmd {
199                crate::commands::Container::Unpack(cmd) => cmd.execute(),
200            },
201            #[cfg(feature = "static-artifact-create")]
202            Some(Cmd::GenCHeader(gen_heder)) => gen_heder.execute(),
203            #[cfg(feature = "wast")]
204            Some(Cmd::Wast(wast)) => wast.execute(),
205            #[cfg(target_os = "linux")]
206            Some(Cmd::Binfmt(binfmt)) => binfmt.execute(),
207            Some(Cmd::Whoami(whoami)) => whoami.run(),
208            Some(Cmd::Add(add)) => add.run(),
209
210            // Deploy commands.
211            Some(Cmd::Deploy(c)) => c.run(),
212            Some(Cmd::App(apps)) => apps.run(),
213            #[cfg(feature = "journal")]
214            Some(Cmd::Journal(journal)) => journal.run(),
215            Some(Cmd::Ssh(ssh)) => ssh.run(),
216            Some(Cmd::Namespace(namespace)) => namespace.run(),
217            Some(Cmd::Domain(namespace)) => namespace.run(),
218            None => {
219                WasmerCmd::command().print_long_help()?;
220                // Note: clap uses an exit code of 2 when CLI parsing fails
221                std::process::exit(2);
222            }
223        }
224    }
225
226    /// The main function for the Wasmer CLI tool.
227    pub fn run() {
228        // We allow windows to print properly colors
229        #[cfg(windows)]
230        colored::control::set_virtual_terminal(true).unwrap();
231
232        PrettyError::report(Self::run_inner())
233    }
234
235    fn run_inner() -> Result<(), anyhow::Error> {
236        let mut args_os = std::env::args_os();
237
238        let args = args_os.next().into_iter();
239
240        let mut binfmt_args: Vec<OsString> = Vec::new();
241        if is_binfmt_interpreter() {
242            // In case of binfmt misc the first argument is wasmer-binfmt-interpreter, the second is the full path to the executable
243            // and the third is the original string for the executable as originally called by the user.
244
245            // For now we are only using the real path and ignoring the original executable name.
246            // Ideally we would use the real path to load the file and the original name to pass it as argv[0] to the wasm module.
247
248            let current_dir = std::env::current_dir()
249                .unwrap()
250                .into_os_string()
251                .into_string()
252                .unwrap();
253            let mut mount_paths = vec!["/home", "/etc", "/tmp", "/var", "/nix", "/opt", "/root"]
254                .into_iter()
255                .filter(|path| {
256                    let path = std::path::Path::new(path);
257                    if !path.is_dir() {
258                        // Not a directory
259                        return false;
260                    }
261                    if std::fs::read_dir(path).is_err() {
262                        // No permissions
263                        return false;
264                    }
265                    true
266                })
267                .collect::<Vec<_>>();
268            if !current_dir.starts_with("/home") {
269                mount_paths.push(current_dir.as_str());
270            }
271
272            binfmt_args.push("run".into());
273            binfmt_args.push("--net".into());
274            // TODO: This does not seem to work, needs further investigation.
275            binfmt_args.push("--forward-host-env".into());
276            for mount_path in mount_paths {
277                binfmt_args.push(format!("--mapdir={mount_path}:{mount_path}").into());
278            }
279            binfmt_args.push(format!("--cwd={current_dir}").into());
280            binfmt_args.push("--quiet".into());
281            binfmt_args.push("--".into());
282            binfmt_args.push(args_os.next().unwrap());
283            args_os.next().unwrap();
284        };
285        let args_vec = args.chain(binfmt_args).chain(args_os).collect::<Vec<_>>();
286
287        match WasmerCmd::try_parse_from(args_vec.iter()) {
288            Ok(args) => args.execute(),
289            Err(e) => {
290                let first_arg_is_subcommand = if let Some(first_arg) = args_vec.get(1) {
291                    let mut ret = false;
292                    let cmd = WasmerCmd::command();
293
294                    for cmd in cmd.get_subcommands() {
295                        if cmd.get_name() == first_arg {
296                            ret = true;
297                            break;
298                        }
299                    }
300
301                    ret
302                } else {
303                    false
304                };
305
306                let might_be_wasmer_run = matches!(
307                    e.kind(),
308                    clap::error::ErrorKind::InvalidSubcommand
309                        | clap::error::ErrorKind::UnknownArgument
310                ) && !first_arg_is_subcommand;
311
312                if might_be_wasmer_run && let Ok(run) = Run::try_parse_from(args_vec.iter()) {
313                    // Try to parse the command using the `wasmer some/package`
314                    // shorthand. Note that this has discoverability issues
315                    // because it's not shown as part of the main argument
316                    // parser's help, but that's fine.
317                    let output = crate::logging::Output::default();
318                    output.initialize_logging();
319                    run.execute(output);
320                }
321
322                e.exit();
323            }
324        }
325    }
326}
327
328#[derive(clap::Parser, Debug)]
329#[allow(clippy::large_enum_variant)]
330/// The options for the wasmer Command Line Interface
331enum Cmd {
332    /// Login into Wasmer
333    Login(Login),
334
335    #[clap(subcommand)]
336    Auth(CmdAuth),
337
338    /// Publish a package to a registry [alias: package publish]
339    #[clap(name = "publish")]
340    Publish(PackagePublish),
341
342    /// Manage the local Wasmer cache
343    Cache(Cache),
344
345    /// Validate a WebAssembly binary
346    Validate(Validate),
347
348    /// Compile a WebAssembly binary
349    #[cfg(feature = "compiler")]
350    Compile(Compile),
351
352    /// Compile a WebAssembly binary into a native executable
353    ///
354    /// To use, you need to set the `WASMER_DIR` environment variable
355    /// to the location of your Wasmer installation. This will probably be `~/.wasmer`. It
356    /// should include a `lib`, `include` and `bin` subdirectories. To create an executable
357    /// you will need `libwasmer`, so by setting `WASMER_DIR` the CLI knows where to look for
358    /// header files and libraries.
359    ///
360    /// Example usage:
361    ///
362    /// ```text
363    /// $ # in two lines:
364    /// $ export WASMER_DIR=/home/user/.wasmer/
365    /// $ wasmer create-exe qjs.wasm -o qjs.exe # or in one line:
366    /// $ WASMER_DIR=/home/user/.wasmer/ wasmer create-exe qjs.wasm -o qjs.exe
367    /// $ file qjs.exe
368    /// qjs.exe: ELF 64-bit LSB pie executable, x86-64 ...
369    /// ```
370    ///
371    /// ## Cross-compilation
372    ///
373    /// Accepted target triple values must follow the
374    /// ['target_lexicon'](https://crates.io/crates/target-lexicon) crate format.
375    ///
376    /// The recommended targets we try to support are:
377    ///
378    /// - "x86_64-linux-gnu"
379    /// - "aarch64-linux-gnu"
380    /// - "x86_64-apple-darwin"
381    /// - "arm64-apple-darwin"
382    #[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
383    #[clap(name = "create-exe", verbatim_doc_comment)]
384    CreateExe(CreateExe),
385
386    /// Compile a WebAssembly binary into an object file
387    ///
388    /// To use, you need to set the `WASMER_DIR` environment variable to the location of your
389    /// Wasmer installation. This will probably be `~/.wasmer`. It should include a `lib`,
390    /// `include` and `bin` subdirectories. To create an object you will need `libwasmer`, so by
391    /// setting `WASMER_DIR` the CLI knows where to look for header files and libraries.
392    ///
393    /// Example usage:
394    ///
395    /// ```text
396    /// $ # in two lines:
397    /// $ export WASMER_DIR=/home/user/.wasmer/
398    /// $ wasmer create-obj qjs.wasm --object-format symbols -o qjs.obj # or in one line:
399    /// $ WASMER_DIR=/home/user/.wasmer/ wasmer create-exe qjs.wasm --object-format symbols -o qjs.obj
400    /// $ file qjs.obj
401    /// qjs.obj: ELF 64-bit LSB relocatable, x86-64 ...
402    /// ```
403    ///
404    /// ## Cross-compilation
405    ///
406    /// Accepted target triple values must follow the
407    /// ['target_lexicon'](https://crates.io/crates/target-lexicon) crate format.
408    ///
409    /// The recommended targets we try to support are:
410    ///
411    /// - "x86_64-linux-gnu"
412    /// - "aarch64-linux-gnu"
413    /// - "x86_64-apple-darwin"
414    /// - "arm64-apple-darwin"
415    #[cfg(feature = "static-artifact-create")]
416    #[structopt(name = "create-obj", verbatim_doc_comment)]
417    CreateObj(CreateObj),
418
419    /// Generate the C static_defs.h header file for the input .wasm module
420    #[cfg(feature = "static-artifact-create")]
421    GenCHeader(GenCHeader),
422
423    /// Get various configuration information needed
424    /// to compile programs which use Wasmer
425    Config(Config),
426
427    /// Update wasmer to the latest version
428    #[clap(name = "self-update")]
429    SelfUpdate(SelfUpdate),
430
431    /// Inspect a WebAssembly file
432    Inspect(Inspect),
433
434    /// Initializes a new wasmer.toml file
435    #[clap(name = "init")]
436    Init(Init),
437
438    /// Run spec testsuite
439    #[cfg(feature = "wast")]
440    Wast(Wast),
441
442    /// Unregister and/or register wasmer as binfmt interpreter
443    #[cfg(target_os = "linux")]
444    Binfmt(Binfmt),
445
446    /// Shows the current logged in user for the current active registry
447    Whoami(Whoami),
448
449    /// Add a Wasmer package's bindings to your application
450    Add(CmdAdd),
451
452    /// Run a WebAssembly file or Wasmer container
453    #[clap(alias = "run-unstable")]
454    Run(Run),
455
456    /// Manage journals (compacting, inspecting, filtering, ...)
457    #[cfg(feature = "journal")]
458    #[clap(subcommand)]
459    Journal(CmdJournal),
460
461    #[clap(subcommand)]
462    Package(crate::commands::Package),
463
464    #[clap(subcommand)]
465    Container(crate::commands::Container),
466
467    // Edge commands
468    /// Deploy apps to Wasmer Edge [alias: app deploy]
469    Deploy(crate::commands::app::deploy::CmdAppDeploy),
470
471    /// Create and manage Wasmer Edge apps
472    #[clap(subcommand, alias = "apps")]
473    App(crate::commands::app::CmdApp),
474
475    /// Run commands/packages on Wasmer Edge in an interactive shell session
476    Ssh(crate::commands::ssh::CmdSsh),
477
478    /// Manage Wasmer namespaces
479    #[clap(subcommand, alias = "namespaces")]
480    Namespace(crate::commands::namespace::CmdNamespace),
481
482    /// Manage DNS records
483    #[clap(subcommand, alias = "domains")]
484    Domain(crate::commands::domain::CmdDomain),
485
486    /// Generate autocompletion for different shells
487    #[clap(name = "gen-completions")]
488    GenCompletions(crate::commands::gen_completions::CmdGenCompletions),
489
490    /// Generate man pages
491    #[clap(name = "gen-man", hide = true)]
492    GenManPage(crate::commands::gen_manpage::CmdGenManPage),
493}
494
495fn is_binfmt_interpreter() -> bool {
496    cfg_if::cfg_if! {
497        if #[cfg(target_os = "linux")] {
498            // Note: we'll be invoked by the kernel as Binfmt::FILENAME
499            let binary_path = match std::env::args_os().next() {
500                Some(path) => std::path::PathBuf::from(path),
501                None => return false,
502            };
503            binary_path.file_name().and_then(|f| f.to_str()) == Some(Binfmt::FILENAME)
504        } else {
505            false
506        }
507    }
508}
509
510fn print_version(verbose: bool) -> Result<(), anyhow::Error> {
511    if !verbose {
512        println!("wasmer {}", env!("CARGO_PKG_VERSION"));
513        return Ok(());
514    }
515
516    println!(
517        "wasmer {} ({} {})",
518        env!("CARGO_PKG_VERSION"),
519        env!("WASMER_BUILD_GIT_HASH_SHORT"),
520        env!("WASMER_BUILD_DATE")
521    );
522    println!("binary: {}", env!("CARGO_PKG_NAME"));
523    println!("commit-hash: {}", env!("WASMER_BUILD_GIT_HASH"));
524    println!("commit-date: {}", env!("WASMER_BUILD_DATE"));
525    println!("host: {}", target_lexicon::HOST);
526
527    let mut runtimes = Vec::<&'static str>::new();
528    if cfg!(feature = "singlepass") {
529        runtimes.push("singlepass");
530    }
531    if cfg!(feature = "cranelift") {
532        runtimes.push("cranelift");
533    }
534    if cfg!(feature = "llvm") {
535        runtimes.push("llvm");
536    }
537
538    if cfg!(feature = "wamr") {
539        runtimes.push("wamr");
540    }
541
542    if cfg!(feature = "wasmi") {
543        runtimes.push("wasmi");
544    }
545
546    if cfg!(feature = "v8") {
547        runtimes.push("v8");
548    }
549
550    println!("runtimes: {}", runtimes.join(", "));
551    Ok(())
552}