mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-07 21:28:21 +00:00
Merge remote-tracking branch 'origin/master' into wasix
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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:#?}",
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
@@ -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(®istry, &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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
191
lib/cli/src/package_source.rs
Normal file
191
lib/cli/src/package_source.rs
Normal 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()),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user