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 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
59/// An executable CLI command.
60pub(crate) trait CliCommand {
61    type Output;
62
63    fn run(self) -> Result<(), anyhow::Error>;
64}
65
66/// An executable CLI command that runs in an async context.
67///
68/// An [`AsyncCliCommand`] automatically implements [`CliCommand`] by creating
69/// a new tokio runtime and blocking.
70#[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                        // https://learn.microsoft.com/en-us/cpp/c-runtime-library/signal-constants
89                        #[cfg(target_os = "windows")]
90                        std::process::exit(3);
91
92                        // POSIX compliant OSs: 128 + SIGINT (2)
93                        #[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/// Command-line arguments for the Wasmer CLI.
138#[derive(clap::Parser, Debug)]
139#[clap(author, version)]
140#[clap(disable_version_flag = true)] // handled manually
141#[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    /// Print version info and exit.
151    #[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            // Deploy commands.
213            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                // Note: clap uses an exit code of 2 when CLI parsing fails
223                std::process::exit(2);
224            }
225        }
226    }
227
228    /// The main function for the Wasmer CLI tool.
229    pub fn run() {
230        // We allow windows to print properly colors
231        #[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            // In case of binfmt misc the first argument is wasmer-binfmt-interpreter, the second is the full path to the executable
245            // and the third is the original string for the executable as originally called by the user.
246
247            // For now we are only using the real path and ignoring the original executable name.
248            // 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.
249
250            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                        // Not a directory
261                        return false;
262                    }
263                    if std::fs::read_dir(path).is_err() {
264                        // No permissions
265                        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            // TODO: This does not seem to work, needs further investigation.
277            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                    // Try to parse the command using the `wasmer some/package`
316                    // shorthand. Note that this has discoverability issues
317                    // because it's not shown as part of the main argument
318                    // parser's help, but that's fine.
319                    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)]
332/// The options for the wasmer Command Line Interface
333enum Cmd {
334    /// Login into Wasmer
335    Login(Login),
336
337    #[clap(subcommand)]
338    Auth(CmdAuth),
339
340    /// Publish a package to a registry [alias: package publish]
341    #[clap(name = "publish")]
342    Publish(PackagePublish),
343
344    /// Manage the local Wasmer cache
345    Cache(Cache),
346
347    /// Validate a WebAssembly binary
348    Validate(Validate),
349
350    /// Compile a WebAssembly binary
351    #[cfg(feature = "compiler")]
352    Compile(Compile),
353
354    /// Compile a WebAssembly binary into a native executable
355    ///
356    /// To use, you need to set the `WASMER_DIR` environment variable
357    /// to the location of your Wasmer installation. This will probably be `~/.wasmer`. It
358    /// should include a `lib`, `include` and `bin` subdirectories. To create an executable
359    /// you will need `libwasmer`, so by setting `WASMER_DIR` the CLI knows where to look for
360    /// header files and libraries.
361    ///
362    /// Example usage:
363    ///
364    /// ```text
365    /// $ # in two lines:
366    /// $ export WASMER_DIR=/home/user/.wasmer/
367    /// $ wasmer create-exe qjs.wasm -o qjs.exe # or in one line:
368    /// $ WASMER_DIR=/home/user/.wasmer/ wasmer create-exe qjs.wasm -o qjs.exe
369    /// $ file qjs.exe
370    /// qjs.exe: ELF 64-bit LSB pie executable, x86-64 ...
371    /// ```
372    ///
373    /// ## Cross-compilation
374    ///
375    /// Accepted target triple values must follow the
376    /// ['target_lexicon'](https://crates.io/crates/target-lexicon) crate format.
377    ///
378    /// The recommended targets we try to support are:
379    ///
380    /// - "x86_64-linux-gnu"
381    /// - "aarch64-linux-gnu"
382    /// - "x86_64-apple-darwin"
383    /// - "arm64-apple-darwin"
384    #[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
385    #[clap(name = "create-exe", verbatim_doc_comment)]
386    CreateExe(CreateExe),
387
388    /// Compile a WebAssembly binary into an object file
389    ///
390    /// To use, you need to set the `WASMER_DIR` environment variable to the location of your
391    /// Wasmer installation. This will probably be `~/.wasmer`. It should include a `lib`,
392    /// `include` and `bin` subdirectories. To create an object you will need `libwasmer`, so by
393    /// setting `WASMER_DIR` the CLI knows where to look for header files and libraries.
394    ///
395    /// Example usage:
396    ///
397    /// ```text
398    /// $ # in two lines:
399    /// $ export WASMER_DIR=/home/user/.wasmer/
400    /// $ wasmer create-obj qjs.wasm --object-format symbols -o qjs.obj # or in one line:
401    /// $ WASMER_DIR=/home/user/.wasmer/ wasmer create-exe qjs.wasm --object-format symbols -o qjs.obj
402    /// $ file qjs.obj
403    /// qjs.obj: ELF 64-bit LSB relocatable, x86-64 ...
404    /// ```
405    ///
406    /// ## Cross-compilation
407    ///
408    /// Accepted target triple values must follow the
409    /// ['target_lexicon'](https://crates.io/crates/target-lexicon) crate format.
410    ///
411    /// The recommended targets we try to support are:
412    ///
413    /// - "x86_64-linux-gnu"
414    /// - "aarch64-linux-gnu"
415    /// - "x86_64-apple-darwin"
416    /// - "arm64-apple-darwin"
417    #[cfg(feature = "static-artifact-create")]
418    #[structopt(name = "create-obj", verbatim_doc_comment)]
419    CreateObj(CreateObj),
420
421    /// Generate the C static_defs.h header file for the input .wasm module
422    #[cfg(feature = "static-artifact-create")]
423    GenCHeader(GenCHeader),
424
425    /// Get various configuration information needed
426    /// to compile programs which use Wasmer
427    Config(Config),
428
429    /// Update wasmer to the latest version
430    #[clap(name = "self-update")]
431    SelfUpdate(SelfUpdate),
432
433    /// Inspect a WebAssembly file
434    Inspect(Inspect),
435
436    /// Initializes a new wasmer.toml file
437    #[clap(name = "init")]
438    Init(Init),
439
440    /// Run spec testsuite
441    #[cfg(feature = "wast")]
442    Wast(Wast),
443
444    /// Unregister and/or register wasmer as binfmt interpreter
445    #[cfg(target_os = "linux")]
446    Binfmt(Binfmt),
447
448    /// Shows the current logged in user for the current active registry
449    Whoami(Whoami),
450
451    /// Add a Wasmer package's bindings to your application
452    Add(CmdAdd),
453
454    /// Run a WebAssembly file or Wasmer container
455    #[clap(alias = "run-unstable")]
456    Run(Run),
457
458    /// Manage journals (compacting, inspecting, filtering, ...)
459    #[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    // Edge commands
470    /// Deploy apps to Wasmer Edge [alias: app deploy]
471    Deploy(crate::commands::app::deploy::CmdAppDeploy),
472
473    /// Create and manage Wasmer Edge apps
474    #[clap(subcommand, alias = "apps")]
475    App(crate::commands::app::CmdApp),
476
477    /// Run commands/packages on Wasmer Edge in an interactive shell session
478    Ssh(crate::commands::ssh::CmdSsh),
479
480    /// Manage Wasmer namespaces
481    #[clap(subcommand, alias = "namespaces")]
482    Namespace(crate::commands::namespace::CmdNamespace),
483
484    /// Manage DNS records
485    #[clap(subcommand, alias = "domains")]
486    Domain(crate::commands::domain::CmdDomain),
487
488    /// Generate autocompletion for different shells
489    #[clap(name = "gen-completions")]
490    GenCompletions(crate::commands::gen_completions::CmdGenCompletions),
491
492    /// Generate man pages
493    #[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            // Note: we'll be invoked by the kernel as Binfmt::FILENAME
501            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}