mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-09 14:18:20 +00:00
Implement wasmer run {url}
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -2600,6 +2600,7 @@ dependencies = [
|
|||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
|
"tokio-util",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -4179,7 +4180,9 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"dirs 4.0.0",
|
"dirs 4.0.0",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
"futures-util",
|
||||||
"graphql_client",
|
"graphql_client",
|
||||||
|
"hex",
|
||||||
"lzma-rs",
|
"lzma-rs",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"semver 1.0.14",
|
"semver 1.0.14",
|
||||||
@@ -4187,9 +4190,11 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tar",
|
"tar",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"url",
|
"url",
|
||||||
"wapm-toml",
|
"wapm-toml",
|
||||||
|
"webc",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ use std::collections::HashMap;
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use url::Url;
|
||||||
use wasmer::FunctionEnv;
|
use wasmer::FunctionEnv;
|
||||||
use wasmer::*;
|
use wasmer::*;
|
||||||
#[cfg(feature = "cache")]
|
#[cfg(feature = "cache")]
|
||||||
@@ -827,6 +829,10 @@ pub(crate) fn try_run_package_or_file(
|
|||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
let debug_msgs_allowed = isatty::stdout_isatty();
|
let debug_msgs_allowed = isatty::stdout_isatty();
|
||||||
|
|
||||||
|
if let Ok(url) = url::Url::parse(&format!("{}", r.path.display())) {
|
||||||
|
return try_run_url(&url, args, r, debug);
|
||||||
|
}
|
||||||
|
|
||||||
// Check "r.path" is a file or a package / command name
|
// Check "r.path" is a file or a package / command name
|
||||||
if r.path.exists() {
|
if r.path.exists() {
|
||||||
if r.path.is_dir() && r.path.join("wapm.toml").exists() {
|
if r.path.is_dir() && r.path.join("wapm.toml").exists() {
|
||||||
@@ -908,3 +914,30 @@ pub(crate) fn try_run_package_or_file(
|
|||||||
// else: local package not found - try to download and install package
|
// else: local package not found - try to download and install package
|
||||||
try_autoinstall_package(args, &sv, package_download_info, r.force_install)
|
try_autoinstall_package(args, &sv, package_download_info, r.force_install)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_run_url(url: &Url, _args: &[String], r: &Run, _debug: bool) -> Result<(), anyhow::Error> {
|
||||||
|
let checksum = wasmer_registry::get_remote_webc_checksum(&url)
|
||||||
|
.map_err(|e| anyhow::anyhow!("error fetching {url}: {e}"))?;
|
||||||
|
|
||||||
|
if !wasmer_registry::get_all_installed_webc_packages()
|
||||||
|
.iter()
|
||||||
|
.any(|p| p.checksum == checksum)
|
||||||
|
{
|
||||||
|
let sp = start_spinner(format!("Installing {}", url));
|
||||||
|
|
||||||
|
wasmer_registry::install_webc_package(&url, &checksum)
|
||||||
|
.map_err(|e| anyhow::anyhow!("error fetching {url}: {e}"))?;
|
||||||
|
|
||||||
|
if let Some(sp) = sp {
|
||||||
|
sp.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let webc_install_path = wasmer_registry::get_webc_dir()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Error installing package: webc download failed"))?
|
||||||
|
.join(checksum);
|
||||||
|
|
||||||
|
let mut r = r.clone();
|
||||||
|
r.path = webc_install_path;
|
||||||
|
r.execute()
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ dirs = "4.0.0"
|
|||||||
graphql_client = "0.11.0"
|
graphql_client = "0.11.0"
|
||||||
serde = { version = "1.0.145", features = ["derive"] }
|
serde = { version = "1.0.145", features = ["derive"] }
|
||||||
anyhow = "1.0.65"
|
anyhow = "1.0.65"
|
||||||
reqwest = { version = "0.11.12", default-features = false, features = ["rustls-tls", "blocking", "multipart", "json"] }
|
reqwest = { version = "0.11.12", default-features = false, features = ["rustls-tls", "blocking", "multipart", "json", "stream"] }
|
||||||
|
futures-util = "0.3.25"
|
||||||
whoami = "1.2.3"
|
whoami = "1.2.3"
|
||||||
serde_json = "1.0.85"
|
serde_json = "1.0.85"
|
||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
@@ -21,3 +22,6 @@ tar = "0.4.38"
|
|||||||
flate2 = "1.0.24"
|
flate2 = "1.0.24"
|
||||||
semver = "1.0.14"
|
semver = "1.0.14"
|
||||||
lzma-rs = "0.2.0"
|
lzma-rs = "0.2.0"
|
||||||
|
webc = { version ="3.0.1", features = ["mmap"] }
|
||||||
|
hex = "0.4.3"
|
||||||
|
tokio = "1.21.2"
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::ptr::read;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use reqwest::header::ACCEPT;
|
||||||
|
use reqwest::header::RANGE;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::ops::Range;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
pub mod graphql {
|
pub mod graphql {
|
||||||
|
|
||||||
@@ -20,7 +26,7 @@ pub mod graphql {
|
|||||||
#[cfg(target_os = "wasi")]
|
#[cfg(target_os = "wasi")]
|
||||||
use {wasm_bus_reqwest::prelude::header::*, wasm_bus_reqwest::prelude::*};
|
use {wasm_bus_reqwest::prelude::header::*, wasm_bus_reqwest::prelude::*};
|
||||||
|
|
||||||
mod proxy {
|
pub mod proxy {
|
||||||
//! Code for dealing with setting things up to proxy network requests
|
//! Code for dealing with setting things up to proxy network requests
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
@@ -940,6 +946,10 @@ pub fn get_checkouts_dir() -> Option<PathBuf> {
|
|||||||
Some(get_wasmer_root_dir()?.join("checkouts"))
|
Some(get_wasmer_root_dir()?.join("checkouts"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_webc_dir() -> Option<PathBuf> {
|
||||||
|
Some(get_wasmer_root_dir()?.join("webc"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returs the path to the directory where all packages on this computer are being stored
|
/// Returs the path to the directory where all packages on this computer are being stored
|
||||||
pub fn get_global_install_dir(registry_host: &str) -> Option<PathBuf> {
|
pub fn get_global_install_dir(registry_host: &str) -> Option<PathBuf> {
|
||||||
Some(get_checkouts_dir()?.join(registry_host))
|
Some(get_checkouts_dir()?.join(registry_host))
|
||||||
@@ -1180,6 +1190,178 @@ pub fn get_all_available_registries() -> Result<Vec<String>, String> {
|
|||||||
Ok(registries)
|
Ok(registries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct RemoteWebcInfo {
|
||||||
|
pub checksum: String,
|
||||||
|
pub manifest: webc::Manifest,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn install_webc_package(url: &Url, checksum: &str) -> Result<(), String> {
|
||||||
|
tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.block_on(async { install_webc_package_inner(url, checksum).await })
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn install_webc_package_inner(url: &Url, checksum: &str) -> Result<(), String> {
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
|
||||||
|
let path = get_webc_dir().ok_or_else(|| format!("no webc dir"))?;
|
||||||
|
|
||||||
|
let _ = std::fs::create_dir_all(&path);
|
||||||
|
|
||||||
|
let webc_path = path.join(checksum);
|
||||||
|
|
||||||
|
let mut file =
|
||||||
|
std::fs::File::create(&webc_path).map_err(|e| format!("{}: {e}", webc_path.display()))?;
|
||||||
|
|
||||||
|
let client = {
|
||||||
|
let builder = reqwest::Client::builder();
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "wasi"))]
|
||||||
|
let builder = if let Some(proxy) =
|
||||||
|
crate::graphql::proxy::maybe_set_up_proxy().map_err(|e| format!("{e}"))?
|
||||||
|
{
|
||||||
|
builder.proxy(proxy)
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.build().map_err(|e| format!("{e}"))?
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.get(url.clone())
|
||||||
|
.header(ACCEPT, "application/webc")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("{e}"))?;
|
||||||
|
|
||||||
|
let mut stream = res.bytes_stream();
|
||||||
|
|
||||||
|
while let Some(item) = stream.next().await {
|
||||||
|
let item = item.map_err(|e| format!("{e}"))?;
|
||||||
|
file.write_all(&item);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a list of all installed webc packages
|
||||||
|
pub fn get_all_installed_webc_packages() -> Vec<RemoteWebcInfo> {
|
||||||
|
let dir = match get_webc_dir() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let read_dir = match std::fs::read_dir(dir) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
read_dir
|
||||||
|
.filter_map(|r| Some(r.ok()?.path()))
|
||||||
|
.filter_map(|path| {
|
||||||
|
webc::WebCMmap::parse(
|
||||||
|
path,
|
||||||
|
&webc::ParseOptions {
|
||||||
|
parse_atoms: false,
|
||||||
|
parse_volumes: false,
|
||||||
|
parse_manifest: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.filter_map(|webc| {
|
||||||
|
let mut checksum = webc.checksum.as_ref().map(|s| &s.data)?.to_vec();
|
||||||
|
while checksum.last().copied() == Some(0) {
|
||||||
|
checksum.pop();
|
||||||
|
}
|
||||||
|
let hex_string = hex::encode(&checksum);
|
||||||
|
Some(RemoteWebcInfo {
|
||||||
|
checksum: hex_string,
|
||||||
|
manifest: webc.manifest.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the checksum of the .webc file, so that we can check whether the
|
||||||
|
/// file is already installed before downloading it
|
||||||
|
pub fn get_remote_webc_checksum(url: &Url) -> Result<String, String> {
|
||||||
|
let request_max_bytes = webc::WebC::get_signature_offset_start() + 4 + 1024 + 8 + 8;
|
||||||
|
let data = get_webc_bytes(url, Some(0..request_max_bytes))?;
|
||||||
|
let mut checksum = webc::WebC::get_checksum_bytes(&data)
|
||||||
|
.map_err(|e| format!("{e}"))?
|
||||||
|
.to_vec();
|
||||||
|
while checksum.last().copied() == Some(0) {
|
||||||
|
checksum.pop();
|
||||||
|
}
|
||||||
|
let hex_string = hex::encode(&checksum);
|
||||||
|
Ok(hex_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Before fetching the entire file from a remote URL, just fetch the manifest
|
||||||
|
/// so we can see if the package has already been installed
|
||||||
|
pub fn get_remote_webc_manifest(url: &Url) -> Result<RemoteWebcInfo, String> {
|
||||||
|
// Request up unti manifest size / manifest len
|
||||||
|
let request_max_bytes = webc::WebC::get_signature_offset_start() + 4 + 1024 + 8 + 8;
|
||||||
|
let data = get_webc_bytes(url, Some(0..request_max_bytes))?;
|
||||||
|
let mut checksum = webc::WebC::get_checksum_bytes(&data)
|
||||||
|
.map_err(|e| format!("{e}"))?
|
||||||
|
.to_vec();
|
||||||
|
while checksum.last().copied() == Some(0) {
|
||||||
|
checksum.pop();
|
||||||
|
}
|
||||||
|
let hex_string = hex::encode(&checksum);
|
||||||
|
|
||||||
|
let (manifest_start, manifest_len) =
|
||||||
|
webc::WebC::get_manifest_offset_size(&data).map_err(|e| format!("{e}"))?;
|
||||||
|
let data_with_manifest =
|
||||||
|
get_webc_bytes(url, Some(0..manifest_start + manifest_len)).map_err(|e| format!("{e}"))?;
|
||||||
|
let manifest = webc::WebC::get_manifest(&data_with_manifest).map_err(|e| format!("{e}"))?;
|
||||||
|
Ok(RemoteWebcInfo {
|
||||||
|
checksum: hex_string,
|
||||||
|
manifest,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_webc_client(url: &Url) -> Result<reqwest::blocking::RequestBuilder, String> {
|
||||||
|
let client = {
|
||||||
|
let builder = reqwest::blocking::Client::builder();
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "wasi"))]
|
||||||
|
let builder = if let Some(proxy) =
|
||||||
|
crate::graphql::proxy::maybe_set_up_proxy().map_err(|e| format!("{e}"))?
|
||||||
|
{
|
||||||
|
builder.proxy(proxy)
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.build().map_err(|e| format!("{e}"))?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(client.get(url.clone()).header(ACCEPT, "application/webc"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_webc_bytes(url: &Url, range: Option<Range<usize>>) -> Result<Vec<u8>, String> {
|
||||||
|
// curl -r 0-500 -L https://wapm.dev/syrusakbary/python -H "Accept: application/webc" --output python.webc
|
||||||
|
|
||||||
|
let mut res = setup_webc_client(url)?;
|
||||||
|
|
||||||
|
if let Some(range) = range.as_ref() {
|
||||||
|
res = res.header(RANGE, format!("bytes={}-{}", range.start, range.end));
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = res.send().map_err(|e| format!("{e}"))?;
|
||||||
|
let bytes = res.bytes().map_err(|e| format!("{e}"))?;
|
||||||
|
|
||||||
|
Ok(bytes.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: this test is segfaulting only on linux-musl, no other OS
|
// TODO: this test is segfaulting only on linux-musl, no other OS
|
||||||
// See https://github.com/wasmerio/wasmer/pull/3215
|
// See https://github.com/wasmerio/wasmer/pull/3215
|
||||||
#[cfg(not(target_env = "musl"))]
|
#[cfg(not(target_env = "musl"))]
|
||||||
|
|||||||
Reference in New Issue
Block a user