Merge branch 'master' into wasix-merge

This commit is contained in:
Christoph Herzog
2022-12-06 13:22:09 +01:00
135 changed files with 13867 additions and 7713 deletions

View File

@@ -10,10 +10,12 @@ use crate::commands::CreateExe;
use crate::commands::CreateObj;
#[cfg(feature = "wast")]
use crate::commands::Wast;
use crate::commands::{Cache, Config, Inspect, List, Run, SelfUpdate, Validate};
use crate::commands::{
Add, Cache, Config, Inspect, List, Login, Run, SelfUpdate, Validate, Whoami,
};
use crate::error::PrettyError;
use clap::{CommandFactory, ErrorKind, Parser};
use std::fmt;
use std::{fmt, str::FromStr};
#[derive(Parser, Debug)]
#[cfg_attr(
@@ -37,24 +39,23 @@ use std::fmt;
/// The options for the wasmer Command Line Interface
enum WasmerCLIOptions {
/// List all locally installed packages
#[clap(name = "list")]
List(List),
/// Run a WebAssembly file. Formats accepted: wasm, wat
#[clap(name = "run")]
Run(Run),
/// Login into a wapm.io-like registry
Login(Login),
/// Wasmer cache
#[clap(subcommand, name = "cache")]
#[clap(subcommand)]
Cache(Cache),
/// Validate a WebAssembly binary
#[clap(name = "validate")]
Validate(Validate),
/// Compile a WebAssembly binary
#[cfg(feature = "compiler")]
#[clap(name = "compile")]
Compile(Compile),
/// Compile a WebAssembly binary into a native executable
@@ -126,7 +127,6 @@ enum WasmerCLIOptions {
/// Get various configuration information needed
/// to compile programs which use Wasmer
#[clap(name = "config")]
Config(Config),
/// Update wasmer to the latest version
@@ -134,18 +134,21 @@ enum WasmerCLIOptions {
SelfUpdate(SelfUpdate),
/// Inspect a WebAssembly file
#[clap(name = "inspect")]
Inspect(Inspect),
/// Run spec testsuite
#[cfg(feature = "wast")]
#[clap(name = "wast")]
Wast(Wast),
/// Unregister and/or register wasmer as binfmt interpreter
#[cfg(target_os = "linux")]
#[clap(name = "binfmt")]
Binfmt(Binfmt),
/// Shows the current logged in user for the current active registry
Whoami(Whoami),
/// Add a WAPM package's bindings to your application.
Add(Add),
}
impl WasmerCLIOptions {
@@ -164,10 +167,13 @@ impl WasmerCLIOptions {
Self::Config(config) => config.execute(),
Self::Inspect(inspect) => inspect.execute(),
Self::List(list) => list.execute(),
Self::Login(login) => login.execute(),
#[cfg(feature = "wast")]
Self::Wast(wast) => wast.execute(),
#[cfg(target_os = "linux")]
Self::Binfmt(binfmt) => binfmt.execute(),
Self::Whoami(whoami) => whoami.execute(),
Self::Add(install) => install.execute(),
}
}
}
@@ -219,8 +225,10 @@ fn wasmer_main_inner() -> Result<(), anyhow::Error> {
WasmerCLIOptions::Run(Run::from_binfmt_args())
} else {
match command.unwrap_or(&"".to_string()).as_ref() {
"cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "run"
| "self-update" | "validate" | "wast" | "binfmt" | "list" => 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() {
@@ -271,7 +279,7 @@ impl fmt::Display for SplitVersion {
#[test]
fn test_split_version() {
assert_eq!(
SplitVersion::new("registry.wapm.io/graphql/python/python").unwrap(),
SplitVersion::parse("registry.wapm.io/graphql/python/python").unwrap(),
SplitVersion {
original: "registry.wapm.io/graphql/python/python".to_string(),
registry: Some("https://registry.wapm.io/graphql".to_string()),
@@ -281,7 +289,7 @@ fn test_split_version() {
}
);
assert_eq!(
SplitVersion::new("registry.wapm.io/python/python").unwrap(),
SplitVersion::parse("registry.wapm.io/python/python").unwrap(),
SplitVersion {
original: "registry.wapm.io/python/python".to_string(),
registry: Some("https://registry.wapm.io/graphql".to_string()),
@@ -291,7 +299,7 @@ fn test_split_version() {
}
);
assert_eq!(
SplitVersion::new("namespace/name@version:command").unwrap(),
SplitVersion::parse("namespace/name@version:command").unwrap(),
SplitVersion {
original: "namespace/name@version:command".to_string(),
registry: None,
@@ -301,7 +309,7 @@ fn test_split_version() {
}
);
assert_eq!(
SplitVersion::new("namespace/name@version").unwrap(),
SplitVersion::parse("namespace/name@version").unwrap(),
SplitVersion {
original: "namespace/name@version".to_string(),
registry: None,
@@ -311,7 +319,7 @@ fn test_split_version() {
}
);
assert_eq!(
SplitVersion::new("namespace/name").unwrap(),
SplitVersion::parse("namespace/name").unwrap(),
SplitVersion {
original: "namespace/name".to_string(),
registry: None,
@@ -321,7 +329,7 @@ fn test_split_version() {
}
);
assert_eq!(
SplitVersion::new("registry.wapm.io/namespace/name").unwrap(),
SplitVersion::parse("registry.wapm.io/namespace/name").unwrap(),
SplitVersion {
original: "registry.wapm.io/namespace/name".to_string(),
registry: Some("https://registry.wapm.io/graphql".to_string()),
@@ -331,13 +339,21 @@ fn test_split_version() {
}
);
assert_eq!(
format!("{}", SplitVersion::new("namespace").unwrap_err()),
format!("{}", SplitVersion::parse("namespace").unwrap_err()),
"Invalid package version: \"namespace\"".to_string(),
);
}
impl SplitVersion {
pub fn new(s: &str) -> Result<SplitVersion, anyhow::Error> {
pub fn parse(s: &str) -> Result<SplitVersion, anyhow::Error> {
s.parse()
}
}
impl FromStr for SplitVersion {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let command = WasmerCLIOptions::command();
let mut prohibited_package_names = command.get_subcommands().map(|s| s.get_name());

View File

@@ -1,4 +1,5 @@
//! The commands available in the Wasmer binary.
mod add;
#[cfg(target_os = "linux")]
mod binfmt;
mod cache;
@@ -11,11 +12,13 @@ mod create_exe;
mod create_obj;
mod inspect;
mod list;
mod login;
mod run;
mod self_update;
mod validate;
#[cfg(feature = "wast")]
mod wast;
mod whoami;
#[cfg(target_os = "linux")]
pub use binfmt::*;
@@ -27,7 +30,10 @@ pub use create_exe::*;
pub use create_obj::*;
#[cfg(feature = "wast")]
pub use wast::*;
pub use {cache::*, config::*, inspect::*, list::*, run::*, self_update::*, validate::*};
pub use {
add::*, cache::*, config::*, inspect::*, list::*, login::*, run::*, self_update::*,
validate::*, whoami::*,
};
/// The kind of object format to emit.
#[derive(Debug, Copy, Clone, clap::Parser)]

210
lib/cli/src/commands/add.rs Normal file
View File

@@ -0,0 +1,210 @@
use std::process::{Command, Stdio};
use anyhow::{Context, Error};
use clap::Parser;
use wasmer_registry::{Bindings, PartialWapmConfig, ProgrammingLanguage};
use crate::cli::SplitVersion;
/// Add a WAPM package's bindings to your application.
#[derive(Debug, Parser)]
pub struct Add {
/// The registry to fetch bindings from.
#[clap(long, env = "WAPM_REGISTRY")]
registry: Option<String>,
/// Add the JavaScript bindings using "npm install".
#[clap(long, groups = &["bindings", "js"])]
npm: bool,
/// Add the JavaScript bindings using "yarn add".
#[clap(long, groups = &["bindings", "js"])]
yarn: bool,
/// Add the package as a dev-dependency.
#[clap(long, requires = "js")]
dev: bool,
/// Add the Python bindings using "pip install".
#[clap(long, groups = &["bindings", "py"])]
pip: bool,
/// The packages to add (e.g. "wasmer/wasmer-pack@0.5.0" or "python/python")
#[clap(parse(try_from_str))]
packages: Vec<SplitVersion>,
}
impl Add {
/// Execute [`Add`].
pub fn execute(&self) -> Result<(), Error> {
anyhow::ensure!(!self.packages.is_empty(), "No packages specified");
let registry = self
.registry()
.context("Unable to determine which registry to use")?;
let bindings = self.lookup_bindings(&registry)?;
let mut cmd = self.target()?.command(&bindings)?;
cmd.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
println!("Running: {cmd:?}");
let status = cmd.status().with_context(|| {
format!(
"Unable to start \"{:?}\". Is it installed?",
cmd.get_program()
)
})?;
anyhow::ensure!(status.success(), "Command failed: {:?}", cmd);
Ok(())
}
fn lookup_bindings(&self, registry: &str) -> Result<Vec<Bindings>, Error> {
println!("Querying WAPM for package bindings");
let mut bindings_to_add = Vec::new();
let language = self.target()?.language();
for pkg in &self.packages {
let bindings = lookup_bindings_for_package(registry, pkg, &language)
.with_context(|| format!("Unable to find bindings for {pkg}"))?;
bindings_to_add.push(bindings);
}
Ok(bindings_to_add)
}
fn registry(&self) -> Result<String, Error> {
match &self.registry {
Some(r) => Ok(r.clone()),
None => {
let cfg = PartialWapmConfig::from_file()
.map_err(Error::msg)
.context("Unable to load WAPM's config file")?;
Ok(cfg.registry.get_current_registry())
}
}
}
fn target(&self) -> Result<Target, Error> {
match (self.pip, self.npm, self.yarn) {
(false, false, false) => Err(anyhow::anyhow!(
"at least one of --npm, --pip or --yarn has to be specified"
)),
(true, false, false) => Ok(Target::Pip),
(false, true, false) => Ok(Target::Npm { dev: self.dev }),
(false, false, true) => Ok(Target::Yarn { dev: self.dev }),
_ => Err(anyhow::anyhow!(
"only one of --npm, --pip or --yarn has to be specified"
)),
}
}
}
fn lookup_bindings_for_package(
registry: &str,
pkg: &SplitVersion,
language: &ProgrammingLanguage,
) -> Result<Bindings, Error> {
let all_bindings =
wasmer_registry::list_bindings(registry, &pkg.package, pkg.version.as_deref())?;
match all_bindings.iter().find(|b| b.language == *language) {
Some(b) => {
#[cfg(feature = "debug")]
{
let Bindings { url, generator, .. } = b;
log::debug!("Found {pkg} bindings generated by {generator} at {url}");
}
Ok(b.clone())
}
None => {
if all_bindings.is_empty() {
anyhow::bail!("The package doesn't contain any bindings");
} else {
todo!();
}
}
}
}
#[derive(Debug, Copy, Clone)]
enum Target {
Pip,
Yarn { dev: bool },
Npm { dev: bool },
}
impl Target {
fn language(self) -> ProgrammingLanguage {
match self {
Target::Pip => ProgrammingLanguage::PYTHON,
Target::Yarn { .. } | Target::Npm { .. } => ProgrammingLanguage::JAVASCRIPT,
}
}
/// Construct a command which we can run to add packages.
///
/// This deliberately runs the command using the OS shell instead of
/// invoking the tool directly. That way we can handle when a version
/// manager (e.g. `nvm` or `asdf`) replaces the tool with a script (e.g.
/// `npm.cmd` or `yarn.ps1`).
///
/// See <https://github.com/wasmerio/wapm-cli/issues/291> for more.
fn command(self, packages: &[Bindings]) -> Result<Command, Error> {
let command_line = match self {
Target::Pip => {
if Command::new("pip").arg("--version").output().is_ok() {
"pip install"
} else if Command::new("pip3").arg("--version").output().is_ok() {
"pip3 install"
} else if Command::new("python").arg("--version").output().is_ok() {
"python -m pip install"
} else if Command::new("python3").arg("--version").output().is_ok() {
"python3 -m pip install"
} else {
return Err(anyhow::anyhow!(
"neither pip, pip3, python or python3 installed"
));
}
}
Target::Yarn { dev } => {
if Command::new("yarn").arg("--version").output().is_err() {
return Err(anyhow::anyhow!("yarn not installed"));
}
if dev {
"yarn add --dev"
} else {
"yarn add"
}
}
Target::Npm { dev } => {
if Command::new("npm").arg("--version").output().is_err() {
return Err(anyhow::anyhow!("yarn not installed"));
}
if dev {
"npm install --dev"
} else {
"npm install"
}
}
};
let mut command_line = command_line.to_string();
for pkg in packages {
command_line.push(' ');
command_line.push_str(&pkg.url);
}
if cfg!(windows) {
let mut cmd = Command::new("cmd");
cmd.arg("/C").arg(command_line);
Ok(cmd)
} else {
let mut cmd = Command::new("sh");
cmd.arg("-c").arg(command_line);
Ok(cmd)
}
}
}

View File

@@ -4,11 +4,8 @@ use super::ObjectFormat;
use crate::store::CompilerOptions;
use anyhow::{Context, Result};
use clap::Parser;
#[cfg(feature = "http")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "http")]
use std::collections::BTreeMap;
use std::env;
use std::fmt::Write as _;
use std::fs;
use std::fs::File;
use std::io::prelude::*;
@@ -240,8 +237,8 @@ impl CreateExe {
if let Some(setup) = cross_compilation.as_ref() {
self.compile_zig(
output_path,
wasm_module_path,
std::path::Path::new("static_defs.h").into(),
&[wasm_module_path],
&[std::path::Path::new("static_defs.h").into()],
setup,
&[],
None,
@@ -311,8 +308,8 @@ impl CreateExe {
if let Some(setup) = cross_compilation.as_ref() {
self.compile_zig(
output_path,
object_file_path,
std::path::Path::new("static_defs.h").into(),
&[object_file_path],
&[std::path::Path::new("static_defs.h").into()],
setup,
&[],
None,
@@ -371,7 +368,7 @@ impl CreateExe {
));
}
let target = if let Some(target_triple) = target_triple.clone() {
let target = if let Some(target_triple) = target_triple {
target_triple
} else {
return Err(anyhow!(
@@ -406,13 +403,7 @@ impl CreateExe {
v.canonicalize().unwrap_or(v)
} else {
{
let libwasmer_path = if target_triple.unwrap_or(Triple::host()).operating_system
== wasmer_types::OperatingSystem::Windows
{
"lib/wasmer.lib"
} else {
"lib/libwasmer.a"
};
let libwasmer_path = "lib/libwasmer.a";
let tarball_dir;
let filename = if let Some(local_tarball) = cross_subc.tarball.as_ref() {
let target_file_path = local_tarball
@@ -494,6 +485,7 @@ impl CreateExe {
run_c_compile(&c_src_path, &c_src_obj, target_triple.clone())
.context("Failed to compile C source code")?;
LinkCode {
object_paths: vec![c_src_obj, wasm_object_path],
output_path,
@@ -511,8 +503,8 @@ impl CreateExe {
fn compile_zig(
&self,
output_path: PathBuf,
object_path: PathBuf,
mut header_code_path: PathBuf,
object_paths: &[PathBuf],
header_code_paths: &[PathBuf],
setup: &CrossCompileSetup,
pirita_atoms: &[String],
pirita_main_atom: Option<&str>,
@@ -548,12 +540,16 @@ impl CreateExe {
std::fs::write(&c_src_path, WASMER_STATIC_MAIN_C_SOURCE)?;
}
if !header_code_path.is_dir() {
header_code_path.pop();
}
let mut header_code_paths = header_code_paths.to_vec();
if header_code_path.display().to_string().is_empty() {
header_code_path = std::env::current_dir()?;
for h in header_code_paths.iter_mut() {
if !h.is_dir() {
h.pop();
}
if h.display().to_string().is_empty() {
*h = std::env::current_dir()?;
}
}
/* Compile main function */
@@ -563,28 +559,49 @@ impl CreateExe {
include_dir.push("include");
let mut cmd = Command::new(zig_binary_path);
let mut cmd_mut: &mut Command = cmd
.arg("cc")
.arg("-target")
.arg(&zig_triple)
.arg(&format!("-L{}", libwasmer_path.display()))
.arg(&format!("-l:{}", lib_filename))
.arg(&format!("-I{}", include_dir.display()))
.arg(&format!("-I{}", header_code_path.display()));
if !zig_triple.contains("windows") {
cmd_mut = cmd_mut.arg("-lunwind");
cmd.arg("build-exe");
cmd.arg("-target");
cmd.arg(&zig_triple);
cmd.arg(&format!("-I{}/", include_dir.display()));
for h in header_code_paths {
cmd.arg(&format!("-I{}/", h.display()));
}
cmd_mut = cmd_mut.arg(&object_path).arg(&c_src_path);
if zig_triple.contains("windows") {
cmd.arg("-lc++");
} else {
cmd.arg("-lc");
}
cmd.arg("-lunwind");
cmd.arg("-OReleaseSafe");
cmd.arg("-fstrip");
cmd.arg("-dead_strip");
cmd.arg("-dead_strip_dylibs");
cmd.arg("-fno-compiler-rt");
cmd.arg(&format!("-femit-bin={}", output_path.display()));
for o in object_paths {
cmd.arg(o);
}
cmd.arg(&c_src_path);
cmd.arg(libwasmer_path.join(&lib_filename));
if zig_triple.contains("windows") {
let mut libwasmer_parent = libwasmer_path.clone();
libwasmer_parent.pop();
let files_winsdk = std::fs::read_dir(libwasmer_parent.join("winsdk"))
.ok()
.map(|res| res.filter_map(|r| Some(r.ok()?.path())).collect::<Vec<_>>())
.unwrap_or_default();
for f in files_winsdk {
cmd.arg(f);
}
}
if let Some(volume_obj) = pirita_volume_path.as_ref() {
cmd_mut = cmd_mut.arg(volume_obj.clone());
cmd.arg(volume_obj.clone());
}
cmd_mut
.arg("-o")
.arg(&output_path)
.output()
.context("Could not execute `zig`")?
#[cfg(feature = "debug")]
log::debug!("{:?}", cmd);
cmd.output().context("Could not execute `zig`")?
};
if !compilation.status.success() {
return Err(anyhow::anyhow!(String::from_utf8_lossy(
@@ -763,8 +780,6 @@ impl CreateExe {
let volume_object_path =
Self::write_volume_obj(volume_bytes, target, tempdir_path)?;
link_objects.push(volume_object_path);
#[cfg(not(windows))]
let c_src_obj = working_dir.join("wasmer_main.o");
#[cfg(windows)]
@@ -784,66 +799,29 @@ impl CreateExe {
std::fs::write(&c_src_path, c_code.as_bytes())
.context("Failed to open C source code file")?;
// TODO: this branch is never hit because object format serialized +
// cross compilation doesn't work
if let Some(setup) = cross_compilation {
let CrossCompileSetup {
ref target,
ref zig_binary_path,
ref library,
} = setup;
// zig treats .o files the same as .c files
link_objects.push(c_src_path);
let mut libwasmer_path = library.to_path_buf();
let zig_triple = triple_to_zig_triple(target);
// Cross compilation is only possible with zig
println!("Library Path: {}", libwasmer_path.display());
println!("Using zig binary: {}", zig_binary_path.display());
println!("Using zig target triple: {}", &zig_triple);
let lib_filename = libwasmer_path
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string();
/* Compile main function */
let compilation = {
libwasmer_path.pop();
let mut include_dir = libwasmer_path.clone();
include_dir.pop();
include_dir.push("include");
println!("include dir: {}", include_dir.display());
let mut cmd = Command::new(zig_binary_path);
let mut cmd_mut: &mut Command = cmd
.arg("cc")
.arg("--verbose")
.arg("-target")
.arg(&zig_triple)
.arg(&format!("-L{}", libwasmer_path.display()))
.arg(&format!("-l:{}", lib_filename))
.arg(&format!("-I{}", include_dir.display()));
if !zig_triple.contains("windows") {
cmd_mut = cmd_mut.arg("-lunwind");
}
cmd_mut
.args(link_objects.into_iter())
.arg(&c_src_path)
.arg("-o")
.arg(&output_path)
.output()
.context("Could not execute `zig`")?
};
if !compilation.status.success() {
return Err(anyhow::anyhow!(String::from_utf8_lossy(
&compilation.stderr
)
.to_string()));
}
self.compile_zig(
output_path,
&link_objects,
&[],
&setup,
&atom_names,
Some(&entrypoint),
Some(volume_object_path),
)?;
} else {
// compile with cc instead of zig
run_c_compile(c_src_path.as_path(), &c_src_obj, self.target_triple.clone())
.context("Failed to compile C source code")?;
link_objects.push(c_src_obj);
link_objects.push(volume_object_path);
LinkCode {
object_paths: link_objects,
output_path,
@@ -866,8 +844,8 @@ impl CreateExe {
if let Some(setup) = cross_compilation.as_ref() {
self.compile_zig(
output_path,
object_file_path,
static_defs_file_path,
&[object_file_path],
&[static_defs_file_path],
setup,
&atom_names,
Some(&entrypoint),
@@ -912,7 +890,8 @@ impl CreateExe {
for atom_name in atom_names.iter() {
let atom_name = Self::normalize_atom_name(atom_name);
c_code_to_instantiate.push_str(&format!(
write!(
c_code_to_instantiate,
"
wasm_module_t *atom_{atom_name} = wasmer_object_module_new(store, \"{atom_name}\");
@@ -923,11 +902,16 @@ impl CreateExe {
return -1;
}}
"
));
deallocate_module.push_str(&format!("wasm_module_delete(atom_{atom_name});"));
)
.unwrap();
write!(deallocate_module, "wasm_module_delete(atom_{atom_name});").unwrap();
}
c_code_to_instantiate.push_str(&format!("wasm_module_t *module = atom_{atom_to_run};"));
write!(
c_code_to_instantiate,
"wasm_module_t *module = atom_{atom_to_run};"
)
.unwrap();
WASMER_STATIC_MAIN_C_SOURCE
.replace("#define WASI", "#define WASI\r\n#define WASI_PIRITA")
@@ -1036,8 +1020,6 @@ impl CreateExe {
.canonicalize()
.context("Failed to find libwasmer")?;
println!("Using libwasmer file: {}", libwasmer_path.display());
let lib_filename = libwasmer_path
.file_name()
.unwrap()
@@ -1350,19 +1332,24 @@ impl LinkCode {
#[cfg(feature = "http")]
mod http_fetch {
use anyhow::{anyhow, Context, Result};
use http_req::{
request::Request,
response::{Response, StatusCode},
uri::Uri,
};
use http_req::{request::Request, response::StatusCode, uri::Uri};
use std::convert::TryFrom;
use target_lexicon::OperatingSystem;
pub fn get_latest_release() -> Result<serde_json::Value> {
let mut writer = Vec::new();
let uri = Uri::try_from("https://api.github.com/repos/wasmerio/wasmer/releases").unwrap();
let response = Request::new(&uri)
.header("User-Agent", "wasmer")
// Increases rate-limiting in GitHub CI
let auth = std::env::var("GITHUB_TOKEN");
let mut response = Request::new(&uri);
if let Ok(token) = auth {
response.header("Authorization", &format!("Bearer {token}"));
}
let response = response
.header("User-Agent", "wasmerio")
.header("Accept", "application/vnd.github.v3+json")
.timeout(Some(std::time::Duration::new(30, 0)))
.send(&mut writer)
@@ -1370,10 +1357,12 @@ mod http_fetch {
.context("Could not lookup wasmer repository on Github.")?;
if response.status_code() != StatusCode::new(200) {
return Err(anyhow!(
"Github API replied with non-200 status code: {}",
response.status_code()
));
#[cfg(feature = "debug")]
log::warn!(
"Warning: Github API replied with non-200 status code: {}. Response: {}",
response.status_code(),
String::from_utf8_lossy(&writer),
);
}
let v: std::result::Result<serde_json::Value, _> = serde_json::from_reader(&*writer);
@@ -1436,31 +1425,64 @@ mod http_fetch {
}
};
// Test if file has been already downloaded
if let Ok(mut cache_path) = super::get_libwasmer_cache_path() {
match std::fs::read_dir(&cache_path).and_then(|r| {
let paths = std::fs::read_dir(&cache_path).and_then(|r| {
r.map(|res| res.map(|e| e.path()))
.collect::<Result<Vec<_>, std::io::Error>>()
}) {
Ok(mut entries) => {
});
if let Ok(mut entries) = paths {
entries.retain(|p| p.to_str().map(|p| p.ends_with(".tar.gz")).unwrap_or(false));
// create-exe on Windows is special: we use the windows-gnu64.tar.gz (GNU ABI)
// to link, not the windows-amd64.tar.gz (MSVC ABI)
if target_triple.operating_system == OperatingSystem::Windows {
entries.retain(|p| {
p.to_str()
.map(|p| p.contains("windows") && p.contains("gnu64"))
.unwrap_or(false)
});
} else {
entries.retain(|p| p.to_str().map(|p| check_arch(p)).unwrap_or(true));
entries.retain(|p| p.to_str().map(|p| check_vendor(p)).unwrap_or(true));
entries.retain(|p| p.to_str().map(|p| check_os(p)).unwrap_or(true));
entries.retain(|p| p.to_str().map(|p| check_env(p)).unwrap_or(true));
if entries.len() == 1 {
cache_path.push(&entries[0]);
if cache_path.exists() {
eprintln!(
"Using cached tarball to cache path `{}`.",
cache_path.display()
);
return Ok(cache_path);
}
}
if !entries.is_empty() {
cache_path.push(&entries[0]);
if cache_path.exists() {
eprintln!(
"Using cached tarball to cache path `{}`.",
cache_path.display()
);
return Ok(cache_path);
}
}
Err(_ioerr) => {}
}
}
if let Some(assets) = release["assets"].as_array_mut() {
let assets = match release["assets"].as_array_mut() {
Some(s) => s,
None => {
return Err(anyhow!(
"GitHub API: no [assets] array in JSON response for latest releases"
));
}
};
// create-exe on Windows is special: we use the windows-gnu64.tar.gz (GNU ABI)
// to link, not the windows-amd64.tar.gz (MSVC ABI)
if target_triple.operating_system == OperatingSystem::Windows {
assets.retain(|a| {
if let Some(name) = a["name"].as_str() {
name.contains("windows") && name.contains("gnu64")
} else {
false
}
});
} else {
assets.retain(|a| {
if let Some(name) = a["name"].as_str() {
check_arch(name)
@@ -1475,6 +1497,7 @@ mod http_fetch {
false
}
});
assets.retain(|a| {
if let Some(name) = a["name"].as_str() {
check_os(name)
@@ -1482,6 +1505,7 @@ mod http_fetch {
false
}
});
assets.retain(|a| {
if let Some(name) = a["name"].as_str() {
check_env(name)
@@ -1489,128 +1513,105 @@ mod http_fetch {
false
}
});
}
if assets.len() == 1 {
let browser_download_url =
if let Some(url) = assets[0]["browser_download_url"].as_str() {
url.to_string()
} else {
return Err(anyhow!(
"Could not get download url from Github API response."
));
};
let filename = browser_download_url
.split('/')
.last()
.unwrap_or("output")
.to_string();
let mut file = std::fs::File::create(&filename)?;
println!("Downloading {} to {}", browser_download_url, &filename);
let download_thread: std::thread::JoinHandle<Result<Response, anyhow::Error>> =
std::thread::spawn(move || {
let uri = Uri::try_from(browser_download_url.as_str())?;
let mut response = Request::new(&uri)
.header("User-Agent", "wasmer")
.send(&mut file)
.map_err(anyhow::Error::new)
.context("Could not lookup wasmer artifact on Github.")?;
if response.status_code() == StatusCode::new(302) {
let redirect_uri =
Uri::try_from(response.headers().get("Location").unwrap().as_str())
.unwrap();
response = Request::new(&redirect_uri)
.header("User-Agent", "wasmer")
.send(&mut file)
.map_err(anyhow::Error::new)
.context("Could not lookup wasmer artifact on Github.")?;
}
Ok(response)
});
match super::get_libwasmer_cache_path() {
Ok(mut cache_path) => {
cache_path.push(&filename);
if !cache_path.exists() {
if let Err(err) = std::fs::copy(&filename, &cache_path) {
eprintln!(
"Could not store tarball to cache path `{}`: {}",
cache_path.display(),
err
);
} else {
eprintln!(
"Cached tarball to cache path `{}`.",
cache_path.display()
);
}
}
}
Err(err) => {
eprintln!(
"Could not determine cache path for downloaded binaries.: {}",
err
);
}
if assets.len() != 1 {
return Err(anyhow!(
"GitHub API: more that one release selected for target {target_triple}: {assets:?}"
));
}
let browser_download_url = if let Some(url) = assets[0]["browser_download_url"].as_str() {
url.to_string()
} else {
return Err(anyhow!(
"Could not get download url from Github API response."
));
};
let filename = browser_download_url
.split('/')
.last()
.unwrap_or("output")
.to_string();
let download_tempdir = tempdir::TempDir::new("wasmer-download")?;
let download_path = download_tempdir.path().join(&filename);
let mut file = std::fs::File::create(&download_path)?;
#[cfg(feature = "debug")]
log::debug!(
"Downloading {} to {}",
browser_download_url,
download_path.display()
);
let mut response = reqwest::blocking::Client::builder()
.redirect(reqwest::redirect::Policy::limited(10))
.timeout(std::time::Duration::from_secs(10))
.build()
.map_err(anyhow::Error::new)
.context("Could not lookup wasmer artifact on Github.")?
.get(browser_download_url.as_str())
.send()
.map_err(anyhow::Error::new)
.context("Could not lookup wasmer artifact on Github.")?;
response
.copy_to(&mut file)
.map_err(|e| anyhow::anyhow!("{e}"))?;
match super::get_libwasmer_cache_path() {
Ok(mut cache_path) => {
cache_path.push(&filename);
if let Err(err) = std::fs::copy(&download_path, &cache_path) {
eprintln!(
"Could not store tarball to cache path `{}`: {}",
cache_path.display(),
err
);
Err(anyhow!(
"Could not copy from {} to {}",
download_path.display(),
cache_path.display()
))
} else {
eprintln!("Cached tarball to cache path `{}`.", cache_path.display());
Ok(cache_path)
}
let _response = download_thread
.join()
.expect("Could not join downloading thread");
match super::get_libwasmer_cache_path() {
Ok(mut cache_path) => {
cache_path.push(&filename);
if !cache_path.exists() {
if let Err(err) = std::fs::copy(&filename, &cache_path) {
eprintln!(
"Could not store tarball to cache path `{}`: {}",
cache_path.display(),
err
);
} else {
eprintln!(
"Cached tarball to cache path `{}`.",
cache_path.display()
);
}
}
}
Err(err) => {
eprintln!(
"Could not determine cache path for downloaded binaries.: {}",
err
);
}
}
return Ok(filename.into());
}
Err(err) => {
eprintln!(
"Could not determine cache path for downloaded binaries.: {}",
err
);
Err(anyhow!("Could not determine libwasmer cache path"))
}
}
Err(anyhow!("Could not get release artifact."))
}
}
fn untar(tarball: std::path::PathBuf, target: std::path::PathBuf) -> Result<Vec<String>> {
let files = std::process::Command::new("tar")
.arg("-tf")
.arg(&tarball)
.output()
.expect("failed to execute process")
.stdout;
use walkdir::WalkDir;
let files_s = String::from_utf8(files)?;
wasmer_registry::try_unpack_targz(&tarball, &target, false)?;
let files = files_s
.lines()
.filter(|p| !p.ends_with('/'))
.map(|s| s.to_string())
.collect::<Vec<String>>();
Ok(WalkDir::new(&target)
.into_iter()
.filter_map(|e| e.ok())
.map(|entry| format!("{}", entry.path().display()))
.collect())
}
let _ = std::fs::create_dir_all(&target);
let _output = std::process::Command::new("tar")
.arg("-xf")
.arg(&tarball)
.arg("-C")
.arg(&target)
.output()
.expect("failed to execute process");
Ok(files)
fn get_zig_exe_str() -> &'static str {
#[cfg(target_os = "windows")]
{
"zig.exe"
}
#[cfg(not(target_os = "windows"))]
{
"zig"
}
}
fn find_zig_binary(path: Option<PathBuf>) -> Result<PathBuf> {
@@ -1643,24 +1644,13 @@ fn find_zig_binary(path: Option<PathBuf>) -> Result<PathBuf> {
OsStr::new("")
},
)) {
p.push("zig");
p.push(get_zig_exe_str());
if p.exists() {
retval = Some(p);
break;
}
}
retval
.or_else(|| {
#[cfg(feature = "http")]
{
try_autoinstall_zig().map(|p| p.join("zig"))
}
#[cfg(not(feature = "http"))]
{
None
}
})
.ok_or_else(|| anyhow!("Could not find `zig` binary in PATH."))?
retval.ok_or_else(|| anyhow!("Could not find `zig` binary in PATH."))?
};
let version = std::process::Command::new(&retval)
@@ -1688,126 +1678,3 @@ fn find_zig_binary(path: Option<PathBuf>) -> Result<PathBuf> {
Ok(retval)
}
}
/// Tries to auto-install zig into ~/.wasmer/utils/zig/{version}
#[cfg(feature = "http")]
fn try_autoinstall_zig() -> Option<PathBuf> {
let zig_dir = wasmer_registry::get_wasmer_root_dir()?
.join("utils")
.join("zig");
let mut existing_version = None;
if !zig_dir.exists() {
return install_zig(&zig_dir);
}
if let Ok(mut rd) = std::fs::read_dir(&zig_dir) {
existing_version = rd.next().and_then(|entry| {
let string = entry.ok()?.file_name().to_str()?.to_string();
if zig_dir.join(&string).join("zig").exists() {
Some(string)
} else {
None
}
})
}
if let Some(exist) = existing_version {
return Some(zig_dir.join(exist));
}
install_zig(&zig_dir)
}
#[cfg(feature = "http")]
fn install_zig(target_targz_path: &Path) -> Option<PathBuf> {
let resp = reqwest::blocking::get("https://ziglang.org/download/index.json");
let resp = resp.ok()?;
let resp = resp.json::<ZiglangOrgJson>();
let resp = resp.ok()?;
let default_key = "master".to_string();
let (latest_version, latest_version_json) = resp
.versions
.get(&default_key)
.map(|v| (&default_key, v))
.or_else(|| resp.versions.iter().next())?;
let latest_version = match latest_version_json.version.as_ref() {
Some(s) => s,
None => latest_version,
};
let install_dir = target_targz_path.join(latest_version);
if install_dir.join("zig").exists() {
return Some(install_dir);
}
let native_host_url = latest_version_json.get_native_host_url()?;
let _ = std::fs::create_dir_all(&install_dir);
wasmer_registry::download_and_unpack_targz(&native_host_url, &install_dir, true).ok()
}
#[cfg(feature = "http")]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct ZiglangOrgJson {
#[serde(flatten)]
versions: BTreeMap<String, ZiglangOrgJsonTarget>,
}
#[cfg(feature = "http")]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct ZiglangOrgJsonTarget {
version: Option<String>,
date: String,
src: ZiglangOrgJsonBuildTarget,
#[serde(rename = "x86_64-freebsd")]
x86_64_freebsd: Option<ZiglangOrgJsonBuildTarget>,
#[serde(rename = "x86_64-macos")]
x86_64_macos: Option<ZiglangOrgJsonBuildTarget>,
#[serde(rename = "aarch64-macos")]
aarch64_macos: Option<ZiglangOrgJsonBuildTarget>,
#[serde(rename = "x86_64-windows")]
x86_64_windows: Option<ZiglangOrgJsonBuildTarget>,
#[serde(rename = "x86_64-linux")]
x86_64_linux: Option<ZiglangOrgJsonBuildTarget>,
#[serde(rename = "aarch64-linux")]
aarch64_linux: Option<ZiglangOrgJsonBuildTarget>,
}
impl ZiglangOrgJsonTarget {
pub fn get_native_host_url(&self) -> Option<String> {
let native_host = format!("{}", target_lexicon::HOST);
if native_host.starts_with("x86_64") {
if native_host.contains("freebsd") {
Some(self.x86_64_freebsd.as_ref()?.tarball.clone())
} else if native_host.contains("darwin") || native_host.contains("macos") {
Some(self.x86_64_macos.as_ref()?.tarball.clone())
} else if native_host.contains("windows") {
Some(self.x86_64_windows.as_ref()?.tarball.clone())
} else if native_host.contains("linux") {
Some(self.x86_64_linux.as_ref()?.tarball.clone())
} else {
None
}
} else if native_host.starts_with("aarch64") {
if native_host.contains("darwin") || native_host.contains("macos") {
Some(self.aarch64_macos.as_ref()?.tarball.clone())
} else if native_host.contains("linux") {
Some(self.aarch64_linux.as_ref()?.tarball.clone())
} else {
None
}
} else {
None
}
}
}
#[cfg(feature = "http")]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct ZiglangOrgJsonBuildTarget {
tarball: String,
shasum: String,
size: String,
}

View File

@@ -36,7 +36,7 @@ impl List {
if empty_table {
table.add_empty_row();
}
let _ = table.printstd();
table.printstd();
Ok(())
}

View File

@@ -0,0 +1,83 @@
use clap::Parser;
#[cfg(not(test))]
use dialoguer::Input;
/// Subcommand for listing packages
#[derive(Debug, Clone, Parser)]
pub struct Login {
/// Registry to log into (default: wapm.io)
#[clap(long, default_value = "wapm.io")]
pub registry: String,
/// Login token
#[clap(name = "TOKEN")]
pub token: Option<String>,
}
impl Login {
fn get_token_or_ask_user(&self) -> Result<String, std::io::Error> {
match self.token.as_ref() {
Some(s) => Ok(s.clone()),
None => {
let registry_host = wasmer_registry::format_graphql(&self.registry);
let registry_tld = tldextract::TldExtractor::new(tldextract::TldOption::default())
.extract(&registry_host)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("Invalid registry for login {}: {e}", self.registry),
)
})?;
let login_prompt = match (
registry_tld.domain.as_deref(),
registry_tld.suffix.as_deref(),
) {
(Some(d), Some(s)) => {
format!("Please paste the login token for https://{d}.{s}/me")
}
_ => "Please paste the login token".to_string(),
};
#[cfg(test)]
{
Ok(login_prompt)
}
#[cfg(not(test))]
{
Input::new().with_prompt(&login_prompt).interact_text()
}
}
}
}
/// 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)? {
Some(s) => println!("Login for WAPM user {:?} saved", s),
None => println!(
"Error: no user found on registry {:?} with token {:?}. Token saved regardless.",
self.registry, token
),
}
Ok(())
}
}
#[test]
fn test_login_2() {
let login = Login {
registry: "wapm.dev".to_string(),
token: None,
};
assert_eq!(
login.get_token_or_ask_user().unwrap(),
"Please paste the login token for https://wapm.dev/me"
);
let login = Login {
registry: "wapm.dev".to_string(),
token: Some("abc".to_string()),
};
assert_eq!(login.get_token_or_ask_user().unwrap(), "abc");
}

View File

@@ -11,6 +11,7 @@ use std::collections::HashMap;
use std::ops::Deref;
use std::path::PathBuf;
use std::str::FromStr;
use url::Url;
use wasmer::FunctionEnv;
use wasmer::*;
#[cfg(feature = "cache")]
@@ -647,17 +648,20 @@ impl Run {
}
}
fn start_spinner(msg: String) -> Option<spinner::SpinnerHandle> {
fn start_spinner(msg: String) -> Option<spinoff::Spinner> {
if !isatty::stdout_isatty() {
return None;
}
Some(
spinner::SpinnerBuilder::new(msg)
.spinner(vec![
"", "", "", "", "", "", "", "", " ", "", "", "", "", "", "", "", "",
])
.start(),
)
#[cfg(target_os = "windows")]
{
use colored::control;
let _ = control::set_virtual_terminal(true);
}
Some(spinoff::Spinner::new(
spinoff::Spinners::Dots,
msg,
spinoff::Color::White,
))
}
/// Before looking up a command from the registry, try to see if we have
@@ -708,8 +712,7 @@ pub(crate) fn try_autoinstall_package(
force_install,
);
if let Some(sp) = sp.take() {
sp.close();
print!("\r");
sp.clear();
}
let _ = std::io::stdout().flush();
let (_, package_dir) = match result {
@@ -767,8 +770,8 @@ fn try_lookup_command(sv: &mut SplitVersion) -> Result<PackageDownloadInfo, anyh
for registry in wasmer_registry::get_all_available_registries().unwrap_or_default() {
let result = wasmer_registry::query_command_from_registry(&registry, &sv.package);
if sp.is_some() {
print!("\r");
if let Some(s) = sp.take() {
s.clear();
}
let _ = std::io::stdout().flush();
let command = sv.package.clone();
@@ -781,8 +784,7 @@ fn try_lookup_command(sv: &mut SplitVersion) -> Result<PackageDownloadInfo, anyh
}
if let Some(sp) = sp.take() {
sp.close();
print!("\r");
sp.clear();
}
let _ = std::io::stdout().flush();
Err(anyhow::anyhow!("command {sv} not found"))
@@ -845,10 +847,22 @@ pub(crate) fn try_run_package_or_file(
return r.execute();
}
// c:// might be parsed as a URL on Windows
let url_string = format!("{}", r.path.display());
if let Ok(url) = url::Url::parse(&url_string) {
if url.scheme() == "http" || url.scheme() == "https" {
match try_run_url(&url, args, r, debug) {
Err(ExecuteLocalPackageError::BeforeExec(_)) => {}
Err(ExecuteLocalPackageError::DuringExec(e)) => return Err(e),
Ok(o) => return Ok(o),
}
}
}
let package = format!("{}", r.path.display());
let mut is_fake_sv = false;
let mut sv = match SplitVersion::new(&package) {
let mut sv = match SplitVersion::parse(&package) {
Ok(o) => o,
Err(_) => {
let mut fake_sv = SplitVersion {
@@ -911,3 +925,41 @@ pub(crate) fn try_run_package_or_file(
// else: local package not found - try to download and install package
try_autoinstall_package(args, &sv, package_download_info, r.force_install)
}
fn try_run_url(
url: &Url,
_args: &[String],
r: &Run,
_debug: bool,
) -> Result<(), ExecuteLocalPackageError> {
let checksum = wasmer_registry::get_remote_webc_checksum(url).map_err(|e| {
ExecuteLocalPackageError::BeforeExec(anyhow::anyhow!("error fetching {url}: {e}"))
})?;
let packages = wasmer_registry::get_all_installed_webc_packages();
if !packages.iter().any(|p| p.checksum == checksum) {
let sp = start_spinner(format!("Installing {}", url));
let result = wasmer_registry::install_webc_package(url, &checksum);
result.map_err(|e| {
ExecuteLocalPackageError::BeforeExec(anyhow::anyhow!("error fetching {url}: {e}"))
})?;
if let Some(sp) = sp {
sp.clear();
}
}
let webc_dir = wasmer_registry::get_webc_dir();
let webc_install_path = webc_dir
.context("Error installing package: no webc dir")
.map_err(ExecuteLocalPackageError::BeforeExec)?
.join(checksum);
let mut r = r.clone();
r.path = webc_install_path;
r.execute().map_err(ExecuteLocalPackageError::DuringExec)
}

View File

@@ -0,0 +1,18 @@
use clap::Parser;
#[derive(Debug, Parser)]
/// The options for the `wasmer whoami` subcommand
pub struct Whoami {
/// Which registry to check the logged in username for
#[clap(long, name = "registry")]
pub registry: Option<String>,
}
impl Whoami {
/// Execute `wasmer whoami`
pub fn execute(&self) -> Result<(), anyhow::Error> {
let (registry, username) = wasmer_registry::whoami(self.registry.as_deref())?;
println!("logged into registry {registry:?} as user {username:?}");
Ok(())
}
}