mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-06 20:58:28 +00:00
Merge branch 'master' into fast-ci
This commit is contained in:
@@ -11,7 +11,7 @@ use crate::commands::CreateObj;
|
||||
#[cfg(feature = "wast")]
|
||||
use crate::commands::Wast;
|
||||
use crate::commands::{
|
||||
Add, Cache, Config, Init, Inspect, List, Login, Publish, Run, SelfUpdate, Validate, Whoami,
|
||||
Add, Cache, Config, Inspect, List, Login, Run, SelfUpdate, Validate, Whoami,
|
||||
};
|
||||
use crate::error::PrettyError;
|
||||
use clap::{CommandFactory, ErrorKind, Parser};
|
||||
@@ -46,10 +46,6 @@ 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),
|
||||
@@ -139,10 +135,6 @@ enum WasmerCLIOptions {
|
||||
/// Inspect a WebAssembly file
|
||||
Inspect(Inspect),
|
||||
|
||||
/// Initializes a new wapm.toml file
|
||||
#[clap(name = "init")]
|
||||
Init(Init),
|
||||
|
||||
/// Run spec testsuite
|
||||
#[cfg(feature = "wast")]
|
||||
Wast(Wast),
|
||||
@@ -173,10 +165,8 @@ 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")]
|
||||
@@ -234,9 +224,10 @@ 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" | "init"
|
||||
| "run" | "self-update" | "validate" | "wast" | "binfmt" | "list" | "login"
|
||||
| "publish" => WasmerCLIOptions::parse(),
|
||||
"add" | "cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "run"
|
||||
| "self-update" | "validate" | "wast" | "binfmt" | "list" | "login" => {
|
||||
WasmerCLIOptions::parse()
|
||||
}
|
||||
_ => {
|
||||
WasmerCLIOptions::try_parse_from(args.iter()).unwrap_or_else(|e| {
|
||||
match e.kind() {
|
||||
|
||||
@@ -10,11 +10,9 @@ 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;
|
||||
@@ -33,8 +31,8 @@ pub use create_obj::*;
|
||||
#[cfg(feature = "wast")]
|
||||
pub use wast::*;
|
||||
pub use {
|
||||
add::*, cache::*, config::*, init::*, inspect::*, list::*, login::*, publish::*, run::*,
|
||||
self_update::*, validate::*, whoami::*,
|
||||
add::*, cache::*, config::*, inspect::*, list::*, login::*, run::*, self_update::*,
|
||||
validate::*, whoami::*,
|
||||
};
|
||||
|
||||
/// The kind of object format to emit.
|
||||
|
||||
@@ -1,503 +0,0 @@
|
||||
use anyhow::Context;
|
||||
use cargo_metadata::{CargoOpt, MetadataCommand};
|
||||
use clap::Parser;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
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 wapm.toml for a library package
|
||||
#[clap(long, group = "crate-type")]
|
||||
pub lib: bool,
|
||||
/// Initialize wapm.toml for a binary package
|
||||
#[clap(long, group = "crate-type")]
|
||||
pub bin: bool,
|
||||
/// Initialize an empty wapm.toml
|
||||
#[clap(long, group = "crate-type")]
|
||||
pub empty: bool,
|
||||
/// Force overwriting the wapm.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 wapm.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 {
|
||||
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() {
|
||||
Some(parse_cargo_toml(&manifest_path)?)
|
||||
} 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: &wapm_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) => {
|
||||
let _ = std::fs::create_dir_all(s);
|
||||
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 = Path::new(&format!("/{path}")).to_path_buf();
|
||||
|
||||
(key, value)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_command(
|
||||
modules: &[wapm_toml::Module],
|
||||
bin_or_lib: BinOrLib,
|
||||
) -> Option<Vec<wapm_toml::Command>> {
|
||||
match bin_or_lib {
|
||||
BinOrLib::Bin => Some(
|
||||
modules
|
||||
.iter()
|
||||
.map(|m| {
|
||||
wapm_toml::Command::V1(wapm_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 wapm.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, true, _) | (true, _, true) => Err(anyhow::anyhow!(
|
||||
"cannot combine --empty with --bin or --lib"
|
||||
)),
|
||||
(true, false, false) => Ok(BinOrLib::Empty),
|
||||
(_, true, true) => Err(anyhow::anyhow!(
|
||||
"cannot initialize a wapm manifest with both --bin and --lib, pick one"
|
||||
)),
|
||||
(false, true, _) => Ok(BinOrLib::Bin),
|
||||
(false, _, true) => Ok(BinOrLib::Lib),
|
||||
_ => Ok(BinOrLib::Bin),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(wapm_toml::Bindings::Wit(wapm_toml::WitBindings {
|
||||
wit_exports: e.path().to_path_buf(),
|
||||
wit_bindgen: semver::Version::parse("0.1.0").unwrap(),
|
||||
}))
|
||||
} else if is_wai {
|
||||
Some(wapm_toml::Bindings::Wai(wapm_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(wapm_toml::Bindings),
|
||||
MultiBindings(Vec<wapm_toml::Bindings>),
|
||||
}
|
||||
|
||||
impl GetBindingsResult {
|
||||
fn first_binding(&self) -> Option<wapm_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,
|
||||
) -> wapm_toml::Manifest {
|
||||
let package_name = package_name.unwrap_or_else(|| {
|
||||
cargo_toml
|
||||
.as_ref()
|
||||
.map(|p| &p.name)
|
||||
.unwrap_or(fallback_package_name)
|
||||
});
|
||||
let namespace = namespace.or_else(|| wasmer_registry::whoami(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 = wapm_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 {
|
||||
wapm_toml::Bindings::Wit(wb) => {
|
||||
format!("found: {}", serde_json::to_string(wb).unwrap_or_default())
|
||||
}
|
||||
wapm_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![wapm_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
|
||||
}),
|
||||
}];
|
||||
|
||||
wapm_toml::Manifest {
|
||||
package: wapm_toml::Package {
|
||||
name: if wasmer_registry::Package::validate_package_name(package_name) {
|
||||
package_name.to_string()
|
||||
} else 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();
|
||||
|
||||
let metadata = match metadata {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
return Err(anyhow::anyhow!("failed to load metadata: {e}")
|
||||
.context(anyhow::anyhow!("{}", 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 {
|
||||
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"),
|
||||
})
|
||||
}
|
||||
@@ -1,679 +0,0 @@
|
||||
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::Package;
|
||||
use wasmer_registry::PartialWapmConfig;
|
||||
|
||||
const CURRENT_DATA_VERSION: i32 = 3;
|
||||
|
||||
/// 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 = wapm_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();
|
||||
}
|
||||
|
||||
// See if a user is logged in. The backend should check for authorization on uploading
|
||||
let (registry, username) =
|
||||
wasmer_registry::whoami(self.registry.as_deref(), self.token.as_deref()).with_context(
|
||||
|| {
|
||||
anyhow::anyhow!(
|
||||
"could not find username / registry for registry = {:?}, token = {}",
|
||||
self.registry,
|
||||
self.token.as_deref().unwrap_or_default()
|
||||
)
|
||||
},
|
||||
)?;
|
||||
|
||||
let registry_present =
|
||||
wasmer_registry::test_if_registry_present(®istry).unwrap_or(false);
|
||||
|
||||
if !registry_present {
|
||||
return Err(anyhow::anyhow!(
|
||||
"registry {} is currently unavailable",
|
||||
registry
|
||||
));
|
||||
}
|
||||
|
||||
// If the package name is not a "/", try to prefix it with the current logged in user
|
||||
if !Package::validate_package_name(&manifest.package.name) {
|
||||
manifest.package.name = format!("{username}/{}", manifest.package.name);
|
||||
}
|
||||
|
||||
// Validate the name again
|
||||
if !Package::validate_package_name(&manifest.package.name) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid package name {:?}",
|
||||
manifest.package.name
|
||||
));
|
||||
}
|
||||
|
||||
if !self.no_validate {
|
||||
validate::validate_directory(&manifest, ®istry, cwd.clone())?;
|
||||
}
|
||||
|
||||
builder.append_path_with_name(&manifest_path_buf, "wapm.toml")?;
|
||||
|
||||
let manifest_string = toml::to_string(&manifest)?;
|
||||
|
||||
let (readme, license) = construct_tar_gz(&mut builder, &manifest, &cwd)?;
|
||||
|
||||
builder.finish().ok();
|
||||
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(
|
||||
self.registry.clone(),
|
||||
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: &wapm_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"));
|
||||
};
|
||||
Ok(SignArchiveResult::Ok {
|
||||
public_key_id: personal_key.public_key_id,
|
||||
signature: (minisign::sign(
|
||||
Some(&minisign::PublicKey::from_base64(
|
||||
&personal_key.public_key_value,
|
||||
)?),
|
||||
&private_key,
|
||||
compressed_archive,
|
||||
None,
|
||||
None,
|
||||
)?
|
||||
.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 db_path =
|
||||
PartialWapmConfig::get_database_file_path().map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||
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)?;
|
||||
}
|
||||
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 migrations = &[
|
||||
(0, include_str!("../../sql/migrations/0000.sql")),
|
||||
(1, include_str!("../../sql/migrations/0001.sql")),
|
||||
(2, include_str!("../../sql/migrations/0002.sql")),
|
||||
];
|
||||
|
||||
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)
|
||||
})?;
|
||||
|
||||
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: &wapm_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: &wapm_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] wapm_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());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
SELECT content
|
||||
FROM wasm_interfaces
|
||||
WHERE interface_name = (?1)
|
||||
AND version = (?2)
|
||||
@@ -1,3 +0,0 @@
|
||||
INSERT INTO wasm_interfaces
|
||||
(interface_name, version, date_added, content)
|
||||
VALUES (?1, ?2, ?3, ?4)
|
||||
@@ -1,5 +0,0 @@
|
||||
SELECT 1
|
||||
FROM wasm_interfaces
|
||||
WHERE interface_name = (?1)
|
||||
AND version = (?2)
|
||||
LIMIT 1
|
||||
@@ -11,7 +11,7 @@ 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(), None)?;
|
||||
let (registry, username) = wasmer_registry::whoami(self.registry.as_deref())?;
|
||||
println!("logged into registry {registry:?} as user {username:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user