mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-08 05:38:19 +00:00
Adress review comments
This commit is contained in:
@@ -10,12 +10,10 @@ use crate::commands::CreateExe;
|
||||
use crate::commands::CreateObj;
|
||||
#[cfg(feature = "wast")]
|
||||
use crate::commands::Wast;
|
||||
use crate::commands::{Cache, Config, Inspect, Run, RunWithoutFile, SelfUpdate, Validate};
|
||||
use crate::commands::{Cache, Config, Inspect, List, Run, SelfUpdate, Validate};
|
||||
use crate::error::PrettyError;
|
||||
use clap::{CommandFactory, ErrorKind, Parser};
|
||||
use spinner::SpinnerHandle;
|
||||
use std::fmt;
|
||||
use wasmer_registry::{get_all_local_packages, PackageDownloadInfo};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[cfg_attr(
|
||||
@@ -40,7 +38,7 @@ use wasmer_registry::{get_all_local_packages, PackageDownloadInfo};
|
||||
enum WasmerCLIOptions {
|
||||
/// List all locally installed packages
|
||||
#[clap(name = "list")]
|
||||
List,
|
||||
List(List),
|
||||
|
||||
/// Run a WebAssembly file. Formats accepted: wasm, wat
|
||||
#[clap(name = "run")]
|
||||
@@ -165,7 +163,7 @@ impl WasmerCLIOptions {
|
||||
Self::CreateObj(create_obj) => create_obj.execute(),
|
||||
Self::Config(config) => config.execute(),
|
||||
Self::Inspect(inspect) => inspect.execute(),
|
||||
Self::List => print_packages(),
|
||||
Self::List(list) => list.execute(),
|
||||
#[cfg(feature = "wast")]
|
||||
Self::Wast(wast) => wast.execute(),
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -239,206 +237,19 @@ fn wasmer_main_inner() -> Result<(), anyhow::Error> {
|
||||
|
||||
// Check if the file is a package name
|
||||
if let WasmerCLIOptions::Run(r) = &options {
|
||||
return try_run_package_or_file(&args, r);
|
||||
return crate::commands::try_run_package_or_file(&args, r);
|
||||
}
|
||||
|
||||
options.execute()
|
||||
}
|
||||
|
||||
fn try_run_package_or_file(args: &[String], r: &Run) -> Result<(), anyhow::Error> {
|
||||
// Check "r.path" is a file or a package / command name
|
||||
if r.path.exists() {
|
||||
if r.path.is_dir() && r.path.join("wapm.toml").exists() {
|
||||
let mut args_without_package = args.to_vec();
|
||||
if args_without_package.get(1) == Some(&format!("{}", r.path.display())) {
|
||||
let _ = args_without_package.remove(1);
|
||||
} else if args_without_package.get(2) == Some(&format!("{}", r.path.display())) {
|
||||
let _ = args_without_package.remove(1);
|
||||
let _ = args_without_package.remove(1);
|
||||
}
|
||||
return RunWithoutFile::try_parse_from(args_without_package.iter())?
|
||||
.into_run_args(r.path.clone(), r.command_name.as_deref())?
|
||||
.execute();
|
||||
}
|
||||
return r.execute();
|
||||
}
|
||||
|
||||
let package = format!("{}", r.path.display());
|
||||
|
||||
let mut is_fake_sv = false;
|
||||
let mut sv = match split_version(&package) {
|
||||
Ok(o) => o,
|
||||
Err(_) => {
|
||||
let mut fake_sv = SplitVersion {
|
||||
original: package.to_string(),
|
||||
registry: None,
|
||||
package: package.to_string(),
|
||||
version: None,
|
||||
command: None,
|
||||
};
|
||||
is_fake_sv = true;
|
||||
match try_lookup_command(&mut fake_sv) {
|
||||
Ok(o) => SplitVersion {
|
||||
original: format!("{}@{}", o.package, o.version),
|
||||
registry: None,
|
||||
package: o.package,
|
||||
version: Some(o.version),
|
||||
command: r.command_name.clone(),
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(
|
||||
anyhow::anyhow!("No package for command {package:?} found, file {package:?} not found either")
|
||||
.context(e)
|
||||
.context(anyhow::anyhow!("{}", r.path.display()))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if sv.command.is_none() {
|
||||
sv.command = r.command_name.clone();
|
||||
}
|
||||
|
||||
if sv.command.is_none() && is_fake_sv {
|
||||
sv.command = Some(package);
|
||||
}
|
||||
|
||||
let mut package_download_info = None;
|
||||
if !sv.package.contains('/') {
|
||||
if let Ok(o) = try_lookup_command(&mut sv) {
|
||||
package_download_info = Some(o);
|
||||
}
|
||||
}
|
||||
|
||||
match try_execute_local_package(args, &sv) {
|
||||
Ok(o) => return Ok(o),
|
||||
Err(ExecuteLocalPackageError::DuringExec(e)) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
println!("finding local package {} failed", sv);
|
||||
// else: local package not found - try to download and install package
|
||||
try_autoinstall_package(args, &sv, package_download_info, r.force_install)
|
||||
}
|
||||
|
||||
fn try_lookup_command(sv: &mut SplitVersion) -> Result<PackageDownloadInfo, anyhow::Error> {
|
||||
use std::io::Write;
|
||||
let sp = start_spinner(format!("Looking up command {} ...", sv.package));
|
||||
|
||||
for registry in wasmer_registry::get_all_available_registries().unwrap_or_default() {
|
||||
let result = wasmer_registry::query_command_from_registry(®istry, &sv.package);
|
||||
print!("\r");
|
||||
let _ = std::io::stdout().flush();
|
||||
let command = sv.package.clone();
|
||||
if let Ok(o) = result {
|
||||
sv.package = o.package.clone();
|
||||
sv.version = Some(o.version.clone());
|
||||
sv.command = Some(command);
|
||||
return Ok(o);
|
||||
}
|
||||
}
|
||||
|
||||
sp.close();
|
||||
print!("\r");
|
||||
let _ = std::io::stdout().flush();
|
||||
Err(anyhow::anyhow!("command {sv} not found"))
|
||||
}
|
||||
|
||||
// We need to distinguish between errors that happen
|
||||
// before vs. during execution
|
||||
enum ExecuteLocalPackageError {
|
||||
BeforeExec(anyhow::Error),
|
||||
DuringExec(anyhow::Error),
|
||||
}
|
||||
|
||||
fn try_execute_local_package(
|
||||
args: &[String],
|
||||
sv: &SplitVersion,
|
||||
) -> Result<(), ExecuteLocalPackageError> {
|
||||
let package = wasmer_registry::get_local_package(None, &sv.package, sv.version.as_deref())
|
||||
.ok_or_else(|| {
|
||||
ExecuteLocalPackageError::BeforeExec(anyhow::anyhow!("no local package {sv:?} found"))
|
||||
})?;
|
||||
|
||||
let package_dir = package
|
||||
.get_path()
|
||||
.map_err(|e| ExecuteLocalPackageError::BeforeExec(anyhow::anyhow!("{e}")))?;
|
||||
|
||||
// Try finding the local package
|
||||
let mut args_without_package = args.to_vec();
|
||||
|
||||
// remove either "run" or $package
|
||||
args_without_package.remove(1);
|
||||
|
||||
// "wasmer package arg1 arg2" => "wasmer arg1 arg2"
|
||||
if (args_without_package.get(1).is_some() && args_without_package[1].starts_with(&sv.original))
|
||||
|| (sv.command.is_some() && args_without_package[1].ends_with(sv.command.as_ref().unwrap()))
|
||||
{
|
||||
args_without_package.remove(1);
|
||||
}
|
||||
|
||||
RunWithoutFile::try_parse_from(args_without_package.iter())
|
||||
.map_err(|e| ExecuteLocalPackageError::DuringExec(e.into()))?
|
||||
.into_run_args(package_dir, sv.command.as_deref())
|
||||
.map_err(ExecuteLocalPackageError::DuringExec)?
|
||||
.execute()
|
||||
.map_err(|e| ExecuteLocalPackageError::DuringExec(e.context(anyhow::anyhow!("{}", sv))))
|
||||
}
|
||||
|
||||
fn try_autoinstall_package(
|
||||
args: &[String],
|
||||
sv: &SplitVersion,
|
||||
package: Option<PackageDownloadInfo>,
|
||||
force_install: bool,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
use std::io::Write;
|
||||
let sp = start_spinner(format!("Installing package {} ...", sv.package));
|
||||
let v = sv.version.as_deref();
|
||||
let result = wasmer_registry::install_package(
|
||||
sv.registry.as_deref(),
|
||||
&sv.package,
|
||||
v,
|
||||
package,
|
||||
force_install,
|
||||
);
|
||||
sp.close();
|
||||
print!("\r");
|
||||
let _ = std::io::stdout().flush();
|
||||
let (_, package_dir) = match result {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
return Err(anyhow::anyhow!("{e}"));
|
||||
}
|
||||
};
|
||||
|
||||
// Try auto-installing the remote package
|
||||
let mut args_without_package = args.to_vec();
|
||||
args_without_package.remove(1);
|
||||
|
||||
let mut run_args = RunWithoutFile::try_parse_from(args_without_package.iter())?;
|
||||
run_args.command_name = sv.command.clone();
|
||||
|
||||
run_args
|
||||
.into_run_args(package_dir, sv.command.as_deref())?
|
||||
.execute()
|
||||
}
|
||||
|
||||
fn start_spinner(msg: String) -> SpinnerHandle {
|
||||
spinner::SpinnerBuilder::new(msg)
|
||||
.spinner(vec![
|
||||
"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷", " ", "⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈",
|
||||
])
|
||||
.start()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct SplitVersion {
|
||||
original: String,
|
||||
registry: Option<String>,
|
||||
package: String,
|
||||
version: Option<String>,
|
||||
command: Option<String>,
|
||||
pub(crate) struct SplitVersion {
|
||||
pub(crate) original: String,
|
||||
pub(crate) registry: Option<String>,
|
||||
pub(crate) package: String,
|
||||
pub(crate) version: Option<String>,
|
||||
pub(crate) command: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for SplitVersion {
|
||||
@@ -521,139 +332,107 @@ fn test_split_version() {
|
||||
);
|
||||
}
|
||||
|
||||
fn split_version(s: &str) -> Result<SplitVersion, anyhow::Error> {
|
||||
let command = WasmerCLIOptions::command();
|
||||
let mut prohibited_package_names = command.get_subcommands().map(|s| s.get_name());
|
||||
impl SplitVersion {
|
||||
pub fn new(s: &str) -> Result<SplitVersion, anyhow::Error> {
|
||||
let command = WasmerCLIOptions::command();
|
||||
let mut prohibited_package_names = command.get_subcommands().map(|s| s.get_name());
|
||||
|
||||
let re1 = regex::Regex::new(r#"(.*)/(.*)@(.*):(.*)"#).unwrap();
|
||||
let re2 = regex::Regex::new(r#"(.*)/(.*)@(.*)"#).unwrap();
|
||||
let re3 = regex::Regex::new(r#"(.*)/(.*)"#).unwrap();
|
||||
let re4 = regex::Regex::new(r#"(.*)/(.*):(.*)"#).unwrap();
|
||||
let re1 = regex::Regex::new(r#"(.*)/(.*)@(.*):(.*)"#).unwrap();
|
||||
let re2 = regex::Regex::new(r#"(.*)/(.*)@(.*)"#).unwrap();
|
||||
let re3 = regex::Regex::new(r#"(.*)/(.*)"#).unwrap();
|
||||
let re4 = regex::Regex::new(r#"(.*)/(.*):(.*)"#).unwrap();
|
||||
|
||||
let mut no_version = false;
|
||||
let mut no_version = false;
|
||||
|
||||
let captures = if re1.is_match(s) {
|
||||
re1.captures(s)
|
||||
.map(|c| {
|
||||
c.iter()
|
||||
.flatten()
|
||||
.map(|m| m.as_str().to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
} else if re2.is_match(s) {
|
||||
re2.captures(s)
|
||||
.map(|c| {
|
||||
c.iter()
|
||||
.flatten()
|
||||
.map(|m| m.as_str().to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
} else if re4.is_match(s) {
|
||||
no_version = true;
|
||||
re4.captures(s)
|
||||
.map(|c| {
|
||||
c.iter()
|
||||
.flatten()
|
||||
.map(|m| m.as_str().to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
} else if re3.is_match(s) {
|
||||
re3.captures(s)
|
||||
.map(|c| {
|
||||
c.iter()
|
||||
.flatten()
|
||||
.map(|m| m.as_str().to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Invalid package version: {s:?}"));
|
||||
};
|
||||
|
||||
let mut namespace = match captures.get(1).cloned() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid package version: {s:?}: no namespace"
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let name = match captures.get(2).cloned() {
|
||||
Some(s) => s,
|
||||
None => return Err(anyhow::anyhow!("Invalid package version: {s:?}: no name")),
|
||||
};
|
||||
|
||||
let mut registry = None;
|
||||
if namespace.contains('/') {
|
||||
let (r, n) = namespace.rsplit_once('/').unwrap();
|
||||
let mut real_registry = r.to_string();
|
||||
if !real_registry.ends_with("graphql") {
|
||||
real_registry = format!("{real_registry}/graphql");
|
||||
}
|
||||
if !real_registry.contains("://") {
|
||||
real_registry = format!("https://{real_registry}");
|
||||
}
|
||||
registry = Some(real_registry);
|
||||
namespace = n.to_string();
|
||||
}
|
||||
|
||||
let sv = SplitVersion {
|
||||
original: s.to_string(),
|
||||
registry,
|
||||
package: format!("{namespace}/{name}"),
|
||||
version: if no_version {
|
||||
None
|
||||
} else {
|
||||
captures.get(3).cloned()
|
||||
},
|
||||
command: captures.get(if no_version { 3 } else { 4 }).cloned(),
|
||||
};
|
||||
|
||||
let svp = sv.package.clone();
|
||||
anyhow::ensure!(
|
||||
!prohibited_package_names.any(|s| s == sv.package.trim()),
|
||||
"Invalid package name {svp:?}"
|
||||
);
|
||||
|
||||
Ok(sv)
|
||||
}
|
||||
|
||||
fn print_packages() -> Result<(), anyhow::Error> {
|
||||
use prettytable::{format, row, Table};
|
||||
|
||||
let rows = get_all_local_packages(None)
|
||||
.into_iter()
|
||||
.filter_map(|pkg| {
|
||||
let package_root_path = pkg.get_path().ok()?;
|
||||
let (manifest, _) =
|
||||
wasmer_registry::get_executable_file_from_path(&package_root_path, None).ok()?;
|
||||
let commands = manifest
|
||||
.command
|
||||
let captures = if re1.is_match(s) {
|
||||
re1.captures(s)
|
||||
.map(|c| {
|
||||
c.iter()
|
||||
.flatten()
|
||||
.map(|m| m.as_str().to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|c| c.get_name())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" \r\n");
|
||||
} else if re2.is_match(s) {
|
||||
re2.captures(s)
|
||||
.map(|c| {
|
||||
c.iter()
|
||||
.flatten()
|
||||
.map(|m| m.as_str().to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
} else if re4.is_match(s) {
|
||||
no_version = true;
|
||||
re4.captures(s)
|
||||
.map(|c| {
|
||||
c.iter()
|
||||
.flatten()
|
||||
.map(|m| m.as_str().to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
} else if re3.is_match(s) {
|
||||
re3.captures(s)
|
||||
.map(|c| {
|
||||
c.iter()
|
||||
.flatten()
|
||||
.map(|m| m.as_str().to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Invalid package version: {s:?}"));
|
||||
};
|
||||
|
||||
Some(row![pkg.registry, pkg.name, pkg.version, commands])
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut namespace = match captures.get(1).cloned() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid package version: {s:?}: no namespace"
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let empty_table = rows.is_empty();
|
||||
let mut table = Table::init(rows);
|
||||
table.set_titles(row!["Registry", "Package", "Version", "Commands"]);
|
||||
table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
|
||||
table.set_format(*format::consts::FORMAT_NO_COLSEP);
|
||||
if empty_table {
|
||||
table.add_empty_row();
|
||||
let name = match captures.get(2).cloned() {
|
||||
Some(s) => s,
|
||||
None => return Err(anyhow::anyhow!("Invalid package version: {s:?}: no name")),
|
||||
};
|
||||
|
||||
let mut registry = None;
|
||||
if namespace.contains('/') {
|
||||
let (r, n) = namespace.rsplit_once('/').unwrap();
|
||||
let mut real_registry = r.to_string();
|
||||
if !real_registry.ends_with("graphql") {
|
||||
real_registry = format!("{real_registry}/graphql");
|
||||
}
|
||||
if !real_registry.contains("://") {
|
||||
real_registry = format!("https://{real_registry}");
|
||||
}
|
||||
registry = Some(real_registry);
|
||||
namespace = n.to_string();
|
||||
}
|
||||
|
||||
let sv = SplitVersion {
|
||||
original: s.to_string(),
|
||||
registry,
|
||||
package: format!("{namespace}/{name}"),
|
||||
version: if no_version {
|
||||
None
|
||||
} else {
|
||||
captures.get(3).cloned()
|
||||
},
|
||||
command: captures.get(if no_version { 3 } else { 4 }).cloned(),
|
||||
};
|
||||
|
||||
let svp = sv.package.clone();
|
||||
anyhow::ensure!(
|
||||
!prohibited_package_names.any(|s| s == sv.package.trim()),
|
||||
"Invalid package name {svp:?}"
|
||||
);
|
||||
|
||||
Ok(sv)
|
||||
}
|
||||
let _ = table.printstd();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_help(verbose: bool) -> Result<(), anyhow::Error> {
|
||||
|
||||
Reference in New Issue
Block a user