Merge branch 'master' into dash-fixes

This commit is contained in:
Johnathan Sharratt
2023-03-07 06:09:23 +01:00
committed by GitHub
45 changed files with 1811 additions and 783 deletions

View File

@@ -44,14 +44,14 @@ jobs:
- build: linux-x64
os: ubuntu-20.04
artifact_name: 'wasmer-linux-amd64'
llvm_url: 'https://github.com/wasmerio/llvm-custom-builds/releases/download/13.x/llvm-linux-amd64.tar.xz'
llvm_url: 'https://github.com/wasmerio/llvm-custom-builds/releases/download/14.x/llvm-linux-amd64.tar.xz'
cross_compilation_artifact_name: 'cross_compiled_from_linux'
use_sccache: true
use_llvm: true
build_wasm: true
- build: macos-x64
os: macos-11
llvm_url: 'https://github.com/wasmerio/llvm-custom-builds/releases/download/13.x/llvm-darwin-amd64.tar.xz'
llvm_url: 'https://github.com/wasmerio/llvm-custom-builds/releases/download/14.x/llvm-darwin-amd64.tar.xz'
artifact_name: 'wasmer-darwin-amd64'
cross_compilation_artifact_name: 'cross_compiled_from_mac'
use_sccache: true
@@ -67,7 +67,7 @@ jobs:
- build: windows-x64
os: windows-2019
artifact_name: 'wasmer-windows-amd64'
llvm_url: 'https://github.com/wasmerio/llvm-custom-builds/releases/download/13.x/llvm-windows-amd64.tar.xz'
llvm_url: 'https://github.com/wasmerio/llvm-custom-builds/releases/download/14.x/llvm-windows-amd64.tar.xz'
cross_compilation_artifact_name: 'cross_compiled_from_win'
use_sccache: true
use_llvm: true
@@ -75,7 +75,7 @@ jobs:
- build: linux-musl-x64
os: ubuntu-latest
artifact_name: 'wasmer-linux-musl-amd64'
#llvm_url: 'https://github.com/wasmerio/llvm-custom-builds/releases/download/13.x/llvm-linux-amd64.tar.xz'
#llvm_url: 'https://github.com/wasmerio/llvm-custom-builds/releases/download/14.x/llvm-linux-amd64.tar.xz'
container: alpine:latest
use_sccache: false
use_llvm: false
@@ -115,7 +115,7 @@ jobs:
mkdir -p ${LLVM_DIR}
curl --proto '=https' --tlsv1.2 -sSf "${{ matrix.llvm_url }}" -L -o - | tar xJv -C ${LLVM_DIR}
echo "${LLVM_DIR}/bin" >> $GITHUB_PATH
echo "LLVM_SYS_120_PREFIX=${LLVM_DIR}" >> $GITHUB_ENV
echo "${LLVM_DIR}/usr/bin" >> $GITHUB_PATH
env:
LLVM_DIR: .llvm
- name: Set up dependencies for Mac OS

View File

@@ -3,7 +3,10 @@ name: test-sys
on:
push:
branches:
- '**'
- master
- 'with-ci-.*'
- 'v3.0.x'
- 'v3.1.x'
pull_request:
workflow_dispatch:
inputs:
@@ -48,7 +51,7 @@ jobs:
sudo apt install -y libtinfo5
- name: Install LLVM (Linux)
run: |
curl --proto '=https' --tlsv1.2 -sSf https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz -L -o /opt/llvm.tar.xz
curl --proto '=https' --tlsv1.2 -sSf https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz -L -o /opt/llvm.tar.xz
mkdir -p /opt/llvm-12
tar xf /opt/llvm.tar.xz --strip-components=1 -C /opt/llvm-12
echo '/opt/llvm-12/bin' >> $GITHUB_PATH
@@ -203,7 +206,7 @@ jobs:
build: linux-x64,
os: ubuntu-22.04,
target: x86_64-unknown-linux-gnu,
llvm_url: 'https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz'
llvm_url: 'https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz'
},
{
build: linux-musl,
@@ -215,7 +218,7 @@ jobs:
build: macos-x64,
os: macos-11,
target: x86_64-apple-darwin,
llvm_url: 'https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/clang+llvm-13.0.0-x86_64-apple-darwin.tar.xz'
llvm_url: 'https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-x86_64-apple-darwin.tar.xz'
},
{
build: macos-arm,
@@ -226,7 +229,7 @@ jobs:
build: windows-x64,
os: windows-2019,
target: x86_64-pc-windows-msvc,
llvm_url: 'https://github.com/wasmerio/llvm-custom-builds/releases/download/13.x/llvm-windows-amd64.tar.xz'
llvm_url: 'https://github.com/wasmerio/llvm-custom-builds/releases/download/14.x/llvm-windows-amd64.tar.xz'
},
{
build: windows-gnu,
@@ -313,7 +316,8 @@ jobs:
tar xf llvm.tar.xz --strip-components=1 -C ${LLVM_DIR}
echo "ENABLE_LLVM=1" >> $GITHUB_ENV
echo "${LLVM_DIR}/bin" >> $GITHUB_PATH
echo "LLVM_SYS_120_PREFIX=${LLVM_DIR}" >> $GITHUB_ENV
echo "${LLVM_DIR}/usr/bin" >> $GITHUB_PATH
echo "LLVM_SYS_140_PREFIX=${LLVM_DIR}" >> $GITHUB_ENV
env:
LLVM_DIR: .llvm
- name: Setup Rust target
@@ -436,19 +440,19 @@ jobs:
build: linux-x64,
os: ubuntu-22.04,
target: x86_64-unknown-linux-gnu,
llvm_url: 'https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz'
llvm_url: 'https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz'
},
{
build: macos-x64,
os: macos-11,
target: x86_64-apple-darwin,
llvm_url: 'https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/clang+llvm-13.0.0-x86_64-apple-darwin.tar.xz'
llvm_url: 'https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-x86_64-apple-darwin.tar.xz'
},
{
build: windows-x64,
os: windows-2019,
target: x86_64-pc-windows-msvc,
llvm_url: 'https://github.com/wasmerio/llvm-custom-builds/releases/download/13.x/llvm-windows-amd64.tar.xz'
llvm_url: 'https://github.com/wasmerio/llvm-custom-builds/releases/download/14.x/llvm-windows-amd64.tar.xz'
},
{
build: linux-musl,
@@ -542,11 +546,11 @@ jobs:
- build: linux-x64
os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
llvm_url: 'https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz'
llvm_url: 'https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz'
- build: macos-x64
os: macos-11
target: x86_64-apple-darwin
llvm_url: 'https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/clang+llvm-13.0.0-x86_64-apple-darwin.tar.xz'
llvm_url: 'https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-x86_64-apple-darwin.tar.xz'
# we only build the integration-test CLI, we don't run tests
- build: macos-arm
os: macos-11

View File

@@ -24,4 +24,5 @@ A comprehensive CI test suite will be run by a Wasmer team member after the PR h
`Didn't find usable system-wide LLVM`
Building Wasmer with the LLVM backend requires LLVM to be installed
Building Wasmer with the LLVM backend requires LLVM 14 or better to be installed
On debian family you need `sudo apt install llvm14 libclang-common-14-dev libpolly-14-dev`

704
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,9 @@ autoexamples = false
[dependencies]
wasmer = { version = "=3.2.0-alpha.1", path = "lib/api", default-features = false }
wasmer-compiler = { version = "=3.2.0-alpha.1", path = "lib/compiler", features = ["compiler"] }
wasmer-compiler = { version = "=3.2.0-alpha.1", path = "lib/compiler", features = [
"compiler",
] }
wasmer-compiler-cranelift = { version = "=3.2.0-alpha.1", path = "lib/compiler-cranelift", optional = true }
wasmer-compiler-singlepass = { version = "=3.2.0-alpha.1", path = "lib/compiler-singlepass", optional = true }
wasmer-compiler-llvm = { version = "=3.2.0-alpha.1", path = "lib/compiler-llvm", optional = true }
@@ -71,13 +73,15 @@ glob = "0.3"
rustc_version = "0.4"
[dev-dependencies]
wasmer = { version = "=3.2.0-alpha.1", path = "lib/api", default-features = false, features = ["cranelift"] }
wasmer = { version = "=3.2.0-alpha.1", path = "lib/api", default-features = false, features = [
"cranelift",
] }
anyhow = "1.0"
criterion = "0.3"
lazy_static = "1.4"
serial_test = "0.5"
compiler-test-derive = { path = "tests/lib/compiler-test-derive" }
tempfile = "3.1"
tempfile = "3.4.0"
# For logging tests using the `RUST_LOG=debug` when testing
test-log = { version = "0.2", default-features = false, features = ["trace"] }
tracing = { version = "0.1", default-features = false, features = ["log"] }
@@ -105,10 +109,7 @@ wast = ["wasmer-wast"]
wasi = ["wasmer-wasi"]
emscripten = ["wasmer-emscripten"]
wat = ["wasmer/wat"]
compiler = [
"wasmer/compiler",
"wasmer-compiler/translator",
]
compiler = ["wasmer/compiler", "wasmer-compiler/translator"]
singlepass = ["wasmer-compiler-singlepass", "compiler"]
cranelift = ["wasmer-compiler-cranelift", "compiler"]
llvm = ["wasmer-compiler-llvm", "compiler"]
@@ -123,9 +124,7 @@ test-singlepass = ["singlepass"]
test-cranelift = ["cranelift"]
test-llvm = ["llvm"]
test-universal = [
"test-generator/test-universal",
]
test-universal = ["test-generator/test-universal"]
# Specifies that we're running in coverage testing mode. This disables tests
# that raise signals because that interferes with tarpaulin.

View File

@@ -133,21 +133,24 @@ else ifeq ($(ENABLE_LLVM), 1)
LLVM_VERSION := $(shell llvm-config --version)
compilers += llvm
# … or try to autodetect LLVM from `llvm-config-<version>`.
else ifneq (, $(shell which llvm-config-13 2>/dev/null))
LLVM_VERSION := $(shell llvm-config-13 --version)
else ifneq (, $(shell which llvm-config-14 2>/dev/null))
LLVM_VERSION := $(shell llvm-config-14 --version)
compilers += llvm
# need force LLVM_SYS_120_PREFIX, or llvm_sys will not build in the case
export LLVM_SYS_120_PREFIX = $(shell llvm-config-13 --prefix)
else ifneq (, $(shell which llvm-config-12 2>/dev/null))
LLVM_VERSION := $(shell llvm-config-12 --version)
# need force LLVM_SYS_140_PREFIX, or llvm_sys will not build in the case
export LLVM_SYS_140_PREFIX = $(shell llvm-config-14 --prefix)
else ifneq (, $(shell which llvm-config-15 2>/dev/null))
LLVM_VERSION := $(shell llvm-config-15 --version)
compilers += llvm
# … otherwise, we try to autodetect LLVM from `llvm-config`
# need force LLVM_SYS_140_PREFIX, or llvm_sys will not build in the case
export LLVM_SYS_140_PREFIX = $(shell llvm-config-15 --prefix)
else ifneq (, $(shell which llvm-config 2>/dev/null))
LLVM_VERSION := $(shell llvm-config --version)
ifneq (, $(findstring 13,$(LLVM_VERSION)))
ifneq (, $(findstring 15,$(LLVM_VERSION)))
compilers += llvm
else ifneq (, $(findstring 12,$(LLVM_VERSION)))
export LLVM_SYS_140_PREFIX = $(shell llvm-config --prefix)
else ifneq (, $(findstring 14,$(LLVM_VERSION)))
compilers += llvm
export LLVM_SYS_140_PREFIX = $(shell llvm-config --prefix)
endif
endif

View File

@@ -64,7 +64,7 @@ git-fetch-with-cli = true
# * Medium - CVSS Score 4.0 - 6.9
# * High - CVSS Score 7.0 - 8.9
# * Critical - CVSS Score 9.0 - 10.0
#severity-threshold =
#severity-threshold =
# This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here:
@@ -111,6 +111,7 @@ confidence-threshold = 0.8
exceptions = [
# Each entry is the crate and version constraint, and its specific allow
# list
{ name = "webc", allow = ["BUSL-1.1"] }
]
@@ -189,8 +190,8 @@ skip = [
{ name = "itoa", version = "=0.4.8" },
{ name = "object", version = "=0.27.1" },
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite
skip-tree = [

View File

@@ -54,7 +54,7 @@ winapi = "0.3"
# - Development Dependencies for `sys`.
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
wat = "1.0"
tempfile = "3.1"
tempfile = "3.4.0"
anyhow = "1.0"
macro-wasmer-universal-test = { version = "3.2.0-alpha.1", path = "./macro-wasmer-universal-test" }

View File

@@ -32,7 +32,7 @@ wasmer-middlewares = { version = "=3.2.0-alpha.1", path = "../middlewares", opti
wasmer-wasi = { version = "=3.2.0-alpha.1", path = "../wasi", features = ["host-fs", "host-vnet"], optional = true }
wasmer-types = { version = "=3.2.0-alpha.1", path = "../types" }
wasmer-vfs = { version = "=3.2.0-alpha.1", path = "../vfs", optional = true, default-features = false, features = ["static-fs"] }
webc = { version = "4.0.0", optional = true }
webc = { version = "5.0.0-rc.5", optional = true }
enumset = "1.0.2"
cfg-if = "1.0"
lazy_static = "1.4"

View File

@@ -249,10 +249,10 @@ fn prepare_webc_env(
package_name: &str,
) -> Option<(WasiFunctionEnv, Imports)> {
use wasmer_vfs::static_fs::StaticFileSystem;
use webc::FsEntryType;
use webc::v1::{FsEntryType, WebC};
let slice = unsafe { std::slice::from_raw_parts(bytes, len) };
let volumes = webc::WebC::parse_volumes_from_fileblock(slice).ok()?;
let volumes = WebC::parse_volumes_from_fileblock(slice).ok()?;
let top_level_dirs = volumes
.into_iter()
.flat_map(|(_, volume)| {

View File

@@ -18,7 +18,7 @@ blake3 = "1.0"
[dev-dependencies]
criterion = "0.3"
tempfile = "3"
tempfile = "3.4.0"
rand = "0.8.3"
wasmer-compiler-singlepass = { path = "../compiler-singlepass", version = "=3.2.0-alpha.1" }

View File

@@ -33,7 +33,6 @@ cfg-if = "1.0"
fern = { version = "0.6", features = ["colored"], optional = true }
log = { version = "0.4", optional = true }
target-lexicon = { version = "0.12", features = ["std"] }
tempfile = "3"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
wasmer-compiler-singlepass = { version = "=3.2.0-alpha.1", path = "../compiler-singlepass", optional = true }

View File

@@ -56,8 +56,7 @@ bytesize = "1.0"
cfg-if = "1.0"
# For debug feature
fern = { version = "0.6", features = ["colored"], optional = true }
tempfile = "3"
tempdir = "0.3.7"
tempfile = "3.4.0"
http_req = { version="^0.8", default-features = false, features = ["rust-tls"] }
reqwest = { version = "^0.11", default-features = false, features = ["rustls-tls", "json", "multipart"] }
serde = { version = "1.0.147", features = ["derive"] }
@@ -73,7 +72,7 @@ toml = "0.5.9"
url = "2.3.1"
libc = { version = "^0.2", default-features = false }
nuke-dir = { version = "0.1.0", optional = true }
webc = { version = "4.0.0", optional = true }
webc = { version = "5.0.0-rc.5", optional = true }
isatty = "0.1.9"
dialoguer = "0.10.2"
tldextract = "0.6.0"
@@ -119,7 +118,7 @@ wast = ["wasmer-wast"]
wasi = ["wasmer-wasi", "wasmer-wasi-local-networking"]
emscripten = ["wasmer-emscripten"]
wat = ["wasmer/wat"]
webc_runner = ["wasi", "wasmer-wasi/webc_runner", "wasmer-wasi/webc_runner_rt_wasi", "wasmer-wasi/webc_runner_rt_emscripten", "nuke-dir", "webc"]
webc_runner = ["wasi", "wasmer-wasi/webc_runner", "wasmer-wasi/webc_runner_rt_wasi", "wasmer-wasi/webc_runner_rt_wcgi", "wasmer-wasi/webc_runner_rt_emscripten", "nuke-dir", "webc"]
compiler = [
"wasmer-compiler/translator",
"wasmer-compiler/compiler",

View File

@@ -15,9 +15,8 @@ use std::process::Stdio;
use tar::Archive;
use wasmer::*;
use wasmer_object::{emit_serialized, get_object_for_target};
use wasmer_types::compilation::symbols::ModuleMetadataSymbolRegistry;
use wasmer_types::ModuleInfo;
use webc::{ParseOptions, WebCMmap};
use wasmer_types::{compilation::symbols::ModuleMetadataSymbolRegistry, ModuleInfo};
use webc::v1::{ParseOptions, WebCMmap};
const LINK_SYSTEM_LIBRARIES_WINDOWS: &[&str] = &["userenv", "Ws2_32", "advapi32", "bcrypt"];
@@ -520,7 +519,7 @@ impl PrefixMapCompilation {
// if prefixes are specified, have to match the atom names exactly
if prefixes.len() != atoms.len() {
println!(
"WARNING: invalid mapping of prefix and atoms: expected prefixes for {} atoms, got {} prefixes",
"WARNING: invalid mapping of prefix and atoms: expected prefixes for {} atoms, got {} prefixes",
atoms.len(), prefixes.len()
);
}
@@ -642,7 +641,7 @@ impl PrefixMapCompilation {
#[test]
fn test_prefix_parsing() {
let tempdir = tempdir::TempDir::new("test-prefix-parsing").unwrap();
let tempdir = tempfile::TempDir::new().unwrap();
let path = tempdir.path();
std::fs::write(path.join("test.obj"), b"").unwrap();
let str1 = format!("ATOM_NAME:PREFIX:{}", path.join("test.obj").display());
@@ -2132,7 +2131,7 @@ mod http_fetch {
.unwrap_or("output")
.to_string();
let download_tempdir = tempdir::TempDir::new("wasmer-download")?;
let download_tempdir = tempfile::TempDir::new()?;
let download_path = download_tempdir.path().join(&filename);
let mut file = std::fs::File::create(&download_path)?;

View File

@@ -85,7 +85,7 @@ impl CreateObj {
println!("Target: {}", target.triple());
let atoms = if let Ok(pirita) =
webc::WebCMmap::parse(input_path.clone(), &webc::ParseOptions::default())
webc::v1::WebCMmap::parse(input_path.clone(), &webc::v1::ParseOptions::default())
{
crate::commands::create_exe::compile_pirita_into_directory(
&pirita,

View File

@@ -5,7 +5,7 @@ use std::path::PathBuf;
use wasmer_compiler::Artifact;
use wasmer_types::compilation::symbols::ModuleMetadataSymbolRegistry;
use wasmer_types::{CpuFeature, MetadataHeader, Triple};
use webc::WebC;
use webc::v1::WebC;
#[derive(Debug, Parser)]
/// The options for the `wasmer gen-c-header` subcommand
@@ -57,7 +57,7 @@ impl GenCHeader {
None => crate::commands::PrefixMapCompilation::hash_for_bytes(&file),
};
if let Ok(pirita) = WebC::parse(&file, &webc::ParseOptions::default()) {
if let Ok(pirita) = WebC::parse(&file, &webc::v1::ParseOptions::default()) {
let atoms = pirita
.manifest
.atoms

View File

@@ -8,12 +8,12 @@ use crate::suggestions::suggest_function_exports;
use crate::warning;
use anyhow::{anyhow, Context, Result};
use clap::Parser;
use std::fs::File;
use std::io::Write;
use std::ops::Deref;
use std::path::PathBuf;
#[cfg(feature = "cache")]
use std::str::FromStr;
use std::{fs::File, net::SocketAddr};
#[cfg(feature = "emscripten")]
use wasmer::FunctionEnv;
use wasmer::*;
@@ -91,6 +91,10 @@ pub struct RunWithoutFile {
#[clap(long = "verbose")]
pub(crate) verbose: Option<u8>,
#[cfg(feature = "webc_runner")]
#[clap(flatten)]
pub(crate) wcgi: WcgiOptions,
/// Enable coredump generation after a WebAssembly trap.
#[clap(name = "COREDUMP PATH", long = "coredump-on-trap", parse(from_os_str))]
coredump_on_trap: Option<PathBuf>,
@@ -223,14 +227,8 @@ impl RunWithPathBuf {
fn inner_execute(&self) -> Result<()> {
#[cfg(feature = "webc_runner")]
{
if let Ok(pf) = WapmContainer::new(self.path.clone()) {
return self
.run_container(
pf,
&self.command_name.clone().unwrap_or_default(),
&self.args,
)
.map_err(|e| anyhow!("Could not run PiritaFile: {e}"));
if let Ok(pf) = WapmContainer::from_path(self.path.clone()) {
return self.run_container(pf, self.command_name.as_deref(), &self.args);
}
}
let (mut store, module) = self.get_store_module()?;
@@ -376,48 +374,54 @@ impl RunWithPathBuf {
fn run_container(
&self,
container: WapmContainer,
id: &str,
id: Option<&str>,
args: &[String],
) -> Result<(), anyhow::Error> {
let mut result = None;
let id = id
.or_else(|| container.manifest().entrypoint.as_deref())
.context("No command specified")?;
let command = container
.manifest()
.commands
.get(id)
.with_context(|| format!("No metadata found for the command, \"{id}\""))?;
#[cfg(feature = "wasi")]
{
if let Some(r) = result {
return r;
}
let (store, _compiler_type) = self.store.get_store()?;
let mut runner = wasmer_wasi::runners::wasi::WasiRunner::new(store);
runner.set_args(args.to_vec());
result = Some(if id.is_empty() {
runner.run(&container).map_err(|e| anyhow::anyhow!("{e}"))
} else {
runner
.run_cmd(&container, id)
.map_err(|e| anyhow::anyhow!("{e}"))
});
let (store, _compiler_type) = self.store.get_store()?;
let mut runner = wasmer_wasi::runners::wasi::WasiRunner::new(store);
runner.set_args(args.to_vec());
if runner.can_run_command(id, command).unwrap_or(false) {
return runner.run_cmd(&container, id).context("WASI runner failed");
}
#[cfg(feature = "emscripten")]
{
if let Some(r) = result {
return r;
}
let (store, _compiler_type) = self.store.get_store()?;
let mut runner = wasmer_wasi::runners::emscripten::EmscriptenRunner::new(store);
runner.set_args(args.to_vec());
result = Some(if id.is_empty() {
runner.run(&container).map_err(|e| anyhow::anyhow!("{e}"))
} else {
runner
.run_cmd(&container, id)
.map_err(|e| anyhow::anyhow!("{e}"))
});
let (store, _compiler_type) = self.store.get_store()?;
let mut runner = wasmer_wasi::runners::emscripten::EmscriptenRunner::new(store);
runner.set_args(args.to_vec());
if runner.can_run_command(id, command).unwrap_or(false) {
return runner
.run_cmd(&container, id)
.context("Emscripten runner failed");
}
result.unwrap_or_else(|| Err(anyhow::anyhow!("neither emscripten or wasi file")))
let mut runner = wasmer_wasi::runners::wcgi::WcgiRunner::new(id);
let (store, _compiler_type) = self.store.get_store()?;
runner
.config()
.args(args)
.store(store)
.addr(self.wcgi.addr)
.envs(self.wasi.env_vars.clone())
.map_directories(self.wasi.mapped_dirs.iter().map(|(g, h)| (h, g)));
if self.wcgi.forward_host_env {
runner.config().forward_host_env();
}
if runner.can_run_command(id, command).unwrap_or(false) {
return runner.run_cmd(&container, id).context("WCGI runner failed");
}
anyhow::bail!(
"Unable to find a runner that supports \"{}\"",
command.runner
);
}
fn get_store_module(&self) -> Result<(Store, Module)> {
@@ -698,3 +702,22 @@ fn generate_coredump(
Ok(())
}
#[derive(Debug, Clone, Parser)]
pub(crate) struct WcgiOptions {
/// The address to serve on.
#[clap(long, short, env, default_value_t = ([127, 0, 0, 1], 8000).into())]
pub(crate) addr: SocketAddr,
/// Forward all host env variables to the wcgi task.
#[clap(long)]
pub(crate) forward_host_env: bool,
}
impl Default for WcgiOptions {
fn default() -> Self {
Self {
addr: ([127, 0, 0, 1], 8000).into(),
forward_host_env: false,
}
}
}

View File

@@ -29,7 +29,7 @@ rayon = "1.5"
package = "inkwell"
version = "0.1.1"
default-features = false
features = ["llvm12-0", "target-x86", "target-aarch64"]
features = ["llvm14-0", "target-x86", "target-aarch64"]
[build-dependencies]
cc = "1.0"

View File

@@ -12,10 +12,10 @@ rand = "0.8.5"
dirs = "4.0.0"
graphql_client = "0.11.0"
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", "stream"] }
futures-util = "0.3.25"
whoami = "1.2.3"
whoami = "1.2.3"
serde_json = "1.0.85"
url = "2.3.1"
thiserror = "1.0.37"
@@ -25,10 +25,9 @@ tar = "0.4.38"
flate2 = "1.0.24"
semver = "1.0.14"
lzma-rs = "0.2.0"
webc = { version ="4.0.0", features = ["mmap"] }
webc = { version = "5.0.0-rc.5", features = ["mmap"] }
hex = "0.4.3"
tokio = "1.24.0"
tempdir = "0.3.7"
log = "0.4.17"
regex = "1.7.0"
fs_extra = "1.2.0"
@@ -37,3 +36,4 @@ tldextract = "0.6.0"
console = "0.15.2"
indicatif = "0.17.2"
lazy_static = "1.4.0"
tempfile = "3.4.0"

View File

@@ -498,7 +498,7 @@ pub fn download_and_unpack_targz(
target_path: &Path,
strip_toplevel: bool,
) -> Result<PathBuf, anyhow::Error> {
let tempdir = tempdir::TempDir::new("wasmer-download-targz")?;
let tempdir = tempfile::TempDir::new()?;
let target_targz_path = tempdir.path().join("package.tar.gz");
@@ -574,7 +574,7 @@ where
pub fn install_package(wasmer_dir: &Path, url: &Url) -> Result<PathBuf, anyhow::Error> {
use fs_extra::dir::copy;
let tempdir = tempdir::TempDir::new("download")
let tempdir = tempfile::TempDir::new()
.map_err(|e| anyhow::anyhow!("could not create download temp dir: {e}"))?;
let target_targz_path = tempdir.path().join("package.tar.gz");
@@ -690,7 +690,7 @@ pub fn get_all_available_registries(wasmer_dir: &Path) -> Result<Vec<String>, St
#[derive(Debug, PartialEq, Clone)]
pub struct RemoteWebcInfo {
pub checksum: String,
pub manifest: webc::Manifest,
pub manifest: webc::metadata::Manifest,
}
pub fn install_webc_package(
@@ -772,9 +772,9 @@ fn get_all_installed_webc_packages_inner(wasmer_dir: &Path) -> Vec<RemoteWebcInf
read_dir
.filter_map(|r| Some(r.ok()?.path()))
.filter_map(|path| {
webc::WebCMmap::parse(
webc::v1::WebCMmap::parse(
path,
&webc::ParseOptions {
&webc::v1::ParseOptions {
parse_atoms: false,
parse_volumes: false,
..Default::default()
@@ -812,11 +812,11 @@ pub fn get_checksum_hash(bytes: &[u8]) -> String {
/// 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, anyhow::Error> {
let request_max_bytes = webc::WebC::get_signature_offset_start() + 4 + 1024 + 8 + 8;
let request_max_bytes = webc::v1::WebC::get_signature_offset_start() + 4 + 1024 + 8 + 8;
let data = get_webc_bytes(url, Some(0..request_max_bytes), None)
.with_context(|| anyhow::anyhow!("note: use --registry to change the registry URL"))?
.unwrap();
let checksum = webc::WebC::get_checksum_bytes(&data)
let checksum = webc::v1::WebC::get_checksum_bytes(&data)
.map_err(|e| anyhow::anyhow!("{e}"))?
.to_vec();
Ok(get_checksum_hash(&checksum))
@@ -826,20 +826,20 @@ pub fn get_remote_webc_checksum(url: &Url) -> Result<String, anyhow::Error> {
/// so we can see if the package has already been installed
pub fn get_remote_webc_manifest(url: &Url) -> Result<RemoteWebcInfo, anyhow::Error> {
// Request up unti manifest size / manifest len
let request_max_bytes = webc::WebC::get_signature_offset_start() + 4 + 1024 + 8 + 8;
let request_max_bytes = webc::v1::WebC::get_signature_offset_start() + 4 + 1024 + 8 + 8;
let data = get_webc_bytes(url, Some(0..request_max_bytes), None)?.unwrap();
let checksum = webc::WebC::get_checksum_bytes(&data)
let checksum = webc::v1::WebC::get_checksum_bytes(&data)
.map_err(|e| anyhow::anyhow!("{e}"))
.context("WebC::get_checksum_bytes failed")?
.to_vec();
let hex_string = get_checksum_hash(&checksum);
let (manifest_start, manifest_len) = webc::WebC::get_manifest_offset_size(&data)
let (manifest_start, manifest_len) = webc::v1::WebC::get_manifest_offset_size(&data)
.map_err(|e| anyhow::anyhow!("{e}"))
.context("WebC::get_manifest_offset_size failed")?;
let data_with_manifest =
get_webc_bytes(url, Some(0..manifest_start + manifest_len), None)?.unwrap();
let manifest = webc::WebC::get_manifest(&data_with_manifest)
let manifest = webc::v1::WebC::get_manifest(&data_with_manifest)
.map_err(|e| anyhow::anyhow!("{e}"))
.context("WebC::get_manifest failed")?;
Ok(RemoteWebcInfo {
@@ -985,7 +985,7 @@ fn test_install_package() {
"https://registry-cdn.wapm.io/packages/wasmer/wabt/wabt-1.0.29.tar.gz".to_string()
);
let fake_wasmer_dir = tempdir::TempDir::new("tmp").unwrap();
let fake_wasmer_dir = tempfile::TempDir::new().unwrap();
let wasmer_dir = fake_wasmer_dir.path();
let path = install_package(wasmer_dir, &url::Url::parse(&wabt.url).unwrap()).unwrap();

View File

@@ -11,8 +11,7 @@ libc = { version = "^0.2", default-features = false, optional = true }
thiserror = "1"
tracing = { version = "0.1" }
typetag = { version = "0.1", optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
webc = { version = "4.0.0", optional = true }
webc = { version = "5.0.0-rc.5", optional = true }
slab = { version = "0.4" }
derivative = "2.2.0"
anyhow = { version = "1.0.66", optional = true }
@@ -23,6 +22,7 @@ filetime = { version = "0.2.18", optional = true }
bytes = "1"
tokio = { version = "1", features = [ "io-util", "sync", "macros" ], default_features = false }
pin-project-lite = "0.2.9"
indexmap = "1.9.2"
[dev-dependencies]
tokio = { version = "1", features = [ "io-util", "rt" ], default_features = false }
@@ -32,8 +32,5 @@ default = ["host-fs", "webc-fs", "static-fs"]
host-fs = ["libc", "fs_extra", "filetime", "tokio/fs", "tokio/io-std"]
webc-fs = ["webc", "anyhow"]
static-fs = ["webc", "anyhow"]
enable-serde = [
"serde",
"typetag"
]
enable-serde = ["typetag"]
no-time = []

View File

@@ -13,19 +13,20 @@ use crate::mem_fs::FileSystem as MemFileSystem;
use crate::{
FileOpener, FileSystem, FsError, Metadata, OpenOptions, OpenOptionsConfig, ReadDir, VirtualFile,
};
use webc::{FsEntry, FsEntryType, OwnedFsEntryFile};
use indexmap::IndexMap;
use webc::v1::{FsEntry, FsEntryType, OwnedFsEntryFile};
/// Custom file system wrapper to map requested file paths
#[derive(Debug)]
pub struct StaticFileSystem {
pub package: String,
pub volumes: Arc<webc::IndexMap<String, webc::Volume<'static>>>,
pub volumes: Arc<IndexMap<String, webc::v1::Volume<'static>>>,
pub memory: Arc<MemFileSystem>,
}
impl StaticFileSystem {
pub fn init(bytes: &'static [u8], package: &str) -> Option<Self> {
let volumes = Arc::new(webc::WebC::parse_volumes_from_fileblock(bytes).ok()?);
let volumes = Arc::new(webc::v1::WebC::parse_volumes_from_fileblock(bytes).ok()?);
let fs = Self {
package: package.to_string(),
volumes: volumes.clone(),
@@ -90,7 +91,7 @@ impl FileOpener for StaticFileSystem {
#[derive(Debug)]
pub struct WebCFile {
pub volumes: Arc<webc::IndexMap<String, webc::Volume<'static>>>,
pub volumes: Arc<IndexMap<String, webc::v1::Volume<'static>>>,
pub package: String,
pub volume: String,
pub path: PathBuf,

View File

@@ -12,7 +12,7 @@ use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
use webc::{FsEntry, FsEntryType, OwnedFsEntryFile, WebC};
use webc::v1::{FsEntry, FsEntryType, OwnedFsEntryFile, WebC};
/// Custom file system wrapper to map requested file paths
#[derive(Debug)]
@@ -23,7 +23,7 @@ where
pub webc: Arc<T>,
pub memory: Arc<MemFileSystem>,
top_level_dirs: Vec<String>,
volumes: Vec<webc::Volume<'static>>,
volumes: Vec<webc::v1::Volume<'static>>,
}
impl<T> WebcFileSystem<T>

View File

@@ -29,7 +29,7 @@ bincode = { version = "1.3" }
chrono = { version = "^0.4", default-features = false, features = [ "wasmbind", "std", "clock" ], optional = true }
derivative = { version = "^2" }
bytes = "1"
webc = { version = "4.0.0", default-features = false, features = ["std"] }
webc = { version = "5.0.0-rc.5", default-features = false }
serde_cbor = { version = "0.11.2", optional = true }
anyhow = { version = "1.0.66" }
lazy_static = "1.4"
@@ -59,6 +59,10 @@ wai-bindgen-wasmer = { path = "../wai-bindgen-wasmer", version = "0.2.3", featur
heapless = "0.7.16"
once_cell = "1.17.0"
pin-project = "1.0.12"
# Used by the WCGI runner
hyper = { version = "0.14", features = ["server", "stream"], optional = true }
wcgi = { version = "0.1.1", optional = true }
wcgi-host = { version = "0.1.0", optional = true }
[dependencies.reqwest]
version = "0.11"
@@ -93,8 +97,9 @@ default = ["sys-default"]
time = ["tokio/time"]
webc_runner = ["serde_cbor", "wasmer/compiler"]
webc_runner_rt_emscripten = ["wasmer-emscripten"]
webc_runner_rt_wasi = []
webc_runner_rt_wcgi = ["hyper", "wcgi", "wcgi-host"]
webc_runner_rt_emscripten = ["wasmer-emscripten"]
sys = ["wasmer/sys", "wasmer-wasi-types/sys", "webc/mmap", "wasmer-vm", "time"]
sys-default = ["wasmer/wat", "wasmer/compiler", "sys", "logging", "host-fs", "sys-poll", "sys-thread", "host-vnet", "host-threads", "host-reqwest" ]
@@ -123,3 +128,4 @@ enable-serde = [
"wasmer-vfs/enable-serde",
"wasmer-wasi-types/enable-serde",
]

View File

@@ -39,6 +39,7 @@ pub mod net;
// TODO: should this be pub?
pub mod fs;
pub mod http;
#[cfg(feature = "webc_runner")]
pub mod runners;
pub mod runtime;
mod state;

View File

@@ -13,6 +13,28 @@ pub struct WasiControlPlane {
state: Arc<State>,
}
#[derive(Debug, Clone)]
pub struct WasiControlPlaneHandle {
inner: std::sync::Weak<State>,
}
impl WasiControlPlaneHandle {
fn new(inner: &Arc<State>) -> Self {
Self {
inner: Arc::downgrade(inner),
}
}
pub fn upgrade(&self) -> Option<WasiControlPlane> {
self.inner.upgrade().map(|state| WasiControlPlane { state })
}
pub fn must_upgrade(&self) -> WasiControlPlane {
let state = self.inner.upgrade().expect("control plane unavailable");
WasiControlPlane { state }
}
}
#[derive(Debug, Clone)]
pub struct ControlPlaneConfig {
/// Total number of tasks (processes + threads) that can be spawned.
@@ -67,6 +89,10 @@ impl WasiControlPlane {
}
}
pub fn handle(&self) -> WasiControlPlaneHandle {
WasiControlPlaneHandle::new(&self.state)
}
/// Get the current count of active tasks (threads).
fn active_task_count(&self) -> usize {
self.state.task_count.load(Ordering::SeqCst)
@@ -99,7 +125,7 @@ impl WasiControlPlane {
}
// Create the process first to do all the allocations before locking.
let mut proc = WasiProcess::new(WasiProcessId::from(0), self.clone());
let mut proc = WasiProcess::new(WasiProcessId::from(0), self.handle());
let mut mutable = self.state.mutable.write().unwrap();

View File

@@ -17,13 +17,12 @@ use wasmer_wasi_types::{
};
use crate::{
os::task::{control_plane::WasiControlPlane, signal::WasiSignalInterval},
syscalls::platform_clock_time_get,
WasiThread, WasiThreadHandle, WasiThreadId,
os::task::signal::WasiSignalInterval, syscalls::platform_clock_time_get, WasiThread,
WasiThreadHandle, WasiThreadId,
};
use super::{
control_plane::ControlPlaneError,
control_plane::{ControlPlaneError, WasiControlPlaneHandle},
signal::{SignalDeliveryError, SignalHandlerAbi},
task_join_handle::{OwnedTaskStatus, TaskJoinHandle},
};
@@ -81,7 +80,7 @@ pub struct WasiProcess {
/// Reference back to the compute engine
// TODO: remove this reference, access should happen via separate state instead
// (we don't want cyclical references)
pub(crate) compute: WasiControlPlane,
pub(crate) compute: WasiControlPlaneHandle,
/// Reference to the exit code for the main thread
pub(crate) finished: Arc<OwnedTaskStatus>,
/// List of all the children spawned from this thread
@@ -134,11 +133,11 @@ impl Drop for WasiProcessWait {
}
impl WasiProcess {
pub fn new(pid: WasiProcessId, compute: WasiControlPlane) -> Self {
pub fn new(pid: WasiProcessId, plane: WasiControlPlaneHandle) -> Self {
WasiProcess {
pid,
ppid: 0u32.into(),
compute,
compute: plane,
inner: Arc::new(RwLock::new(WasiProcessInner {
threads: Default::default(),
thread_count: Default::default(),
@@ -184,7 +183,7 @@ impl WasiProcess {
/// Creates a a thread and returns it
pub fn new_thread(&self) -> Result<WasiThreadHandle, ControlPlaneError> {
let task_count_guard = self.compute.register_task()?;
let task_count_guard = self.compute.must_upgrade().register_task()?;
let mut inner = self.inner.write().unwrap();
let id = inner.thread_seed.inc();
@@ -232,7 +231,7 @@ impl WasiProcess {
if self.waiting.load(Ordering::Acquire) > 0 {
let mut triggered = false;
for pid in children.iter() {
if let Some(process) = self.compute.get_process(*pid) {
if let Some(process) = self.compute.must_upgrade().get_process(*pid) {
process.signal_process(signal);
triggered = true;
}
@@ -301,7 +300,7 @@ impl WasiProcess {
}
let mut waits = Vec::new();
for pid in children {
if let Some(process) = self.compute.get_process(pid) {
if let Some(process) = self.compute.must_upgrade().get_process(pid) {
let children = self.children.clone();
waits.push(async move {
let join = process.join().await;
@@ -330,7 +329,7 @@ impl WasiProcess {
let mut waits = Vec::new();
for pid in children {
if let Some(process) = self.compute.get_process(pid) {
if let Some(process) = self.compute.must_upgrade().get_process(pid) {
let children = self.children.clone();
waits.push(async move {
let join = process.join().await;
@@ -358,11 +357,6 @@ impl WasiProcess {
thread.set_status_finished(Ok(exit_code))
}
}
/// Gains access to the compute control plane
pub fn control_plane(&self) -> &WasiControlPlane {
&self.compute
}
}
impl SignalHandlerAbi for WasiProcess {

View File

@@ -0,0 +1,213 @@
use std::{path::PathBuf, sync::Arc};
use bytes::Bytes;
use wasmer_vfs::{webc_fs::WebcFileSystem, FileSystem};
use webc::{
metadata::Manifest,
v1::{ParseOptions, WebC, WebCMmap, WebCOwned},
Version,
};
/// A parsed WAPM package.
#[derive(Debug, Clone)]
pub struct WapmContainer {
repr: Repr,
}
#[allow(dead_code)] // Some pub(crate) items are only used behind #[cfg] code
impl WapmContainer {
/// Parses a .webc container file. Since .webc files
/// can be very large, only file paths are allowed.
pub fn from_path(path: PathBuf) -> std::result::Result<Self, WebcParseError> {
let webc = webc::v1::WebCMmap::parse(path, &ParseOptions::default())?;
Ok(Self {
repr: Repr::V1Mmap(Arc::new(webc)),
})
}
pub fn from_bytes(bytes: Bytes) -> std::result::Result<Self, WebcParseError> {
match webc::detect(bytes.as_ref())? {
Version::V1 => {
let webc = WebCOwned::parse(bytes.into(), &ParseOptions::default())?;
Ok(WapmContainer {
repr: Repr::V1Owned(Arc::new(webc)),
})
}
Version::V2 => todo!(),
other => Err(WebcParseError::UnsupportedVersion(other)),
}
}
/// Returns the bytes of a file or a stringified error
pub fn get_file<'b>(&'b self, path: &str) -> Result<&'b [u8], String> {
match &self.repr {
Repr::V1Mmap(mapped) => mapped
.get_file(&mapped.get_package_name(), path)
.map_err(|e| e.0),
Repr::V1Owned(owned) => owned
.get_file(&owned.get_package_name(), path)
.map_err(|e| e.0),
}
}
/// Returns a list of volumes in this container
pub fn get_volumes(&self) -> Vec<String> {
match &self.repr {
Repr::V1Mmap(mapped) => mapped.volumes.keys().cloned().collect(),
Repr::V1Owned(owned) => owned.volumes.keys().cloned().collect(),
}
}
pub fn get_atom(&self, name: &str) -> Option<&[u8]> {
match &self.repr {
Repr::V1Mmap(mapped) => mapped.get_atom(&mapped.get_package_name(), name).ok(),
Repr::V1Owned(owned) => owned.get_atom(&owned.get_package_name(), name).ok(),
}
}
/// Lookup .wit bindings by name and parse them
pub fn get_bindings<T: Bindings>(
&self,
bindings: &str,
) -> std::result::Result<T, ParseBindingsError> {
let bindings = self
.manifest()
.bindings
.iter()
.find(|b| b.name == bindings)
.ok_or_else(|| ParseBindingsError::NoBindings(bindings.to_string()))?;
T::parse_bindings(self, &bindings.annotations).map_err(ParseBindingsError::ParseBindings)
}
pub fn manifest(&self) -> &Manifest {
match &self.repr {
Repr::V1Mmap(mapped) => &mapped.manifest,
Repr::V1Owned(owned) => &owned.manifest,
}
}
// HACK(Michael-F-Bryan): WapmContainer originally exposed its Arc<WebCMmap>
// field, so every man and his dog accessed it directly instead of going
// through the WapmContainer abstraction. This is an escape hatch to make
// that code keep working for the time being.
// #[deprecated]
pub(crate) fn v1(&self) -> &WebC<'_> {
match &self.repr {
Repr::V1Mmap(mapped) => mapped,
Repr::V1Owned(owned) => owned,
}
}
/// Load a volume as a [`FileSystem`] node.
pub(crate) fn volume_fs(&self, package_name: &str) -> Box<dyn FileSystem + Send + Sync> {
match &self.repr {
Repr::V1Mmap(mapped) => {
Box::new(WebcFileSystem::init(Arc::clone(mapped), package_name))
}
Repr::V1Owned(owned) => Box::new(WebcFileSystem::init(Arc::clone(owned), package_name)),
}
}
/// Get the entire container as a single filesystem and a list of suggested
/// directories to preopen.
pub(crate) fn container_fs(&self) -> (Box<dyn FileSystem + Send + Sync>, Vec<String>) {
match &self.repr {
Repr::V1Mmap(mapped) => {
let fs = WebcFileSystem::init_all(Arc::clone(mapped));
let top_level_dirs = fs.top_level_dirs().clone();
(Box::new(fs), top_level_dirs)
}
Repr::V1Owned(owned) => {
let fs = WebcFileSystem::init_all(Arc::clone(owned));
let top_level_dirs = fs.top_level_dirs().clone();
(Box::new(fs), top_level_dirs)
}
}
}
}
#[derive(Debug, Clone)]
enum Repr {
V1Mmap(Arc<WebCMmap>),
V1Owned(Arc<WebCOwned>),
}
/// Error that happened while parsing .wit bindings
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
pub enum ParseBindingsError {
/// No bindings are available for the given lookup key
NoBindings(String),
/// Error happened during parsing
ParseBindings(String),
}
/// Trait to parse bindings (any kind of bindings) for container .wasm files (usually .wit format)
pub trait Bindings {
/// Function that takes annotations in a free-form `Value` struct and returns the parsed bindings or an error
fn parse_bindings(_: &WapmContainer, _: &serde_cbor::Value) -> Result<Self, String>
where
Self: Sized;
}
/// WIT bindings
#[derive(Default, Debug, Copy, Clone)]
pub struct WitBindings {}
impl WitBindings {
/// Unused: creates default wit bindings
pub fn parse(_s: &str) -> Result<Self, String> {
Ok(Self::default())
}
}
impl Bindings for WitBindings {
fn parse_bindings(
container: &WapmContainer,
value: &serde_cbor::Value,
) -> Result<Self, String> {
let value: webc::metadata::BindingsExtended =
serde_cbor::from_slice(&serde_cbor::to_vec(value).unwrap())
.map_err(|e| format!("could not parse WitBindings annotations: {e}"))?;
let mut wit_bindgen_filepath = value.exports().unwrap_or_default().to_string();
for v in container.get_volumes() {
let schema = format!("{v}://");
if wit_bindgen_filepath.starts_with(&schema) {
wit_bindgen_filepath = wit_bindgen_filepath.replacen(&schema, "", 1);
break;
}
}
let wit_bindings = container
.get_file(&wit_bindgen_filepath)
.map_err(|e| format!("could not get WitBindings file {wit_bindgen_filepath:?}: {e}"))?;
let wit_bindings_str = std::str::from_utf8(wit_bindings)
.map_err(|e| format!("could not get WitBindings file {wit_bindgen_filepath:?}: {e}"))?;
Self::parse(wit_bindings_str)
}
}
/// Error that ocurred while parsing the .webc file
#[derive(Debug)]
pub enum WebcParseError {
/// Parse error
Parse(webc::v1::Error),
Detect(webc::DetectError),
UnsupportedVersion(Version),
}
impl From<webc::v1::Error> for WebcParseError {
fn from(e: webc::v1::Error) -> Self {
WebcParseError::Parse(e)
}
}
impl From<webc::DetectError> for WebcParseError {
fn from(e: webc::DetectError) -> Self {
WebcParseError::Detect(e)
}
}

View File

@@ -1,17 +1,14 @@
#![cfg(feature = "webc_runner_rt_emscripten")]
//! WebC container support for running Emscripten modules
use crate::runners::WapmContainer;
use anyhow::anyhow;
use anyhow::{anyhow, Context, Error};
use serde::{Deserialize, Serialize};
use std::error::Error as StdError;
use std::sync::Arc;
use wasmer::{FunctionEnv, Instance, Module, Store};
use wasmer_emscripten::{
generate_emscripten_env, is_emscripten_module, run_emscripten_instance, EmEnv,
EmscriptenGlobals,
};
use webc::{Command, WebCMmap};
use webc::metadata::{annotations::Emscripten, Command};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct EmscriptenRunner {
@@ -49,35 +46,39 @@ impl EmscriptenRunner {
impl crate::runners::Runner for EmscriptenRunner {
type Output = ();
fn can_run_command(&self, _: &str, command: &Command) -> Result<bool, Box<dyn StdError>> {
fn can_run_command(&self, _: &str, command: &Command) -> Result<bool, Error> {
Ok(command
.runner
.starts_with("https://webc.org/runner/emscripten"))
.starts_with(webc::metadata::annotations::EMSCRIPTEN_RUNNER_URI))
}
#[allow(unreachable_code, unused_variables)]
fn run_command(
&mut self,
command_name: &str,
_command: &Command,
command: &Command,
container: &WapmContainer,
) -> Result<Self::Output, Box<dyn StdError>> {
let atom_name = container.get_atom_name_for_command("emscripten", command_name)?;
let main_args = container.get_main_args_for_command(command_name);
let atom_bytes = container.get_atom(&container.get_package_name(), &atom_name)?;
) -> Result<Self::Output, Error> {
let Emscripten {
atom: atom_name,
main_args,
..
} = command.get_annotation("emscripten")?.unwrap_or_default();
let atom_name = atom_name.context("The atom name is required")?;
let atom_bytes = container
.get_atom(&atom_name)
.with_context(|| format!("Unable to read the \"{atom_name}\" atom"))?;
let mut module = Module::new(&self.store, atom_bytes)?;
module.set_name(&atom_name);
let (mut globals, env) =
prepare_emscripten_env(&mut self.store, &module, container.webc.clone(), &atom_name)?;
let (mut globals, env) = prepare_emscripten_env(&mut self.store, &module, &atom_name)?;
exec_module(
&mut self.store,
&module,
&mut globals,
env,
container.webc.clone(),
&atom_name,
main_args.unwrap_or_default(),
)?;
@@ -89,7 +90,6 @@ impl crate::runners::Runner for EmscriptenRunner {
fn prepare_emscripten_env(
store: &mut Store,
module: &Module,
_atom: Arc<WebCMmap>,
name: &str,
) -> Result<(EmscriptenGlobals, FunctionEnv<EmEnv>), anyhow::Error> {
if !is_emscripten_module(module) {
@@ -110,7 +110,6 @@ fn exec_module(
module: &Module,
globals: &mut EmscriptenGlobals,
em_env: FunctionEnv<EmEnv>,
_atom: Arc<WebCMmap>,
name: &str,
args: Vec<String>,
) -> Result<(), anyhow::Error> {

View File

@@ -1,208 +1,14 @@
#![cfg(feature = "webc_runner")]
use std::error::Error as StdError;
use std::path::PathBuf;
use std::sync::Arc;
use webc::*;
mod container;
mod runner;
#[cfg(feature = "webc_runner_rt_emscripten")]
pub mod emscripten;
#[cfg(feature = "webc_runner_rt_wasi")]
pub mod wasi;
#[cfg(feature = "webc_runner_rt_wcgi")]
pub mod wcgi;
/// Parsed WAPM file, memory-mapped to an on-disk path
#[derive(Debug, Clone)]
pub struct WapmContainer {
/// WebC container
pub webc: Arc<WebCMmap>,
}
impl core::ops::Deref for WapmContainer {
type Target = webc::WebC<'static>;
fn deref<'a>(&'a self) -> &WebC<'static> {
&self.webc.webc
}
}
/// Error that ocurred while parsing the .webc file
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum WebcParseError {
/// Parse error
Parse(webc::Error),
}
impl From<webc::Error> for WebcParseError {
fn from(e: webc::Error) -> Self {
WebcParseError::Parse(e)
}
}
impl WapmContainer {
/// Parses a .webc container file. Since .webc files
/// can be very large, only file paths are allowed.
pub fn new(path: PathBuf) -> std::result::Result<Self, WebcParseError> {
let webc = webc::WebCMmap::parse(path, &webc::ParseOptions::default())?;
Ok(Self {
webc: Arc::new(webc),
})
}
/// Returns the bytes of a file or a stringified error
pub fn get_file<'b>(&'b self, path: &str) -> Result<&'b [u8], String> {
self.webc
.get_file(&self.webc.get_package_name(), path)
.map_err(|e| e.0)
}
/// Returns a list of volumes in this container
pub fn get_volumes(&self) -> Vec<String> {
self.webc.volumes.keys().cloned().collect::<Vec<_>>()
}
/// Lookup .wit bindings by name and parse them
pub fn get_bindings<T: Bindings>(
&self,
bindings: &str,
) -> std::result::Result<T, ParseBindingsError> {
let bindings = self
.webc
.manifest
.bindings
.iter()
.find(|b| b.name == bindings)
.ok_or_else(|| ParseBindingsError::NoBindings(bindings.to_string()))?;
T::parse_bindings(self, &bindings.annotations).map_err(ParseBindingsError::ParseBindings)
}
}
/// Error that happened while parsing .wit bindings
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
pub enum ParseBindingsError {
/// No bindings are available for the given lookup key
NoBindings(String),
/// Error happened during parsing
ParseBindings(String),
}
/// Trait to parse bindings (any kind of bindings) for container .wasm files (usually .wit format)
pub trait Bindings {
/// Function that takes annotations in a free-form `Value` struct and returns the parsed bindings or an error
fn parse_bindings(_: &WapmContainer, _: &serde_cbor::Value) -> Result<Self, String>
where
Self: Sized;
}
/// WIT bindings
#[derive(Default, Debug, Copy, Clone)]
pub struct WitBindings {}
impl WitBindings {
/// Unused: creates default wit bindings
pub fn parse(_s: &str) -> Result<Self, String> {
Ok(Self::default())
}
}
impl Bindings for WitBindings {
fn parse_bindings(
container: &WapmContainer,
value: &serde_cbor::Value,
) -> Result<Self, String> {
let value: webc::BindingsExtended =
serde_cbor::from_slice(&serde_cbor::to_vec(value).unwrap())
.map_err(|e| format!("could not parse WitBindings annotations: {e}"))?;
let mut wit_bindgen_filepath = value.exports().unwrap_or_default().to_string();
for v in container.get_volumes() {
let schema = format!("{v}://");
if wit_bindgen_filepath.starts_with(&schema) {
wit_bindgen_filepath = wit_bindgen_filepath.replacen(&schema, "", 1);
break;
}
}
let wit_bindings = container
.get_file(&wit_bindgen_filepath)
.map_err(|e| format!("could not get WitBindings file {wit_bindgen_filepath:?}: {e}"))?;
let wit_bindings_str = std::str::from_utf8(wit_bindings)
.map_err(|e| format!("could not get WitBindings file {wit_bindgen_filepath:?}: {e}"))?;
Self::parse(wit_bindings_str)
}
}
/// Trait that all runners have to implement
pub trait Runner {
/// The return value of the output of the runner
type Output;
/// Returns whether the Runner will be able to run the `Command`
fn can_run_command(
&self,
command_name: &str,
command: &Command,
) -> Result<bool, Box<dyn StdError>>;
/// Implementation to run the given command
///
/// - use `cmd.annotations` to get the metadata for the given command
/// - use `container.get_atom()` to get the
fn run_command(
&mut self,
command_name: &str,
cmd: &Command,
container: &WapmContainer,
) -> Result<Self::Output, Box<dyn StdError>>;
/// Runs the container if the container has an `entrypoint` in the manifest
fn run(&mut self, container: &WapmContainer) -> Result<Self::Output, Box<dyn StdError>> {
let cmd = match container.webc.webc.manifest.entrypoint.as_ref() {
Some(s) => s,
None => {
let path = format!("{}", container.webc.path.display());
return Err(Box::new(webc::Error(format!(
"Cannot run {path:?}: not executable (no entrypoint in manifest)"
))));
}
};
self.run_cmd(container, cmd)
}
/// Runs the given `cmd` on the container
fn run_cmd(
&mut self,
container: &WapmContainer,
cmd: &str,
) -> Result<Self::Output, Box<dyn StdError>> {
let path = format!("{}", container.webc.path.display());
let command_to_exec = container
.webc
.webc
.manifest
.commands
.get(cmd)
.ok_or_else(|| anyhow::anyhow!("{path}: command {cmd:?} not found in manifest"))?;
let _path = format!("{}", container.webc.path.display());
match self.can_run_command(cmd, command_to_exec) {
Ok(true) => {}
Ok(false) => {
return Err(Box::new(webc::Error(format!(
"Cannot run command {cmd:?} with runner {:?}",
command_to_exec.runner
))));
}
Err(e) => {
return Err(Box::new(webc::Error(format!(
"Cannot run command {cmd:?} with runner {:?}: {e}",
command_to_exec.runner
))));
}
}
self.run_command(cmd, command_to_exec, container)
}
}
pub use self::{
container::{Bindings, WapmContainer, WebcParseError, WitBindings},
runner::Runner,
};

View File

@@ -0,0 +1,63 @@
use anyhow::Error;
use webc::metadata::Command;
use crate::runners::WapmContainer;
/// Trait that all runners have to implement
pub trait Runner {
/// The return value of the output of the runner
type Output;
/// Returns whether the Runner will be able to run the `Command`
fn can_run_command(&self, command_name: &str, command: &Command) -> Result<bool, Error>;
/// Implementation to run the given command
///
/// - use `cmd.annotations` to get the metadata for the given command
/// - use `container.get_atom()` to get the
fn run_command(
&mut self,
command_name: &str,
cmd: &Command,
container: &WapmContainer,
) -> Result<Self::Output, Error>;
/// Runs the container if the container has an `entrypoint` in the manifest
fn run(&mut self, container: &WapmContainer) -> Result<Self::Output, Error> {
let cmd = match container.manifest().entrypoint.as_ref() {
Some(s) => s,
None => {
anyhow::bail!("Cannot run the package: not executable (no entrypoint in manifest)");
}
};
self.run_cmd(container, cmd)
}
/// Runs the given `cmd` on the container
fn run_cmd(&mut self, container: &WapmContainer, cmd: &str) -> Result<Self::Output, Error> {
let command_to_exec = container
.manifest()
.commands
.get(cmd)
.ok_or_else(|| anyhow::anyhow!("command {cmd:?} not found in manifest"))?;
match self.can_run_command(cmd, command_to_exec) {
Ok(true) => {}
Ok(false) => {
anyhow::bail!(
"Cannot run command {cmd:?} with runner {:?}",
command_to_exec.runner
);
}
Err(e) => {
anyhow::bail!(
"Cannot run command {cmd:?} with runner {:?}: {e}",
command_to_exec.runner
);
}
}
self.run_command(cmd, command_to_exec, container)
}
}

View File

@@ -1,20 +1,21 @@
#![cfg(feature = "webc_runner_rt_wasi")]
//! WebC container support for running WASI modules
use crate::runners::WapmContainer;
use crate::{WasiEnv, WasiEnvBuilder};
use serde::{Deserialize, Serialize};
use std::error::Error as StdError;
use std::sync::Arc;
use wasmer::{Module, Store};
use wasmer_vfs::webc_fs::WebcFileSystem;
use webc::{Command, WebCMmap};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
use crate::{runners::WapmContainer, PluggableRuntimeImplementation, VirtualTaskManager};
use crate::{WasiEnv, WasiEnvBuilder};
use anyhow::{Context, Error};
use serde::{Deserialize, Serialize};
use wasmer::{Module, Store};
use webc::metadata::{annotations::Wasi, Command};
#[derive(Debug, Serialize, Deserialize)]
pub struct WasiRunner {
args: Vec<String>,
#[serde(skip, default)]
store: Store,
#[serde(skip, default)]
tasks: Option<Arc<dyn VirtualTaskManager>>,
}
impl WasiRunner {
@@ -23,6 +24,7 @@ impl WasiRunner {
Self {
args: Vec::new(),
store,
tasks: None,
}
}
@@ -32,51 +34,68 @@ impl WasiRunner {
}
/// Builder method to provide CLI args to the runner
pub fn with_args(mut self, args: Vec<String>) -> Self {
pub fn with_args<A, S>(mut self, args: A) -> Self
where
A: IntoIterator<Item = S>,
S: Into<String>,
{
self.set_args(args);
self
}
/// Set the CLI args
pub fn set_args(&mut self, args: Vec<String>) {
self.args = args;
pub fn set_args<A, S>(&mut self, args: A)
where
A: IntoIterator<Item = S>,
S: Into<String>,
{
self.args = args.into_iter().map(|s| s.into()).collect();
}
pub fn with_task_manager(mut self, tasks: impl VirtualTaskManager) -> Self {
self.set_task_manager(tasks);
self
}
pub fn set_task_manager(&mut self, tasks: impl VirtualTaskManager) {
self.tasks = Some(Arc::new(tasks));
}
}
impl crate::runners::Runner for WasiRunner {
type Output = ();
fn can_run_command(
&self,
_command_name: &str,
command: &Command,
) -> Result<bool, Box<dyn StdError>> {
Ok(command.runner.starts_with("https://webc.org/runner/wasi"))
fn can_run_command(&self, _command_name: &str, command: &Command) -> Result<bool, Error> {
Ok(command
.runner
.starts_with(webc::metadata::annotations::WASI_RUNNER_URI))
}
#[allow(unreachable_code, unused_variables)]
fn run_command(
&mut self,
command_name: &str,
_command: &Command,
command: &Command,
container: &WapmContainer,
) -> Result<Self::Output, Box<dyn StdError>> {
let atom_name = container.get_atom_name_for_command("wasi", command_name)?;
let atom_bytes = container.get_atom(&container.get_package_name(), &atom_name)?;
) -> Result<Self::Output, Error> {
let atom_name = match command.get_annotation("wasi")? {
Some(Wasi { atom, .. }) => atom,
None => command_name.to_string(),
};
let atom = container
.get_atom(&atom_name)
.with_context(|| format!("Unable to get the \"{atom_name}\" atom"))?;
let mut module = Module::new(&self.store, atom_bytes)?;
let mut module = Module::new(&self.store, atom)?;
module.set_name(&atom_name);
let builder = prepare_webc_env(container.webc.clone(), &atom_name, &self.args)?;
let mut builder = prepare_webc_env(container, &atom_name, &self.args)?;
let init = builder.build_init()?;
if let Some(tasks) = &self.tasks {
let rt = PluggableRuntimeImplementation::new(Arc::clone(tasks));
builder.set_runtime(Arc::new(rt));
}
let (instance, env) = WasiEnv::instantiate(init, module, &mut self.store)?;
let _result = instance
.exports
.get_function("_start")?
.call(&mut self.store, &[])?;
builder.run(module)?;
Ok(())
}
@@ -84,15 +103,17 @@ impl crate::runners::Runner for WasiRunner {
// https://github.com/tokera-com/ate/blob/42c4ce5a0c0aef47aeb4420cc6dc788ef6ee8804/term-lib/src/eval/exec.rs#L444
fn prepare_webc_env(
webc: Arc<WebCMmap>,
container: &WapmContainer,
command: &str,
args: &[String],
) -> Result<WasiEnvBuilder, anyhow::Error> {
let filesystem = Box::new(WebcFileSystem::init_all(webc));
let (filesystem, preopen_dirs) = container.container_fs();
let mut builder = WasiEnv::builder(command).args(args);
for f_name in filesystem.top_level_dirs() {
builder.add_preopen_build(|p| p.directory(f_name).read(true).write(true).create(true))?;
for entry in preopen_dirs {
builder.add_preopen_build(|p| p.directory(&entry).read(true).write(true).create(true))?;
}
builder.set_fs(filesystem);
Ok(builder)

View File

@@ -0,0 +1,251 @@
use std::{
collections::HashMap,
ops::Deref,
path::{Path, PathBuf},
pin::Pin,
sync::Arc,
task::Poll,
};
use anyhow::Error;
use futures::{Future, FutureExt, StreamExt, TryFutureExt};
use http::{Request, Response};
use hyper::{service::Service, Body};
use tokio::{
io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt},
runtime::Handle,
};
use wasmer::Module;
use wasmer_vfs::{FileSystem, PassthruFileSystem, RootFileSystemBuilder, TmpFileSystem};
use wcgi_host::CgiDialect;
use crate::{
http::HttpClientCapabilityV1,
runners::wcgi::{Callbacks, MappedDirectory},
Capabilities, Pipe, PluggableRuntimeImplementation, VirtualTaskManager, WasiEnv,
};
/// The shared object that manages the instantiaion of WASI executables and
/// communicating with them via the CGI protocol.
#[derive(Clone, Debug)]
pub(crate) struct Handler(Arc<SharedState>);
impl Handler {
pub(crate) fn new(state: SharedState) -> Self {
Handler(Arc::new(state))
}
pub(crate) async fn handle(&self, req: Request<Body>) -> Result<Response<Body>, Error> {
let (parts, body) = req.into_parts();
let (req_body_sender, req_body_receiver) = Pipe::channel();
let (res_body_sender, res_body_receiver) = Pipe::channel();
let (stderr_sender, stderr_receiver) = Pipe::channel();
let builder = WasiEnv::builder(self.program.to_string());
let mut request_specific_env = HashMap::new();
self.dialect
.prepare_environment_variables(parts, &mut request_specific_env);
let rt = PluggableRuntimeImplementation::new(Arc::clone(&self.task_manager));
let builder = builder
.envs(self.env.iter())
.envs(request_specific_env)
.args(self.args.iter())
.stdin(Box::new(req_body_receiver))
.stdout(Box::new(res_body_sender))
.stderr(Box::new(stderr_sender))
.capabilities(Capabilities {
insecure_allow_all: true,
http_client: HttpClientCapabilityV1::new_allow_all(),
})
.runtime(Arc::new(rt))
.sandbox_fs(self.fs()?)
.preopen_dir(Path::new("/"))?;
let module = self.module.clone();
let done = self
.task_manager
.runtime()
.spawn_blocking(move || builder.run(module))
.map_err(Error::from)
.and_then(|r| async { r.map_err(Error::from) });
let handle = self.task_manager.runtime().clone();
let callbacks = Arc::clone(&self.callbacks);
handle.spawn(async move {
consume_stderr(stderr_receiver, callbacks).await;
});
self.task_manager.runtime().spawn(async move {
if let Err(e) = drive_request_to_completion(&handle, done, body, req_body_sender).await
{
tracing::error!(
error = &*e as &dyn std::error::Error,
"Unable to drive the request to completion"
);
}
});
let mut res_body_receiver = tokio::io::BufReader::new(res_body_receiver);
let parts = self
.dialect
.extract_response_header(&mut res_body_receiver)
.await?;
let chunks = futures::stream::try_unfold(res_body_receiver, |mut r| async move {
match r.fill_buf().await {
Ok(chunk) if chunk.is_empty() => Ok(None),
Ok(chunk) => {
let chunk = chunk.to_vec();
r.consume(chunk.len());
Ok(Some((chunk, r)))
}
Err(e) => Err(e),
}
});
let body = hyper::Body::wrap_stream(chunks);
let response = hyper::Response::from_parts(parts, body);
Ok(response)
}
fn fs(&self) -> Result<TmpFileSystem, Error> {
let root_fs = RootFileSystemBuilder::new().build();
if !self.mapped_dirs.is_empty() {
let fs_backing: Arc<dyn FileSystem + Send + Sync> =
Arc::new(PassthruFileSystem::new(crate::default_fs_backing()));
for MappedDirectory { host, guest } in self.mapped_dirs.iter() {
let guest = match guest.starts_with('/') {
true => PathBuf::from(guest),
false => Path::new("/").join(guest),
};
tracing::trace!(
host=%host.display(),
guest=%guest.display(),
"mounting directory to instance fs",
);
root_fs
.mount(host.clone(), &fs_backing, guest.clone())
.map_err(|error| {
anyhow::anyhow!(
"Unable to mount \"{}\" to \"{}\": {error}",
host.display(),
guest.display()
)
})?;
}
}
Ok(root_fs)
}
}
impl Deref for Handler {
type Target = Arc<SharedState>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Drive the request to completion by streaming the request body to the
/// instance and waiting for it to exit.
async fn drive_request_to_completion(
handle: &Handle,
done: impl Future<Output = Result<(), Error>>,
mut request_body: hyper::Body,
mut instance_stdin: impl AsyncWrite + Send + Unpin + 'static,
) -> Result<(), Error> {
let request_body_send = handle
.spawn(async move {
// Copy the request into our instance, chunk-by-chunk. If the instance
// dies before we finish writing the body, the instance's side of the
// pipe will be automatically closed and we'll error out.
while let Some(res) = request_body.next().await {
// FIXME(theduke): figure out how to propagate a body error to the
// CGI instance.
let chunk = res?;
instance_stdin.write_all(chunk.as_ref()).await?;
}
instance_stdin.shutdown().await?;
Ok::<(), Error>(())
})
.map_err(Error::from)
.and_then(|r| async { r });
futures::try_join!(done, request_body_send)?;
Ok(())
}
/// Read the instance's stderr, taking care to preserve output even when WASI
/// pipe errors occur so users still have *something* they use for
/// troubleshooting.
async fn consume_stderr(
stderr: impl AsyncRead + Send + Unpin + 'static,
callbacks: Arc<dyn Callbacks>,
) {
let mut stderr = tokio::io::BufReader::new(stderr);
// Note: we don't want to just read_to_end() because a reading error
// would cause us to lose all of stderr. At least this way we'll be
// able to show users the partial result.
loop {
match stderr.fill_buf().await {
Ok(chunk) if chunk.is_empty() => {
// EOF - the instance's side of the pipe was closed.
break;
}
Ok(chunk) => {
callbacks.on_stderr(chunk);
let bytes_read = chunk.len();
stderr.consume(bytes_read);
}
Err(e) => {
callbacks.on_stderr_error(e);
break;
}
}
}
}
#[derive(Clone, derivative::Derivative)]
#[derivative(Debug)]
pub(crate) struct SharedState {
pub(crate) program: String,
pub(crate) env: HashMap<String, String>,
pub(crate) args: Vec<String>,
pub(crate) mapped_dirs: Vec<MappedDirectory>,
pub(crate) module: Module,
pub(crate) dialect: CgiDialect,
pub(crate) task_manager: Arc<dyn VirtualTaskManager>,
#[derivative(Debug = "ignore")]
pub(crate) callbacks: Arc<dyn Callbacks>,
}
impl Service<Request<Body>> for Handler {
type Response = Response<Body>;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Response<Body>, Error>> + Send>>;
fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
// TODO: We probably should implement some sort of backpressure here...
Poll::Ready(Ok(()))
}
fn call(&mut self, request: Request<Body>) -> Self::Future {
// Note: all fields are reference-counted so cloning is pretty cheap
let handler = self.clone();
let fut = async move { handler.handle(request).await };
fut.boxed()
}
}

View File

@@ -0,0 +1,12 @@
mod handler;
mod runner;
use std::path::PathBuf;
pub use self::runner::{Callbacks, Config, WcgiRunner};
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct MappedDirectory {
pub host: PathBuf,
pub guest: String,
}

View File

@@ -0,0 +1,384 @@
use std::{collections::HashMap, convert::Infallible, net::SocketAddr, path::PathBuf, sync::Arc};
use anyhow::{Context, Error};
use futures::future::AbortHandle;
use wasmer::{Engine, Module, Store};
use wasmer_vfs::FileSystem;
use wcgi_host::CgiDialect;
use webc::metadata::{
annotations::{Wasi, Wcgi},
Command, Manifest,
};
use crate::{
runners::{
wcgi::{
handler::{Handler, SharedState},
MappedDirectory,
},
WapmContainer,
},
runtime::task_manager::tokio::TokioTaskManager,
VirtualTaskManager,
};
pub struct WcgiRunner {
program_name: String,
config: Config,
}
// TODO(Michael-F-Bryan): When we rewrite the existing runner infrastructure,
// make the "Runner" trait contain just these two methods.
impl WcgiRunner {
fn supports(cmd: &Command) -> Result<bool, Error> {
Ok(cmd
.runner
.starts_with(webc::metadata::annotations::WCGI_RUNNER_URI))
}
#[tracing::instrument(skip(self, ctx))]
fn run_(&mut self, command_name: &str, ctx: &RunnerContext<'_>) -> Result<(), Error> {
let wasi: Wasi = ctx
.command()
.get_annotation("wasi")
.context("Unable to retrieve the WASI metadata")?
.unwrap_or_else(|| Wasi::new(command_name));
let module = self
.load_module(&wasi, ctx)
.context("Couldn't load the module")?;
let handler = self.create_handler(module, &wasi, ctx)?;
let task_manager = Arc::clone(&handler.task_manager);
let make_service = hyper::service::make_service_fn(move |_| {
let handler = handler.clone();
async { Ok::<_, Infallible>(handler) }
});
let address = self.config.addr;
tracing::info!(%address, "Starting the server");
let callbacks = Arc::clone(&self.config.callbacks);
task_manager
.block_on(async {
let (shutdown, abort_handle) =
futures::future::abortable(futures::future::pending::<()>());
callbacks.started(abort_handle);
hyper::Server::bind(&address)
.serve(make_service)
.with_graceful_shutdown(async {
let _ = shutdown.await;
tracing::info!("Shutting down gracefully");
})
.await
})
.context("Unable to start the server")?;
Ok(())
}
}
impl WcgiRunner {
pub fn new(program_name: impl Into<String>) -> Self {
WcgiRunner {
program_name: program_name.into(),
config: Config::default(),
}
}
pub fn config(&mut self) -> &mut Config {
&mut self.config
}
fn load_module(&self, wasi: &Wasi, ctx: &RunnerContext<'_>) -> Result<Module, Error> {
let atom_name = &wasi.atom;
let atom = ctx
.get_atom(atom_name)
.with_context(|| format!("Unable to retrieve the \"{atom_name}\" atom"))?;
let module = ctx.compile(atom).context("Unable to compile the atom")?;
Ok(module)
}
fn create_handler(
&self,
module: Module,
wasi: &Wasi,
ctx: &RunnerContext<'_>,
) -> Result<Handler, Error> {
let env = construct_env(wasi, self.config.forward_host_env, &self.config.env);
let args = construct_args(wasi, &self.config.args);
let Wcgi { dialect, .. } = ctx.command().get_annotation("wcgi")?.unwrap_or_default();
let dialect = match dialect {
Some(d) => d.parse().context("Unable to parse the CGI dialect")?,
None => CgiDialect::Wcgi,
};
let shared = SharedState {
program: self.program_name.clone(),
env,
args,
mapped_dirs: self.config.mapped_dirs.clone(),
task_manager: self
.config
.task_manager
.clone()
.unwrap_or_else(|| Arc::new(TokioTaskManager::default())),
module,
dialect,
callbacks: Arc::clone(&self.config.callbacks),
};
Ok(Handler::new(shared))
}
}
fn construct_args(wasi: &Wasi, extras: &[String]) -> Vec<String> {
let mut args = Vec::new();
if let Some(main_args) = &wasi.main_args {
args.extend(main_args.iter().cloned());
}
args.extend(extras.iter().cloned());
args
}
fn construct_env(
wasi: &Wasi,
forward_host_env: bool,
overrides: &HashMap<String, String>,
) -> HashMap<String, String> {
let mut env: HashMap<String, String> = HashMap::new();
for item in wasi.env.as_deref().unwrap_or_default() {
// TODO(Michael-F-Bryan): Convert "wasi.env" in the webc crate from an
// Option<Vec<String>> to a HashMap<String, String> so we avoid this
// string.split() business
match item.split_once('=') {
Some((k, v)) => {
env.insert(k.to_string(), v.to_string());
}
None => {
env.insert(item.to_string(), String::new());
}
}
}
if forward_host_env {
env.extend(std::env::vars());
}
env.extend(overrides.clone());
env
}
// TODO(Michael-F-Bryan): Pass this to Runner::run() as "&dyn RunnerContext"
// when we rewrite the "Runner" trait.
struct RunnerContext<'a> {
container: &'a WapmContainer,
command: &'a Command,
engine: Engine,
store: Arc<Store>,
}
#[allow(dead_code)]
impl RunnerContext<'_> {
fn command(&self) -> &Command {
self.command
}
fn manifest(&self) -> &Manifest {
self.container.manifest()
}
fn engine(&self) -> &Engine {
&self.engine
}
fn store(&self) -> &Store {
&self.store
}
fn volume(&self, _name: &str) -> Option<Box<dyn FileSystem>> {
todo!("Implement a read-only filesystem backed by a volume");
}
fn get_atom(&self, name: &str) -> Option<&[u8]> {
self.container.get_atom(name)
}
fn compile(&self, wasm: &[u8]) -> Result<Module, Error> {
// TODO(Michael-F-Bryan): wire this up to wasmer-cache
Module::new(&self.engine, wasm).map_err(Error::from)
}
}
impl crate::runners::Runner for WcgiRunner {
type Output = ();
fn can_run_command(&self, _: &str, command: &Command) -> Result<bool, Error> {
WcgiRunner::supports(command)
}
fn run_command(
&mut self,
command_name: &str,
command: &Command,
container: &WapmContainer,
) -> Result<Self::Output, Error> {
let store = self.config.store.clone().unwrap_or_default();
let ctx = RunnerContext {
container,
command,
engine: store.engine().clone(),
store,
};
self.run_(command_name, &ctx)
}
}
#[derive(derivative::Derivative)]
#[derivative(Debug)]
pub struct Config {
task_manager: Option<Arc<dyn VirtualTaskManager>>,
addr: SocketAddr,
args: Vec<String>,
env: HashMap<String, String>,
forward_host_env: bool,
mapped_dirs: Vec<MappedDirectory>,
#[derivative(Debug = "ignore")]
callbacks: Arc<dyn Callbacks>,
store: Option<Arc<Store>>,
}
impl Config {
pub fn task_manager(&mut self, task_manager: impl VirtualTaskManager) -> &mut Self {
self.task_manager = Some(Arc::new(task_manager));
self
}
pub fn addr(&mut self, addr: SocketAddr) -> &mut Self {
self.addr = addr;
self
}
/// Add an argument to the WASI executable's command-line arguments.
pub fn arg(&mut self, arg: impl Into<String>) -> &mut Self {
self.args.push(arg.into());
self
}
/// Add multiple arguments to the WASI executable's command-line arguments.
pub fn args<A, S>(&mut self, args: A) -> &mut Self
where
A: IntoIterator<Item = S>,
S: Into<String>,
{
self.args.extend(args.into_iter().map(|s| s.into()));
self
}
/// Expose an environment variable to the guest.
pub fn env(&mut self, name: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.env.insert(name.into(), value.into());
self
}
/// Expose multiple environment variables to the guest.
pub fn envs<I, K, V>(&mut self, variables: I) -> &mut Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
self.env
.extend(variables.into_iter().map(|(k, v)| (k.into(), v.into())));
self
}
/// Forward all of the host's environment variables to the guest.
pub fn forward_host_env(&mut self) -> &mut Self {
self.forward_host_env = true;
self
}
pub fn map_directory(
&mut self,
host: impl Into<PathBuf>,
guest: impl Into<String>,
) -> &mut Self {
self.mapped_dirs.push(MappedDirectory {
host: host.into(),
guest: guest.into(),
});
self
}
pub fn map_directories<I, H, G>(&mut self, mappings: I) -> &mut Self
where
I: IntoIterator<Item = (H, G)>,
H: Into<PathBuf>,
G: Into<String>,
{
let mappings = mappings.into_iter().map(|(h, g)| MappedDirectory {
host: h.into(),
guest: g.into(),
});
self.mapped_dirs.extend(mappings);
self
}
/// Set callbacks that will be triggered at various points in the runner's
/// lifecycle.
pub fn callbacks(&mut self, callbacks: impl Callbacks + Send + Sync + 'static) -> &mut Self {
self.callbacks = Arc::new(callbacks);
self
}
pub fn store(&mut self, store: Store) -> &mut Self {
self.store = Some(Arc::new(store));
self
}
}
impl Default for Config {
fn default() -> Self {
Self {
task_manager: None,
addr: ([127, 0, 0, 1], 8000).into(),
env: HashMap::new(),
forward_host_env: false,
mapped_dirs: Vec::new(),
args: Vec::new(),
callbacks: Arc::new(NoopCallbacks),
store: None,
}
}
}
/// Callbacks that are triggered at various points in the lifecycle of a runner
/// and any WebAssembly instances it may start.
pub trait Callbacks: Send + Sync + 'static {
/// A callback that is called whenever the server starts.
fn started(&self, _abort: AbortHandle) {}
/// Data was written to stderr by an instance.
fn on_stderr(&self, _stderr: &[u8]) {}
/// Reading from stderr failed.
fn on_stderr_error(&self, _error: std::io::Error) {}
}
struct NoopCallbacks;
impl Callbacks for NoopCallbacks {}

View File

@@ -260,6 +260,7 @@ impl WasiEnvInit {
/// The environment provided to the WASI imports.
#[derive(Debug)]
pub struct WasiEnv {
pub control_plane: WasiControlPlane,
/// Represents the process this environment is attached to
pub process: WasiProcess,
/// Represents the thread this environment is attached to
@@ -312,6 +313,7 @@ impl WasiEnv {
// Currently only used by fork/spawn related syscalls.
pub(crate) fn duplicate(&self) -> Self {
Self {
control_plane: self.control_plane.clone(),
process: self.process.clone(),
poll_seed: self.poll_seed,
thread: self.thread.clone(),
@@ -330,7 +332,7 @@ impl WasiEnv {
/// Forking the WasiState is used when either fork or vfork is called
pub fn fork(&self) -> Result<(Self, WasiThreadHandle), ControlPlaneError> {
let process = self.process.compute.new_process()?;
let process = self.control_plane.new_process()?;
let handle = process.new_thread()?;
let thread = handle.as_thread();
@@ -341,6 +343,7 @@ impl WasiEnv {
let bin_factory = self.bin_factory.clone();
let new_env = Self {
control_plane: self.control_plane.clone(),
process,
thread,
vfork: None,
@@ -379,6 +382,7 @@ impl WasiEnv {
};
let mut env = Self {
control_plane: init.control_plane,
process,
thread: thread.as_thread(),
vfork: None,

View File

@@ -99,7 +99,7 @@ pub fn proc_join<M: MemorySize>(
// Otherwise we wait for the specific PID
let env = ctx.data();
let pid: WasiProcessId = pid.into();
let process = env.process.control_plane().get_process(pid);
let process = env.control_plane.get_process(pid);
if let Some(process) = process {
let exit_code = wasi_try_ok!(__asyncify(&mut ctx, None, async move {
let code = process.join().await.unwrap_or(Errno::Child);

View File

@@ -15,14 +15,12 @@ pub fn proc_parent<M: MemorySize>(
if pid == env.process.pid() {
let memory = env.memory_view(&ctx);
wasi_try_mem!(ret_parent.write(&memory, env.process.ppid().raw() as Pid));
Errno::Success
} else if let Some(process) = env.control_plane.get_process(pid) {
let memory = env.memory_view(&ctx);
wasi_try_mem!(ret_parent.write(&memory, process.pid().raw() as Pid));
Errno::Success
} else {
let control_plane = env.process.control_plane();
if let Some(process) = control_plane.get_process(pid) {
let memory = env.memory_view(&ctx);
wasi_try_mem!(ret_parent.write(&memory, process.pid().raw() as Pid));
} else {
return Errno::Badf;
}
Errno::Badf
}
Errno::Success
}

View File

@@ -23,7 +23,7 @@ pub fn proc_signal<M: MemorySize>(
let process = {
let pid: WasiProcessId = pid.into();
ctx.data().process.compute.get_process(pid)
ctx.data().control_plane.get_process(pid)
};
if let Some(process) = process {
process.signal_process(sig);

View File

@@ -39,7 +39,7 @@ pub fn proc_spawn<M: MemorySize>(
ret_handles: WasmPtr<BusHandles, M>,
) -> Result<BusErrno, WasiError> {
let env = ctx.data();
let control_plane = env.process.control_plane();
let control_plane = &env.control_plane;
let memory = env.memory_view(&ctx);
let name = unsafe { get_input_str_bus_ok!(&memory, name, name_len) };
let args = unsafe { get_input_str_bus_ok!(&memory, args, args_len) };

View File

@@ -9,7 +9,10 @@ use wasmer_vfs::FileSystem;
use tracing::*;
#[allow(unused_imports)]
use tracing::{error, warn};
use webc::{Annotation, UrlOrManifest, WebC};
use webc::{
metadata::{Annotation, UrlOrManifest},
v1::WebC,
};
use crate::{
bin_factory::{BinaryPackage, BinaryPackageCommand},
@@ -125,8 +128,8 @@ fn wapm_extract_version(data: &WapmWebQuery) -> Option<PiritaVersionedDownload>
}
pub fn parse_static_webc(data: Vec<u8>) -> Result<BinaryPackage, anyhow::Error> {
let options = webc::ParseOptions::default();
match webc::WebCOwned::parse(data, &options) {
let options = webc::v1::ParseOptions::default();
match webc::v1::WebCOwned::parse(data, &options) {
Ok(webc) => unsafe {
let webc = Arc::new(webc);
return parse_webc(webc.as_webc_ref(), webc.clone())
@@ -164,14 +167,14 @@ async fn download_webc(
};
// build the parse options
let options = webc::ParseOptions::default();
let options = webc::v1::ParseOptions::default();
// fast path
let path = compute_path(cache_dir, name);
#[cfg(feature = "sys")]
if path.exists() {
match webc::WebCMmap::parse(path.clone(), &options) {
match webc::v1::WebCMmap::parse(path.clone(), &options) {
Ok(webc) => unsafe {
let webc = Arc::new(webc);
return parse_webc(webc.as_webc_ref(), webc.clone()).with_context(|| {
@@ -230,7 +233,7 @@ async fn download_webc(
);
}
match webc::WebCMmap::parse(path.clone(), &options) {
match webc::v1::WebCMmap::parse(path.clone(), &options) {
Ok(webc) => unsafe {
let webc = Arc::new(webc);
return parse_webc(webc.as_webc_ref(), webc.clone())
@@ -242,7 +245,7 @@ async fn download_webc(
}
}
let webc_raw = webc::WebCOwned::parse(data, &options)
let webc_raw = webc::v1::WebCOwned::parse(data, &options)
.with_context(|| format!("Failed to parse downloaded from '{pirita_download_url}'"))?;
let webc = Arc::new(webc_raw);
// FIXME: add SAFETY comment
@@ -275,7 +278,7 @@ async fn download_package(
}
// TODO: should return Result<_, anyhow::Error>
unsafe fn parse_webc<'a, T>(webc: webc::WebC<'a>, ownership: Arc<T>) -> Option<BinaryPackage>
unsafe fn parse_webc<'a, T>(webc: webc::v1::WebC<'a>, ownership: Arc<T>) -> Option<BinaryPackage>
where
T: std::fmt::Debug + Send + Sync + 'static,
T: Deref<Target = WebC<'static>>,

223
lib/wasi/tests/runners.rs Normal file
View File

@@ -0,0 +1,223 @@
#![cfg(feature = "webc_runner")]
use std::{path::Path, time::Duration};
use once_cell::sync::Lazy;
use reqwest::Client;
use wasmer_wasi::runners::{Runner, WapmContainer};
#[cfg(feature = "webc_runner_rt_wasi")]
mod wasi {
use tokio::runtime::Handle;
use wasmer::Store;
use wasmer_wasi::{
runners::wasi::WasiRunner, runtime::task_manager::tokio::TokioTaskManager, WasiError,
};
use super::*;
#[tokio::test]
async fn can_run_wat2wasm() {
let webc = download_cached("https://wapm.io/wasmer/wabt").await;
let store = Store::default();
let container = WapmContainer::from_bytes(webc).unwrap();
let runner = WasiRunner::new(store);
let command = &container.manifest().commands["wat2wasm"];
assert!(runner.can_run_command("wat2wasm", command).unwrap());
}
#[tokio::test]
async fn wat2wasm() {
let webc = download_cached("https://wapm.io/wasmer/wabt").await;
let store = Store::default();
let tasks = TokioTaskManager::new(Handle::current());
let container = WapmContainer::from_bytes(webc).unwrap();
// Note: we don't have any way to intercept stdin or stdout, so blindly
// assume that everything is fine if it runs successfully.
let handle = std::thread::spawn(move || {
WasiRunner::new(store)
.with_task_manager(tasks)
.run_cmd(&container, "wat2wasm")
});
let err = handle.join().unwrap().unwrap_err();
let runtime_error = err
.chain()
.find_map(|e| e.downcast_ref::<WasiError>())
.unwrap();
let exit_code = match runtime_error {
WasiError::Exit(code) => *code,
other => unreachable!("Something else went wrong: {:?}", other),
};
assert_eq!(exit_code, 1);
}
#[tokio::test]
async fn python() {
let webc = download_cached("https://wapm.io/python/python").await;
let store = Store::default();
let tasks = TokioTaskManager::new(Handle::current());
let container = WapmContainer::from_bytes(webc).unwrap();
let handle = std::thread::spawn(move || {
WasiRunner::new(store)
.with_task_manager(tasks)
.with_args(["-c", "import sys; sys.exit(42)"])
.run_cmd(&container, "python")
});
let err = handle.join().unwrap().unwrap_err();
let runtime_error = err
.chain()
.find_map(|e| e.downcast_ref::<WasiError>())
.unwrap();
let exit_code = match runtime_error {
WasiError::Exit(code) => *code,
other => unreachable!("Something else went wrong: {:?}", other),
};
assert_eq!(exit_code, 42);
}
}
#[cfg(feature = "webc_runner_rt_wcgi")]
mod wcgi {
use std::future::Future;
use futures::{channel::mpsc::Sender, future::AbortHandle, SinkExt, StreamExt};
use rand::Rng;
use tokio::runtime::Handle;
use wasmer_wasi::{runners::wcgi::WcgiRunner, runtime::task_manager::tokio::TokioTaskManager};
use super::*;
#[tokio::test]
async fn can_run_staticserver() {
let webc = download_cached("https://wapm.io/Michael-F-Bryan/staticserver").await;
let container = WapmContainer::from_bytes(webc).unwrap();
let runner = WcgiRunner::new("staticserver");
let entrypoint = container.manifest().entrypoint.as_ref().unwrap();
assert!(runner
.can_run_command(entrypoint, &container.manifest().commands[entrypoint])
.unwrap());
}
#[tokio::test]
async fn staticserver() {
let webc = download_cached("https://wapm.io/Michael-F-Bryan/staticserver").await;
let tasks = TokioTaskManager::new(Handle::current());
let container = WapmContainer::from_bytes(webc).unwrap();
let mut runner = WcgiRunner::new("staticserver");
let port = rand::thread_rng().gen_range(10000_u16..65535_u16);
let (cb, started) = callbacks(Handle::current());
runner
.config()
.addr(([127, 0, 0, 1], port).into())
.task_manager(tasks)
.callbacks(cb);
// The server blocks so we need to start it on a background thread.
let join_handle = std::thread::spawn(move || {
runner.run(&container).unwrap();
});
// wait for the server to have started
let abort_handle = started.await;
// Now the server is running, we can check that it is working by
// fetching "/" and checking for known content. We also want the server
// to close connections immediately so the graceful shutdown can kill
// the server immediately instead of waiting for the connection to time
// out.
let resp = client()
.get(format!("http://localhost:{port}/"))
.header("Connection", "close")
.send()
.await
.unwrap();
let body = resp.error_for_status().unwrap().text().await.unwrap();
assert!(body.contains("<title>Index of /</title>"), "{}", body);
// Make sure the server is shutdown afterwards. Failing tests will leak
// the server thread, but that's fine.
abort_handle.abort();
if let Err(e) = join_handle.join() {
std::panic::resume_unwind(e);
}
}
fn callbacks(handle: Handle) -> (Callbacks, impl Future<Output = AbortHandle>) {
let (sender, mut rx) = futures::channel::mpsc::channel(1);
let cb = Callbacks { sender, handle };
let fut = async move { rx.next().await.unwrap() };
(cb, fut)
}
struct Callbacks {
sender: Sender<AbortHandle>,
handle: Handle,
}
impl wasmer_wasi::runners::wcgi::Callbacks for Callbacks {
fn started(&self, abort: futures::stream::AbortHandle) {
let mut sender = self.sender.clone();
self.handle.spawn(async move {
sender.send(abort).await.unwrap();
});
}
fn on_stderr(&self, stderr: &[u8]) {
panic!(
"Something was written to stderr: {}",
String::from_utf8_lossy(stderr)
);
}
}
}
async fn download_cached(url: &str) -> bytes::Bytes {
let uri: http::Uri = url.parse().unwrap();
let file_name = Path::new(uri.path()).file_name().unwrap();
let cache_dir = Path::new(env!("CARGO_TARGET_TMPDIR")).join(module_path!());
let cached_path = cache_dir.join(file_name);
if cached_path.exists() {
return std::fs::read(&cached_path).unwrap().into();
}
let response = client()
.get(url)
.header("Accept", "application/webc")
.send()
.await
.unwrap();
assert_eq!(
response.status(),
200,
"Unable to get \"{url}\": {}",
response.status(),
);
let body = response.bytes().await.unwrap();
std::fs::create_dir_all(&cache_dir).unwrap();
std::fs::write(&cached_path, &body).unwrap();
body
}
fn client() -> Client {
static CLIENT: Lazy<Client> = Lazy::new(|| {
Client::builder()
.connect_timeout(Duration::from_secs(30))
.build()
.unwrap()
});
CLIENT.clone()
}

View File

@@ -19,7 +19,7 @@ object = "0.30.0"
[dependencies]
anyhow = "1"
tempfile = "3"
tempfile = "3.4.0"
target-lexicon = "0.12.5"
tar = "0.4.38"
flate2 = "1.0.24"

View File

@@ -17,7 +17,7 @@ wasmer-wasi = { path = "../../../lib/wasi", version = "=3.2.0-alpha.1" }
wasmer-vfs = { path = "../../../lib/vfs", version = "=3.2.0-alpha.1" }
wast = "38.0"
serde = "1"
tempfile = "3"
tempfile = "3.4.0"
thiserror = "1.0"
tokio = { version = "1", features = [ "io-util", "rt" ], default_features = false }

View File

@@ -10,7 +10,7 @@ publish = false
[dependencies]
glob = "0.3"
gumdrop = "0.8"
tempfile = "3"
tempfile = "3.4.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
wast = "24.0"