Merge remote-tracking branch 'origin/master' into wasix

This commit is contained in:
Christoph Herzog
2022-12-13 01:40:22 +01:00
54 changed files with 1566 additions and 1559 deletions

View File

@@ -15,7 +15,6 @@ use crate::commands::{
};
use crate::error::PrettyError;
use clap::{CommandFactory, ErrorKind, Parser};
use std::{fmt, str::FromStr};
#[derive(Parser, Debug)]
#[cfg_attr(
@@ -243,218 +242,9 @@ fn wasmer_main_inner() -> Result<(), anyhow::Error> {
}
};
// Check if the file is a package name
if let WasmerCLIOptions::Run(r) = &options {
#[cfg(not(feature = "debug"))]
let debug = false;
#[cfg(feature = "debug")]
let debug = r.options.debug;
return crate::commands::try_run_package_or_file(&args, r, debug);
}
options.execute()
}
#[derive(Debug, Clone, PartialEq, Default)]
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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let version = self.version.as_deref().unwrap_or("latest");
let command = self
.command
.as_ref()
.map(|s| format!(":{s}"))
.unwrap_or_default();
write!(f, "{}@{version}{command}", self.package)
}
}
#[test]
fn test_split_version() {
assert_eq!(
SplitVersion::parse("registry.wapm.io/graphql/python/python").unwrap(),
SplitVersion {
original: "registry.wapm.io/graphql/python/python".to_string(),
registry: Some("https://registry.wapm.io/graphql".to_string()),
package: "python/python".to_string(),
version: None,
command: None,
}
);
assert_eq!(
SplitVersion::parse("registry.wapm.io/python/python").unwrap(),
SplitVersion {
original: "registry.wapm.io/python/python".to_string(),
registry: Some("https://registry.wapm.io/graphql".to_string()),
package: "python/python".to_string(),
version: None,
command: None,
}
);
assert_eq!(
SplitVersion::parse("namespace/name@version:command").unwrap(),
SplitVersion {
original: "namespace/name@version:command".to_string(),
registry: None,
package: "namespace/name".to_string(),
version: Some("version".to_string()),
command: Some("command".to_string()),
}
);
assert_eq!(
SplitVersion::parse("namespace/name@version").unwrap(),
SplitVersion {
original: "namespace/name@version".to_string(),
registry: None,
package: "namespace/name".to_string(),
version: Some("version".to_string()),
command: None,
}
);
assert_eq!(
SplitVersion::parse("namespace/name").unwrap(),
SplitVersion {
original: "namespace/name".to_string(),
registry: None,
package: "namespace/name".to_string(),
version: None,
command: None,
}
);
assert_eq!(
SplitVersion::parse("registry.wapm.io/namespace/name").unwrap(),
SplitVersion {
original: "registry.wapm.io/namespace/name".to_string(),
registry: Some("https://registry.wapm.io/graphql".to_string()),
package: "namespace/name".to_string(),
version: None,
command: None,
}
);
assert_eq!(
format!("{}", SplitVersion::parse("namespace").unwrap_err()),
"Invalid package version: \"namespace\"".to_string(),
);
}
impl SplitVersion {
pub fn parse(s: &str) -> Result<SplitVersion, anyhow::Error> {
s.parse()
}
}
impl FromStr for SplitVersion {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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 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_help(verbose: bool) -> Result<(), anyhow::Error> {
let mut cmd = WasmerCLIOptions::command();
if verbose {

View File

@@ -4,8 +4,6 @@ use anyhow::{Context, Error};
use clap::Parser;
use wasmer_registry::{Bindings, PartialWapmConfig, ProgrammingLanguage};
use crate::cli::SplitVersion;
/// Add a WAPM package's bindings to your application.
#[derive(Debug, Parser)]
pub struct Add {
@@ -26,7 +24,7 @@ pub struct Add {
pip: bool,
/// The packages to add (e.g. "wasmer/wasmer-pack@0.5.0" or "python/python")
#[clap(parse(try_from_str))]
packages: Vec<SplitVersion>,
packages: Vec<wasmer_registry::Package>,
}
impl Add {
@@ -103,11 +101,11 @@ impl Add {
fn lookup_bindings_for_package(
registry: &str,
pkg: &SplitVersion,
pkg: &wasmer_registry::Package,
language: &ProgrammingLanguage,
) -> Result<Bindings, Error> {
let all_bindings =
wasmer_registry::list_bindings(registry, &pkg.package, pkg.version.as_deref())?;
wasmer_registry::list_bindings(registry, &pkg.package(), pkg.version.as_deref())?;
match all_bindings.iter().find(|b| b.language == *language) {
Some(b) => {

View File

@@ -402,52 +402,34 @@ impl CreateExe {
let library = if let Some(v) = cross_subc.library_path.clone() {
v.canonicalize().unwrap_or(v)
} else {
{
let libwasmer_path = "lib/libwasmer.a";
let tarball_dir;
let filename = if let Some(local_tarball) = cross_subc.tarball.as_ref() {
let target_file_path = local_tarball
.parent()
.and_then(|parent| Some(parent.join(local_tarball.file_stem()?)))
.unwrap_or_else(|| local_tarball.clone());
let target_file_path = target_file_path
.parent()
.and_then(|parent| Some(parent.join(target_file_path.file_stem()?)))
.unwrap_or_else(|| target_file_path.clone());
let _ = std::fs::create_dir_all(&target_file_path);
let files = untar(local_tarball.clone(), target_file_path.clone())?;
tarball_dir = target_file_path.canonicalize().unwrap_or(target_file_path);
files.iter().find(|f| f.contains(libwasmer_path)).cloned().ok_or_else(|| {
anyhow!("Could not find libwasmer for {} target in the provided tarball path (files = {files:#?}, libwasmer_path = {libwasmer_path:?})", target)})?
let (filename, tarball_dir) =
if let Some(local_tarball) = cross_subc.tarball.as_ref() {
Self::find_filename(local_tarball, &target)
} else {
#[cfg(feature = "http")]
{
// check if the tarball for the target already exists locally
let local_tarball = std::fs::read_dir(get_libwasmer_cache_path()?)?
.filter_map(|e| e.ok())
.filter_map(|e| {
let path = format!("{}", e.path().display());
if path.ends_with(".tar.gz") {
Some(e.path())
} else {
None
}
})
.filter_map(|p| Self::filter_tarballs(&p, &target))
.next();
if let Some(local_tarball) = local_tarball.as_ref() {
Self::find_filename(local_tarball, &target)
} else {
let release = http_fetch::get_latest_release()?;
let tarball = http_fetch::download_release(release, target.clone())?;
let target_file_path = tarball
.parent()
.and_then(|parent| Some(parent.join(tarball.file_stem()?)))
.unwrap_or_else(|| tarball.clone());
let target_file_path = target_file_path
.parent()
.and_then(|parent| Some(parent.join(target_file_path.file_stem()?)))
.unwrap_or_else(|| target_file_path.clone());
tarball_dir = target_file_path
.canonicalize()
.unwrap_or_else(|_| target_file_path.clone());
let files = untar(tarball, target_file_path)?;
files.into_iter().find(|f| f.contains(libwasmer_path)).ok_or_else(|| {
anyhow!("Could not find libwasmer for {} target in the fetched release from Github: you can download it manually and specify its path with the --cross-compilation-library-path LIBRARY_PATH flag.", target)})?
Self::find_filename(&tarball, &target)
}
#[cfg(not(feature = "http"))]
return Err(anyhow!("This wasmer binary isn't compiled with an HTTP request library (feature flag `http`). To cross-compile, specify the path of the non-native libwasmer or release tarball with the --library-path LIBRARY_PATH or --tarball TARBALL_PATH flag."));
};
tarball_dir.join(&filename)
}
}?;
tarball_dir.join(&filename)
};
let ccs = CrossCompileSetup {
target,
@@ -460,6 +442,72 @@ impl CreateExe {
}
}
fn find_filename(
local_tarball: &Path,
target: &Triple,
) -> Result<(String, PathBuf), anyhow::Error> {
let target_file_path = local_tarball
.parent()
.and_then(|parent| Some(parent.join(local_tarball.file_stem()?)))
.unwrap_or_else(|| local_tarball.to_path_buf());
let target_file_path = target_file_path
.parent()
.and_then(|parent| Some(parent.join(target_file_path.file_stem()?)))
.unwrap_or_else(|| target_file_path.clone());
std::fs::create_dir_all(&target_file_path)
.map_err(|e| anyhow::anyhow!("{e}"))
.context(anyhow::anyhow!("{}", target_file_path.display()))?;
let files = untar(local_tarball.to_path_buf(), target_file_path.clone())?;
let tarball_dir = target_file_path.canonicalize().unwrap_or(target_file_path);
let file = files
.iter()
.find(|f| f.ends_with("libwasmer.a")).cloned()
.ok_or_else(|| {
anyhow!("Could not find libwasmer.a for {} target in the provided tarball path (files = {files:#?})", target)
})?;
Ok((file, tarball_dir))
}
fn filter_tarballs(p: &Path, target: &Triple) -> Option<PathBuf> {
if let Architecture::Aarch64(_) = target.architecture {
if !p.file_name()?.to_str()?.contains("aarch64") {
return None;
}
}
if let Architecture::X86_64 = target.architecture {
if !p.file_name()?.to_str()?.contains("x86_64") {
return None;
}
}
if let OperatingSystem::Windows = target.operating_system {
if !p.file_name()?.to_str()?.contains("windows") {
return None;
}
}
if let OperatingSystem::Darwin = target.operating_system {
if !(p.file_name()?.to_str()?.contains("apple")
|| p.file_name()?.to_str()?.contains("darwin"))
{
return None;
}
}
if let OperatingSystem::Linux = target.operating_system {
if !p.file_name()?.to_str()?.contains("linux") {
return None;
}
}
Some(p.to_path_buf())
}
fn compile_c(
&self,
wasm_object_path: PathBuf,
@@ -573,9 +621,6 @@ impl CreateExe {
}
cmd.arg("-lunwind");
cmd.arg("-OReleaseSafe");
cmd.arg("-fstrip");
cmd.arg("-dead_strip");
cmd.arg("-dead_strip_dylibs");
cmd.arg("-fno-compiler-rt");
cmd.arg(&format!("-femit-bin={}", output_path.display()));
@@ -1329,7 +1374,6 @@ impl LinkCode {
}
}
#[cfg(feature = "http")]
mod http_fetch {
use anyhow::{anyhow, Context, Result};
use http_req::{request::Request, response::StatusCode, uri::Uri};
@@ -1379,7 +1423,7 @@ mod http_fetch {
}
Err(anyhow!(
"Could not get expected Github API response.\n\nReason: response format is not recognized:\n{:#?}", ""
"Could not get expected Github API response.\n\nReason: response format is not recognized:\n{response:#?}",
))
}

View File

@@ -9,10 +9,10 @@ impl List {
pub fn execute(&self) -> Result<(), anyhow::Error> {
use prettytable::{format, row, Table};
let rows = wasmer_registry::get_all_local_packages(None)
let rows = wasmer_registry::get_all_local_packages()
.into_iter()
.filter_map(|pkg| {
let package_root_path = pkg.get_path().ok()?;
let package_root_path = pkg.path;
let (manifest, _) =
wasmer_registry::get_executable_file_from_path(&package_root_path, None)
.ok()?;

View File

@@ -1,7 +1,7 @@
use crate::cli::SplitVersion;
use crate::common::get_cache_dir;
#[cfg(feature = "debug")]
use crate::logging;
use crate::package_source::PackageSource;
use crate::store::{CompilerType, StoreOptions};
use crate::suggestions::suggest_function_exports;
use crate::warning;
@@ -11,12 +11,10 @@ use std::collections::HashMap;
use std::ops::Deref;
use std::path::PathBuf;
use std::str::FromStr;
use url::Url;
use wasmer::FunctionEnv;
use wasmer::*;
#[cfg(feature = "cache")]
use wasmer_cache::{Cache, FileSystemCache, Hash};
use wasmer_registry::PackageDownloadInfo;
use wasmer_types::Type as ValueType;
#[cfg(feature = "webc_runner")]
use wasmer_wasi::runners::{Runner, WapmContainer};
@@ -27,6 +25,17 @@ mod wasi;
#[cfg(feature = "wasi")]
use wasi::Wasi;
/// The options for the `wasmer run` subcommand, runs either a package, URL or a file
#[derive(Debug, Parser, Clone, Default)]
pub struct Run {
/// File to run
#[clap(name = "SOURCE", parse(try_from_str))]
pub(crate) path: PackageSource,
/// Options to run the file / package / URL with
#[clap(flatten)]
pub(crate) options: RunWithoutFile,
}
/// Same as `wasmer run`, but without the required `path` argument (injected previously)
#[derive(Debug, Parser, Clone, Default)]
pub struct RunWithoutFile {
@@ -83,103 +92,65 @@ pub struct RunWithoutFile {
pub(crate) args: Vec<String>,
}
#[allow(dead_code)]
fn is_dir(e: &walkdir::DirEntry) -> bool {
let meta = match e.metadata() {
Ok(o) => o,
Err(_) => return false,
};
meta.is_dir()
}
impl RunWithoutFile {
/// Given a local path, returns the `Run` command (overriding the `--path` argument).
pub fn into_run_args(
mut self,
package_root_dir: PathBuf, // <- package dir
command: Option<&str>,
_debug_output_allowed: bool,
) -> Result<Run, anyhow::Error> {
let (manifest, pathbuf) =
wasmer_registry::get_executable_file_from_path(&package_root_dir, command)?;
#[cfg(feature = "wasi")]
{
let default = HashMap::default();
let fs = manifest.fs.as_ref().unwrap_or(&default);
for (alias, real_dir) in fs.iter() {
let real_dir = package_root_dir.join(&real_dir);
if !real_dir.exists() {
if _debug_output_allowed {
println!(
"warning: cannot map {alias:?} to {}: directory does not exist",
real_dir.display()
);
}
continue;
}
self.wasi.map_dir(alias, real_dir.clone());
}
}
Ok(Run {
path: pathbuf,
options: RunWithoutFile {
force_install: self.force_install,
#[cfg(feature = "cache")]
disable_cache: self.disable_cache,
invoke: self.invoke,
// If the RunWithoutFile was constructed via a package name,
// the correct syntax is "package:command-name" (--command-name would be
// interpreted as a CLI argument for the .wasm file)
command_name: None,
#[cfg(feature = "cache")]
cache_key: self.cache_key,
store: self.store,
#[cfg(feature = "wasi")]
wasi: self.wasi,
#[cfg(feature = "io-devices")]
enable_experimental_io_devices: self.enable_experimental_io_devices,
#[cfg(feature = "debug")]
debug: self.debug,
#[cfg(feature = "debug")]
verbose: self.verbose,
args: self.args,
},
})
}
}
#[derive(Debug, Parser, Clone, Default)]
/// The options for the `wasmer run` subcommand
pub struct Run {
/// Same as `Run`, but uses a resolved local file path.
#[derive(Debug, Clone, Default)]
pub struct RunWithPathBuf {
/// File to run
#[clap(name = "FILE", parse(from_os_str))]
pub(crate) path: PathBuf,
#[clap(flatten)]
/// Options for running the file
pub(crate) options: RunWithoutFile,
}
impl Deref for Run {
impl Deref for RunWithPathBuf {
type Target = RunWithoutFile;
fn deref(&self) -> &Self::Target {
&self.options
}
}
impl Run {
impl RunWithPathBuf {
/// Execute the run command
pub fn execute(&self) -> Result<()> {
let mut self_clone = self.clone();
if self_clone.path.is_dir() {
let (manifest, pathbuf) = wasmer_registry::get_executable_file_from_path(
&self_clone.path,
self_clone.command_name.as_deref(),
)?;
#[cfg(feature = "wasi")]
{
let default = HashMap::default();
let fs = manifest.fs.as_ref().unwrap_or(&default);
for (alias, real_dir) in fs.iter() {
let real_dir = self_clone.path.join(&real_dir);
if !real_dir.exists() {
#[cfg(feature = "debug")]
if self_clone.debug {
println!(
"warning: cannot map {alias:?} to {}: directory does not exist",
real_dir.display()
);
}
continue;
}
self_clone.options.wasi.map_dir(alias, real_dir.clone());
}
}
self_clone.path = pathbuf;
}
#[cfg(feature = "debug")]
if self.debug {
logging::set_up_logging(self.verbose.unwrap_or(0)).unwrap();
logging::set_up_logging(self_clone.verbose.unwrap_or(0)).unwrap();
}
self.inner_execute().with_context(|| {
self_clone.inner_execute().with_context(|| {
format!(
"failed to run `{}`{}",
self.path.display(),
self_clone.path.display(),
if CompilerType::enabled().is_empty() {
" (no compilers enabled)"
} else {
@@ -589,6 +560,19 @@ impl Run {
.collect::<Result<Vec<_>>>()?;
Ok(func.call(ctx, &invoke_args)?)
}
}
impl Run {
/// Executes the `wasmer run` command
pub fn execute(&self) -> Result<(), anyhow::Error> {
// downloads and installs the package if necessary
let path_to_run = self.path.download_and_get_filepath()?;
RunWithPathBuf {
path: path_to_run,
options: self.options.clone(),
}
.execute()
}
/// Create Run instance for arguments/env,
/// assuming we're being run from a CFP binfmt interpreter.
@@ -602,364 +586,30 @@ impl Run {
#[cfg(target_os = "linux")]
fn from_binfmt_args_fallible() -> Result<Run> {
let argv = std::env::args_os().collect::<Vec<_>>();
let argv = std::env::args().collect::<Vec<_>>();
let (_interpreter, executable, original_executable, args) = match &argv[..] {
[a, b, c, d @ ..] => (a, b, c, d),
_ => {
bail!("Wasmer binfmt interpreter needs at least three arguments (including $0) - must be registered as binfmt interpreter with the CFP flags. (Got arguments: {:?})", argv);
}
};
// TODO: Optimally, args and env would be passed as an UTF-8 Vec.
// (Can be pulled out of std::os::unix::ffi::OsStrExt)
// But I don't want to duplicate or rewrite run.rs today.
let args = args
.iter()
.enumerate()
.map(|(i, s)| {
s.clone().into_string().map_err(|s| {
anyhow!(
"Cannot convert argument {} ({:?}) to UTF-8 string",
i + 1,
s
)
})
})
.collect::<Result<Vec<_>>>()?;
let original_executable = original_executable
.clone()
.into_string()
.map_err(|s| anyhow!("Cannot convert executable name {:?} to UTF-8 string", s))?;
let store = StoreOptions::default();
// TODO: store.compiler.features.all = true; ?
Ok(Self {
path: executable.into(),
// unwrap is safe, since parsing never fails
path: PackageSource::parse(executable).unwrap(),
options: RunWithoutFile {
args,
command_name: Some(original_executable),
args: args.to_vec(),
command_name: Some(original_executable.to_string()),
store,
wasi: Wasi::for_binfmt_interpreter()?,
..Default::default()
},
})
}
#[cfg(not(target_os = "linux"))]
fn from_binfmt_args_fallible() -> Result<Run> {
bail!("binfmt_misc is only available on linux.")
}
}
fn start_spinner(msg: String) -> Option<spinoff::Spinner> {
if !isatty::stdout_isatty() {
return None;
}
#[cfg(target_os = "windows")]
{
use colored::control;
let _ = control::set_virtual_terminal(true);
}
Some(spinoff::Spinner::new(
spinoff::Spinners::Dots,
msg,
spinoff::Color::White,
))
}
/// Before looking up a command from the registry, try to see if we have
/// the command already installed
fn try_run_local_command(
args: &[String],
sv: &SplitVersion,
debug_msgs_allowed: bool,
) -> Result<(), ExecuteLocalPackageError> {
let result = wasmer_registry::try_finding_local_command(&sv.original).ok_or_else(|| {
ExecuteLocalPackageError::BeforeExec(anyhow::anyhow!(
"could not find command {} locally",
sv.original
))
})?;
let package_dir = result
.get_path()
.map_err(|e| ExecuteLocalPackageError::BeforeExec(anyhow::anyhow!("{e}")))?;
// Try auto-installing the remote package
let args_without_package = fixup_args(args, &sv.original);
let mut run_args = RunWithoutFile::try_parse_from(args_without_package.iter())
.map_err(|e| ExecuteLocalPackageError::DuringExec(e.into()))?;
run_args.command_name = sv.command.clone();
run_args
.into_run_args(package_dir, sv.command.as_deref(), debug_msgs_allowed)
.map_err(ExecuteLocalPackageError::DuringExec)?
.execute()
.map_err(ExecuteLocalPackageError::DuringExec)
}
pub(crate) fn try_autoinstall_package(
args: &[String],
sv: &SplitVersion,
package: Option<PackageDownloadInfo>,
force_install: bool,
) -> Result<(), anyhow::Error> {
use std::io::Write;
let mut sp = start_spinner(format!("Installing package {} ...", sv.package));
let debug_msgs_allowed = sp.is_some();
let v = sv.version.as_deref();
let result = wasmer_registry::install_package(
sv.registry.as_deref(),
&sv.package,
v,
package,
force_install,
);
if let Some(sp) = sp.take() {
sp.clear();
}
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 args_without_package = fixup_args(args, &sv.original);
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(), debug_msgs_allowed)?
.execute()
}
// 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,
debug_msgs_allowed: bool,
) -> 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 args_without_package = fixup_args(args, &sv.original);
RunWithoutFile::try_parse_from(args_without_package.iter())
.map_err(|e| ExecuteLocalPackageError::DuringExec(e.into()))?
.into_run_args(package_dir, sv.command.as_deref(), debug_msgs_allowed)
.map_err(ExecuteLocalPackageError::DuringExec)?
.execute()
.map_err(|e| ExecuteLocalPackageError::DuringExec(e.context(anyhow::anyhow!("{}", sv))))
}
fn try_lookup_command(sv: &mut SplitVersion) -> Result<PackageDownloadInfo, anyhow::Error> {
use std::io::Write;
let mut 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(&registry, &sv.package);
if let Some(s) = sp.take() {
s.clear();
}
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);
}
}
if let Some(sp) = sp.take() {
sp.clear();
}
let _ = std::io::stdout().flush();
Err(anyhow::anyhow!("command {sv} not found"))
}
/// Removes the difference between "wasmer run {file} arg1 arg2" and "wasmer {file} arg1 arg2"
fn fixup_args(args: &[String], command: &str) -> Vec<String> {
let mut args_without_package = args.to_vec();
if args_without_package.get(1).map(|s| s.as_str()) == Some(command) {
let _ = args_without_package.remove(1);
} else if args_without_package.get(2).map(|s| s.as_str()) == Some(command) {
let _ = args_without_package.remove(1);
let _ = args_without_package.remove(1);
}
args_without_package
}
#[test]
fn test_fixup_args() {
let first_args = vec![
format!("wasmer"),
format!("run"),
format!("python/python"),
format!("--arg1"),
format!("--arg2"),
];
let second_args = vec![
format!("wasmer"), // no "run"
format!("python/python"),
format!("--arg1"),
format!("--arg2"),
];
let arg1_transformed = fixup_args(&first_args, "python/python");
let arg2_transformed = fixup_args(&second_args, "python/python");
assert_eq!(arg1_transformed, arg2_transformed);
}
pub(crate) fn try_run_package_or_file(
args: &[String],
r: &Run,
debug: bool,
) -> Result<(), anyhow::Error> {
let debug_msgs_allowed = isatty::stdout_isatty();
// 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 args_without_package = fixup_args(args, &format!("{}", r.path.display()));
return RunWithoutFile::try_parse_from(args_without_package.iter())?
.into_run_args(
r.path.clone(),
r.command_name.as_deref(),
debug_msgs_allowed,
)?
.execute();
}
return r.execute();
}
// c:// might be parsed as a URL on Windows
let url_string = format!("{}", r.path.display());
if let Ok(url) = url::Url::parse(&url_string) {
if url.scheme() == "http" || url.scheme() == "https" {
match try_run_url(&url, args, r, debug) {
Err(ExecuteLocalPackageError::BeforeExec(_)) => {}
Err(ExecuteLocalPackageError::DuringExec(e)) => return Err(e),
Ok(o) => return Ok(o),
}
}
}
let package = format!("{}", r.path.display());
let mut is_fake_sv = false;
let mut sv = match SplitVersion::parse(&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_run_local_command(args, &fake_sv, debug) {
Ok(()) => return Ok(()),
Err(ExecuteLocalPackageError::DuringExec(e)) => return Err(e),
_ => {}
}
match try_lookup_command(&mut fake_sv) {
Ok(o) => SplitVersion {
original: package.to_string(),
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, debug_msgs_allowed) {
Ok(o) => return Ok(o),
Err(ExecuteLocalPackageError::DuringExec(e)) => return Err(e),
_ => {}
}
if debug && isatty::stdout_isatty() {
eprintln!("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_run_url(
url: &Url,
_args: &[String],
r: &Run,
_debug: bool,
) -> Result<(), ExecuteLocalPackageError> {
let checksum = wasmer_registry::get_remote_webc_checksum(url).map_err(|e| {
ExecuteLocalPackageError::BeforeExec(anyhow::anyhow!("error fetching {url}: {e}"))
})?;
let packages = wasmer_registry::get_all_installed_webc_packages();
if !packages.iter().any(|p| p.checksum == checksum) {
let sp = start_spinner(format!("Installing {}", url));
let result = wasmer_registry::install_webc_package(url, &checksum);
result.map_err(|e| {
ExecuteLocalPackageError::BeforeExec(anyhow::anyhow!("error fetching {url}: {e}"))
})?;
if let Some(sp) = sp {
sp.clear();
}
}
let webc_dir = wasmer_registry::get_webc_dir();
let webc_install_path = webc_dir
.context("Error installing package: no webc dir")
.map_err(ExecuteLocalPackageError::BeforeExec)?
.join(checksum);
let mut r = r.clone();
r.path = webc_install_path;
r.execute().map_err(ExecuteLocalPackageError::DuringExec)
}

View File

@@ -24,6 +24,7 @@ pub mod c_gen;
pub mod cli;
#[cfg(feature = "debug")]
pub mod logging;
pub mod package_source;
pub mod store;
pub mod suggestions;
pub mod utils;

View File

@@ -0,0 +1,191 @@
//! Module for parsing and installing packages
use anyhow::Context;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use url::Url;
/// Source of a package
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum PackageSource {
/// Download from a URL
Url(Url),
/// Run a local file
File(String),
/// Download from a package
Package(wasmer_registry::Package),
}
impl Default for PackageSource {
fn default() -> Self {
PackageSource::File(String::new())
}
}
impl FromStr for PackageSource {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl PackageSource {
/// Parses a package source and transforms it to a URL or a File
pub fn parse(s: &str) -> Result<Self, String> {
// If the file is a http:// URL, run the URL
if let Ok(url) = url::Url::parse(s) {
if url.scheme() == "http" || url.scheme() == "https" {
return Ok(Self::Url(url));
}
}
Ok(match wasmer_registry::Package::from_str(s) {
Ok(o) => Self::Package(o),
Err(_) => Self::File(s.to_string()),
})
}
/// Downloads the package (if any) to the installation directory, returns the path
/// of the package directory (containing the wapm.toml)
pub fn download_and_get_filepath(&self) -> Result<PathBuf, anyhow::Error> {
let url = match self {
Self::File(f) => {
let path = Path::new(&f).to_path_buf();
return if path.exists() {
Ok(path)
} else {
Err(anyhow::anyhow!(
"invalid package name, could not find file {f}"
))
};
}
Self::Url(u) => {
if let Some(path) = wasmer_registry::Package::is_url_already_installed(u) {
return Ok(path);
} else {
u.clone()
}
}
Self::Package(p) => {
let package_path = Path::new(&p.file()).to_path_buf();
if package_path.exists() {
return Ok(package_path);
} else if let Some(path) = p.already_installed() {
return Ok(path);
} else {
p.url()?
}
}
};
let extra = if let Self::Package(p) = self {
format!(", file {} does not exist either", p.file())
} else {
String::new()
};
let mut sp = start_spinner(format!("Installing package {url} ..."));
let opt_path = wasmer_registry::install_package(&url);
if let Some(sp) = sp.take() {
use std::io::Write;
sp.clear();
let _ = std::io::stdout().flush();
}
let path = opt_path
.with_context(|| anyhow::anyhow!("could not install package from URL {url}{extra}"))?;
Ok(path)
}
}
fn start_spinner(msg: String) -> Option<spinoff::Spinner> {
if !isatty::stdout_isatty() {
return None;
}
#[cfg(target_os = "windows")]
{
use colored::control;
let _ = control::set_virtual_terminal(true);
}
Some(spinoff::Spinner::new(
spinoff::Spinners::Dots,
msg,
spinoff::Color::White,
))
}
#[test]
fn test_package_source() {
assert_eq!(
PackageSource::parse("registry.wapm.io/graphql/python/python").unwrap(),
PackageSource::File("registry.wapm.io/graphql/python/python".to_string()),
);
assert_eq!(
PackageSource::parse("/absolute/path/test.wasm").unwrap(),
PackageSource::File("/absolute/path/test.wasm".to_string()),
);
assert_eq!(
PackageSource::parse("C://absolute/path/test.wasm").unwrap(),
PackageSource::File("C://absolute/path/test.wasm".to_string()),
);
assert_eq!(
PackageSource::parse("namespace/name@latest").unwrap(),
PackageSource::Package(wasmer_registry::Package {
namespace: "namespace".to_string(),
name: "name".to_string(),
version: Some("latest".to_string()),
})
);
assert_eq!(
PackageSource::parse("namespace/name@latest:command").unwrap(),
PackageSource::File("namespace/name@latest:command".to_string()),
);
assert_eq!(
PackageSource::parse("namespace/name@1.0.2").unwrap(),
PackageSource::Package(wasmer_registry::Package {
namespace: "namespace".to_string(),
name: "name".to_string(),
version: Some("1.0.2".to_string()),
})
);
assert_eq!(
PackageSource::parse("namespace/name@1.0.2-rc.2").unwrap(),
PackageSource::Package(wasmer_registry::Package {
namespace: "namespace".to_string(),
name: "name".to_string(),
version: Some("1.0.2-rc.2".to_string()),
})
);
assert_eq!(
PackageSource::parse("namespace/name").unwrap(),
PackageSource::Package(wasmer_registry::Package {
namespace: "namespace".to_string(),
name: "name".to_string(),
version: None,
})
);
assert_eq!(
PackageSource::parse("https://wapm.io/syrusakbary/python").unwrap(),
PackageSource::Url(url::Url::parse("https://wapm.io/syrusakbary/python").unwrap()),
);
assert_eq!(
PackageSource::parse("command").unwrap(),
PackageSource::File("command".to_string()),
);
assert_eq!(
PackageSource::parse("python@latest").unwrap(),
PackageSource::File("python@latest".to_string()),
);
}