mod add;
mod app;
mod auth;
#[cfg(target_os = "linux")]
mod binfmt;
mod cache;
#[cfg(feature = "compiler")]
mod compile;
mod config;
mod connect;
mod container;
mod cron;
pub(crate) mod domain;
mod gen_completions;
mod gen_manpage;
mod init;
mod inspect;
#[cfg(feature = "journal")]
mod journal;
pub(crate) mod namespace;
mod package;
mod run;
mod self_update;
pub mod ssh;
mod validate;
#[cfg(feature = "wast")]
mod wast;
use itertools::Itertools;
use std::io::IsTerminal as _;
use tokio::task::JoinHandle;
#[cfg(target_os = "linux")]
pub use binfmt::*;
use clap::{CommandFactory, Parser};
#[cfg(feature = "compiler")]
pub use compile::*;
#[cfg(feature = "wast")]
pub use wast::*;
#[cfg(feature = "journal")]
pub use self::journal::*;
pub use self::{
add::*, auth::*, cache::*, config::*, container::*, init::*, inspect::*, package::*,
publish::*, run::Run, self_update::*, validate::*,
};
use crate::error::PrettyError;
use git_version::git_version;
pub(crate) trait CliCommand {
type Output;
fn run(self) -> Result<(), anyhow::Error>;
}
#[async_trait::async_trait]
pub(crate) trait AsyncCliCommand: Send + Sync {
type Output: Send + Sync;
async fn run_async(self) -> Result<Self::Output, anyhow::Error>;
fn setup(
&self,
done: tokio::sync::oneshot::Receiver<()>,
) -> Option<JoinHandle<anyhow::Result<()>>> {
if std::io::stdin().is_terminal() {
return Some(tokio::task::spawn(async move {
tokio::select! {
_ = done => {}
_ = tokio::signal::ctrl_c() => {
let term = console::Term::stdout();
let _ = term.show_cursor();
#[cfg(target_os = "windows")]
std::process::exit(3);
#[cfg(not(target_os = "windows"))]
std::process::exit(130);
}
}
Ok::<(), anyhow::Error>(())
}));
}
None
}
}
impl<O: Send + Sync, C: AsyncCliCommand<Output = O>> CliCommand for C {
type Output = O;
fn run(self) -> Result<(), anyhow::Error> {
tokio::runtime::Runtime::new()?.block_on(async {
let (snd, rcv) = tokio::sync::oneshot::channel();
let handle = self.setup(rcv);
if let Err(e) = AsyncCliCommand::run_async(self).await {
if let Some(handle) = handle {
handle.abort();
}
return Err(e);
}
if let Some(handle) = handle {
if snd.send(()).is_err() {
tracing::warn!("Failed to send 'done' signal to setup thread!");
handle.abort();
} else {
handle.await??;
}
}
Ok::<(), anyhow::Error>(())
})?;
Ok(())
}
}
#[derive(clap::Parser, Debug)]
#[clap(author, version)]
#[clap(disable_version_flag = true)] #[cfg_attr(feature = "headless", clap(
name = "wasmer-headless",
about = concat!("wasmer-headless ", env!("CARGO_PKG_VERSION")),
))]
#[cfg_attr(not(feature = "headless"), clap(
name = "wasmer",
about = concat!("wasmer ", env!("CARGO_PKG_VERSION")),
))]
pub struct WasmerCmd {
#[clap(short = 'V', long)]
version: bool,
#[clap(flatten)]
output: crate::logging::Output,
#[clap(subcommand)]
cmd: Option<Cmd>,
}
impl WasmerCmd {
fn execute(self) -> Result<(), anyhow::Error> {
let WasmerCmd {
cmd,
version,
output,
} = self;
output.initialize_logging();
if version {
return print_version(output.is_verbose());
}
match cmd {
Some(Cmd::GenManPage(cmd)) => cmd.execute(),
Some(Cmd::GenCompletions(cmd)) => cmd.execute(),
Some(Cmd::Run(options)) => options.execute(output),
Some(Cmd::SelfUpdate(options)) => options.execute(),
Some(Cmd::Cache(cache)) => cache.execute(),
Some(Cmd::Validate(validate)) => validate.execute(),
#[cfg(feature = "compiler")]
Some(Cmd::Compile(compile)) => compile.execute(),
Some(Cmd::Config(config)) => config.run(),
Some(Cmd::Inspect(inspect)) => inspect.execute(),
Some(Cmd::Init(init)) => init.run(),
Some(Cmd::Login(login)) => login.run(),
Some(Cmd::Auth(auth)) => auth.run(),
Some(Cmd::Publish(publish)) => publish.run().map(|_| ()),
Some(Cmd::Package(cmd)) => match cmd {
Package::Download(cmd) => cmd.execute(),
Package::Build(cmd) => cmd.execute().map(|_| ()),
Package::Tag(cmd) => cmd.run(),
Package::Push(cmd) => cmd.run(),
Package::Publish(cmd) => cmd.run().map(|_| ()),
Package::Tree(cmd) => cmd.run(),
Package::Unpack(cmd) => cmd.execute(),
Package::Search(cmd) => cmd.run(),
Package::Get(cmd) => cmd.run(),
},
Some(Cmd::Container(cmd)) => match cmd {
crate::commands::Container::Unpack(cmd) => cmd.execute(),
},
#[cfg(feature = "wast")]
Some(Cmd::Wast(wast)) => wast.execute(),
#[cfg(target_os = "linux")]
Some(Cmd::Binfmt(binfmt)) => binfmt.execute(),
Some(Cmd::Whoami(whoami)) => whoami.run(),
Some(Cmd::Add(add)) => add.run(),
Some(Cmd::Deploy(c)) => c.run(),
Some(Cmd::App(apps)) => apps.run(),
Some(Cmd::Cron(cron)) => cron.run(),
#[cfg(feature = "journal")]
Some(Cmd::Journal(journal)) => journal.run(),
Some(Cmd::Ssh(ssh)) => ssh.run(),
Some(Cmd::Namespace(namespace)) => namespace.run(),
Some(Cmd::Domain(namespace)) => namespace.run(),
None => {
WasmerCmd::command().print_long_help()?;
std::process::exit(2);
}
}
}
pub fn run() {
#[cfg(windows)]
colored::control::set_virtual_terminal(true).unwrap();
PrettyError::report(Self::run_inner())
}
fn run_inner() -> Result<(), anyhow::Error> {
let mut args_os = std::env::args_os();
let args = args_os.next().into_iter();
let mut binfmt_args = Vec::new();
if is_binfmt_interpreter() {
let current_dir = std::env::current_dir().unwrap();
let mut mount_paths = ["/home", "/etc", "/tmp", "/var", "/nix", "/opt", "/root"]
.into_iter()
.map(std::path::PathBuf::from)
.filter(|path| {
if !path.is_dir() {
return false;
}
if std::fs::read_dir(path).is_err() {
return false;
}
true
})
.collect_vec();
if mount_paths
.iter()
.all(|path| !current_dir.starts_with(path))
{
mount_paths.push(current_dir.clone());
}
binfmt_args.push("run".into());
binfmt_args.push("--net".into());
binfmt_args.push("--forward-host-env".into());
for mount_path in mount_paths {
if let Some(mount_path_str) = mount_path.to_str() {
binfmt_args.push(format!("--volume={mount_path_str}:{mount_path_str}").into());
}
}
if let Some(current_dir_str) = current_dir.to_str() {
binfmt_args.push(format!("--cwd={current_dir_str}").into());
}
binfmt_args.push("--quiet".into());
binfmt_args.push("--".into());
binfmt_args.push(args_os.next().unwrap());
args_os.next().unwrap();
};
let args_vec = args.chain(binfmt_args).chain(args_os).collect_vec();
match WasmerCmd::try_parse_from(args_vec.iter()) {
Ok(args) => args.execute(),
Err(e) => {
let first_arg_is_subcommand = if let Some(first_arg) = args_vec.get(1) {
let mut ret = false;
let cmd = WasmerCmd::command();
for cmd in cmd.get_subcommands() {
if cmd.get_name() == first_arg {
ret = true;
break;
}
}
ret
} else {
false
};
let might_be_wasmer_run = matches!(
e.kind(),
clap::error::ErrorKind::InvalidSubcommand
| clap::error::ErrorKind::UnknownArgument
) && !first_arg_is_subcommand;
if might_be_wasmer_run && let Ok(run) = Run::try_parse_from(args_vec.iter()) {
let output = crate::logging::Output::default();
output.initialize_logging();
run.execute(output);
}
e.exit();
}
}
}
}
#[derive(clap::Parser, Debug)]
#[allow(clippy::large_enum_variant)]
enum Cmd {
Login(Login),
#[clap(subcommand)]
Auth(CmdAuth),
#[clap(name = "publish")]
Publish(PackagePublish),
Cache(Cache),
Validate(Validate),
#[cfg(feature = "compiler")]
Compile(Compile),
Config(Config),
#[clap(name = "self-update")]
SelfUpdate(SelfUpdate),
Inspect(Inspect),
#[clap(name = "init")]
Init(Init),
#[cfg(feature = "wast")]
Wast(Wast),
#[cfg(target_os = "linux")]
Binfmt(Binfmt),
Whoami(Whoami),
Add(CmdAdd),
#[clap(alias = "run-unstable")]
Run(Run),
#[cfg(feature = "journal")]
#[clap(subcommand)]
Journal(CmdJournal),
#[clap(subcommand)]
Package(crate::commands::Package),
#[clap(subcommand)]
Container(crate::commands::Container),
Deploy(crate::commands::app::deploy::CmdAppDeploy),
#[clap(subcommand, alias = "apps")]
App(crate::commands::app::CmdApp),
#[clap(subcommand)]
Cron(crate::commands::cron::CmdCron),
Ssh(crate::commands::ssh::CmdSsh),
#[clap(subcommand, alias = "namespaces")]
Namespace(crate::commands::namespace::CmdNamespace),
#[clap(subcommand, alias = "domains")]
Domain(crate::commands::domain::CmdDomain),
#[clap(name = "gen-completions")]
GenCompletions(crate::commands::gen_completions::CmdGenCompletions),
#[clap(name = "gen-man", hide = true)]
GenManPage(crate::commands::gen_manpage::CmdGenManPage),
}
fn is_binfmt_interpreter() -> bool {
cfg_select! {
target_os = "linux" => {
let binary_path = match std::env::args_os().next() {
Some(path) => std::path::PathBuf::from(path),
None => return false,
};
binary_path.file_name().and_then(|f| f.to_str()) == Some(Binfmt::FILENAME)
}
_ => {
false
}
}
}
fn print_version(verbose: bool) -> Result<(), anyhow::Error> {
if !verbose {
println!("wasmer {}", env!("CARGO_PKG_VERSION"));
return Ok(());
}
println!("wasmer {}", env!("CARGO_PKG_VERSION"));
println!("binary: {}", env!("CARGO_PKG_NAME"));
let git_hash = git_version!(
args = [
"--abbrev=40",
"--always",
"--dirty=-modified",
"--exclude=*"
],
fallback = "",
)
.to_string();
if !git_hash.is_empty() {
println!("commit-hash: {git_hash}",);
}
if !env!("WASMER_REPRODUCIBLE_BUILD")
.parse::<bool>()
.expect("build-time variable expected")
{
println!("commit-date: {}", env!("WASMER_BUILD_DATE"));
}
println!("host: {}", target_lexicon::HOST);
let cpu_features = wasmer_types::target::CpuFeature::for_host()
.iter()
.map(|f| f.to_string())
.join(" ");
println!("CPU flags: {cpu_features}");
let mut runtimes = Vec::new();
if cfg!(feature = "singlepass") {
runtimes.push("Singlepass");
}
if cfg!(feature = "cranelift") {
runtimes.push("Cranelift");
}
if cfg!(feature = "llvm") {
runtimes.push("LLVM");
}
if cfg!(feature = "v8") {
runtimes.push("V8");
}
println!("runtimes: {}", runtimes.join(", "));
#[allow(clippy::useless_vec)]
#[allow(unused_mut)]
let mut features = vec!["wasix".to_string()];
#[cfg(feature = "napi-v8")]
{
for napi_version in enum_iterator::all::<wasmer_napi::NapiVersion>() {
if !matches!(napi_version, wasmer_napi::NapiVersion::Unknown) {
features.push(napi_version.to_string());
}
}
features.push(wasmer_napi::NAPI_EXTENSION_WASMER_MODULE_NAME.to_string());
}
println!("features: {}", features.join(", "));
Ok(())
}