Merge branch 'wasix-core-changes' into wasix

This commit is contained in:
Christoph Herzog
2022-12-27 15:08:28 +01:00
74 changed files with 4856 additions and 978 deletions

View File

@@ -11,7 +11,7 @@ use crate::commands::CreateObj;
#[cfg(feature = "wast")]
use crate::commands::Wast;
use crate::commands::{
Add, Cache, Config, Inspect, List, Login, Run, SelfUpdate, Validate, Whoami,
Add, Cache, Config, Init, Inspect, List, Login, Publish, Run, SelfUpdate, Validate, Whoami,
};
use crate::error::PrettyError;
use clap::{CommandFactory, ErrorKind, Parser};
@@ -46,6 +46,10 @@ enum WasmerCLIOptions {
/// Login into a wapm.io-like registry
Login(Login),
/// Login into a wapm.io-like registry
#[clap(name = "publish")]
Publish(Publish),
/// Wasmer cache
#[clap(subcommand)]
Cache(Cache),
@@ -135,6 +139,10 @@ enum WasmerCLIOptions {
/// Inspect a WebAssembly file
Inspect(Inspect),
/// Initializes a new wasmer.toml file
#[clap(name = "init")]
Init(Init),
/// Run spec testsuite
#[cfg(feature = "wast")]
Wast(Wast),
@@ -165,8 +173,10 @@ impl WasmerCLIOptions {
Self::CreateObj(create_obj) => create_obj.execute(),
Self::Config(config) => config.execute(),
Self::Inspect(inspect) => inspect.execute(),
Self::Init(init) => init.execute(),
Self::List(list) => list.execute(),
Self::Login(login) => login.execute(),
Self::Publish(publish) => publish.execute(),
#[cfg(feature = "wast")]
Self::Wast(wast) => wast.execute(),
#[cfg(target_os = "linux")]
@@ -224,10 +234,9 @@ fn wasmer_main_inner() -> Result<(), anyhow::Error> {
WasmerCLIOptions::Run(Run::from_binfmt_args())
} else {
match command.unwrap_or(&"".to_string()).as_ref() {
"add" | "cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "run"
| "self-update" | "validate" | "wast" | "binfmt" | "list" | "login" => {
WasmerCLIOptions::parse()
}
"add" | "cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "init"
| "run" | "self-update" | "validate" | "wast" | "binfmt" | "list" | "login"
| "publish" => WasmerCLIOptions::parse(),
_ => {
WasmerCLIOptions::try_parse_from(args.iter()).unwrap_or_else(|e| {
match e.kind() {

View File

@@ -10,9 +10,11 @@ mod config;
mod create_exe;
#[cfg(feature = "static-artifact-create")]
mod create_obj;
mod init;
mod inspect;
mod list;
mod login;
mod publish;
mod run;
mod self_update;
mod validate;
@@ -31,8 +33,8 @@ pub use create_obj::*;
#[cfg(feature = "wast")]
pub use wast::*;
pub use {
add::*, cache::*, config::*, inspect::*, list::*, login::*, run::*, self_update::*,
validate::*, whoami::*,
add::*, cache::*, config::*, init::*, inspect::*, list::*, login::*, publish::*, run::*,
self_update::*, validate::*, whoami::*,
};
/// The kind of object format to emit.

View File

@@ -2,7 +2,7 @@ use std::process::{Command, Stdio};
use anyhow::{Context, Error};
use clap::Parser;
use wasmer_registry::{Bindings, PartialWapmConfig, ProgrammingLanguage};
use wasmer_registry::{Bindings, ProgrammingLanguage, WasmerConfig};
/// Add a WAPM package's bindings to your application.
#[derive(Debug, Parser)]
@@ -76,7 +76,9 @@ impl Add {
match &self.registry {
Some(r) => Ok(r.clone()),
None => {
let cfg = PartialWapmConfig::from_file()
let wasmer_dir =
WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("{e}"))?;
let cfg = WasmerConfig::from_file(&wasmer_dir)
.map_err(Error::msg)
.context("Unable to load WAPM's config file")?;
Ok(cfg.registry.get_current_registry())

View File

@@ -3,10 +3,22 @@ use anyhow::{Context, Result};
use clap::Parser;
use std::env;
use std::path::PathBuf;
use std::str::ParseBoolError;
use wasmer_registry::WasmerConfig;
#[derive(Debug, Parser)]
/// The options for the `wasmer config` subcommand
/// The options for the `wasmer config` subcommand: `wasmer config get --OPTION` or `wasmer config set [FLAG]`
pub struct Config {
#[clap(flatten)]
flags: Flags,
/// Subcommand for `wasmer config get | set`
#[clap(subcommand)]
set: Option<GetOrSet>,
}
/// Normal configuration
#[derive(Debug, Parser)]
pub struct Flags {
/// Print the installation prefix.
#[clap(long, conflicts_with = "pkg-config")]
prefix: bool,
@@ -31,12 +43,118 @@ pub struct Config {
#[clap(long, conflicts_with = "pkg-config")]
cflags: bool,
/// It outputs the necessary details for compiling
/// Print the path to the wasmer configuration file where all settings are stored
#[clap(long, conflicts_with = "pkg-config")]
config_path: bool,
/// Outputs the necessary details for compiling
/// and linking a program to Wasmer, using the `pkg-config` format.
#[clap(long)]
pkg_config: bool,
}
/// Subcommand for `wasmer config set`
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub enum GetOrSet {
/// `wasmer config get $KEY`
#[clap(subcommand)]
Get(RetrievableConfigField),
/// `wasmer config set $KEY $VALUE`
#[clap(subcommand)]
Set(StorableConfigField),
}
/// Subcommand for `wasmer config get`
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub enum RetrievableConfigField {
/// Print the registry URL of the currently active registry
#[clap(name = "registry.url")]
RegistryUrl,
/// Print the token for the currently active registry or nothing if not logged in
#[clap(name = "registry.token")]
RegistryToken,
/// Print whether telemetry is currently enabled
#[clap(name = "telemetry.enabled")]
TelemetryEnabled,
/// Print whether update notifications are enabled
#[clap(name = "update-notifications.enabled")]
UpdateNotificationsEnabled,
/// Print the proxy URL
#[clap(name = "proxy.url")]
ProxyUrl,
}
/// Setting that can be stored in the wasmer config
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub enum StorableConfigField {
/// Set the registry URL of the currently active registry
#[clap(name = "registry.url")]
RegistryUrl(SetRegistryUrl),
/// Set the token for the currently active registry or nothing if not logged in
#[clap(name = "registry.token")]
RegistryToken(SetRegistryToken),
/// Set whether telemetry is currently enabled
#[clap(name = "telemetry.enabled")]
TelemetryEnabled(SetTelemetryEnabled),
/// Set whether update notifications are enabled
#[clap(name = "update-notifications.enabled")]
UpdateNotificationsEnabled(SetUpdateNotificationsEnabled),
/// Set the active proxy URL
#[clap(name = "proxy.url")]
ProxyUrl(SetProxyUrl),
}
/// Set the current active registry URL
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub struct SetRegistryUrl {
/// Url of the registry
#[clap(name = "URL")]
pub url: String,
}
/// Set or change the token for the current active registry
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub struct SetRegistryToken {
/// Token to set
#[clap(name = "TOKEN")]
pub token: String,
}
/// Set if update notifications are enabled
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub struct SetUpdateNotificationsEnabled {
/// Whether to enable update notifications
#[clap(name = "ENABLED", possible_values = ["true", "false"])]
pub enabled: BoolString,
}
/// "true" or "false" for handling input in the CLI
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct BoolString(pub bool);
impl std::str::FromStr for BoolString {
type Err = ParseBoolError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(bool::from_str(s)?))
}
}
/// Set if telemetry is enabled
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub struct SetTelemetryEnabled {
/// Whether to enable telemetry
#[clap(name = "ENABLED", possible_values = ["true", "false"])]
pub enabled: BoolString,
}
/// Set if a proxy URL should be used
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub struct SetProxyUrl {
/// Set if a proxy URL should be used (empty = unset proxy)
#[clap(name = "URL")]
pub url: String,
}
impl Config {
/// Runs logic for the `config` subcommand
pub fn execute(&self) -> Result<()> {
@@ -44,6 +162,12 @@ impl Config {
.context("failed to retrieve the wasmer config".to_string())
}
fn inner_execute(&self) -> Result<()> {
if let Some(s) = self.set.as_ref() {
return s.execute();
}
let flags = &self.flags;
let key = "WASMER_DIR";
let wasmer_dir = env::var(key)
.or_else(|e| {
@@ -65,7 +189,7 @@ impl Config {
let cflags = format!("-I{}", includedir);
let libs = format!("-L{} -lwasmer", libdir);
if self.pkg_config {
if flags.pkg_config {
println!("prefix={}", prefixdir);
println!("exec_prefix={}", bindir);
println!("includedir={}", includedir);
@@ -79,24 +203,114 @@ impl Config {
return Ok(());
}
if self.prefix {
if flags.prefix {
println!("{}", prefixdir);
}
if self.bindir {
if flags.bindir {
println!("{}", bindir);
}
if self.includedir {
if flags.includedir {
println!("{}", includedir);
}
if self.libdir {
if flags.libdir {
println!("{}", libdir);
}
if self.libs {
if flags.libs {
println!("{}", libs);
}
if self.cflags {
if flags.cflags {
println!("{}", cflags);
}
if flags.config_path {
let wasmer_dir = WasmerConfig::get_wasmer_dir()
.map_err(|e| anyhow::anyhow!("could not find wasmer dir: {e}"))?;
let path = WasmerConfig::get_file_location(&wasmer_dir);
println!("{}", path.display());
}
Ok(())
}
}
impl GetOrSet {
fn execute(&self) -> Result<()> {
let wasmer_dir = WasmerConfig::get_wasmer_dir()
.map_err(|e| anyhow::anyhow!("could not find wasmer dir: {e}"))?;
let config_file = WasmerConfig::get_file_location(&wasmer_dir);
let mut config = WasmerConfig::from_file(&wasmer_dir).map_err(|e| {
anyhow::anyhow!(
"could not find config file {e} at {}",
config_file.display()
)
})?;
match self {
GetOrSet::Get(g) => match g {
RetrievableConfigField::RegistryUrl => {
println!("{}", config.registry.get_current_registry());
}
RetrievableConfigField::RegistryToken => {
if let Some(s) = config
.registry
.get_login_token_for_registry(&config.registry.get_current_registry())
{
println!("{s}");
}
}
RetrievableConfigField::TelemetryEnabled => {
println!("{:?}", config.telemetry_enabled);
}
RetrievableConfigField::UpdateNotificationsEnabled => {
println!("{:?}", config.update_notifications_enabled);
}
RetrievableConfigField::ProxyUrl => {
if let Some(s) = config.proxy.url.as_ref() {
println!("{s}");
} else {
println!("none");
}
}
},
GetOrSet::Set(s) => {
match s {
StorableConfigField::RegistryUrl(s) => {
config.registry.set_current_registry(&s.url);
let current_registry = config.registry.get_current_registry();
if let Some(u) = wasmer_registry::utils::get_username(&current_registry)
.ok()
.and_then(|o| o)
{
println!(
"Successfully logged into registry {current_registry:?} as user {u:?}"
);
}
}
StorableConfigField::RegistryToken(t) => {
config.registry.set_login_token_for_registry(
&config.registry.get_current_registry(),
&t.token,
wasmer_registry::config::UpdateRegistry::LeaveAsIs,
);
}
StorableConfigField::TelemetryEnabled(t) => {
config.telemetry_enabled = t.enabled.0;
}
StorableConfigField::ProxyUrl(p) => {
if p.url == "none" || p.url.is_empty() {
config.proxy.url = None;
} else {
config.proxy.url = Some(p.url.clone());
}
}
StorableConfigField::UpdateNotificationsEnabled(u) => {
config.update_notifications_enabled = u.enabled.0;
}
}
config
.save(config_file)
.with_context(|| anyhow::anyhow!("could not save config file"))?;
}
}
Ok(())
}
}

View File

@@ -0,0 +1,517 @@
use anyhow::Context;
use cargo_metadata::{CargoOpt, MetadataCommand};
use clap::Parser;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use wasmer_registry::WasmerConfig;
static NOTE: &str =
"# See more keys and definitions at https://docs.wasmer.io/ecosystem/wapm/manifest";
const NEWLINE: &str = if cfg!(windows) { "\r\n" } else { "\n" };
/// CLI args for the `wasmer init` command
#[derive(Debug, Parser)]
pub struct Init {
/// Initialize wasmer.toml for a library package
#[clap(long, group = "crate-type")]
pub lib: bool,
/// Initialize wasmer.toml for a binary package
#[clap(long, group = "crate-type")]
pub bin: bool,
/// Initialize an empty wasmer.toml
#[clap(long, group = "crate-type")]
pub empty: bool,
/// Force overwriting the wasmer.toml, even if it already exists
#[clap(long)]
pub overwrite: bool,
/// Don't display debug output
#[clap(long)]
pub quiet: bool,
/// Namespace to init with, default = current logged in user or _
#[clap(long)]
pub namespace: Option<String>,
/// Package name to init with, default = Cargo.toml name or current directory name
#[clap(long)]
pub package_name: Option<String>,
/// Version of the initialized package
#[clap(long)]
pub version: Option<semver::Version>,
/// If the `manifest-path` is a Cargo.toml, use that file to initialize the wasmer.toml
#[clap(long)]
pub manifest_path: Option<PathBuf>,
/// Add default dependencies for common packages
#[clap(long, value_enum)]
pub template: Option<Template>,
/// Include file paths into the target container filesystem
#[clap(long)]
pub include: Vec<String>,
/// Directory of the output file name. wasmer init will error if the target dir
/// already contains a wasmer.toml. Also sets the package name.
#[clap(name = "PACKAGE_PATH")]
pub out: Option<PathBuf>,
}
/// What template to use for the initialized wasmer.toml
#[derive(Debug, PartialEq, Eq, Copy, Clone, clap::ValueEnum)]
pub enum Template {
/// Add dependency on Python
Python,
/// Add dependency on JS
Js,
}
#[derive(Debug, PartialEq, Copy, Clone)]
enum BinOrLib {
Bin,
Lib,
Empty,
}
// minimal version of the Cargo.toml [package] section
#[derive(Debug, Clone)]
struct MiniCargoTomlPackage {
cargo_toml_path: PathBuf,
name: String,
version: semver::Version,
description: Option<String>,
homepage: Option<String>,
repository: Option<String>,
license: Option<String>,
readme: Option<PathBuf>,
license_file: Option<PathBuf>,
#[allow(dead_code)]
workspace_root: PathBuf,
#[allow(dead_code)]
build_dir: PathBuf,
}
static WASMER_TOML_NAME: &str = "wasmer.toml";
impl Init {
/// `wasmer init` execution
pub fn execute(&self) -> Result<(), anyhow::Error> {
let bin_or_lib = self.get_bin_or_lib()?;
// See if the directory has a Cargo.toml file, if yes, copy the license / readme, etc.
let manifest_path = match self.manifest_path.as_ref() {
Some(s) => s.clone(),
None => {
let cargo_toml_path = self
.out
.clone()
.unwrap_or_else(|| std::env::current_dir().unwrap())
.join("Cargo.toml");
cargo_toml_path
.canonicalize()
.unwrap_or_else(|_| cargo_toml_path.clone())
}
};
let cargo_toml = if manifest_path.exists() {
parse_cargo_toml(&manifest_path).ok()
} else {
None
};
let (fallback_package_name, target_file) = self.target_file()?;
if target_file.exists() && !self.overwrite {
anyhow::bail!(
"wasmer project already initialized in {}",
target_file.display(),
);
}
let constructed_manifest = construct_manifest(
cargo_toml.as_ref(),
&fallback_package_name,
self.package_name.as_deref(),
&target_file,
&manifest_path,
bin_or_lib,
self.namespace.clone(),
self.version.clone(),
self.template.as_ref(),
self.include.as_slice(),
self.quiet,
)?;
if let Some(parent) = target_file.parent() {
let _ = std::fs::create_dir_all(parent);
}
// generate the wasmer.toml and exit
Self::write_wasmer_toml(&target_file, &constructed_manifest)
}
/// Writes the metadata to a wasmer.toml file
fn write_wasmer_toml(
path: &PathBuf,
toml: &wasmer_toml::Manifest,
) -> Result<(), anyhow::Error> {
let toml_string = toml::to_string_pretty(&toml)?
.replace(
"[dependencies]",
&format!("{NOTE}{NEWLINE}{NEWLINE}[dependencies]"),
)
.lines()
.collect::<Vec<_>>()
.join(NEWLINE);
std::fs::write(&path, &toml_string)
.with_context(|| format!("Unable to write to \"{}\"", path.display()))?;
Ok(())
}
fn target_file(&self) -> Result<(String, PathBuf), anyhow::Error> {
match self.out.as_ref() {
None => {
let current_dir = std::env::current_dir()?;
let package_name = self
.package_name
.clone()
.or_else(|| {
current_dir
.canonicalize()
.ok()?
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
})
.ok_or_else(|| anyhow::anyhow!("no current dir name"))?;
Ok((package_name, current_dir.join(WASMER_TOML_NAME)))
}
Some(s) => {
std::fs::create_dir_all(s)
.map_err(|e| anyhow::anyhow!("{e}"))
.with_context(|| anyhow::anyhow!("{}", s.display()))?;
let package_name = self
.package_name
.clone()
.or_else(|| {
s.canonicalize()
.ok()?
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
})
.ok_or_else(|| anyhow::anyhow!("no dir name"))?;
Ok((package_name, s.join(WASMER_TOML_NAME)))
}
}
}
fn get_filesystem_mapping(include: &[String]) -> Option<HashMap<String, PathBuf>> {
if include.is_empty() {
return None;
}
Some(
include
.iter()
.map(|path| {
if path == "." || path == "/" {
return ("/".to_string(), Path::new("/").to_path_buf());
}
let key = format!("./{path}");
let value = PathBuf::from(format!("/{path}"));
(key, value)
})
.collect(),
)
}
fn get_command(
modules: &[wasmer_toml::Module],
bin_or_lib: BinOrLib,
) -> Option<Vec<wasmer_toml::Command>> {
match bin_or_lib {
BinOrLib::Bin => Some(
modules
.iter()
.map(|m| {
wasmer_toml::Command::V1(wasmer_toml::CommandV1 {
name: m.name.clone(),
module: m.name.clone(),
main_args: None,
package: None,
})
})
.collect(),
),
BinOrLib::Lib | BinOrLib::Empty => None,
}
}
/// Returns the dependencies based on the `--template` flag
fn get_dependencies(template: Option<&Template>) -> HashMap<String, String> {
let mut map = HashMap::default();
match template {
Some(Template::Js) => {
map.insert("quickjs".to_string(), "quickjs/quickjs@latest".to_string());
}
Some(Template::Python) => {
map.insert("python".to_string(), "python/python@latest".to_string());
}
_ => {}
}
map
}
// Returns whether the template for the wasmer.toml should be a binary, a library or an empty file
fn get_bin_or_lib(&self) -> Result<BinOrLib, anyhow::Error> {
match (self.empty, self.bin, self.lib) {
(true, false, false) => Ok(BinOrLib::Empty),
(false, true, false) => Ok(BinOrLib::Bin),
(false, false, true) => Ok(BinOrLib::Lib),
(false, false, false) => Ok(BinOrLib::Bin),
_ => anyhow::bail!("Only one of --bin, --lib, or --empty can be provided"),
}
}
/// Get bindings returns the first .wai / .wit file found and
/// optionally takes a warning callback that is triggered when > 1 .wai files are found
fn get_bindings(target_file: &Path, bin_or_lib: BinOrLib) -> Option<GetBindingsResult> {
match bin_or_lib {
BinOrLib::Bin | BinOrLib::Empty => None,
BinOrLib::Lib => target_file.parent().and_then(|parent| {
let all_bindings = walkdir::WalkDir::new(parent)
.min_depth(1)
.max_depth(3)
.follow_links(false)
.into_iter()
.filter_map(|e| e.ok())
.filter_map(|e| {
let is_wit = e.path().extension().and_then(|s| s.to_str()) == Some(".wit");
let is_wai = e.path().extension().and_then(|s| s.to_str()) == Some(".wai");
if is_wit {
Some(wasmer_toml::Bindings::Wit(wasmer_toml::WitBindings {
wit_exports: e.path().to_path_buf(),
wit_bindgen: semver::Version::parse("0.1.0").unwrap(),
}))
} else if is_wai {
Some(wasmer_toml::Bindings::Wai(wasmer_toml::WaiBindings {
exports: None,
imports: vec![e.path().to_path_buf()],
wai_version: semver::Version::parse("0.2.0").unwrap(),
}))
} else {
None
}
})
.collect::<Vec<_>>();
if all_bindings.is_empty() {
None
} else if all_bindings.len() == 1 {
Some(GetBindingsResult::OneBinding(all_bindings[0].clone()))
} else {
Some(GetBindingsResult::MultiBindings(all_bindings))
}
}),
}
}
}
enum GetBindingsResult {
OneBinding(wasmer_toml::Bindings),
MultiBindings(Vec<wasmer_toml::Bindings>),
}
impl GetBindingsResult {
fn first_binding(&self) -> Option<wasmer_toml::Bindings> {
match self {
Self::OneBinding(s) => Some(s.clone()),
Self::MultiBindings(s) => s.get(0).cloned(),
}
}
}
#[allow(clippy::too_many_arguments)]
fn construct_manifest(
cargo_toml: Option<&MiniCargoTomlPackage>,
fallback_package_name: &String,
package_name: Option<&str>,
target_file: &Path,
manifest_path: &Path,
bin_or_lib: BinOrLib,
namespace: Option<String>,
version: Option<semver::Version>,
template: Option<&Template>,
include_fs: &[String],
quiet: bool,
) -> Result<wasmer_toml::Manifest, anyhow::Error> {
if let Some(ct) = cargo_toml.as_ref() {
let msg = format!(
"NOTE: Initializing wasmer.toml file with metadata from Cargo.toml{NEWLINE} -> {}",
ct.cargo_toml_path.display()
);
if !quiet {
println!("{msg}");
}
log::warn!("{msg}");
}
let package_name = package_name.unwrap_or_else(|| {
cargo_toml
.as_ref()
.map(|p| &p.name)
.unwrap_or(fallback_package_name)
});
let wasmer_dir = WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("{e}"))?;
let namespace = namespace.or_else(|| {
wasmer_registry::whoami(&wasmer_dir, None, None)
.ok()
.map(|o| o.1)
});
let version = version.unwrap_or_else(|| {
cargo_toml
.as_ref()
.map(|t| t.version.clone())
.unwrap_or_else(|| semver::Version::parse("0.1.0").unwrap())
});
let license = cargo_toml.as_ref().and_then(|t| t.license.clone());
let license_file = cargo_toml.as_ref().and_then(|t| t.license_file.clone());
let readme = cargo_toml.as_ref().and_then(|t| t.readme.clone());
let repository = cargo_toml.as_ref().and_then(|t| t.repository.clone());
let homepage = cargo_toml.as_ref().and_then(|t| t.homepage.clone());
let description = cargo_toml
.as_ref()
.and_then(|t| t.description.clone())
.unwrap_or_else(|| format!("Description for package {package_name}"));
let default_abi = wasmer_toml::Abi::Wasi;
let bindings = Init::get_bindings(target_file, bin_or_lib);
if let Some(GetBindingsResult::MultiBindings(m)) = bindings.as_ref() {
let found = m
.iter()
.map(|m| match m {
wasmer_toml::Bindings::Wit(wb) => {
format!("found: {}", serde_json::to_string(wb).unwrap_or_default())
}
wasmer_toml::Bindings::Wai(wb) => {
format!("found: {}", serde_json::to_string(wb).unwrap_or_default())
}
})
.collect::<Vec<_>>()
.join("\r\n");
let msg = vec![
String::new(),
" It looks like your project contains multiple *.wai files.".to_string(),
" Make sure you update the [[module.bindings]] appropriately".to_string(),
String::new(),
found,
];
let msg = msg.join("\r\n");
if !quiet {
println!("{msg}");
}
log::warn!("{msg}");
}
let modules = vec![wasmer_toml::Module {
name: package_name.to_string(),
source: cargo_toml
.as_ref()
.map(|p| {
// Normalize the path to /target/release to be relative to the parent of the Cargo.toml
let outpath = p
.build_dir
.join("release")
.join(&format!("{package_name}.wasm"));
let canonicalized_outpath = outpath.canonicalize().unwrap_or(outpath);
let outpath_str = format!("{}", canonicalized_outpath.display());
let manifest_canonicalized = manifest_path
.parent()
.and_then(|p| p.canonicalize().ok())
.unwrap_or_else(|| manifest_path.to_path_buf());
let manifest_str = format!("{}/", manifest_canonicalized.display());
let relative_str = outpath_str.replacen(&manifest_str, "", 1);
Path::new(&relative_str).to_path_buf()
})
.unwrap_or_else(|| Path::new(&format!("{package_name}.wasm")).to_path_buf()),
kind: None,
abi: default_abi,
bindings: bindings.as_ref().and_then(|b| b.first_binding()),
interfaces: Some({
let mut map = HashMap::new();
map.insert("wasi".to_string(), "0.1.0-unstable".to_string());
map
}),
}];
Ok(wasmer_toml::Manifest {
package: wasmer_toml::Package {
name: if let Some(s) = namespace {
format!("{s}/{package_name}")
} else {
package_name.to_string()
},
version,
description,
license,
license_file,
readme,
repository,
homepage,
wasmer_extra_flags: None,
disable_command_rename: false,
rename_commands_to_raw_command_name: false,
},
dependencies: Some(Init::get_dependencies(template)),
command: Init::get_command(&modules, bin_or_lib),
module: match bin_or_lib {
BinOrLib::Empty => None,
_ => Some(modules),
},
fs: Init::get_filesystem_mapping(include_fs),
base_directory_path: target_file
.parent()
.map(|o| o.to_path_buf())
.unwrap_or_else(|| target_file.to_path_buf()),
})
}
fn parse_cargo_toml(manifest_path: &PathBuf) -> Result<MiniCargoTomlPackage, anyhow::Error> {
let mut metadata = MetadataCommand::new();
metadata.manifest_path(&manifest_path);
metadata.no_deps();
metadata.features(CargoOpt::AllFeatures);
let metadata = metadata.exec().with_context(|| {
format!(
"Unable to load metadata from \"{}\"",
manifest_path.display()
)
})?;
let package = metadata
.root_package()
.ok_or_else(|| anyhow::anyhow!("no root package found in cargo metadata"))
.context(anyhow::anyhow!("{}", manifest_path.display()))?;
Ok(MiniCargoTomlPackage {
cargo_toml_path: manifest_path.clone(),
name: package.name.clone(),
version: package.version.clone(),
description: package.description.clone(),
homepage: package.homepage.clone(),
repository: package.repository.clone(),
license: package.license.clone(),
readme: package.readme.clone().map(|s| s.into_std_path_buf()),
license_file: package.license_file.clone().map(|f| f.into_std_path_buf()),
workspace_root: metadata.workspace_root.into_std_path_buf(),
build_dir: metadata
.target_directory
.into_std_path_buf()
.join("wasm32-wasi"),
})
}

View File

@@ -1,4 +1,5 @@
use clap::Parser;
use wasmer_registry::WasmerConfig;
/// Subcommand for listing packages
#[derive(Debug, Copy, Clone, Parser)]
@@ -8,8 +9,9 @@ impl List {
/// execute [List]
pub fn execute(&self) -> Result<(), anyhow::Error> {
use prettytable::{format, row, Table};
let rows = wasmer_registry::get_all_local_packages()
let wasmer_dir =
WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
let rows = wasmer_registry::get_all_local_packages(&wasmer_dir)
.into_iter()
.filter_map(|pkg| {
let package_root_path = pkg.path;

View File

@@ -1,6 +1,7 @@
use clap::Parser;
#[cfg(not(test))]
use dialoguer::Input;
use wasmer_registry::WasmerConfig;
/// Subcommand for listing packages
#[derive(Debug, Clone, Parser)]
@@ -51,7 +52,9 @@ impl Login {
/// execute [List]
pub fn execute(&self) -> Result<(), anyhow::Error> {
let token = self.get_token_or_ask_user()?;
match wasmer_registry::login::login_and_save_token(&self.registry, &token)? {
let wasmer_dir =
WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
match wasmer_registry::login::login_and_save_token(&wasmer_dir, &self.registry, &token)? {
Some(s) => println!("Login for WAPM user {:?} saved", s),
None => println!(
"Error: no user found on registry {:?} with token {:?}. Token saved regardless.",

View File

@@ -0,0 +1,659 @@
use anyhow::Context;
use clap::Parser;
use flate2::{write::GzEncoder, Compression};
use rusqlite::{params, Connection, OpenFlags, TransactionBehavior};
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use tar::Builder;
use thiserror::Error;
use time::{self, OffsetDateTime};
use wasmer_registry::publish::SignArchiveResult;
use wasmer_registry::{WasmerConfig, PACKAGE_TOML_FALLBACK_NAME};
const MIGRATIONS: &[(i32, &str)] = &[
(0, include_str!("../../sql/migrations/0000.sql")),
(1, include_str!("../../sql/migrations/0001.sql")),
(2, include_str!("../../sql/migrations/0002.sql")),
];
const CURRENT_DATA_VERSION: usize = MIGRATIONS.len();
/// CLI options for the `wasmer publish` command
#[derive(Debug, Parser)]
pub struct Publish {
/// Registry to publish to
#[clap(long)]
pub registry: Option<String>,
/// Run the publish logic without sending anything to the registry server
#[clap(long, name = "dry-run")]
pub dry_run: bool,
/// Run the publish command without any output
#[clap(long)]
pub quiet: bool,
/// Override the package of the uploaded package in the wasmer.toml
#[clap(long)]
pub package_name: Option<String>,
/// Override the package version of the uploaded package in the wasmer.toml
#[clap(long)]
pub version: Option<semver::Version>,
/// Override the token (by default, it will use the current logged in user)
#[clap(long)]
pub token: Option<String>,
/// Skip validation of the uploaded package
#[clap(long)]
pub no_validate: bool,
/// Directory containing the `wasmer.toml` (defaults to current root dir)
#[clap(name = "PACKAGE_PATH")]
pub package_path: Option<String>,
}
#[derive(Debug, Error)]
enum PublishError {
#[error("Cannot publish without a module.")]
NoModule,
#[error("Unable to publish the \"{module}\" module because \"{}\" is not a file", path.display())]
SourceMustBeFile { module: String, path: PathBuf },
#[error("Unable to load the bindings for \"{module}\" because \"{}\" doesn't exist", path.display())]
MissingBindings { module: String, path: PathBuf },
#[error("Error building package when parsing module \"{0}\": {1}.")]
ErrorBuildingPackage(String, io::Error),
#[error(
"Path \"{0}\", specified in the manifest as part of the package file system does not exist.",
)]
MissingManifestFsPath(String),
#[error("When processing the package filesystem, found path \"{0}\" which is not a directory")]
PackageFileSystemEntryMustBeDirectory(String),
}
impl Publish {
/// Executes `wasmer publish`
pub fn execute(&self) -> Result<(), anyhow::Error> {
let mut builder = Builder::new(Vec::new());
let cwd = match self.package_path.as_ref() {
Some(s) => std::env::current_dir()?.join(s),
None => std::env::current_dir()?,
};
let manifest_path_buf = cwd.join("wasmer.toml");
let manifest = std::fs::read_to_string(&manifest_path_buf)
.map_err(|e| anyhow::anyhow!("could not find manifest: {e}"))
.with_context(|| anyhow::anyhow!("{}", manifest_path_buf.display()))?;
let mut manifest = wasmer_toml::Manifest::parse(&manifest)?;
manifest.base_directory_path = cwd.clone();
if let Some(package_name) = self.package_name.as_ref() {
manifest.package.name = package_name.to_string();
}
if let Some(version) = self.version.as_ref() {
manifest.package.version = version.clone();
}
let registry = match self.registry.as_deref() {
Some(s) => wasmer_registry::format_graphql(s),
None => {
let wasmer_dir = WasmerConfig::get_wasmer_dir()
.map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
let config = WasmerConfig::from_file(&wasmer_dir)
.map_err(|e| anyhow::anyhow!("could not load config {e}"))?;
config.registry.get_current_registry()
}
};
if !self.no_validate {
validate::validate_directory(&manifest, &registry, cwd.clone())?;
}
builder.append_path_with_name(&manifest_path_buf, PACKAGE_TOML_FALLBACK_NAME)?;
let manifest_string = toml::to_string(&manifest)?;
let (readme, license) = construct_tar_gz(&mut builder, &manifest, &cwd)?;
builder
.finish()
.map_err(|e| anyhow::anyhow!("failed to finish .tar.gz builder: {e}"))?;
let tar_archive_data = builder.into_inner().map_err(|_| PublishError::NoModule)?;
let archive_name = "package.tar.gz".to_string();
let archive_dir = tempfile::TempDir::new()?;
let archive_dir_path: &std::path::Path = archive_dir.as_ref();
fs::create_dir(archive_dir_path.join("wapm_package"))?;
let archive_path = archive_dir_path.join("wapm_package").join(&archive_name);
let mut compressed_archive = fs::File::create(&archive_path).unwrap();
let mut gz_enc = GzEncoder::new(&mut compressed_archive, Compression::best());
gz_enc.write_all(&tar_archive_data).unwrap();
let _compressed_archive = gz_enc.finish().unwrap();
let mut compressed_archive_reader = fs::File::open(&archive_path)?;
let maybe_signature_data = sign_compressed_archive(&mut compressed_archive_reader)?;
let archived_data_size = archive_path.metadata()?.len();
assert!(archive_path.exists());
assert!(archive_path.is_file());
if self.dry_run {
// dry run: publish is done here
println!(
"Successfully published package `{}@{}`",
manifest.package.name, manifest.package.version
);
log::info!(
"Publish succeeded, but package was not published because it was run in dry-run mode"
);
return Ok(());
}
wasmer_registry::publish::try_chunked_uploading(
Some(registry),
self.token.clone(),
&manifest.package,
&manifest_string,
&license,
&readme,
&archive_name,
&archive_path,
&maybe_signature_data,
archived_data_size,
self.quiet,
)
.map_err(on_error)
}
}
fn construct_tar_gz(
builder: &mut tar::Builder<Vec<u8>>,
manifest: &wasmer_toml::Manifest,
cwd: &Path,
) -> Result<(Option<String>, Option<String>), anyhow::Error> {
let package = &manifest.package;
let modules = manifest.module.as_ref().ok_or(PublishError::NoModule)?;
let readme = match package.readme.as_ref() {
None => None,
Some(s) => {
let path = append_path_to_tar_gz(builder, &manifest.base_directory_path, s).map_err(
|(p, e)| PublishError::ErrorBuildingPackage(format!("{}", p.display()), e),
)?;
fs::read_to_string(path).ok()
}
};
let license_file = match package.license_file.as_ref() {
None => None,
Some(s) => {
let path = append_path_to_tar_gz(builder, &manifest.base_directory_path, s).map_err(
|(p, e)| PublishError::ErrorBuildingPackage(format!("{}", p.display()), e),
)?;
fs::read_to_string(path).ok()
}
};
for module in modules {
append_path_to_tar_gz(builder, &manifest.base_directory_path, &module.source).map_err(
|(normalized_path, _)| PublishError::SourceMustBeFile {
module: module.name.clone(),
path: normalized_path,
},
)?;
if let Some(bindings) = &module.bindings {
for path in bindings.referenced_files(&manifest.base_directory_path)? {
append_path_to_tar_gz(builder, &manifest.base_directory_path, &path).map_err(
|(normalized_path, _)| PublishError::MissingBindings {
module: module.name.clone(),
path: normalized_path,
},
)?;
}
}
}
// bundle the package filesystem
let default = std::collections::HashMap::default();
for (_alias, path) in manifest.fs.as_ref().unwrap_or(&default).iter() {
let normalized_path = normalize_path(cwd, path);
let path_metadata = normalized_path.metadata().map_err(|_| {
PublishError::MissingManifestFsPath(normalized_path.to_string_lossy().to_string())
})?;
if path_metadata.is_dir() {
builder.append_dir_all(path, &normalized_path)
} else {
return Err(PublishError::PackageFileSystemEntryMustBeDirectory(
path.to_string_lossy().to_string(),
)
.into());
}
.map_err(|_| {
PublishError::MissingManifestFsPath(normalized_path.to_string_lossy().to_string())
})?;
}
Ok((readme, license_file))
}
fn append_path_to_tar_gz(
builder: &mut tar::Builder<Vec<u8>>,
base_path: &Path,
target_path: &Path,
) -> Result<PathBuf, (PathBuf, io::Error)> {
let normalized_path = normalize_path(base_path, target_path);
normalized_path
.metadata()
.map_err(|e| (normalized_path.clone(), e))?;
builder
.append_path_with_name(&normalized_path, &target_path)
.map_err(|e| (normalized_path.clone(), e))?;
Ok(normalized_path)
}
fn on_error(e: anyhow::Error) -> anyhow::Error {
#[cfg(feature = "telemetry")]
sentry::integrations::anyhow::capture_anyhow(&e);
e
}
fn normalize_path(cwd: &Path, path: &Path) -> PathBuf {
let mut out = PathBuf::from(cwd);
let mut components = path.components();
if path.is_absolute() {
log::warn!(
"Interpreting absolute path {} as a relative path",
path.to_string_lossy()
);
components.next();
}
for comp in components {
out.push(comp);
}
out
}
/// Takes the package archive as a File and attempts to sign it using the active key
/// returns the public key id used to sign it and the signature string itself
pub fn sign_compressed_archive(
compressed_archive: &mut fs::File,
) -> anyhow::Result<SignArchiveResult> {
let key_db = open_db()?;
let personal_key = if let Ok(v) = get_active_personal_key(&key_db) {
v
} else {
return Ok(SignArchiveResult::NoKeyRegistered);
};
let password = rpassword::prompt_password(format!(
"Please enter your password for the key pair {}:",
&personal_key.public_key_id
))
.ok();
let private_key = if let Some(priv_key_location) = personal_key.private_key_location {
match minisign::SecretKey::from_file(&priv_key_location, password) {
Ok(priv_key_data) => priv_key_data,
Err(e) => {
log::error!(
"Could not read private key from location {}: {}",
priv_key_location,
e
);
return Err(e.into());
}
}
} else {
// TODO: add more info about why this might have happened and what the user can do about it
log::warn!("Active key does not have a private key location registered with it!");
return Err(anyhow!("Cannot sign package, no private key"));
};
let public_key = minisign::PublicKey::from_base64(&personal_key.public_key_value)?;
let signature = minisign::sign(
Some(&public_key),
&private_key,
compressed_archive,
None,
None,
)?;
Ok(SignArchiveResult::Ok {
public_key_id: personal_key.public_key_id,
signature: signature.to_string(),
})
}
/// Opens an exclusive read/write connection to the database, creating it if it does not exist
pub fn open_db() -> anyhow::Result<Connection> {
let wasmer_dir =
WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
let db_path = WasmerConfig::get_database_file_path(&wasmer_dir);
let mut conn = Connection::open_with_flags(
db_path,
OpenFlags::SQLITE_OPEN_CREATE
| OpenFlags::SQLITE_OPEN_READ_WRITE
| OpenFlags::SQLITE_OPEN_FULL_MUTEX,
)?;
apply_migrations(&mut conn)?;
Ok(conn)
}
/// Applies migrations to the database
pub fn apply_migrations(conn: &mut Connection) -> anyhow::Result<()> {
let user_version = conn.pragma_query_value(None, "user_version", |val| val.get(0))?;
for data_version in user_version..CURRENT_DATA_VERSION {
log::debug!("Applying migration {}", data_version);
apply_migration(conn, data_version as i32)?;
}
Ok(())
}
#[derive(Debug, Error)]
enum MigrationError {
#[error(
"Critical internal error: the data version {0} is not handleded; current data version: {1}"
)]
MigrationNumberDoesNotExist(i32, i32),
#[error("Critical internal error: failed to commit trasaction migrating to data version {0}")]
CommitFailed(i32),
#[error("Critical internal error: transaction failed on migration number {0}: {1}")]
TransactionFailed(i32, String),
}
/// Applies migrations to the database and updates the `user_version` pragma.
/// Every migration must leave the database in a valid state.
fn apply_migration(conn: &mut Connection, migration_number: i32) -> Result<(), MigrationError> {
let tx = conn
.transaction_with_behavior(TransactionBehavior::Immediate)
.map_err(|e| MigrationError::TransactionFailed(migration_number, format!("{}", e)))?;
let migration_to_apply = MIGRATIONS
.iter()
.find_map(|(number, sql)| {
if *number == migration_number {
Some(sql)
} else {
None
}
})
.ok_or({
MigrationError::MigrationNumberDoesNotExist(
migration_number,
CURRENT_DATA_VERSION as i32,
)
})?;
tx.execute_batch(migration_to_apply)
.map_err(|e| MigrationError::TransactionFailed(migration_number, format!("{}", e)))?;
tx.pragma_update(None, "user_version", &(migration_number + 1))
.map_err(|e| MigrationError::TransactionFailed(migration_number, format!("{}", e)))?;
tx.commit()
.map_err(|_| MigrationError::CommitFailed(migration_number))
}
/// Information about one of the user's keys
#[derive(Debug)]
pub struct PersonalKey {
/// Flag saying if the key will be used (there can only be one active key at a time)
pub active: bool,
/// The public key's tag. Used to identify the key pair
pub public_key_id: String,
/// The raw value of the public key in base64
pub public_key_value: String,
/// The location in the file system of the private key
pub private_key_location: Option<String>,
/// The type of private/public key this is
pub key_type_identifier: String,
/// The time at which the key was registered with wapm
pub date_created: OffsetDateTime,
}
fn get_active_personal_key(conn: &Connection) -> anyhow::Result<PersonalKey> {
let mut stmt = conn.prepare(
"SELECT active, public_key_value, private_key_location, date_added, key_type_identifier, public_key_id FROM personal_keys
where active = 1",
)?;
let result = stmt
.query_map(params![], |row| {
Ok(PersonalKey {
active: row.get(0)?,
public_key_value: row.get(1)?,
private_key_location: row.get(2)?,
date_created: {
use time::format_description::well_known::Rfc3339;
let time_str: String = row.get(3)?;
OffsetDateTime::parse(&time_str, &Rfc3339)
.unwrap_or_else(|_| panic!("Failed to parse time string {}", &time_str))
},
key_type_identifier: row.get(4)?,
public_key_id: row.get(5)?,
})
})?
.next();
if let Some(res) = result {
Ok(res?)
} else {
Err(anyhow!("No active key found"))
}
}
mod interfaces {
use rusqlite::{params, Connection, TransactionBehavior};
pub const WASM_INTERFACE_EXISTENCE_CHECK: &str =
include_str!("./sql/wasm_interface_existence_check.sql");
pub const INSERT_WASM_INTERFACE: &str = include_str!("./sql/insert_interface.sql");
pub const GET_WASM_INTERFACE: &str = include_str!("./sql/get_interface.sql");
pub fn interface_exists(
conn: &mut Connection,
interface_name: &str,
version: &str,
) -> anyhow::Result<bool> {
let mut stmt = conn.prepare(WASM_INTERFACE_EXISTENCE_CHECK)?;
Ok(stmt.exists(params![interface_name, version])?)
}
pub fn load_interface_from_db(
conn: &mut Connection,
interface_name: &str,
version: &str,
) -> anyhow::Result<wasmer_wasm_interface::Interface> {
let mut stmt = conn.prepare(GET_WASM_INTERFACE)?;
let interface_string: String =
stmt.query_row(params![interface_name, version], |row| row.get(0))?;
wasmer_wasm_interface::parser::parse_interface(&interface_string).map_err(|e| {
anyhow!(
"Failed to parse interface {} version {} in database: {}",
interface_name,
version,
e
)
})
}
pub fn import_interface(
conn: &mut Connection,
interface_name: &str,
version: &str,
content: &str,
) -> anyhow::Result<()> {
// fail if we already have this interface
{
let mut key_check = conn.prepare(WASM_INTERFACE_EXISTENCE_CHECK)?;
let result = key_check.exists(params![interface_name, version])?;
if result {
return Err(anyhow!(
"Interface {}, version {} already exists",
interface_name,
version
));
}
}
let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
let time_string = get_current_time_in_format().expect("Could not get current time");
log::debug!("Adding interface {:?} {:?}", interface_name, version);
tx.execute(
INSERT_WASM_INTERFACE,
params![interface_name, version, time_string, content],
)?;
tx.commit()?;
Ok(())
}
/// Gets the current time in our standard format
pub fn get_current_time_in_format() -> Option<String> {
use time::format_description::well_known::Rfc3339;
let cur_time = time::OffsetDateTime::now_utc();
cur_time.format(&Rfc3339).ok()
}
}
mod validate {
use super::interfaces;
use std::{
fs,
io::Read,
path::{Path, PathBuf},
};
use thiserror::Error;
use wasmer_registry::interface::InterfaceFromServer;
use wasmer_wasm_interface::{validate, Interface};
pub fn validate_directory(
manifest: &wasmer_toml::Manifest,
registry: &str,
pkg_path: PathBuf,
) -> anyhow::Result<()> {
// validate as dir
if let Some(modules) = manifest.module.as_ref() {
for module in modules.iter() {
let source_path = if module.source.is_relative() {
manifest.base_directory_path.join(&module.source)
} else {
module.source.clone()
};
let source_path_string = source_path.to_string_lossy().to_string();
let mut wasm_file =
fs::File::open(&source_path).map_err(|_| ValidationError::MissingFile {
file: source_path_string.clone(),
})?;
let mut wasm_buffer = Vec::new();
wasm_file.read_to_end(&mut wasm_buffer).map_err(|err| {
ValidationError::MiscCannotRead {
file: source_path_string.clone(),
error: format!("{}", err),
}
})?;
if let Some(bindings) = &module.bindings {
validate_bindings(bindings, &manifest.base_directory_path)?;
}
// hack, short circuit if no interface for now
if module.interfaces.is_none() {
return validate_wasm_and_report_errors_old(
&wasm_buffer[..],
source_path_string,
);
}
let mut conn = super::open_db()?;
let mut interface: Interface = Default::default();
for (interface_name, interface_version) in
module.interfaces.clone().unwrap_or_default().into_iter()
{
if !interfaces::interface_exists(
&mut conn,
&interface_name,
&interface_version,
)? {
// download interface and store it if we don't have it locally
let interface_data_from_server = InterfaceFromServer::get(
registry,
interface_name.clone(),
interface_version.clone(),
)?;
interfaces::import_interface(
&mut conn,
&interface_name,
&interface_version,
&interface_data_from_server.content,
)?;
}
let sub_interface = interfaces::load_interface_from_db(
&mut conn,
&interface_name,
&interface_version,
)?;
interface = interface.merge(sub_interface).map_err(|e| {
anyhow!("Failed to merge interface {}: {}", &interface_name, e)
})?;
}
validate::validate_wasm_and_report_errors(&wasm_buffer, &interface).map_err(
|e| ValidationError::InvalidWasm {
file: source_path_string,
error: format!("{:?}", e),
},
)?;
}
}
log::debug!("package at path {:#?} validated", &pkg_path);
Ok(())
}
fn validate_bindings(
bindings: &wasmer_toml::Bindings,
base_directory_path: &Path,
) -> Result<(), ValidationError> {
// Note: checking for referenced files will make sure they all exist.
let _ = bindings.referenced_files(base_directory_path)?;
Ok(())
}
#[derive(Debug, Error)]
pub enum ValidationError {
#[error("WASM file \"{file}\" detected as invalid because {error}")]
InvalidWasm { file: String, error: String },
#[error("Could not find file {file}")]
MissingFile { file: String },
#[error("Failed to read file {file}; {error}")]
MiscCannotRead { file: String, error: String },
#[error(transparent)]
Imports(#[from] wasmer_toml::ImportsError),
}
// legacy function, validates wasm. TODO: clean up
pub fn validate_wasm_and_report_errors_old(
wasm: &[u8],
file_name: String,
) -> anyhow::Result<()> {
use wasmparser::WasmDecoder;
let mut parser = wasmparser::ValidatingParser::new(wasm, None);
loop {
let state = parser.read();
match state {
wasmparser::ParserState::EndWasm => return Ok(()),
wasmparser::ParserState::Error(e) => {
return Err(ValidationError::InvalidWasm {
file: file_name,
error: format!("{}", e),
}
.into());
}
_ => {}
}
}
}
}

View File

@@ -0,0 +1,4 @@
SELECT content
FROM wasm_interfaces
WHERE interface_name = (?1)
AND version = (?2)

View File

@@ -0,0 +1,3 @@
INSERT INTO wasm_interfaces
(interface_name, version, date_added, content)
VALUES (?1, ?2, ?3, ?4)

View File

@@ -0,0 +1,5 @@
SELECT 1
FROM wasm_interfaces
WHERE interface_name = (?1)
AND version = (?2)
LIMIT 1

View File

@@ -1,4 +1,5 @@
use clap::Parser;
use wasmer_registry::WasmerConfig;
#[derive(Debug, Parser)]
/// The options for the `wasmer whoami` subcommand
@@ -11,7 +12,10 @@ pub struct Whoami {
impl Whoami {
/// Execute `wasmer whoami`
pub fn execute(&self) -> Result<(), anyhow::Error> {
let (registry, username) = wasmer_registry::whoami(self.registry.as_deref())?;
let wasmer_dir =
WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
let (registry, username) =
wasmer_registry::whoami(&wasmer_dir, self.registry.as_deref(), None)?;
println!("logged into registry {registry:?} as user {username:?}");
Ok(())
}

View File

@@ -4,6 +4,7 @@ use anyhow::Context;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use url::Url;
use wasmer_registry::WasmerConfig;
/// Source of a package
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
@@ -47,7 +48,7 @@ impl PackageSource {
}
/// Downloads the package (if any) to the installation directory, returns the path
/// of the package directory (containing the wapm.toml)
/// of the package directory (containing the wasmer.toml)
pub fn download_and_get_filepath(&self) -> Result<PathBuf, anyhow::Error> {
let url = match self {
Self::File(f) => {
@@ -61,20 +62,28 @@ impl PackageSource {
};
}
Self::Url(u) => {
if let Some(path) = wasmer_registry::Package::is_url_already_installed(u) {
let wasmer_dir = WasmerConfig::get_wasmer_dir()
.map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
if let Some(path) =
wasmer_registry::Package::is_url_already_installed(u, &wasmer_dir)
{
return Ok(path);
} else {
u.clone()
}
}
Self::Package(p) => {
let wasmer_dir = WasmerConfig::get_wasmer_dir()
.map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
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() {
} else if let Some(path) = p.already_installed(&wasmer_dir) {
return Ok(path);
} else {
p.url()?
let config = WasmerConfig::from_file(&wasmer_dir)
.map_err(|e| anyhow::anyhow!("error loading wasmer config file: {e}"))?;
p.url(&config.registry.get_current_registry())?
}
}
};
@@ -85,8 +94,10 @@ impl PackageSource {
String::new()
};
let wasmer_dir =
WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
let mut sp = start_spinner(format!("Installing package {url} ..."));
let opt_path = wasmer_registry::install_package(&url);
let opt_path = wasmer_registry::install_package(&wasmer_dir, &url);
if let Some(sp) = sp.take() {
use std::io::Write;
sp.clear();