mirror of
https://github.com/extism/extism.git
synced 2026-01-11 23:08:06 -05:00
Compare commits
12 Commits
fix-androi
...
edition-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22f9cc8e26 | ||
|
|
f8e16dc875 | ||
|
|
2524707334 | ||
|
|
de65e22f68 | ||
|
|
59acffa8ac | ||
|
|
1f46f9842d | ||
|
|
b249f09b90 | ||
|
|
4e638e14b1 | ||
|
|
d7956ff08c | ||
|
|
87c3384f1e | ||
|
|
07047eaab0 | ||
|
|
1e281e93cd |
37
.github/dependabot.yml
vendored
37
.github/dependabot.yml
vendored
@@ -9,33 +9,12 @@ updates:
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
# This is the name of your group, it will be used in PR titles and branch names
|
||||
wasmtime-deps:
|
||||
# A pattern can be...
|
||||
patterns:
|
||||
- "wasmtime"
|
||||
- "wasi-common"
|
||||
- "wiggle"
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directory: "python"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "mix"
|
||||
directory: "elixir"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "node"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "bundler"
|
||||
directory: "ruby"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
shell: bash
|
||||
run: cargo build --release -p ${{ env.LIBEXTISM_CRATE }}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: libextism-${{ matrix.os }}
|
||||
path: |
|
||||
|
||||
31
.github/workflows/release.yml
vendored
31
.github/workflows/release.yml
vendored
@@ -7,11 +7,6 @@ on:
|
||||
|
||||
name: Release
|
||||
|
||||
env:
|
||||
RUNTIME_MANIFEST: runtime/Cargo.toml
|
||||
RUNTIME_CRATE: libextism
|
||||
RUSTFLAGS: -C target-feature=-crt-static
|
||||
ARTIFACT_DIR: release-artifacts
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -21,6 +16,11 @@ jobs:
|
||||
release:
|
||||
name: ${{ matrix.os }} ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
env:
|
||||
RUNTIME_MANIFEST: runtime/Cargo.toml
|
||||
RUNTIME_CRATE: libextism
|
||||
RUSTFLAGS: -C target-feature=-crt-static
|
||||
ARTIFACT_DIR: release-artifacts-${{ matrix.os }}-${{ matrix.target }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -38,20 +38,6 @@ jobs:
|
||||
static-dll-artifact: ''
|
||||
pc-in: 'extism.pc.in'
|
||||
static-pc-in: 'extism-static.pc.in'
|
||||
- os: 'ubuntu'
|
||||
target: 'aarch64-linux-android'
|
||||
artifact: 'libextism.so'
|
||||
static-artifact: 'libextism.a'
|
||||
static-dll-artifact: ''
|
||||
pc-in: 'extism.pc.in'
|
||||
static-pc-in: 'extism-static.pc.in'
|
||||
- os: 'ubuntu'
|
||||
target: 'x86_64-linux-android'
|
||||
artifact: 'libextism.so'
|
||||
static-artifact: 'libextism.a'
|
||||
static-dll-artifact: ''
|
||||
pc-in: 'extism.pc.in'
|
||||
static-pc-in: 'extism-static.pc.in'
|
||||
- os: 'ubuntu'
|
||||
target: 'aarch64-unknown-linux-gnu'
|
||||
artifact: 'libextism.so'
|
||||
@@ -206,7 +192,7 @@ jobs:
|
||||
ls -ll ${DEST_DIR}
|
||||
|
||||
- name: Upload Artifact to Summary
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_DIR }}
|
||||
path: ${{ env.ARTIFACT_DIR }}
|
||||
@@ -225,9 +211,10 @@ jobs:
|
||||
needs: [release]
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_DIR }}
|
||||
pattern: release-artifacts-*
|
||||
merge-multiple: true
|
||||
|
||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
|
||||
@@ -4,7 +4,7 @@ members = ["extism-maturin", "manifest", "runtime", "libextism", "convert", "con
|
||||
exclude = ["kernel"]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
authors = ["The Extism Authors", "oss@extism.org"]
|
||||
license = "BSD-3-Clause"
|
||||
homepage = "https://extism.org"
|
||||
|
||||
16
README.md
16
README.md
@@ -36,6 +36,22 @@ function linking, and more. Extism users build:
|
||||
- web applications
|
||||
- & much more...
|
||||
|
||||
# Supported Targets
|
||||
|
||||
We currently provide releases for the following targets:
|
||||
|
||||
- aarch64-apple-darwin
|
||||
- aarch64-unknown-linux-gnu
|
||||
- aarch64-unknown-linux-musl
|
||||
- x86_64-apple-darwin
|
||||
- x86_64-pc-windows-gnu
|
||||
- x86_64-pc-windows-msvc
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
|
||||
For Android we suggest taking a look at the [Chicory SDK](https://github.com/extism/chicory-sdk) for a pure Java
|
||||
Extism runtime.
|
||||
|
||||
# Run WebAssembly In Your App
|
||||
|
||||
Pick a SDK to import into your program, and refer to the documentation to get
|
||||
|
||||
@@ -15,7 +15,7 @@ use base64::Engine;
|
||||
/// and [`FromBytesOwned`] using [`serde_json::from_slice`]
|
||||
#[macro_export]
|
||||
macro_rules! encoding {
|
||||
($pub:vis $name:ident, $to_vec:expr, $from_slice:expr) => {
|
||||
($pub:vis $name:ident, $to_vec:expr_2021, $from_slice:expr_2021) => {
|
||||
#[doc = concat!(stringify!($name), " encoding")]
|
||||
#[derive(Debug)]
|
||||
$pub struct $name<T>(pub T);
|
||||
|
||||
@@ -240,10 +240,10 @@ struct DataPtrLength {
|
||||
}
|
||||
|
||||
#[cfg(feature = "json_schema")]
|
||||
fn wasmdata_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
fn wasmdata_schema(g: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
use schemars::{schema::SchemaObject, JsonSchema};
|
||||
let mut schema: SchemaObject = <String>::json_schema(gen).into();
|
||||
let objschema: SchemaObject = <DataPtrLength>::json_schema(gen).into();
|
||||
let mut schema: SchemaObject = <String>::json_schema(g).into();
|
||||
let objschema: SchemaObject = <DataPtrLength>::json_schema(g).into();
|
||||
let types = schemars::schema::SingleOrVec::<schemars::schema::InstanceType>::Vec(vec![
|
||||
schemars::schema::InstanceType::String,
|
||||
schemars::schema::InstanceType::Object,
|
||||
|
||||
@@ -9,9 +9,9 @@ repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
wasmtime = {version = ">= 26.0.0, < 27.0.0"}
|
||||
wasi-common = {version = ">= 26.0.0, < 27.0.0"}
|
||||
wiggle = {version = ">= 26.0.0, < 27.0.0"}
|
||||
wasmtime = {version = ">= 27.0.0, < 31.0.0"}
|
||||
wasi-common = {version = ">= 27.0.0, < 31.0.0"}
|
||||
wiggle = {version = ">= 27.0.0, < 31.0.0"}
|
||||
anyhow = "1"
|
||||
serde = {version = "1", features = ["derive"]}
|
||||
serde_json = "1"
|
||||
@@ -21,7 +21,7 @@ tracing = "0.1"
|
||||
tracing-subscriber = {version = "0.3.18", features = ["std", "env-filter", "fmt"]}
|
||||
url = "2"
|
||||
glob = "0.3"
|
||||
ureq = {version = "2.5", optional=true}
|
||||
ureq = {version = "3.0", optional=true}
|
||||
extism-manifest = { workspace = true }
|
||||
extism-convert = { workspace = true, features = ["extism-path"] }
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
@@ -39,7 +39,7 @@ cbindgen = { version = "0.28", default-features = false }
|
||||
[dev-dependencies]
|
||||
criterion = "0.5.1"
|
||||
quickcheck = "1"
|
||||
rand = "0.8.5"
|
||||
rand = "0.9.0"
|
||||
|
||||
[[bench]]
|
||||
name = "bench"
|
||||
|
||||
@@ -88,7 +88,7 @@ println!("{:?}", res);
|
||||
|
||||
### Plug-in State
|
||||
|
||||
Plug-ins may be stateful or stateless. Plug-ins can maintain state b/w calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. You can see this by making subsequent calls to the export:
|
||||
Plug-ins may be stateful or stateless. Plug-ins can maintain state between calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. You can see this by making subsequent calls to the export:
|
||||
|
||||
```rust
|
||||
let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
|
||||
@@ -219,5 +219,13 @@ println!("{}", res);
|
||||
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
Plug-ins can't directly print anything to the console. They can however use Extism's built-in logging functionality, for example the [`log!` macro in the rust-pdk](https://docs.rs/extism-pdk/1.4.0/extism_pdk/macro.log.html) or the [`logInfo` function in the haskell-pdk](https://hackage.haskell.org/package/extism-pdk-1.2.0.0/docs/Extism-PDK.html#v:logInfo).
|
||||
|
||||
Inside your host application, the rust-sdk emits these as [tracing](https://github.com/tokio-rs/tracing) events. The simplest way to make the logged messages visible is by adding the `tracing_subscriber` dependency to your crate and then initializing a tracing subscriber at the top of your main function:
|
||||
|
||||
```rust
|
||||
tracing_subscriber::fmt::init();
|
||||
```
|
||||
|
||||
|
||||
@@ -206,15 +206,16 @@ impl CurrentPlugin {
|
||||
anyhow::bail!("expected extism_context to be an externref value",)
|
||||
};
|
||||
|
||||
match xs
|
||||
.data_mut(&mut *store)?
|
||||
.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>()
|
||||
{
|
||||
Some(xs) => match xs.downcast_mut::<T>() {
|
||||
Some(xs) => Ok(xs),
|
||||
None => anyhow::bail!("could not downcast extism_context inner value"),
|
||||
},
|
||||
None => anyhow::bail!("could not downcast extism_context"),
|
||||
if let Some(d) = xs.data_mut(&mut *store)? {
|
||||
match d.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
|
||||
Some(xs) => match xs.downcast_mut::<T>() {
|
||||
Some(xs) => Ok(xs),
|
||||
None => anyhow::bail!("could not downcast extism_context inner value"),
|
||||
},
|
||||
None => anyhow::bail!("could not downcast extism_context"),
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("extism_context not found")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,14 +86,16 @@ fn to_module(engine: &Engine, wasm: &extism_manifest::Wasm) -> Result<(String, M
|
||||
#[cfg(feature = "register-http")]
|
||||
{
|
||||
// Setup request
|
||||
let mut req = ureq::request(method.as_deref().unwrap_or("GET"), url);
|
||||
let mut req = ureq::http::request::Builder::new()
|
||||
.method(method.as_deref().unwrap_or("GET").to_uppercase().as_str())
|
||||
.uri(url);
|
||||
|
||||
for (k, v) in headers.iter() {
|
||||
req = req.set(k, v);
|
||||
req = req.header(k, v);
|
||||
}
|
||||
|
||||
// Fetch WASM code
|
||||
let mut r = req.call()?.into_reader();
|
||||
let mut r = ureq::run(req.body(())?)?.into_body().into_reader();
|
||||
let mut data = Vec::new();
|
||||
r.read_to_end(&mut data)?;
|
||||
|
||||
|
||||
@@ -233,17 +233,22 @@ pub(crate) fn http_request(
|
||||
)));
|
||||
}
|
||||
|
||||
let mut r = ureq::request(req.method.as_deref().unwrap_or("GET"), &req.url);
|
||||
let mut r = ureq::http::request::Builder::new()
|
||||
.method(
|
||||
req.method
|
||||
.as_deref()
|
||||
.unwrap_or("GET")
|
||||
.to_uppercase()
|
||||
.as_str(),
|
||||
)
|
||||
.uri(&req.url);
|
||||
|
||||
for (k, v) in req.headers.iter() {
|
||||
r = r.set(k, v);
|
||||
r = r.header(k, v);
|
||||
}
|
||||
|
||||
// Set HTTP timeout to respect the manifest timeout
|
||||
if let Some(remaining) = data.time_remaining() {
|
||||
r = r.timeout(remaining);
|
||||
}
|
||||
|
||||
let timeout = data.time_remaining();
|
||||
let res = if body_offset > 0 {
|
||||
let handle = match data.memory_handle(body_offset) {
|
||||
Some(h) => h,
|
||||
@@ -252,9 +257,15 @@ pub(crate) fn http_request(
|
||||
}
|
||||
};
|
||||
let buf: &[u8] = data.memory_bytes(handle)?;
|
||||
r.send_bytes(buf)
|
||||
let agent = ureq::agent();
|
||||
let config = agent.configure_request(r.body(buf)?);
|
||||
let req = config.timeout_global(timeout).build();
|
||||
ureq::run(req)
|
||||
} else {
|
||||
r.call()
|
||||
let agent = ureq::agent();
|
||||
let config = agent.configure_request(r.body(())?);
|
||||
let req = config.timeout_global(timeout).build();
|
||||
ureq::run(req)
|
||||
};
|
||||
|
||||
if let Some(handle) = data.memory_handle(body_offset) {
|
||||
@@ -264,26 +275,26 @@ pub(crate) fn http_request(
|
||||
let reader = match res {
|
||||
Ok(res) => {
|
||||
if let Some(headers) = &mut data.http_headers {
|
||||
for name in res.headers_names() {
|
||||
if let Some(h) = res.header(&name) {
|
||||
headers.insert(name, h.to_string());
|
||||
for (name, h) in res.headers() {
|
||||
if let Ok(h) = h.to_str() {
|
||||
headers.insert(name.as_str().to_string(), h.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
data.http_status = res.status();
|
||||
Some(res.into_reader())
|
||||
data.http_status = res.status().as_u16();
|
||||
Some(res.into_body().into_reader())
|
||||
}
|
||||
Err(e) => {
|
||||
// Catch timeout and return
|
||||
if let Some(d) = data.time_remaining() {
|
||||
if e.kind() == ureq::ErrorKind::Io && d.as_nanos() == 0 {
|
||||
if matches!(e, ureq::Error::Timeout(_)) && d.as_nanos() == 0 {
|
||||
anyhow::bail!("timeout");
|
||||
}
|
||||
}
|
||||
let msg = e.to_string();
|
||||
if let Some(res) = e.into_response() {
|
||||
data.http_status = res.status();
|
||||
Some(res.into_reader())
|
||||
if let ureq::Error::StatusCode(res) = e {
|
||||
data.http_status = res;
|
||||
None
|
||||
} else {
|
||||
return Err(Error::msg(msg));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
sync::TryLockError,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
@@ -855,15 +856,20 @@ impl Plugin {
|
||||
|
||||
// Set host context
|
||||
let r = if let Some(host_context) = host_context {
|
||||
let inner = self
|
||||
if let Some(inner) = self
|
||||
.host_context
|
||||
.data_mut(&mut self.store)
|
||||
.map_err(|x| (x, -1))?;
|
||||
if let Some(inner) = inner.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
|
||||
let x: Box<T> = Box::new(host_context);
|
||||
*inner = x;
|
||||
.map_err(|x| (x, -1))?
|
||||
{
|
||||
if let Some(inner) = inner.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
|
||||
let x: Box<T> = Box::new(host_context);
|
||||
*inner = x;
|
||||
}
|
||||
|
||||
Some(self.host_context)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Some(self.host_context)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -906,7 +912,7 @@ impl Plugin {
|
||||
let mut res = func.call(self.store_mut(), &[], results.as_mut_slice());
|
||||
|
||||
// Reset host context
|
||||
if let Ok(inner) = self.host_context.data_mut(&mut self.store) {
|
||||
if let Ok(Some(inner)) = self.host_context.data_mut(&mut self.store) {
|
||||
if let Some(inner) = inner.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
|
||||
let x: Box<dyn Any + Send + Sync> = Box::new(());
|
||||
*inner = x;
|
||||
@@ -1083,7 +1089,12 @@ impl Plugin {
|
||||
input: T,
|
||||
) -> Result<U, Error> {
|
||||
let lock = self.instance.clone();
|
||||
let mut lock = lock.lock().unwrap();
|
||||
let mut lock = lock.try_lock().map_err(|e| match e {
|
||||
TryLockError::Poisoned(_) => anyhow::anyhow!(
|
||||
"instance lock was poisoned; previous thread panicked while calling into wasm"
|
||||
),
|
||||
TryLockError::WouldBlock => anyhow::anyhow!("cannot make reentrant calls into plugin"),
|
||||
})?;
|
||||
let data = input.to_bytes()?;
|
||||
self.raw_call(&mut lock, name, data, None::<()>)
|
||||
.map_err(|e| e.0)
|
||||
@@ -1108,7 +1119,12 @@ impl Plugin {
|
||||
C: Any + Send + Sync + 'static,
|
||||
{
|
||||
let lock = self.instance.clone();
|
||||
let mut lock = lock.lock().unwrap();
|
||||
let mut lock = lock.try_lock().map_err(|e| match e {
|
||||
TryLockError::Poisoned(_) => anyhow::anyhow!(
|
||||
"instance lock was poisoned; previous thread panicked while calling into wasm"
|
||||
),
|
||||
TryLockError::WouldBlock => anyhow::anyhow!("cannot make reentrant calls into plugin"),
|
||||
})?;
|
||||
let data = input.to_bytes()?;
|
||||
self.raw_call(&mut lock, name, data, Some(host_context))
|
||||
.map_err(|e| e.0)
|
||||
@@ -1127,7 +1143,18 @@ impl Plugin {
|
||||
input: T,
|
||||
) -> Result<U, (Error, i32)> {
|
||||
let lock = self.instance.clone();
|
||||
let mut lock = lock.lock().unwrap();
|
||||
let mut lock = lock.try_lock().map_err(|e| match e {
|
||||
TryLockError::Poisoned(_) => (
|
||||
anyhow::anyhow!(
|
||||
"instance lock was poisoned; previous thread panicked while calling into wasm"
|
||||
),
|
||||
-1,
|
||||
),
|
||||
TryLockError::WouldBlock => (
|
||||
anyhow::anyhow!("cannot make reentrant calls into plugin"),
|
||||
-1,
|
||||
),
|
||||
})?;
|
||||
let data = input.to_bytes().map_err(|e| (e, -1))?;
|
||||
self.raw_call(&mut lock, name, data, None::<()>)
|
||||
.and_then(move |_| self.output().map_err(|e| (e, -1)))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -412,7 +412,7 @@ quickcheck! {
|
||||
quickcheck! {
|
||||
fn check_alloc_with_load_and_store(amounts: Vec<u16>) -> bool {
|
||||
use rand::Rng;
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = rand::rng();
|
||||
let (mut store, mut instance) = init_kernel_test();
|
||||
let instance = &mut instance;
|
||||
for a in amounts {
|
||||
@@ -425,7 +425,7 @@ quickcheck! {
|
||||
}
|
||||
|
||||
for _ in 0..16 {
|
||||
let i = rng.gen_range(ptr..ptr+a as u64);
|
||||
let i = rng.random_range(ptr..ptr+a as u64);
|
||||
extism_store_u8(&mut store, instance, i, i as u8);
|
||||
if extism_load_u8(&mut store, instance, i as u64) != i as u8 {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user