mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-13 05:48:45 +00:00
Merge branch 'master' into dash-fixes
This commit is contained in:
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
26
.github/workflows/test.yaml
vendored
26
.github/workflows/test.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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
704
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
19
Cargo.toml
19
Cargo.toml
@@ -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.
|
||||
|
||||
21
Makefile
21
Makefile
@@ -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
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)| {
|
||||
|
||||
2
lib/cache/Cargo.toml
vendored
2
lib/cache/Cargo.toml
vendored
@@ -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" }
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
213
lib/wasi/src/runners/container.rs
Normal file
213
lib/wasi/src/runners/container.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
63
lib/wasi/src/runners/runner.rs
Normal file
63
lib/wasi/src/runners/runner.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
251
lib/wasi/src/runners/wcgi/handler.rs
Normal file
251
lib/wasi/src/runners/wcgi/handler.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
12
lib/wasi/src/runners/wcgi/mod.rs
Normal file
12
lib/wasi/src/runners/wcgi/mod.rs
Normal 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,
|
||||
}
|
||||
384
lib/wasi/src/runners/wcgi/runner.rs
Normal file
384
lib/wasi/src/runners/wcgi/runner.rs
Normal 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 {}
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) };
|
||||
|
||||
@@ -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
223
lib/wasi/tests/runners.rs
Normal 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()
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user