Compare commits

..

14 Commits

Author SHA1 Message Date
Benjamin Eckel
f4c61e8c44 feat: add default cache config (#615)
Co-authored-by: zach <zach@dylibso.com>
2023-11-29 14:32:01 -06:00
Benjamin Eckel
452bb2f498 chore: Bump 0.5.4 (#549) 2023-10-25 15:46:30 -05:00
zach
c043decd95 chore: backport latest kernel changes (#547)
- Updates to fix a possible issue with a value on stack being stored at
a very high offset in extism kernel memory
2023-10-25 11:37:37 -07:00
Benjamin Eckel
ec70b0e7b8 Release: 0.5.3 stable release (#538)
Cuts a new 0.5.3 release from stable.
2023-10-24 12:29:39 -05:00
zach
1452b9fb42 chore: backport latest kernel changes to stable (#528) 2023-10-20 10:16:33 -07:00
Benjamin Eckel
9f69a68df4 chore: fix elixir release action (#497)
This is 404ing for some reason. But Elixir doesn't need the shared
library so i'm removing it.

https://github.com/extism/extism/actions/runs/6425026823/job/17446865267
2023-10-05 17:32:20 -05:00
Benjamin Eckel
dbf9f34dc6 chore: Release elixir v0.5.1 (#496) 2023-10-05 17:14:54 -05:00
Benjamin Eckel
c8a9478da1 chore: also bump rust SDK to 0.5.2 2023-09-21 10:22:39 -05:00
Benjamin Eckel
e89ddd5a2a chore: Bump to 0.5.2 2023-09-21 09:41:27 -05:00
zach
93392e0884 fix(stable): improve the way the kernel calculates how many pages to allocate (#471)
Fixes a bug in the kernel memory allocator where the space used by the
`MemoryRoot` wasn't being considered

---------

Co-authored-by: Benjamin Eckel <bhelx@users.noreply.github.com>
2023-09-20 16:02:42 -07:00
Benjamin Eckel
4ebd0eb372 chore: always run the publish of SDK 2023-09-18 19:17:47 -05:00
zach
8feee0c693 cleanup(stable): use wasm-strip on extism-runtime.wasm (#467) 2023-09-18 19:08:27 -05:00
Benjamin Eckel
773ab32a45 chore: Bump to 0.5.1 2023-09-18 18:55:46 -05:00
Benjamin Eckel
6a041d0c39 fix: Fixes rounding issue in kernel num_pages (#466)
There were some scenarios where the kernel was not allocating enough
pages to get data back into the plugin. So some host functions were
failing when the output was right on this boundary.

Related https://github.com/moonrepo/proto/issues/208

We will come back with tests and cherry pick this over to main. For now
we want to get out a 0.5.1 fix
2023-09-18 18:53:30 -05:00
208 changed files with 6495 additions and 7760 deletions

View File

@@ -1,4 +1,12 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-node.yml
- manifest/**
- runtime/**
- libextism/**
- browser/**
workflow_dispatch:
name: Browser CI

View File

@@ -1,4 +1,12 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-cpp.yml
- manifest/**
- runtime/**
- libextism/**
- cpp/**
workflow_dispatch:
name: C++ CI

View File

@@ -1,28 +0,0 @@
on:
workflow_dispatch:
name: D CI
jobs:
d:
name: D
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
# TODO: Use multiple versions once stable
d_version: [ldc-1.33.0]
rust:
- stable
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Setup D environment
uses: dlang-community/setup-dlang@v1
with:
compiler: ${{ matrix.d_version }}
- name: Test D Host SDK
run: |
dub --version
LD_LIBRARY_PATH=/usr/local/lib dub test

View File

@@ -1,4 +1,12 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-dotnet.yml
- manifest/**
- runtime/**
- libextism/**
- dotnet/**
workflow_dispatch:
name: .NET CI

View File

@@ -1,4 +1,12 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-elixir.yml
- manifest/**
- runtime/**
- rust/**
- elixir/**
workflow_dispatch:
name: Elixir CI

View File

@@ -1,4 +1,16 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-go.yml
- manifest/**
- runtime/**
- libextism/**
- extism.go
- extism_test.go
- go.mod
- libextism.pc
- go/**
workflow_dispatch:
name: Go CI

View File

@@ -1,4 +1,12 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-haskell.yml
- manifest/**
- runtime/**
- libextism/**
- haskell/**
workflow_dispatch:
name: Haskell CI

View File

@@ -1,4 +1,12 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-java.yml
- manifest/**
- runtime/**
- libextism/**
- java/**
workflow_dispatch:
name: Java CI

View File

@@ -1,4 +1,12 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-node.yml
- manifest/**
- runtime/**
- libextism/**
- node/**
workflow_dispatch:
name: Node CI

View File

@@ -1,4 +1,14 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-ocaml.yml
- manifest/**
- runtime/**
- libextism/**
- ocaml/**
- dune-project
- extism.opam
workflow_dispatch:
name: OCaml CI

View File

@@ -1,4 +1,14 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-php.yml
- manifest/**
- runtime/**
- libextism/**
- php/**
- composer.json
- composer.lock
workflow_dispatch:
name: PHP CI

View File

@@ -1,4 +1,12 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-python.yml
- manifest/**
- runtime/**
- libextism/**
- python/**
workflow_dispatch:
name: Python CI

View File

@@ -1,4 +1,12 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-ruby.yml
- manifest/**
- runtime/**
- libextism/**
- ruby/**
workflow_dispatch:
name: Ruby CI

View File

@@ -3,7 +3,6 @@ on:
paths:
- .github/actions/extism/**
- .github/workflows/ci-rust.yml
- convert/**
- manifest/**
- runtime/**
- rust/**
@@ -13,8 +12,9 @@ on:
name: Rust CI
env:
RUNTIME_CRATE: extism
RUNTIME_CRATE: extism-runtime
LIBEXTISM_CRATE: libextism
RUST_SDK_CRATE: extism
jobs:
lib:
@@ -40,13 +40,13 @@ jobs:
uses: actions/cache@v3
with:
path: target/release/libextism.*
key: ${{ runner.os }}-libextism-${{ hashFiles('runtime/**') }}-${{ hashFiles('manifest/**') }}-${{ hashFiles('convert/**') }}
key: ${{ runner.os }}-libextism-${{ hashFiles('runtime/**') }}-${{ hashFiles('manifest/**') }}
- name: Cache target
id: cache-target
uses: actions/cache@v3
with:
path: target/**
key: ${{ runner.os }}-target-${{ github.sha }}
key: ${{ runner.os }}-target-${{ env.GITHUB_SHA }}
- name: Build
if: steps.cache-libextism.outputs.cache-hit != 'true'
shell: bash
@@ -80,40 +80,26 @@ jobs:
uses: actions/cache@v3
with:
path: target/**
key: ${{ runner.os }}-target-${{ github.sha }}
key: ${{ runner.os }}-target-${{ env.GITHUB_SHA }}
- name: Format
run: cargo fmt --check -p ${{ env.RUNTIME_CRATE }}
- name: Lint
run: cargo clippy --release --all-features --no-deps -p ${{ env.RUNTIME_CRATE }}
- name: Test
run: cargo test --release -p ${{ env.RUNTIME_CRATE }}
- name: Test all features
run: cargo test --all-features --release -p ${{ env.RUNTIME_CRATE }}
- name: Test no features
run: cargo test --no-default-features --release -p ${{ env.RUNTIME_CRATE }}
bench:
name: Benchmarking
rust:
name: Rust
needs: lib
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Cache Rust environment
uses: Swatinem/rust-cache@v1
- name: Cache target
id: cache-target
uses: actions/cache@v3
with:
path: target/**
key: ${{ runner.os }}-target-${{ github.sha }}
- run: cargo install cargo-criterion
- run: cargo criterion
- uses: ./.github/actions/extism
- name: Test Rust Host SDK
run: LD_LIBRARY_PATH=/usr/local/lib cargo test --release -p ${{ env.RUST_SDK_CRATE }}

View File

@@ -1,4 +1,12 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-zig.yml
- manifest/**
- runtime/**
- libextism/**
- zig/**
workflow_dispatch:
name: Zig CI

View File

@@ -1,47 +0,0 @@
on:
workflow_dispatch:
pull_request:
paths:
- kernel/**
name: Kernel
jobs:
kernel:
name: Build extism-runtime.wasm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
target: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- name: Install deps
run: |
sudo apt install wabt --yes
- name: Build kernel
shell: bash
continue-on-error: true
run: |
cd kernel
sh build.sh
git diff --exit-code
export GIT_EXIT_CODE=$?
- uses: peter-evans/create-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: ${{ env.GIT_EXIT_CODE }} != 0
with:
title: "update(kernel): extism-runtime.wasm in ${{ github.event.pull_request.head.ref }}"
body: "Automated PR to update `runtime/src/extism-runtime.wasm` in PR #${{ github.event.number }}"
base: "${{ github.event.pull_request.head.ref }}"
branch: "update-kernel--${{ github.event.pull_request.head.ref }}"
commit-message: "update(kernel): extism-runtime.wasm in ${{ github.event.pull_request.head.ref }}"
delete-branch: true

View File

@@ -1,26 +0,0 @@
on:
workflow_dispatch:
name: Release extism-convert
jobs:
release-convert:
name: release-extism-convert
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Rust env
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
target: ${{ matrix.target }}
- name: Release Rust Convert Crate
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |
cargo publish --manifest-path convert/Cargo.toml

View File

@@ -10,10 +10,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
filter: tree:0
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v3.0.3
with:
@@ -24,28 +20,10 @@ jobs:
name: release-artifacts
- name: Extract Archive
run: |
mkdir -p dotnet/nuget/runtimes/win-x64/native/
tar -xvzf libextism-x86_64-pc-windows-msvc-main.tar.gz -C dotnet/nuget/runtimes/win-x64/native/
mkdir -p dotnet/nuget/runtimes/osx-arm64/native/
tar -xvzf libextism-aarch64-apple-darwin-main.tar.gz -C dotnet/nuget/runtimes/osx-arm64/native/
mkdir -p dotnet/nuget/runtimes/osx-x64/native/
tar -xvzf libextism-x86_64-apple-darwin-main.tar.gz -C dotnet/nuget/runtimes/osx-x64/native/
mkdir -p dotnet/nuget/runtimes/linux-x64/native/
tar -xvzf libextism-x86_64-unknown-linux-gnu-main.tar.gz -C dotnet/nuget/runtimes/linux-x64/native/
mkdir -p dotnet/nuget/runtimes/linux-arm64/native/
tar -xvzf libextism-aarch64-unknown-linux-gnu-main.tar.gz -C dotnet/nuget/runtimes/linux-arm64/native/
mkdir -p dotnet/nuget/runtimes/linux-musl-arm64/native/
tar -xvzf libextism-aarch64-unknown-linux-musl-main.tar.gz -C dotnet/nuget/runtimes/linux-musl-arm64/native/
- name: Pack NuGet packages
tar -xvzf libextism-x86_64-pc-windows-msvc-v*.tar.gz --directory dotnet/nuget/runtimes
mv dotnet/nuget/runtimes/extism.dll dotnet/nuget/runtimes/win-x64.dll
- name: Publish win-x64
run: |
find ./dotnet/nuget -type f -name "*.csproj" -exec dotnet pack {} -o release-artifacts \;
- name: Publish NuGet packages
run: |
dotnet nuget push --source https://api.nuget.org/v3/index.json ./release-artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }}
cd dotnet/nuget
dotnet pack -o dist
dotnet nuget push --source https://api.nuget.org/v3/index.json ./dist/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }}

View File

@@ -10,13 +10,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install extism shared library
shell: bash
run: |
mkdir -p /home/runner/.local/bin/
export PATH="/home/runner/.local/bin/:$PATH"
curl https://raw.githubusercontent.com/extism/cli/main/install.sh | sh
extism --sudo --prefix /usr/local install
- name: Setup Elixir Host SDK
uses: erlef/setup-beam@v1
with:

View File

@@ -1,26 +0,0 @@
on:
workflow_dispatch:
name: Release extism-manifest
jobs:
release-manifest:
name: release-extism-manifest
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Rust env
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
target: ${{ matrix.target }}
- name: Release Rust Manifest Crate
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |
cargo publish --manifest-path manifest/Cargo.toml

View File

@@ -1,8 +1,7 @@
name: Release Python SDK
on:
release:
types: [published, edited]
workflow_dispatch:
name: Release Python SDK
jobs:
release-sdks:
@@ -12,30 +11,26 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Install poetry
run: pipx install poetry
- uses: actions/setup-python@v4
- name: Setup Python env
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: "3.9"
check-latest: true
- name: Run image
uses: abatilo/actions-poetry@v2
- name: install twine
- name: Build Python Host SDK
run: |
pip install twine
cd python
cp ../LICENSE .
make clean
poetry install --no-dev
poetry build
- name: download release
run: |
tag='${{ github.ref }}'
tag="${tag/refs\/tags\//}"
mkdir dist
cd dist
gh release download "$tag" -p 'extism_sys-*'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Release Python Host SDK
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: ${{ secrets.PYPI_API_USER }}
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: python/dist/
- name: upload release
run: |
twine upload dist/*
env:
TWINE_USERNAME: ${{ secrets.PYPI_API_USER }}
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

View File

@@ -1,16 +1,16 @@
on:
workflow_dispatch:
name: Release Runtime/Rust SDK
name: Release Rust SDK
jobs:
release-runtime:
release-sdks:
name: release-rust
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Rust env
uses: actions-rs/toolchain@v1
with:
@@ -19,8 +19,23 @@ jobs:
override: true
target: ${{ matrix.target }}
- name: Release Runtime
- name: Release Rust Manifest Crate
if: always()
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |
cargo publish --manifest-path runtime/Cargo.toml
# order of crate publication matter: manifest, runtime, rust
cargo publish --manifest-path manifest/Cargo.toml
# allow for crates.io to update so dependant crates can locate extism-manifest
sleep 5
- name: Release Rust Host SDK
if: always()
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |
cargo publish --manifest-path runtime/Cargo.toml --no-verify
cargo publish --manifest-path rust/Cargo.toml

View File

@@ -1,9 +1,7 @@
on:
release:
types: [created]
workflow_dispatch:
push:
branches: [ main ]
tags:
- 'v*'
name: Release
@@ -13,66 +11,20 @@ env:
RUSTFLAGS: -C target-feature=-crt-static
ARTIFACT_DIR: release-artifacts
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
release:
name: ${{ matrix.os }} ${{ matrix.target }}
runs-on: ${{ matrix.os }}-latest
release-linux:
name: linux
runs-on: ubuntu-latest
strategy:
matrix:
include:
- os: 'macos'
target: 'x86_64-apple-darwin'
artifact: 'libextism.dylib'
static-artifact: 'libextism.a'
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'macos'
target: 'aarch64-apple-darwin'
artifact: 'libextism.dylib'
static-artifact: 'libextism.a'
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'aarch64-unknown-linux-gnu'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'aarch64-unknown-linux-musl'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'x86_64-unknown-linux-gnu'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'x86_64-unknown-linux-musl'
artifact: ''
static-artifact: 'libextism.a'
pc-in: ''
static-pc-in: 'extism-static.pc.in'
- os: 'windows'
target: 'x86_64-pc-windows-gnu'
artifact: 'extism.dll'
static-artifact: 'libextism.a'
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'windows'
target: 'x86_64-pc-windows-msvc'
artifact: 'extism.dll'
static-artifact: 'extism.lib'
pc-in: ''
static-pc-in: ''
target:
[
aarch64-unknown-linux-gnu,
aarch64-unknown-linux-musl,
x86_64-unknown-linux-gnu,
]
# i686-unknown-linux-gnu,
if: always()
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -85,68 +37,16 @@ jobs:
override: true
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
prefix-key: "${{matrix.os}}-${{matrix.target}}"
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-on-failure: "true"
- name: Build Target (${{ matrix.os }} ${{ matrix.target }})
- name: Build Target (${{ matrix.target }})
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.os != 'windows' }}
use-cross: true
command: build
args: --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
- name: set extism-maturin version
shell: bash
run: |
pyproject="$(cat extism-maturin/pyproject.toml)"
version="${{ github.ref }}"
if [[ "$version" = "refs/heads/main" ]]; then
version="0.0.0-dev"
else
version="${version/refs\/tags\/v/}"
fi
<<<"$pyproject" >extism-maturin/pyproject.toml sed -e 's/^version = "0.0.0.replaced-by-ci"/version = "'"$version"'"/g'
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Build wheels
uses: PyO3/maturin-action@v1
# maturin's cffi integration struggles with gnu headers on windows.
# there's partial work towards fixing this in `extism-maturin/build.rs`, but it's
# not sufficient to get it to work. omit it for now!
if: ${{ matrix.target != 'x86_64-pc-windows-gnu' && matrix.target != 'aarch64-unknown-linux-gnu' }}
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter -m extism-maturin/Cargo.toml
sccache: 'true'
manylinux: auto
- name: Build GNU Linux wheels
uses: PyO3/maturin-action@v1
# One of our deps, "ring", needs a newer sysroot than what "manylinux: auto" provides.
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter -m extism-maturin/Cargo.toml
sccache: 'true'
manylinux: 2_28
- name: Add pkg-config files except on MSVC
if: ${{ matrix.target != 'x86_64-pc-windows-msvc' }}
shell: bash
run: |
SRC_DIR=target/${{ matrix.target }}/release
cp libextism/extism*.pc.in ${SRC_DIR}
- name: Prepare Artifact
shell: bash
run: |
EXT=so
SRC_DIR=target/${{ matrix.target }}/release
DEST_DIR=${{ env.ARTIFACT_DIR }}
RELEASE_NAME=libextism-${{ matrix.target }}-${{ github.ref_name }}
@@ -156,62 +56,161 @@ jobs:
# compress the shared library & create checksum
cp runtime/extism.h ${SRC_DIR}
cp LICENSE ${SRC_DIR}
tar -C ${SRC_DIR} -czvf ${ARCHIVE} extism.h \
${{ matrix.artifact }} ${{ matrix.static-artifact }} \
${{ matrix.pc-in }} ${{ matrix.static-pc-in }}
tar -C ${SRC_DIR} -czvf ${ARCHIVE} libextism.${EXT} extism.h
ls -ll ${ARCHIVE}
if &>/dev/null which shasum; then
shasum -a 256 ${ARCHIVE} > ${CHECKSUM}
else
# windows doesn't have shasum available, so we use certutil instead.
certutil -hashfile ${ARCHIVE} SHA256 >${CHECKSUM}
fi
shasum -a 256 ${ARCHIVE} > ${CHECKSUM}
# copy archive and checksum into release artifact directory
mkdir -p ${DEST_DIR}
cp ${ARCHIVE} ${DEST_DIR}
cp ${CHECKSUM} ${DEST_DIR}
# copy any built wheels.
if [ -e dist/*.whl ]; then
cp dist/*.whl ${DEST_DIR}
fi
ls -ll ${DEST_DIR}
ls ${DEST_DIR}
- name: Upload Artifact to Summary
uses: actions/upload-artifact@v3
with:
name: ${{ env.ARTIFACT_DIR }}
path: ${{ env.ARTIFACT_DIR }}
path: |
*.tar.gz
*.txt
- name: Upload Artifact to Draft Release
- name: Upload Artifact to Release
uses: softprops/action-gh-release@v1
with:
files: |
*.tar.gz
*.txt
release-macos:
name: macos
runs-on: macos-latest
strategy:
matrix:
target: [x86_64-apple-darwin, aarch64-apple-darwin]
if: always()
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
target: ${{ matrix.target }}
- name: Build Target (${{ matrix.target }})
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
- name: Prepare Artifact
run: |
EXT=dylib
SRC_DIR=target/${{ matrix.target }}/release
DEST_DIR=${{ env.ARTIFACT_DIR }}
RELEASE_NAME=libextism-${{ matrix.target }}-${{ github.ref_name }}
ARCHIVE=${RELEASE_NAME}.tar.gz
CHECKSUM=${RELEASE_NAME}.checksum.txt
# compress the shared library & create checksum
cp runtime/extism.h ${SRC_DIR}
cp LICENSE ${SRC_DIR}
tar -C ${SRC_DIR} -czvf ${ARCHIVE} libextism.${EXT} extism.h
ls -ll ${ARCHIVE}
shasum -a 256 ${ARCHIVE} > ${CHECKSUM}
# copy archive and checksum into release artifact directory
mkdir -p ${DEST_DIR}
cp ${ARCHIVE} ${DEST_DIR}
cp ${CHECKSUM} ${DEST_DIR}
ls ${DEST_DIR}
- name: Upload Artifact to Summary
uses: actions/upload-artifact@v3
with:
name: ${{ env.ARTIFACT_DIR }}
path: |
*.tar.gz
*.txt
- name: Upload Artifact to Release
uses: softprops/action-gh-release@v1
with:
files: |
*.tar.gz
*.txt
release-windows:
name: windows
runs-on: windows-latest
strategy:
matrix:
target:
[x86_64-pc-windows-gnu, x86_64-pc-windows-msvc]
# i686-pc-windows-gnu,
# i686-pc-windows-msvc,
# aarch64-pc-windows-msvc
if: always()
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
target: ${{ matrix.target }}
- name: Build Target (${{ matrix.target }})
uses: actions-rs/cargo@v1
with:
command: build
args: --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
- name: Prepare Artifact
shell: bash
run: |
EXT=dll
SRC_DIR=target/${{ matrix.target }}/release
DEST_DIR=${{ env.ARTIFACT_DIR }}
RELEASE_NAME=libextism-${{ matrix.target }}-${{ github.ref_name }}
ARCHIVE=${RELEASE_NAME}.tar.gz
CHECKSUM=${RELEASE_NAME}.checksum.txt
# compress the shared library & create checksum
cp runtime/extism.h ${SRC_DIR}
cp LICENSE ${SRC_DIR}
tar -C ${SRC_DIR} -czvf ${ARCHIVE} extism.${EXT} extism.h
ls -ll ${ARCHIVE}
certutil -hashfile ${ARCHIVE} SHA256 >${CHECKSUM}
# copy archive and checksum into release artifact directory
mkdir -p ${DEST_DIR}
cp ${ARCHIVE} ${DEST_DIR}
cp ${CHECKSUM} ${DEST_DIR}
ls ${DEST_DIR}
- name: Upload Artifact to Summary
uses: actions/upload-artifact@v3
with:
name: ${{ env.ARTIFACT_DIR }}
path: |
*.tar.gz
*.txt
- name: Upload Artifact to Release
uses: softprops/action-gh-release@v1
with:
draft: true
files: |
${{ env.ARTIFACT_DIR }}/*
if: startsWith(github.ref, 'refs/tags/')
release-latest:
name: create latest release
runs-on: ubuntu-latest
needs: [release]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/download-artifact@v3
with:
name: ${{ env.ARTIFACT_DIR }}
- uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
automatic_release_tag: "latest"
prerelease: true
title: "Development Build"
files: |
*.tar.gz
*.txt
*.whl
if: github.ref == 'refs/heads/main'

7
.gitignore vendored
View File

@@ -15,8 +15,6 @@ python/poetry.lock
c/main
cpp/test/test
cpp/example
.dub
dub.selections.json
go/main
ruby/.bundle/
ruby/.yardoc
@@ -43,7 +41,4 @@ java/*.iml
java/*.log
java/.idea
java/.DS_Store
extism-maturin/src/extism.h
runtime/*.log
libextism/example
libextism/extism*.pc

View File

@@ -1 +1 @@
version = 0.26.0
version = 0.24.1

View File

@@ -1,4 +1,8 @@
[workspace]
resolver = "2"
members = ["extism-maturin", "manifest", "runtime", "libextism", "convert"]
members = [
"manifest",
"runtime",
"rust",
"libextism",
]
exclude = ["kernel"]

View File

@@ -1,9 +1,7 @@
DEST?=/usr/local
SOEXT=so
AEXT=a
FEATURES?=default
DEFAULT_FEATURES?=yes
RUST_TARGET?=
UNAME := $(shell uname -s)
ifeq ($(UNAME),Darwin)
@@ -20,43 +18,25 @@ else
FEATURE_FLAGS=--features $(FEATURES)
endif
ifeq ($(RUST_TARGET),)
TARGET_FLAGS=
else
TARGET_FLAGS=--target $(RUST_TARGET)
endif
build:
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml $(TARGET_FLAGS)
sed -e "s%@CMAKE_INSTALL_PREFIX@%$(DEST)%" libextism/extism.pc.in > libextism/extism.pc
sed -e "s%@CMAKE_INSTALL_PREFIX@%$(DEST)%" libextism/extism-static.pc.in > libextism/extism-static.pc
bench:
@(cargo criterion $(TARGET_FLAGS) || echo 'For nicer output use cargo-criterion: `cargo install cargo-criterion` - using `cargo bench`') && cargo bench $(TARGET_FLAGS)
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
.PHONY: kernel
kernel:
cd kernel && bash build.sh
lint:
cargo clippy --release --no-deps --manifest-path runtime/Cargo.toml $(TARGET_FLAGS)
cargo clippy --release --no-deps --manifest-path runtime/Cargo.toml
debug:
RUSTFLAGS=-g RUST_TARGET=$(RUST_TARGET) $(MAKE) build
RUSTFLAGS=-g $(MAKE) build
install:
echo $(RUST_TARGET)
mkdir -p $(DEST)/lib $(DEST)/include $(DEST)/lib/pkgconfig
mkdir -p $(DEST)/lib $(DEST)/include
install runtime/extism.h $(DEST)/include/extism.h
if [ -f target/$(RUST_TARGET)/release/libextism.$(SOEXT) ]; then \
install target/$(RUST_TARGET)/release/libextism.$(SOEXT) $(DEST)/lib/libextism.$(SOEXT); \
fi
install target/$(RUST_TARGET)/release/libextism.$(AEXT) $(DEST)/lib/libextism.$(AEXT)
install libextism/extism.pc $(DEST)/lib/pkgconfig/extism.pc
install libextism/extism-static.pc $(DEST)/lib/pkgconfig/extism-static.pc
install target/release/libextism.$(SOEXT) $(DEST)/lib/libextism.$(SOEXT)
uninstall:
rm -f $(DEST)/include/extism.h $(DEST)/lib/libextism.$(SOEXT) $(DEST)/lib/libextism.$(AEXT) \
$(DEST)/lib/pkgconfig/extism*.pc
rm -f $(DEST)/include/extism.h $(DEST)/lib/libextism.$(SOEXT)

View File

@@ -1,6 +1,6 @@
### _Welcome!_
**Please note:** This project still under active development and APIs are changing as we hit 1.0. Currently, the main branch has many breaking changes. Our current release is 0.5.x. Until we release 1.0, we are cutting these releases from the [stable](https://github.com/extism/extism/tree/stable) branch.
**Please note:** This project still under active development and APIs may change until we hit v1.0.
If you're interested in working on or building with Extism, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know - we are happy to help get you started.
@@ -8,24 +8,19 @@ If you're interested in working on or building with Extism, please join our [Dis
# [Extism](https://extism.org)
The universal plug-in system. Run WebAssembly extensions inside your app. Use idiomatic Host SDKs for [Go](https://github.com/extism/go-sdk#readme),
[Ruby](https://github.com/extism/ruby-sdk#readme),
[Python](https://github.com/extism/python-sdk#readme),
[JavaScript](https://github.com/extism/js-sdk#readme),
[Rust](/runtime/#readme),
[C](libextism/#readme),
[C++](https://github.com/extism/cpp-sdk/#readme),
[OCaml](https://github.com/extism/ocaml-sdk#readme),
[Haskell](https://github.com/extism/haskell-sdk#readme),
[PHP](https://github.com/extism/php-sdk#readme),
[Elixir](https://github.com/extism/elixir-sdk#readme),
[.NET](https://github.com/extism/dotnet-sdk#readme),
[Java](https://github.com/extism/java-sdk#readme),
[Zig](https://github.com/extism/zig-sdk#readme),
[D](https://github.com/extism/d-sdk#readme),
&amp; more (others coming soon).
The universal plug-in system. Run WebAssembly extensions inside your app. Use idiomatic Host SDKs for [Go](https://extism.org/docs/integrate-into-your-codebase/go-host-sdk),
[Ruby](https://extism.org/docs/integrate-into-your-codebase/ruby-host-sdk), [Python](https://extism.org/docs/integrate-into-your-codebase/python-host-sdk),
[Node](https://extism.org/docs/integrate-into-your-codebase/node-host-sdk), [Rust](https://extism.org/docs/integrate-into-your-codebase/rust-host-sdk),
[C](https://extism.org/docs/integrate-into-your-codebase/c-host-sdk), [C++](https://extism.org/docs/integrate-into-your-codebase/cpp-host-sdk),
[OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk),
[Haskell](https://extism.org/docs/integrate-into-your-codebase/haskell-host-sdk),
[PHP](https://extism.org/docs/integrate-into-your-codebase/php-host-sdk),
[Elixir/Erlang](https://extism.org/docs/integrate-into-your-codebase/elixir-or-erlang-host-sdk),
[.NET](https://extism.org/docs/integrate-into-your-codebase/dotnet-host-sdk),
[Java](https://extism.org/docs/integrate-into-your-codebase/java-host-sdk),
[Zig](https://extism.org/docs/integrate-into-your-codebase/zig-host-sdk) &amp; more (others coming soon).
Plug-in development kits (PDK) for plug-in authors supported in [Rust](https://github.com/extism/rust-pdk#readme), [AssemblyScript](https://github.com/extism/assemblyscript-pdk#readme), [Go](https://github.com/extism/go-pdk#readme), [C/C++](https://github.com/extism/c-pdk#readme), [Haskell](https://github.com/extism/haskell-pdk#readme), [JavaScript](https://github.com/extism/js-pdk#readme), [C#](https://github.com/extism/dotnet-pdk#readme), [F#](https://github.com/extism/dotnet-pdk#readme) and [Zig](https://github.com/extism/zig-pdk#readme).
Plug-in development kits (PDK) for plug-in authors supported in [Rust](https://github.com/extism/rust-pdk), [AssemblyScript](https://github.com/extism/assemblyscript-pdk), [Go](https://github.com/extism/go-pdk), [C/C++](https://github.com/extism/c-pdk), [Haskell](https://github.com/extism/haskell-pdk), and [Zig](https://github.com/extism/zig-pdk).
<p align="center">
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/210286900-39b144fd-1b26-4dd0-b7a9-2b5755bc174d.png" alt="Extism embedded SDK language support"/>

View File

@@ -1,4 +0,0 @@
# Browser Host SDK
This contains the `0.x` version of the SDK. This library is deprecated and all new integrations should use the new [Universal JavaScript library](https://github.com/extism/js-sdk#readme).

Binary file not shown.

View File

@@ -1,12 +1,12 @@
{
"name": "@extism/runtime-browser",
"version": "0.3.1",
"version": "0.3.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@extism/runtime-browser",
"version": "0.3.1",
"version": "0.3.0",
"license": "BSD-3-Clause",
"dependencies": {
"@bjorn3/browser_wasi_shim": "^0.2.7"
@@ -39,13 +39,12 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
"@babel/highlight": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
@@ -100,14 +99,13 @@
}
},
"node_modules/@babel/generator": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz",
"integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==",
"dev": true,
"dependencies": {
"@babel/types": "^7.23.0",
"@babel/types": "^7.20.7",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
},
"engines": {
@@ -156,34 +154,34 @@
}
},
"node_modules/@babel/helper-environment-visitor": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
"integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
"dev": true,
"dependencies": {
"@babel/template": "^7.22.15",
"@babel/types": "^7.23.0"
"@babel/template": "^7.18.10",
"@babel/types": "^7.19.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-hoist-variables": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
"dev": true,
"dependencies": {
"@babel/types": "^7.22.5"
"@babel/types": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
@@ -242,30 +240,30 @@
}
},
"node_modules/@babel/helper-split-export-declaration": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.22.5"
"@babel/types": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -295,13 +293,13 @@
}
},
"node_modules/@babel/highlight": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"@babel/helper-validator-identifier": "^7.18.6",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
},
"engines": {
@@ -309,9 +307,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz",
"integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@@ -515,33 +513,33 @@
}
},
"node_modules/@babel/template": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
"integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.22.13",
"@babel/parser": "^7.22.15",
"@babel/types": "^7.22.15"
"@babel/code-frame": "^7.18.6",
"@babel/parser": "^7.20.7",
"@babel/types": "^7.20.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz",
"integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.0",
"@babel/types": "^7.23.0",
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.20.7",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.20.13",
"@babel/types": "^7.20.7",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@@ -550,13 +548,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz",
"integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
@@ -8290,13 +8288,12 @@
}
},
"@babel/code-frame": {
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
"dev": true,
"requires": {
"@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
"@babel/highlight": "^7.18.6"
}
},
"@babel/compat-data": {
@@ -8337,14 +8334,13 @@
}
},
"@babel/generator": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz",
"integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==",
"dev": true,
"requires": {
"@babel/types": "^7.23.0",
"@babel/types": "^7.20.7",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
},
"dependencies": {
@@ -8382,28 +8378,28 @@
}
},
"@babel/helper-environment-visitor": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
"dev": true
},
"@babel/helper-function-name": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
"integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
"dev": true,
"requires": {
"@babel/template": "^7.22.15",
"@babel/types": "^7.23.0"
"@babel/template": "^7.18.10",
"@babel/types": "^7.19.0"
}
},
"@babel/helper-hoist-variables": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
"dev": true,
"requires": {
"@babel/types": "^7.22.5"
"@babel/types": "^7.18.6"
}
},
"@babel/helper-module-imports": {
@@ -8447,24 +8443,24 @@
}
},
"@babel/helper-split-export-declaration": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
"dev": true,
"requires": {
"@babel/types": "^7.22.5"
"@babel/types": "^7.18.6"
}
},
"@babel/helper-string-parser": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/helper-validator-option": {
@@ -8485,20 +8481,20 @@
}
},
"@babel/highlight": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"@babel/helper-validator-identifier": "^7.18.6",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz",
"integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==",
"dev": true
},
"@babel/plugin-syntax-async-generators": {
@@ -8639,42 +8635,42 @@
}
},
"@babel/template": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
"integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.22.13",
"@babel/parser": "^7.22.15",
"@babel/types": "^7.22.15"
"@babel/code-frame": "^7.18.6",
"@babel/parser": "^7.20.7",
"@babel/types": "^7.20.7"
}
},
"@babel/traverse": {
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz",
"integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.0",
"@babel/types": "^7.23.0",
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.20.7",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.20.13",
"@babel/types": "^7.20.7",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz",
"integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "@extism/runtime-browser",
"version": "0.3.1",
"version": "0.3.0",
"description": "Extism runtime in the browser",
"scripts": {
"build": "node build.js && tsc --emitDeclarationOnly --outDir dist",
@@ -36,4 +36,4 @@
"dependencies": {
"@bjorn3/browser_wasi_shim": "^0.2.7"
}
}
}

View File

@@ -5,7 +5,7 @@ import { ExtismPlugin } from './plugin';
* Can be a {@link Manifest} or just the raw bytes of the WASM module as an ArrayBuffer.
* We recommend using {@link Manifest}
*/
type ManifestData = Manifest | ArrayBuffer | WebAssembly.Module;
type ManifestData = Manifest | ArrayBuffer;
/**
* A Context is needed to create plugins. The Context
@@ -21,8 +21,8 @@ export default class ExtismContext {
* @returns A new Plugin scoped to this Context
*/
async newPlugin(manifest: ManifestData, functions: Record<string, any> = {}, config?: PluginConfig) {
let moduleData: ArrayBuffer | WebAssembly.Module | null = null;
if (manifest instanceof ArrayBuffer || manifest instanceof WebAssembly.Module) {
let moduleData: ArrayBuffer | null = null;
if (manifest instanceof ArrayBuffer) {
moduleData = manifest;
} else if ((manifest as Manifest).wasm) {
const wasmData = (manifest as Manifest).wasm;

View File

@@ -5,7 +5,7 @@ import { WASI, Fd } from '@bjorn3/browser_wasi_shim';
export type ExtismFunction = any;
export class ExtismPlugin {
moduleData: ArrayBuffer | WebAssembly.Module;
moduleData: ArrayBuffer;
allocator: Allocator;
config?: PluginConfig;
vars: Record<string, Uint8Array>;
@@ -14,7 +14,7 @@ export class ExtismPlugin {
module?: WebAssembly.WebAssemblyInstantiatedSource;
functions: Record<string, ExtismFunction>;
constructor(moduleData: ArrayBuffer | WebAssembly.Module, functions: Record<string, ExtismFunction> = {}, config?: PluginConfig) {
constructor(moduleData: ArrayBuffer, functions: Record<string, ExtismFunction> = {}, config?: PluginConfig) {
this.moduleData = moduleData;
this.allocator = new Allocator(1024 * 1024);
this.config = config;
@@ -78,25 +78,11 @@ export class ExtismPlugin {
wasi_snapshot_preview1: wasi.wasiImport,
env: environment,
};
if (this.moduleData instanceof WebAssembly.Module) {
const instance = new WebAssembly.Instance(this.moduleData, env);
this.module = {
instance,
module: this.moduleData
}
//@ts-ignore
wasi.inst = instance;
}
else {
this.module = await WebAssembly.instantiate(this.moduleData, env);
//@ts-ignore
wasi.inst = this.module.instance;
}
if (!this.module) throw Error("Unable to instantiate module");
this.module = await WebAssembly.instantiate(this.moduleData, env);
// normally we would call wasi.start here but it doesn't respect when there is
// no _start function
//@ts-ignore
wasi.inst = this.module.instance;
if (this.module.instance.exports._start) {
//@ts-ignore
this.module.instance.exports._start();

View File

@@ -24,8 +24,6 @@ void hello_world(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
outputs[0].v.i64 = inputs[0].v.i64;
}
void free_data(void *x) { puts("Freeing userdata"); }
uint8_t *read_file(const char *filename, size_t *len) {
FILE *fp = fopen(filename, "rb");
@@ -55,31 +53,30 @@ int main(int argc, char *argv[]) {
exit(1);
}
ExtismContext *ctx = extism_context_new();
size_t len = 0;
uint8_t *data = read_file("../wasm/code-functions.wasm", &len);
ExtismValType inputs[] = {PTR};
ExtismValType outputs[] = {PTR};
ExtismFunction *f =
extism_function_new("hello_world", inputs, 1, outputs, 1, hello_world,
"Hello, again!", free_data);
char *errmsg = NULL;
ExtismPlugin *plugin = extism_plugin_new(
data, len, (const ExtismFunction **)&f, 1, true, &errmsg);
ExtismValType inputs[] = {I64};
ExtismValType outputs[] = {I64};
ExtismFunction *f = extism_function_new("hello_world", inputs, 1, outputs, 1,
hello_world, "Hello, again!", NULL);
ExtismPlugin plugin =
extism_plugin_new(ctx, data, len, (const ExtismFunction **)&f, 1, true);
free(data);
if (plugin == NULL) {
puts(errmsg);
extism_plugin_new_error_free(errmsg);
if (plugin < 0) {
puts(extism_error(ctx, -1));
exit(1);
}
assert(extism_plugin_call(plugin, "count_vowels", (uint8_t *)argv[1],
assert(extism_plugin_call(ctx, plugin, "count_vowels", (uint8_t *)argv[1],
strlen(argv[1])) == 0);
ExtismSize out_len = extism_plugin_output_length(plugin);
const uint8_t *output = extism_plugin_output_data(plugin);
ExtismSize out_len = extism_plugin_output_length(ctx, plugin);
const uint8_t *output = extism_plugin_output_data(ctx, plugin);
write(STDOUT_FILENO, output, out_len);
write(STDOUT_FILENO, "\n", 1);
extism_plugin_free(plugin);
extism_plugin_free(ctx, plugin);
extism_function_free(f);
extism_context_free(ctx);
return 0;
}

24
composer.lock generated
View File

@@ -12,12 +12,12 @@
"source": {
"type": "git",
"url": "https://github.com/ircmaxell/FFIMe.git",
"reference": "431a3c13d9906b974d50b13bf8295097ea000c5e"
"reference": "5f648f95ecf23262a2e58f4e4c9001bd1b5f9c98"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ircmaxell/FFIMe/zipball/431a3c13d9906b974d50b13bf8295097ea000c5e",
"reference": "431a3c13d9906b974d50b13bf8295097ea000c5e",
"url": "https://api.github.com/repos/ircmaxell/FFIMe/zipball/5f648f95ecf23262a2e58f4e4c9001bd1b5f9c98",
"reference": "5f648f95ecf23262a2e58f4e4c9001bd1b5f9c98",
"shasum": ""
},
"require": {
@@ -50,7 +50,7 @@
"issues": "https://github.com/ircmaxell/FFIMe/issues",
"source": "https://github.com/ircmaxell/FFIMe/tree/master"
},
"time": "2023-04-03T00:43:12+00:00"
"time": "2022-09-01T18:56:19+00:00"
},
{
"name": "ircmaxell/php-c-parser",
@@ -58,12 +58,12 @@
"source": {
"type": "git",
"url": "https://github.com/ircmaxell/php-c-parser.git",
"reference": "29e0223704e4ee00c66f43506f5f52db151b3517"
"reference": "55e0a4fdf88d6e955d928860e1e107a68492c1cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ircmaxell/php-c-parser/zipball/29e0223704e4ee00c66f43506f5f52db151b3517",
"reference": "29e0223704e4ee00c66f43506f5f52db151b3517",
"url": "https://api.github.com/repos/ircmaxell/php-c-parser/zipball/55e0a4fdf88d6e955d928860e1e107a68492c1cf",
"reference": "55e0a4fdf88d6e955d928860e1e107a68492c1cf",
"shasum": ""
},
"require": {
@@ -99,7 +99,7 @@
"issues": "https://github.com/ircmaxell/php-c-parser/issues",
"source": "https://github.com/ircmaxell/php-c-parser/tree/master"
},
"time": "2023-03-23T10:58:24+00:00"
"time": "2022-08-27T17:37:14+00:00"
},
{
"name": "ircmaxell/php-object-symbolresolver",
@@ -107,12 +107,12 @@
"source": {
"type": "git",
"url": "https://github.com/ircmaxell/php-object-symbolresolver.git",
"reference": "dfe1b1aa6c15b198bdef50fff8485e98e89f2a09"
"reference": "3734df2b22d7c8273ee6f6f2155fddde6056d057"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ircmaxell/php-object-symbolresolver/zipball/dfe1b1aa6c15b198bdef50fff8485e98e89f2a09",
"reference": "dfe1b1aa6c15b198bdef50fff8485e98e89f2a09",
"url": "https://api.github.com/repos/ircmaxell/php-object-symbolresolver/zipball/3734df2b22d7c8273ee6f6f2155fddde6056d057",
"reference": "3734df2b22d7c8273ee6f6f2155fddde6056d057",
"shasum": ""
},
"require": {
@@ -144,7 +144,7 @@
"issues": "https://github.com/ircmaxell/php-object-symbolresolver/issues",
"source": "https://github.com/ircmaxell/php-object-symbolresolver/tree/master"
},
"time": "2022-09-15T18:21:50+00:00"
"time": "2022-08-14T19:30:20+00:00"
}
],
"packages-dev": [],

View File

@@ -1,26 +0,0 @@
[package]
name = "extism-convert"
version = "0.2.0"
edition = "2021"
authors = ["The Extism Authors", "oss@extism.org"]
license = "BSD-3-Clause"
readme = "./README.md"
homepage = "https://extism.org"
repository = "https://github.com/extism/extism"
description = "Traits to make Rust types usable with Extism"
[dependencies]
anyhow = "1.0.75"
base64 = "~0.21"
prost = { version = "0.12.0", optional = true }
rmp-serde = { version = "1.1.2", optional = true }
serde = "1.0.186"
serde_json = "1.0.105"
[dev-dependencies]
serde = { version = "1.0.186", features = ["derive"] }
[features]
default = ["msgpack", "protobuf"]
msgpack = ["rmp-serde"]
protobuf = ["prost"]

View File

@@ -1,12 +0,0 @@
# extism-convert
The [extism-convert](https://crates.io/crates/extism-convert) crate is used by the [Rust SDK](https://crates.io/crates/extism) and [Rust PDK](https://crates.io/crates/extism-pdk) to provide a shared interface for
encoding and decoding values that can be passed to Extism function calls.
A set of types (Json, Msgpack, Protobuf) that can be used to specify a serde encoding are also provided. These are
similar to [axum extractors](https://docs.rs/axum/latest/axum/extract/index.html#intro) - they are
implemented as a tuple struct with a single field that is meant to be extracted using pattern matching.
## Documentation
See [extism-convert on docs.rs](https://docs.rs/extism-convert/latest/extism_convert/) for in-depth documentation.

View File

@@ -1,118 +0,0 @@
use crate::*;
use base64::Engine;
/// The `encoding` macro can be used to create newtypes that implement a particular encoding for the
/// inner value.
///
/// For example, the following line creates a new JSON encoding using serde_json:
///
/// ```
/// extism_convert::encoding!(MyJson, serde_json::to_vec, serde_json::from_slice);
/// ```
///
/// This will create a struct `struct MyJson<T>(pub T)` and implement `ToBytes` using `serde_json::to_vec`
/// and `FromBytesOwned` using `serde_json::from_vec`
#[macro_export]
macro_rules! encoding {
($name:ident, $to_vec:expr, $from_slice:expr) => {
#[doc = concat!(stringify!($name), " encoding")]
pub struct $name<T>(pub T);
impl<T> $name<T> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<T: serde::de::DeserializeOwned> $crate::FromBytesOwned for $name<T> {
fn from_bytes_owned(data: &[u8]) -> std::result::Result<Self, $crate::Error> {
let x = $from_slice(data)?;
std::result::Result::Ok($name(x))
}
}
impl<'a, T: serde::Serialize> $crate::ToBytes<'a> for $name<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> std::result::Result<Self::Bytes, $crate::Error> {
let enc = $to_vec(&self.0)?;
std::result::Result::Ok(enc)
}
}
};
}
encoding!(Json, serde_json::to_vec, serde_json::from_slice);
#[cfg(feature = "msgpack")]
encoding!(Msgpack, rmp_serde::to_vec, rmp_serde::from_slice);
impl<'a> ToBytes<'a> for serde_json::Value {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(serde_json::to_vec(self)?)
}
}
impl FromBytesOwned for serde_json::Value {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(serde_json::from_slice(data)?)
}
}
/// Base64 conversion
///
/// When using `Base64` with `ToBytes` any type that implement `AsRef<[T]>` may be used as the inner value,
/// but only `Base64<String>` and `Base64<Vec>` may be used with `FromBytes`
///
/// A value wrapped in `Base64` will automatically be encoded/decoded using base64, the inner value should not
/// already be base64 encoded.
pub struct Base64<T: AsRef<[u8]>>(pub T);
impl<'a, T: AsRef<[u8]>> ToBytes<'a> for Base64<T> {
type Bytes = String;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(base64::engine::general_purpose::STANDARD.encode(&self.0))
}
}
impl FromBytesOwned for Base64<Vec<u8>> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Base64(
base64::engine::general_purpose::STANDARD.decode(data)?,
))
}
}
impl FromBytesOwned for Base64<String> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Base64(String::from_utf8(
base64::engine::general_purpose::STANDARD.decode(data)?,
)?))
}
}
/// Protobuf encoding
///
/// Allows for `prost` Protobuf messages to be used as arguments to Extism plugin calls
#[cfg(feature = "protobuf")]
pub struct Protobuf<T: prost::Message>(pub T);
#[cfg(feature = "protobuf")]
impl<'a, T: prost::Message> ToBytes<'a> for Protobuf<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.0.encode_to_vec())
}
}
#[cfg(feature = "protobuf")]
impl<T: Default + prost::Message> FromBytesOwned for Protobuf<T> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Protobuf(T::decode(data)?))
}
}

View File

@@ -1,100 +0,0 @@
use crate::*;
/// `FromBytes` is used to define how a type should be decoded when working with
/// Extism memory. It is used for plugin output and host function input.
pub trait FromBytes<'a>: Sized {
/// Decode a value from a slice of bytes
fn from_bytes(data: &'a [u8]) -> Result<Self, Error>;
}
/// `FromBytesOwned` is similar to `FromBytes` but it doesn't borrow from the input slice.
/// `FromBytes` is automatically implemented for all types that implement `FromBytesOwned`
pub trait FromBytesOwned: Sized {
/// Decode a value from a slice of bytes, the resulting value should not borrow the input
/// data.
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error>;
}
impl<'a> FromBytes<'a> for &'a [u8] {
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
Ok(data)
}
}
impl<'a> FromBytes<'a> for &'a str {
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
Ok(std::str::from_utf8(data)?)
}
}
impl<'a, T: FromBytesOwned> FromBytes<'a> for T {
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
T::from_bytes_owned(data)
}
}
impl FromBytesOwned for Box<[u8]> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(data.to_vec().into_boxed_slice())
}
}
impl FromBytesOwned for Vec<u8> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(data.to_vec())
}
}
impl FromBytesOwned for String {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(std::str::from_utf8(data)?.to_string())
}
}
impl FromBytesOwned for f64 {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Self::from_le_bytes(data.try_into()?))
}
}
impl FromBytesOwned for f32 {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Self::from_le_bytes(data.try_into()?))
}
}
impl FromBytesOwned for i64 {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Self::from_le_bytes(data.try_into()?))
}
}
impl FromBytesOwned for i32 {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Self::from_le_bytes(data.try_into()?))
}
}
impl FromBytesOwned for u64 {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Self::from_le_bytes(data.try_into()?))
}
}
impl FromBytesOwned for u32 {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Self::from_le_bytes(data.try_into()?))
}
}
impl FromBytesOwned for () {
fn from_bytes_owned(_: &[u8]) -> Result<Self, Error> {
Ok(())
}
}
impl<'a, T: FromBytes<'a>> FromBytes<'a> for std::io::Cursor<T> {
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
Ok(std::io::Cursor::new(T::from_bytes(data)?))
}
}

View File

@@ -1,29 +0,0 @@
//! The [extism-convert](https://crates.io/crates/extism-convert) crate is used by the [Rust SDK](https://crates.io/crates/extism) and [Rust PDK](https://crates.io/crates/extism-pdk) to provide a shared interface for
//! encoding and decoding values that can be passed to Extism function calls.
//!
//! A set of types (Json, Msgpack) that can be used to specify a serde encoding are also provided. These are
//! similar to [axum extractors](https://docs.rs/axum/latest/axum/extract/index.html#intro) - they are
//! implemented as a tuple struct with a single field that is meant to be extracted using pattern matching.
pub use anyhow::Error;
mod encoding;
mod from_bytes;
mod memory_handle;
mod to_bytes;
pub use encoding::{Base64, Json};
#[cfg(feature = "msgpack")]
pub use encoding::Msgpack;
#[cfg(feature = "protobuf")]
pub use encoding::Protobuf;
pub use from_bytes::{FromBytes, FromBytesOwned};
pub use memory_handle::MemoryHandle;
pub use to_bytes::ToBytes;
#[cfg(test)]
mod tests;

View File

@@ -1,42 +0,0 @@
/// `MemoryHandle` describes where in memory a block of data is stored
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub struct MemoryHandle {
/// The offset of the region in Extism linear memory
pub offset: u64,
/// The length of the memory region
pub length: u64,
}
impl MemoryHandle {
/// Create a new `MemoryHandle` from an offset in memory and length
///
/// # Safety
/// This function is unsafe because the specified memory region may not be valid.
pub unsafe fn new(offset: u64, length: u64) -> MemoryHandle {
MemoryHandle { offset, length }
}
/// `NULL` equivalent
pub fn null() -> MemoryHandle {
MemoryHandle {
offset: 0,
length: 0,
}
}
/// Get the offset of a memory handle
pub fn offset(&self) -> u64 {
self.offset
}
/// Get the length of the memory region
pub fn len(&self) -> usize {
self.length as usize
}
/// Returns `true` when the length is 0
pub fn is_empty(&self) -> bool {
self.length == 0
}
}

View File

@@ -1,39 +0,0 @@
use crate::*;
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
struct Testing {
a: String,
b: i64,
c: f32,
}
#[test]
fn roundtrip_json() {
let x = Testing {
a: "foobar".to_string(),
b: 123,
c: 456.7,
};
let bytes = Json(&x).to_bytes().unwrap();
let Json(y): Json<Testing> = FromBytes::from_bytes(&bytes).unwrap();
assert_eq!(x, y);
}
#[test]
fn roundtrip_msgpack() {
let x = Testing {
a: "foobar".to_string(),
b: 123,
c: 456.7,
};
let bytes = Msgpack(&x).to_bytes().unwrap();
let Msgpack(y): Msgpack<Testing> = FromBytes::from_bytes(&bytes).unwrap();
assert_eq!(x, y);
}
#[test]
fn roundtrip_base64() {
let bytes = Base64("this is a test").to_bytes().unwrap();
let Base64(s): Base64<String> = FromBytes::from_bytes(bytes.as_bytes()).unwrap();
assert_eq!(s, "this is a test");
}

View File

@@ -1,102 +0,0 @@
use crate::*;
/// `ToBytes` is used to define how a type should be encoded when working with
/// Extism memory. It is used for plugin input and host function output.
pub trait ToBytes<'a> {
/// A configurable byte slice representation, allows any type that implements `AsRef<[u8]>`
type Bytes: AsRef<[u8]>;
/// `to_bytes` converts a value into `Self::Bytes`
fn to_bytes(&self) -> Result<Self::Bytes, Error>;
}
impl<'a> ToBytes<'a> for () {
type Bytes = [u8; 0];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok([])
}
}
impl<'a> ToBytes<'a> for Vec<u8> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.clone())
}
}
impl<'a> ToBytes<'a> for String {
type Bytes = String;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.clone())
}
}
impl<'a> ToBytes<'a> for &'a [u8] {
type Bytes = &'a [u8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self)
}
}
impl<'a> ToBytes<'a> for &'a str {
type Bytes = &'a str;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self)
}
}
impl<'a> ToBytes<'a> for f64 {
type Bytes = [u8; 8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.to_le_bytes())
}
}
impl<'a> ToBytes<'a> for f32 {
type Bytes = [u8; 4];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.to_le_bytes())
}
}
impl<'a> ToBytes<'a> for i64 {
type Bytes = [u8; 8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.to_le_bytes())
}
}
impl<'a> ToBytes<'a> for i32 {
type Bytes = [u8; 4];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.to_le_bytes())
}
}
impl<'a> ToBytes<'a> for u64 {
type Bytes = [u8; 8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.to_le_bytes())
}
}
impl<'a> ToBytes<'a> for u32 {
type Bytes = [u8; 4];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.to_le_bytes())
}
}
impl<'a, T: ToBytes<'a>> ToBytes<'a> for &'a T {
type Bytes = T::Bytes;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
<T as ToBytes>::to_bytes(self)
}
}

View File

@@ -221,7 +221,6 @@ public:
typedef ExtismValType ValType;
typedef ExtismValUnion ValUnion;
typedef ExtismVal Val;
typedef uint64_t MemoryHandle;
class CurrentPlugin {
ExtismCurrentPlugin *pointer;
@@ -230,18 +229,16 @@ public:
CurrentPlugin(ExtismCurrentPlugin *p) : pointer(p) {}
uint8_t *memory() { return extism_current_plugin_memory(this->pointer); }
uint8_t *memory(MemoryHandle offs) { return this->memory() + offs; }
ExtismSize memoryLength(MemoryHandle offs) {
ExtismSize memory_length(uint64_t offs) {
return extism_current_plugin_memory_length(this->pointer, offs);
}
MemoryHandle alloc(ExtismSize size) {
uint64_t alloc(ExtismSize size) {
return extism_current_plugin_memory_alloc(this->pointer, size);
}
void free(MemoryHandle handle) {
extism_current_plugin_memory_free(this->pointer, handle);
void free(uint64_t offs) {
extism_current_plugin_memory_free(this->pointer, offs);
}
void returnString(Val &output, const std::string &s) {
@@ -259,7 +256,7 @@ public:
return nullptr;
}
if (length != nullptr) {
*length = this->memoryLength(inp.v.i64);
*length = this->memory_length(inp.v.i64);
}
return this->memory() + inp.v.i64;
}
@@ -321,7 +318,7 @@ public:
this->func = std::shared_ptr<ExtismFunction>(ptr, extism_function_free);
}
void setNamespace(std::string s) {
void set_namespace(std::string s) {
extism_function_set_namespace(this->func.get(), s.c_str());
}
@@ -339,51 +336,111 @@ public:
};
class Plugin {
std::shared_ptr<ExtismContext> context;
ExtismPlugin plugin;
std::vector<Function> functions;
public:
ExtismPlugin *plugin;
// Create a new plugin
Plugin(const uint8_t *wasm, ExtismSize length, bool with_wasi = false,
std::vector<Function> functions = std::vector<Function>())
std::vector<Function> functions = std::vector<Function>(),
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
extism_context_new(), extism_context_free))
: functions(functions) {
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
char *errmsg = nullptr;
this->plugin = extism_plugin_new(wasm, length, ptrs.data(), ptrs.size(),
with_wasi, &errmsg);
if (this->plugin == nullptr) {
std::string s(errmsg);
extism_plugin_new_error_free(errmsg);
throw Error(s);
this->plugin = extism_plugin_new(ctx.get(), wasm, length, ptrs.data(),
ptrs.size(), with_wasi);
if (this->plugin < 0) {
const char *err = extism_error(ctx.get(), -1);
throw Error(err == nullptr ? "Unable to load plugin" : err);
}
this->context = ctx;
}
Plugin(const std::string &str, bool with_wasi = false,
std::vector<Function> functions = {})
: Plugin((const uint8_t *)str.c_str(), str.size(), with_wasi, functions) {
}
std::vector<Function> functions = {},
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
extism_context_new(), extism_context_free))
: Plugin((const uint8_t *)str.c_str(), str.size(), with_wasi, functions,
ctx) {}
Plugin(const std::vector<uint8_t> &data, bool with_wasi = false,
std::vector<Function> functions = {})
: Plugin(data.data(), data.size(), with_wasi, functions) {}
std::vector<Function> functions = {},
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
extism_context_new(), extism_context_free))
: Plugin(data.data(), data.size(), with_wasi, functions, ctx) {}
CancelHandle cancelHandle() {
return CancelHandle(extism_plugin_cancel_handle(this->plugin));
CancelHandle cancel_handle() {
return CancelHandle(
extism_plugin_cancel_handle(this->context.get(), this->id()));
}
#ifndef EXTISM_NO_JSON
// Create a new plugin from Manifest
Plugin(const Manifest &manifest, bool with_wasi = false,
std::vector<Function> functions = {})
: Plugin(manifest.json().c_str(), with_wasi, functions) {}
std::vector<Function> functions = {},
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
extism_context_new(), extism_context_free)) {
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
auto buffer = manifest.json();
this->plugin =
extism_plugin_new(ctx.get(), (const uint8_t *)buffer.c_str(),
buffer.size(), ptrs.data(), ptrs.size(), with_wasi);
if (this->plugin < 0) {
const char *err = extism_error(ctx.get(), -1);
throw Error(err == nullptr ? "Unable to load plugin from manifest" : err);
}
this->context = ctx;
}
#endif
~Plugin() {
extism_plugin_free(this->plugin);
this->plugin = nullptr;
extism_plugin_free(this->context.get(), this->plugin);
this->plugin = -1;
}
ExtismPlugin id() const { return this->plugin; }
ExtismContext *get_context() const { return this->context.get(); }
void update(const uint8_t *wasm, size_t length, bool with_wasi = false,
std::vector<Function> functions = {}) {
this->functions = functions;
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
bool b = extism_plugin_update(this->context.get(), this->plugin, wasm,
length, ptrs.data(), ptrs.size(), with_wasi);
if (!b) {
const char *err = extism_error(this->context.get(), -1);
throw Error(err == nullptr ? "Unable to update plugin" : err);
}
}
#ifndef EXTISM_NO_JSON
void update(const Manifest &manifest, bool with_wasi = false,
std::vector<Function> functions = {}) {
this->functions = functions;
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
auto buffer = manifest.json();
bool b = extism_plugin_update(
this->context.get(), this->plugin, (const uint8_t *)buffer.c_str(),
buffer.size(), ptrs.data(), ptrs.size(), with_wasi);
if (!b) {
const char *err = extism_error(this->context.get(), -1);
throw Error(err == nullptr ? "Unable to update plugin" : err);
}
}
void config(const Config &data) {
@@ -400,9 +457,10 @@ public:
#endif
void config(const char *json, size_t length) {
bool b = extism_plugin_config(this->plugin, (const uint8_t *)json, length);
bool b = extism_plugin_config(this->context.get(), this->plugin,
(const uint8_t *)json, length);
if (!b) {
const char *err = extism_plugin_error(this->plugin);
const char *err = extism_error(this->context.get(), this->plugin);
throw Error(err == nullptr ? "Unable to update plugin config" : err);
}
}
@@ -414,10 +472,10 @@ public:
// Call a plugin
Buffer call(const std::string &func, const uint8_t *input,
ExtismSize input_length) const {
int32_t rc =
extism_plugin_call(this->plugin, func.c_str(), input, input_length);
int32_t rc = extism_plugin_call(this->context.get(), this->plugin,
func.c_str(), input, input_length);
if (rc != 0) {
const char *error = extism_plugin_error(this->plugin);
const char *error = extism_error(this->context.get(), this->plugin);
if (error == nullptr) {
throw Error("extism_call failed");
}
@@ -425,8 +483,10 @@ public:
throw Error(error);
}
ExtismSize length = extism_plugin_output_length(this->plugin);
const uint8_t *ptr = extism_plugin_output_data(this->plugin);
ExtismSize length =
extism_plugin_output_length(this->context.get(), this->plugin);
const uint8_t *ptr =
extism_plugin_output_data(this->context.get(), this->plugin);
return Buffer(ptr, length);
}
@@ -443,13 +503,56 @@ public:
}
// Returns true if the specified function exists
bool functionExists(const std::string &func) const {
return extism_plugin_function_exists(this->plugin, func.c_str());
bool function_exists(const std::string &func) const {
return extism_plugin_function_exists(this->context.get(), this->plugin,
func.c_str());
}
};
class Context {
public:
std::shared_ptr<ExtismContext> pointer;
// Create a new context;
Context() {
this->pointer = std::shared_ptr<ExtismContext>(extism_context_new(),
extism_context_free);
}
// Create plugin from uint8_t*
Plugin plugin(const uint8_t *wasm, size_t length, bool with_wasi = false,
std::vector<Function> functions = {}) const {
return Plugin(wasm, length, with_wasi, functions, this->pointer);
}
// Create plugin from std::string
Plugin plugin(const std::string &str, bool with_wasi = false,
std::vector<Function> functions = {}) const {
return Plugin((const uint8_t *)str.c_str(), str.size(), with_wasi,
functions, this->pointer);
}
// Create plugin from uint8_t vector
Plugin plugin(const std::vector<uint8_t> &data, bool with_wasi = false,
std::vector<Function> functions = {}) const {
return Plugin(data.data(), data.size(), with_wasi, functions,
this->pointer);
}
#ifndef EXTISM_NO_JSON
// Create plugin from Manifest
Plugin plugin(const Manifest &manifest, bool with_wasi = false,
std::vector<Function> functions = {}) const {
return Plugin(manifest, with_wasi, functions, this->pointer);
}
#endif
// Remove all plugins
void reset() { extism_context_reset(this->pointer.get()); }
};
// Set global log file for plugins
inline bool setLogFile(const char *filename, const char *level) {
inline bool set_log_file(const char *filename, const char *level) {
return extism_log_file(filename, level);
}

View File

@@ -1,7 +1,6 @@
#include "../extism.hpp"
#include <fstream>
#include <thread>
#include <gtest/gtest.h>
@@ -16,10 +15,16 @@ const std::string code = "../../wasm/code.wasm";
namespace {
using namespace extism;
TEST(Context, Basic) {
Context context;
ASSERT_NE(context.pointer, nullptr);
}
TEST(Plugin, Manifest) {
Manifest manifest = Manifest::path(code);
manifest.set_config("a", "1");
ASSERT_NO_THROW(Plugin plugin(manifest));
Plugin plugin(manifest);
Buffer buf = plugin.call("count_vowels", "this is a test");
@@ -32,17 +37,19 @@ TEST(Plugin, BadManifest) {
}
TEST(Plugin, Bytes) {
Context context;
auto wasm = read(code.c_str());
ASSERT_NO_THROW(Plugin plugin(wasm));
Plugin plugin(wasm);
ASSERT_NO_THROW(Plugin plugin = context.plugin(wasm));
Plugin plugin = context.plugin(wasm);
Buffer buf = plugin.call("count_vowels", "this is another test");
ASSERT_EQ(buf.string(), "{\"count\": 6}");
}
TEST(Plugin, UpdateConfig) {
Context context;
auto wasm = read(code.c_str());
Plugin plugin(wasm);
Plugin plugin = context.plugin(wasm);
Config config;
config["abc"] = "123";
@@ -50,11 +57,12 @@ TEST(Plugin, UpdateConfig) {
}
TEST(Plugin, FunctionExists) {
Context context;
auto wasm = read(code.c_str());
Plugin plugin(wasm);
Plugin plugin = context.plugin(wasm);
ASSERT_FALSE(plugin.functionExists("bad_function"));
ASSERT_TRUE(plugin.functionExists("count_vowels"));
ASSERT_FALSE(plugin.function_exists("bad_function"));
ASSERT_TRUE(plugin.function_exists("count_vowels"));
}
TEST(Plugin, HostFunction) {
@@ -77,38 +85,6 @@ TEST(Plugin, HostFunction) {
ASSERT_EQ((std::string)buf, "test");
}
void callThread(Plugin *plugin) {
auto buf = plugin->call("count_vowels", "aaa").string();
ASSERT_EQ(buf.size(), 10);
ASSERT_EQ(buf, "testing123");
}
TEST(Plugin, MultipleThreads) {
auto wasm = read("../../wasm/code-functions.wasm");
auto t = std::vector<ValType>{ValType::I64};
Function hello_world =
Function("hello_world", t, t,
[](CurrentPlugin plugin, const std::vector<Val> &params,
std::vector<Val> &results, void *user_data) {
auto offs = plugin.alloc(10);
memcpy(plugin.memory() + offs, "testing123", 10);
results[0].v.i64 = (int64_t)offs;
});
auto functions = std::vector<Function>{
hello_world,
};
Plugin plugin(wasm, true, functions);
std::vector<std::thread> threads;
for (int i = 0; i < 3; i++) {
threads.push_back(std::thread(callThread, &plugin));
}
for (auto &th : threads) {
th.join();
}
}
}; // namespace
int main(int argc, char **argv) {

7
d/.gitignore vendored
View File

@@ -1,7 +0,0 @@
# Generated by host system's C preprocessor
# See https://dlang.org/spec/importc.html#manual-cpp
runtime.i
docs.json
extism-test-*
*.lst

View File

@@ -1,4 +0,0 @@
# D Host SDK
Development of this library has moved to [this repo](https://github.com/extism/d-sdk#readme).

View File

@@ -1,17 +0,0 @@
.dub
docs.json
__dummy.html
docs/
/extism_hello
/hello
hello.so
hello.dylib
hello.dll
hello.a
hello.lib
hello-test-*
*.exe
*.pdb
*.o
*.obj
*.lst

View File

@@ -1,15 +0,0 @@
{
"name": "hello",
"description": "A minimal Extism usage example",
"license": "BSD-3-Clause",
"authors": [
"Chance Snow <git@chancesnow.me>",
"Extism contributors"
],
"copyright": "Copyright © 2023, Extism contributors",
"dependencies": {
"extism": {
"path": "../../../"
}
}
}

View File

@@ -1,33 +0,0 @@
import std.conv: castFrom, to;
import std.file;
import std.functional: toDelegate;
import std.stdio;
import std.string: representation;
import std.typecons : Yes;
import extism;
void main() {
auto wasm = cast(ubyte[]) read("wasm/code-functions.wasm");
// FIXME: Creating the plugin results in EXC_BAD_ACCESS (segfault?) from `extism_plugin_new`
auto plugin = new Plugin(wasm, [
Function!string(
"hello_world", /* Inputs */ [ValType.i64], /* Outputs */ [ValType.i64], toDelegate(&helloWorld), "Hello, again!"
),
], Yes.withWasi);
auto input = "aeiou";
plugin.call("count_vowels", cast(ubyte[]) input.representation);
writeln(plugin.outputData);
}
///
void helloWorld(CurrentPlugin plugin, const Val[] inputs, Val[] outputs, void *data) {
writeln("Hello from D!");
writeln(data);
ExtismSize ptr_offs = inputs[0].v.i64;
auto buf = plugin.memory(ptr_offs);
writeln(buf);
outputs[0].v.i64 = inputs[0].v.i64;
}

View File

@@ -1,258 +0,0 @@
module extism;
import std.conv: castFrom, to;
import std.meta: Alias;
import std.string: fromStringz, toStringz;
import runtime;
/// A list of all possible value types in WebAssembly.
enum ValType {
i32 = I32,
i64 = I64,
f32 = F32,
f64 = F64,
v128 = V128,
funcRef = FuncRef,
externRef = ExternRef,
}
// Opaque Pointers
///
alias CancelHandle = Alias!(void*);
///
alias CurrentPlugin = Alias!(void*);
///
alias ExtismSize = ulong;
/// A union type for host function argument/return values.
union ValUnion {
///
int i32;
///
long i64;
///
float f32;
///
double f64;
}
/// Holds the type and value of a function argument/return.
struct Val {
///
ValType t;
///
ValUnion v;
}
/// Host function signature.
///
/// Used to register host functions with plugins.
/// Params:
/// plugin: the currently executing plugin from within a host function
/// inputs: argument values
/// outputs: return values
/// data: user data associated with the host function
alias FunctionType = void delegate(
CurrentPlugin plugin, const Val[] inputs, Val[] outputs, void* data
);
/// Returns: A slice of an allocated memory block of the currently running plugin.
ubyte[] memory(CurrentPlugin plugin, ulong n) {
auto length = extism_current_plugin_memory_length(cast(ExtismCurrentPlugin*) plugin, n);
return extism_current_plugin_memory(cast(ExtismCurrentPlugin*) plugin)[n .. (n + length)];
}
/// Allocate a memory block in the currently running plugin.
ulong memoryAlloc(CurrentPlugin plugin, ulong n) {
return extism_current_plugin_memory_alloc(cast(ExtismCurrentPlugin*) plugin, n);
}
/// Free an allocated memory block.
void memoryFree(CurrentPlugin plugin, ulong ptr) {
extism_current_plugin_memory_free(cast(ExtismCurrentPlugin*) plugin, ptr);
}
/// A host function, where `T` is the type of its user data.
struct Function(T) {
/// Managed pointer.
ExtismFunction* func;
alias func this;
private string _namespace = null;
/// Create a new host function.
/// Params:
/// name: function name, this should be valid UTF-8
/// inputs: argument types
/// outputs: return types
/// func: the function to call
/// userData: a pointer that will be passed to the function when it's called. This value should live as long as the function exists.
/// freeUserData: a callback to release the `userData`` value when the resulting `Function` is freed.
this(
string name, const ValType[] inputs, const ValType[] outputs, FunctionType func,
T userData, void function(T userData) freeUserData = null
) {
import std.functional: toDelegate;
// Bind the given host function with C linkage
auto funcClosure = ((
ExtismCurrentPlugin* plugin,
const ExtismVal* inputs, ulong numInputs, ExtismVal* outputs, ulong numOutputs,
void* data
) {
func(plugin, (cast(const Val*) inputs)[0 .. numInputs], (cast(Val*) outputs)[0 .. numOutputs], data);
}).toDelegate.bindDelegate;
// Bind the `freeUserData` function with C linkage
auto freeUserDataHandler = freeUserData is null ? null : ((void* userDataPtr) {
if (userDataPtr is null) return;
freeUserData((&userDataPtr).to!T);
}).toDelegate.bindDelegate;
this.func = extism_function_new(
name.toStringz,// See https://dlang.org/spec/importc.html#enums
// See https://forum.dlang.org/post/qmidcpaxctbuphcyvkdc@forum.dlang.org
castFrom!(const(ValType)*)
.to!(typeof(ExtismVal.t)*)(inputs.ptr), inputs.length,
castFrom!(const(ValType)*).to!(typeof(ExtismVal.t)*)(outputs.ptr), outputs.length,
funcClosure,
&userData,
freeUserDataHandler,
);
}
~this() {
extism_function_free(func);
}
/// Get the namespace of this function.
string namespace() {
return this._namespace;
}
/// Set the namespace of this function.
void namespace(string value) {
this._namespace = value;
extism_function_set_namespace(func, value.toStringz);
}
}
///
struct Plugin {
import std.uuid: UUID;
/// Managed pointer.
ExtismPlugin* plugin;
alias plugin this;
/// Create a new plugin.
/// Params:
/// wasm: is a WASM module (wat or wasm) or a JSON encoded manifest
/// functions: is an array of ExtismFunction*
/// withWasi: enables/disables WASI
/// Throws: `Exception` if the plugin could not be created.
this(const ubyte[] wasm, const(ExtismFunction)*[] functions, bool withWasi = false) {
char* errorMsgPtr = null;
plugin = extism_plugin_new(
wasm.ptr, wasm.length, cast(ExtismFunction**) functions.ptr, functions.length, withWasi, &errorMsgPtr
);
// See https://github.com/extism/extism/blob/ddcbeec3debe787293a9957c8be88f80a64b7c22/c/main.c#L67
// Instead of terminating the host process, throw.
if (plugin !is null)
return;
auto errorMsg = errorMsgPtr.fromStringz.idup;
extism_plugin_new_error_free(errorMsgPtr);
// TODO: Subclass `Exception` for better error handling
throw new Exception(errorMsg);
}
~this() {
extism_plugin_free(plugin);
}
/// Get a plugin's ID.
UUID id() {
return UUID(extism_plugin_id(plugin)[0 .. 16]);
}
/// Get the error associated with this `Plugin`.
string error() {
return extism_error(plugin).fromStringz.idup;
}
/// Update plugin config values, this will merge with the existing values.
bool config(ubyte[] json) {
return extism_plugin_config(plugin, json.ptr, json.length);
}
/// See_Also: `cancel`
const(CancelHandle) cancelHandle() {
return extism_plugin_cancel_handle(plugin);
}
/// Cancel a running plugin.
/// See_Also: `cancelHandle`
bool cancel(const CancelHandle handle) {
return extism_plugin_cancel(cast(ExtismCancelHandle*) handle);
}
/// Returns: Whether a function with `name` exists.
bool functionExists(string name) {
return extism_plugin_function_exists(plugin, name.toStringz);
}
/// Call a function.
/// Params:
/// funcName: is the function to call
/// data: is the input data
void call(string funcName, ubyte[] data = null) {
// Returns `0` if the call was successful, otherwise `-1`.
if (extism_plugin_call(plugin, funcName.toStringz, data.ptr, data.length) != 0)
throw new Exception(this.error);
}
/// Get the plugin's output data.
///
/// Note: This copies the data into a managed buffer.
/// Remarks: Use `outputData.length` to retrieve size of plugin output.
ubyte[] outputData() {
import std.algorithm: copy;
auto outputLength = extism_plugin_output_length(plugin);
auto outputData = extism_plugin_output_data(plugin)[0 .. outputLength];
auto buffer = new ubyte[outputLength];
assert(
outputData.copy(buffer).length == 0,
"Output data was not completely copied into buffer, i.e. buffer was not filled."
);
return buffer;
}
}
/// Get the error associated with a `Context` or `Plugin`, if plugin is -1 then the context error will be returned.
/// Set log file and level.
bool setLogFile(string filename, string logLevel) {
return extism_log_file(filename.toStringz, logLevel.toStringz);
}
/// Get the Extism version string.
string version_() {
return extism_version().fromStringz.idup;
}
import std.traits: isDelegate;
/// Transform the given delegate into a static function pointer with C linkage.
/// See_Also: <a href="https://stackoverflow.com/a/22845722/1363247">stackoverflow.com/a/22845722/1363247</a>
package auto bindDelegate(Func)(Func f) if (isDelegate!Func) {
import std.traits: ParameterTypeTuple, ReturnType;
static Func delegate_;
delegate_ = f;
extern (C) static ReturnType!Func func(ParameterTypeTuple!Func args) {
return delegate_(args);
}
return &func;
}

View File

@@ -1 +0,0 @@
#include "runtime/extism.h"

View File

@@ -1,4 +0,0 @@
# Dotnet Host SDK
This contains the `0.x` version of the SDK. Development of this library has moved to [this repo](https://github.com/extism/dotnet-sdk#readme).

View File

@@ -1,32 +0,0 @@
<Project>
<PropertyGroup>
<Authors>Extism Contributors</Authors>
<PackageTags>extism, wasm, plugin</PackageTags>
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://extism.org</PackageProjectUrl>
<RepositoryUrl>https://github.com/your/repository.git</RepositoryUrl>
<TargetFramework>netstandard2.1</TargetFramework>
<Deterministic>true</Deterministic>
<NoBuild>true</NoBuild>
<IncludeBuildOutput>false</IncludeBuildOutput>
<MinVerIgnoreHeight>true</MinVerIgnoreHeight>
<MinVerTagPrefix>v</MinVerTagPrefix>
<MinVerVerbosity>normal</MinVerVerbosity>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MinVer" Version="4.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<NuspecFile>Extism.runtime.all.nuspec</NuspecFile>
</PropertyGroup>
<Target Name="Replace" BeforeTargets="GenerateNuspec">
<WriteLinesToFile
File="$(NuspecFile)"
Lines="$([System.IO.File]::ReadAllText($(NuspecFile)).Replace('$Version','$(MinVerVersion)'))"
Overwrite="true"/>
</Target>
</Project>

View File

@@ -1,27 +0,0 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>Extism.runtime.all</id>
<version>$Version</version>
<authors>Extism Contributors</authors>
<owners>Extism Contributors</owners>
<description>Internal implementation package for Extism</description>
<tags>extism wasm plugin</tags>
<license type="expression">BSD-3-Clause</license>
<projectUrl>https://github.com/extism/extism</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<copyright>Copyright © 2023 Extism Contributors</copyright>
<readme>README.md</readme>
<dependencies>
<dependency id="Extism.runtime.linux-arm64" version="$Version" />
<dependency id="Extism.runtime.linux-musl-arm64" version="$Version" />
<dependency id="Extism.runtime.linux-x64" version="$Version" />
<dependency id="Extism.runtime.osx-arm64" version="$Version" />
<dependency id="Extism.runtime.osx-x64" version="$Version" />
<dependency id="Extism.runtime.win-x64" version="$Version" />
</dependencies>
</metadata>
<files>
<file src="README.md" target="" />
</files>
</package>

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Extism.runtime.linux-arm64</PackageId>
<Description>Internal implementation package for Extism to work on Linux ARM64</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\linux-arm64\native\libextism.so"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\linux-arm64\native\libextism.so" />
</ItemGroup>
</Project>

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Extism.runtime.linux-musl-arm64</PackageId>
<Description>Internal implementation package for Extism to work on Linux Musl ARM64</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\linux-musl-arm64\native\libextism.so"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\linux-musl-arm64\native\libextism.so" />
</ItemGroup>
</Project>

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Extism.runtime.linux-x64</PackageId>
<Description>Internal implementation package for Extism to work on Linux x64</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\linux-x64\native\libextism.so"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\linux-x64\native\libextism.so" />
</ItemGroup>
</Project>

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Extism.runtime.osx-arm64</PackageId>
<Description>Internal implementation package for Extism to work on macOS ARM64</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\osx-arm64\native\libextism.dylib"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\osx-arm64\native\libextism.dylib" />
</ItemGroup>
</Project>

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Extism.runtime.osx-x64</PackageId>
<Description>Internal implementation package for Extism to work on macOS x64</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\osx-x64\native\libextism.dylib"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\osx-x64\native\libextism.dylib" />
</ItemGroup>
</Project>

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Extism.runtime.win-x64</PackageId>
<Description>Internal implementation package for Extism to work on Windows x64</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\win-x64\native\extism.dll"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\win-x64\native\extism.dll" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<NoBuild>true</NoBuild>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>
<PropertyGroup>
<PackageId>Extism.runtime.win-x64</PackageId>
<Version>0.7.0</Version>
<Authors>Extism Contributors</Authors>
<Description>Internal implementation package for Extism to work on Windows x64</Description>
<Tags>extism, wasm, plugin</Tags>
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes/win-x64.dll"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\win-x64\native\extism.dll" />
</ItemGroup>
</Project>

View File

@@ -1,2 +0,0 @@
# Extism
The cross-language framework for building with WebAssembly (wasm).

View File

@@ -4,6 +4,8 @@ using Extism.Sdk.Native;
using System.Runtime.InteropServices;
using System.Text;
var context = new Context();
var userData = Marshal.StringToHGlobalAnsi("Hello again!");
using var helloWorld = new HostFunction(
@@ -28,7 +30,7 @@ void HelloWorld(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> ou
}
var wasm = File.ReadAllBytes("./code-functions.wasm");
using var plugin = new Plugin(wasm, new[] { helloWorld }, withWasi: true);
using var plugin = context.CreatePlugin(wasm, new[] { helloWorld }, withWasi: true);
var output = Encoding.UTF8.GetString(
plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World!"))

View File

@@ -0,0 +1,191 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace Extism.Sdk.Native;
/// <summary>
/// Represents an Extism context through which you can load <see cref="Plugin"/>s.
/// </summary>
public unsafe class Context : IDisposable
{
private readonly ConcurrentDictionary<int, Plugin> _plugins = new ConcurrentDictionary<int, Plugin>();
private const int DisposedMarker = 1;
private int _disposed;
/// <summary>
/// Initialize a new Extism Context.
/// </summary>
public Context()
{
unsafe
{
NativeHandle = LibExtism.extism_context_new();
}
}
/// <summary>
/// Native pointer to the Extism Context.
/// </summary>
internal LibExtism.ExtismContext* NativeHandle { get; }
/// <summary>
/// Loads an Extism <see cref="Plugin"/>.
/// </summary>
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
/// <param name="functions">List of host functions expected by the plugin.</param>
/// <param name="withWasi">Enable/Disable WASI.</param>
public Plugin CreatePlugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi)
{
CheckNotDisposed();
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
unsafe
{
fixed (byte* wasmPtr = wasm)
fixed (IntPtr* functionsPtr = functionHandles)
{
var index = LibExtism.extism_plugin_new(NativeHandle, wasmPtr, wasm.Length, functionsPtr, functions.Length, withWasi);
if (index == -1)
{
var errorMsg = GetError();
if (errorMsg != null)
{
throw new ExtismException(errorMsg);
}
else
{
throw new ExtismException("Failed to create plugin.");
}
}
return _plugins[index] = new Plugin(this, functions, index);
}
}
}
/// <summary>
/// Get a plugin by index.
/// </summary>
/// <param name="index">Index of plugin.</param>
/// <returns></returns>
public Plugin GetPlugin(int index)
{
return _plugins[index];
}
/// <summary>
/// Remove all plugins from this <see cref="Context"/>'s registry.
/// </summary>
public void Reset()
{
CheckNotDisposed();
LibExtism.extism_context_reset(NativeHandle);
}
/// <summary>
/// Get this this <see cref="Context"/>'s last error.
/// </summary>
/// <returns></returns>
internal string? GetError()
{
CheckNotDisposed();
var result = LibExtism.extism_error(NativeHandle, -1);
return Marshal.PtrToStringUTF8(result);
}
/// <summary>
/// Frees all resources held by this Context.
/// </summary>
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, DisposedMarker) == DisposedMarker)
{
// Already disposed.
return;
}
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Throw an appropriate exception if the plugin has been disposed.
/// </summary>
/// <exception cref="ObjectDisposedException"></exception>
protected void CheckNotDisposed()
{
Interlocked.MemoryBarrier();
if (_disposed == DisposedMarker)
{
ThrowDisposedException();
}
}
[DoesNotReturn]
private static void ThrowDisposedException()
{
throw new ObjectDisposedException(nameof(Context));
}
/// <summary>
/// Frees all resources held by this Context.
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Free up any managed resources here
}
foreach (var plugin in _plugins.Values)
{
plugin.Dispose();
}
// Free up unmanaged resources
LibExtism.extism_context_free(NativeHandle);
}
/// <summary>
/// Destructs the current Context and frees all resources used by it.
/// </summary>
~Context()
{
Dispose(false);
}
/// <summary>
/// Get the Extism version string.
/// </summary>
public static string GetExtismVersion()
{
var pointer = LibExtism.extism_version();
return Marshal.PtrToStringUTF8(pointer);
}
/// <summary>
/// Set Extism's log file and level. This is applied for all <see cref="Context"/>s.
/// </summary>
/// <param name="logPath">Log file; can be 'stdout' or 'stderr' to write logs to the console.</param>
/// <param name="level">The log level to write at.</param>
public static bool SetExtismLogFile(string logPath, LogLevel level)
{
var logLevel = level switch
{
LogLevel.Error => LibExtism.LogLevels.Error,
LogLevel.Warning => LibExtism.LogLevels.Warn,
LogLevel.Info => LibExtism.LogLevels.Info,
LogLevel.Debug => LibExtism.LogLevels.Debug,
LogLevel.Trace => LibExtism.LogLevels.Trace,
_ => throw new NotImplementedException(),
};
return LibExtism.extism_log_file(logPath, logLevel);
}
}

View File

@@ -97,10 +97,10 @@ public struct ExtismVal
internal static class LibExtism
{
/// <summary>
/// An Extism Plugin
/// A `Context` is used to store and manage plugins.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct ExtismPlugin { }
internal struct ExtismContext { }
/// <summary>
/// Host function signature
@@ -180,9 +180,24 @@ internal static class LibExtism
[DllImport("extism", EntryPoint = "extism_function_free")]
internal static extern void extism_function_free(IntPtr ptr);
/// <summary>
/// Create a new context.
/// </summary>
/// <returns>A pointer to the newly created context.</returns>
[DllImport("extism")]
unsafe internal static extern ExtismContext* extism_context_new();
/// <summary>
/// Remove a context from the registry and free associated memory.
/// </summary>
/// <param name="context"></param>
[DllImport("extism")]
unsafe internal static extern void extism_context_free(ExtismContext* context);
/// <summary>
/// Load a WASM plugin.
/// </summary>
/// <param name="context">Pointer to the context the plugin will be associated with.</param>
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
/// <param name="wasmSize">The length of the `wasm` parameter.</param>
/// <param name="functions">Array of host function pointers.</param>
@@ -190,68 +205,98 @@ internal static class LibExtism
/// <param name="withWasi">Enables/disables WASI.</param>
/// <returns></returns>
[DllImport("extism")]
unsafe internal static extern ExtismPlugin* extism_plugin_new(byte* wasm, int wasmSize, IntPtr* functions, int nFunctions, bool withWasi, IntPtr* errmsg);
unsafe internal static extern int extism_plugin_new(ExtismContext* context, byte* wasm, int wasmSize, IntPtr* functions, int nFunctions, bool withWasi);
/// <summary>
/// Update a plugin, keeping the existing ID.
/// Similar to <see cref="extism_plugin_new"/> but takes an `plugin` argument to specify which plugin to update.
/// Memory for this plugin will be reset upon update.
/// </summary>
/// <param name="context">Pointer to the context the plugin is associated with.</param>
/// <param name="plugin">Pointer to the plugin you want to update.</param>
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
/// <param name="wasmSize">The length of the `wasm` parameter.</param>
/// <param name="functions">Array of host function pointers.</param>
/// <param name="nFunctions">Number of host functions.</param>
/// <param name="withWasi">Enables/disables WASI.</param>
/// <returns></returns>
[DllImport("extism")]
unsafe internal static extern bool extism_plugin_update(ExtismContext* context, int plugin, byte* wasm, long wasmSize, Span<IntPtr> functions, long nFunctions, bool withWasi);
/// <summary>
/// Remove a plugin from the registry and free associated memory.
/// </summary>
/// <param name="context">Pointer to the context the plugin is associated with.</param>
/// <param name="plugin">Pointer to the plugin you want to free.</param>
[DllImport("extism")]
unsafe internal static extern void extism_plugin_free(ExtismPlugin* plugin);
unsafe internal static extern void extism_plugin_free(ExtismContext* context, int plugin);
/// <summary>
/// Remove all plugins from the registry.
/// </summary>
/// <param name="context"></param>
[DllImport("extism")]
unsafe internal static extern void extism_context_reset(ExtismContext* context);
/// <summary>
/// Update plugin config values, this will merge with the existing values.
/// </summary>
/// <param name="context">Pointer to the context the plugin is associated with.</param>
/// <param name="plugin">Pointer to the plugin you want to update the configurations for.</param>
/// <param name="json">The configuration JSON encoded in UTF8.</param>
/// <param name="jsonLength">The length of the `json` parameter.</param>
/// <returns></returns>
[DllImport("extism")]
unsafe internal static extern bool extism_plugin_config(ExtismPlugin* plugin, byte* json, int jsonLength);
unsafe internal static extern bool extism_plugin_config(ExtismContext* context, int plugin, byte* json, int jsonLength);
/// <summary>
/// Returns true if funcName exists.
/// </summary>
/// <param name="context"></param>
/// <param name="plugin"></param>
/// <param name="funcName"></param>
/// <returns></returns>
[DllImport("extism")]
unsafe internal static extern bool extism_plugin_function_exists(ExtismPlugin* plugin, string funcName);
unsafe internal static extern bool extism_plugin_function_exists(ExtismContext* context, int plugin, string funcName);
/// <summary>
/// Call a function.
/// </summary>
/// <param name="context"></param>
/// <param name="plugin"></param>
/// <param name="funcName">The function to call.</param>
/// <param name="data">Input data.</param>
/// <param name="dataLen">The length of the `data` parameter.</param>
/// <returns></returns>
[DllImport("extism")]
unsafe internal static extern int extism_plugin_call(ExtismPlugin* plugin, string funcName, byte* data, int dataLen);
unsafe internal static extern int extism_plugin_call(ExtismContext* context, int plugin, string funcName, byte* data, int dataLen);
/// <summary>
/// Get the error associated with a Plugin
/// Get the error associated with a Context or Plugin, if plugin is -1 then the context error will be returned.
/// </summary>
/// <param name="plugin">A plugin pointer</param>
/// <param name="context"></param>
/// <param name="plugin">A plugin pointer, or -1 for the context error.</param>
/// <returns></returns>
[DllImport("extism")]
unsafe internal static extern IntPtr extism_plugin_error(ExtismPlugin* plugin);
unsafe internal static extern IntPtr extism_error(ExtismContext* context, nint plugin);
/// <summary>
/// Get the length of a plugin's output data.
/// </summary>
/// <param name="context"></param>
/// <param name="plugin"></param>
/// <returns></returns>
[DllImport("extism")]
unsafe internal static extern long extism_plugin_output_length(ExtismPlugin* plugin);
unsafe internal static extern long extism_plugin_output_length(ExtismContext* context, int plugin);
/// <summary>
/// Get the plugin's output data.
/// </summary>
/// <param name="context"></param>
/// <param name="plugin"></param>
/// <returns></returns>
[DllImport("extism")]
unsafe internal static extern IntPtr extism_plugin_output_data(ExtismPlugin* plugin);
unsafe internal static extern IntPtr extism_plugin_output_data(ExtismContext* context, int plugin);
/// <summary>
/// Set log file and level.
@@ -263,11 +308,11 @@ internal static class LibExtism
internal static extern bool extism_log_file(string filename, string logLevel);
/// <summary>
/// Get the Extism Plugin ID, a 16-bit UUID in host order
/// Get the Extism version string.
/// </summary>
/// <returns></returns>
// [DllImport("extism")]
// unsafe internal static extern IntPtr extism_plugin_id(ExtismPlugin* plugin);
[DllImport("extism", EntryPoint = "extism_version")]
internal static extern IntPtr extism_version();
/// <summary>
/// Extism Log Levels

View File

@@ -6,43 +6,51 @@ namespace Extism.Sdk.Native;
/// <summary>
/// Represents a WASM Extism plugin.
/// </summary>
public unsafe class Plugin : IDisposable
public class Plugin : IDisposable
{
private const int DisposedMarker = 1;
private readonly Context _context;
private readonly HostFunction[] _functions;
private int _disposed;
/// <summary>
/// Native pointer to the Extism Plugin.
/// </summary>
internal LibExtism.ExtismPlugin* NativeHandle { get; }
/// <summary>
/// Create a and load a plug-in
/// Using this constructor will give the plug-in it's own internal Context
/// </summary>
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
/// <param name="functions">List of host functions expected by the plugin.</param>
/// <param name="withWasi">Enable/Disable WASI.</param>
public Plugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi) {
_functions = functions;
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
public static Plugin Create(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi) {
var context = new Context();
return context.CreatePlugin(wasm, functions, withWasi);
}
unsafe
internal Plugin(Context context, HostFunction[] functions, int index)
{
_context = context;
_functions = functions;
Index = index;
}
/// <summary>
/// A pointer to the native Plugin struct.
/// </summary>
internal int Index { get; }
/// <summary>
/// Update a plugin, keeping the existing ID.
/// </summary>
/// <param name="wasm">The plugin WASM bytes.</param>
/// <param name="withWasi">Enable/Disable WASI.</param>
unsafe public bool Update(ReadOnlySpan<byte> wasm, bool withWasi)
{
CheckNotDisposed();
var functions = _functions.Select(f => f.NativeHandle).ToArray();
fixed (byte* wasmPtr = wasm)
{
fixed (byte* wasmPtr = wasm)
fixed (IntPtr* functionsPtr = functionHandles)
{
NativeHandle = LibExtism.extism_plugin_new(wasmPtr, wasm.Length, functionsPtr, functions.Length, withWasi, null);
if (NativeHandle == null)
{
throw new ExtismException("Unable to create plugin");
// TODO: handle error
// var s = Marshal.PtrToStringUTF8(result);
// LibExtism.extism_plugin_new_error_free(errmsg);
// throw new ExtismException(s);
}
}
return LibExtism.extism_plugin_update(_context.NativeHandle, Index, wasmPtr, wasm.Length, functions, 0, withWasi);
}
}
@@ -56,7 +64,7 @@ public unsafe class Plugin : IDisposable
fixed (byte* jsonPtr = json)
{
return LibExtism.extism_plugin_config(NativeHandle, jsonPtr, json.Length);
return LibExtism.extism_plugin_config(_context.NativeHandle, Index, jsonPtr, json.Length);
}
}
@@ -67,7 +75,7 @@ public unsafe class Plugin : IDisposable
{
CheckNotDisposed();
return LibExtism.extism_plugin_function_exists(NativeHandle, name);
return LibExtism.extism_plugin_function_exists(_context.NativeHandle, Index, name);
}
/// <summary>
@@ -85,7 +93,7 @@ public unsafe class Plugin : IDisposable
fixed (byte* dataPtr = data)
{
int response = LibExtism.extism_plugin_call(NativeHandle, functionName, dataPtr, data.Length);
int response = LibExtism.extism_plugin_call(_context.NativeHandle, Index, functionName, dataPtr, data.Length);
if (response == 0)
{
return OutputData();
@@ -113,7 +121,7 @@ public unsafe class Plugin : IDisposable
{
CheckNotDisposed();
return (int)LibExtism.extism_plugin_output_length(NativeHandle);
return (int)LibExtism.extism_plugin_output_length(_context.NativeHandle, Index);
}
/// <summary>
@@ -127,7 +135,7 @@ public unsafe class Plugin : IDisposable
unsafe
{
var ptr = LibExtism.extism_plugin_output_data(NativeHandle).ToPointer();
var ptr = LibExtism.extism_plugin_output_data(_context.NativeHandle, Index).ToPointer();
return new Span<byte>(ptr, length);
}
}
@@ -140,7 +148,7 @@ public unsafe class Plugin : IDisposable
{
CheckNotDisposed();
var result = LibExtism.extism_plugin_error(NativeHandle);
var result = LibExtism.extism_error(_context.NativeHandle, Index);
return Marshal.PtrToStringUTF8(result);
}
@@ -189,7 +197,7 @@ public unsafe class Plugin : IDisposable
}
// Free up unmanaged resources
LibExtism.extism_plugin_free(NativeHandle);
LibExtism.extism_plugin_free(_context.NativeHandle, Index);
}
/// <summary>

View File

@@ -11,11 +11,24 @@ namespace Extism.Sdk.Tests;
public class BasicTests
{
[Fact]
public void CountHelloWorldVowels()
public void CountHelloWorldVowelsWithoutContext()
{
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code.wasm"));
using var plugin = new Plugin(wasm, Array.Empty<HostFunction>(), withWasi: true);
using var plugin = Plugin.Create(wasm, Array.Empty<HostFunction>(), withWasi: true);
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
}
[Fact]
public void CountHelloWorldVowels()
{
using var context = new Context();
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code.wasm"));
using var plugin = context.CreatePlugin(wasm, Array.Empty<HostFunction>(), withWasi: true);
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
@@ -24,6 +37,8 @@ public class BasicTests
[Fact]
public void CountVowelsHostFunctions()
{
using var context = new Context();
var userData = Marshal.StringToHGlobalAnsi("Hello again!");
using var helloWorld = new HostFunction(
@@ -36,7 +51,7 @@ public class BasicTests
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code-functions.wasm"));
using var plugin = new Plugin(wasm, new[] { helloWorld }, withWasi: true);
using var plugin = context.CreatePlugin(wasm, new[] { helloWorld }, withWasi: true);
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));

View File

@@ -1,116 +0,0 @@
; Configure which static analysis checks are enabled
[analysis.config.StaticAnalysisConfig]
; Check variable, class, struct, interface, union, and function names against t
; he Phobos style guide
style_check="enabled"
; Check for array literals that cause unnecessary allocation
enum_array_literal_check="enabled"
; Check for poor exception handling practices
exception_check="enabled"
; Check for use of the deprecated 'delete' keyword
delete_check="enabled"
; Check for use of the deprecated floating point operators
float_operator_check="enabled"
; Check number literals for readability
number_style_check="enabled"
; Checks that opEquals, opCmp, toHash, and toString are either const, immutable
; , or inout.
object_const_check="enabled"
; Checks for .. expressions where the left side is larger than the right.
backwards_range_check="enabled"
; Checks for if statements whose 'then' block is the same as the 'else' block
if_else_same_check="enabled"
; Checks for some problems with constructors
constructor_check="enabled"
; Checks for unused variables
unused_variable_check="enabled"
; Checks for unused labels
unused_label_check="enabled"
; Checks for unused function parameters
unused_parameter_check="enabled"
; Checks for duplicate attributes
duplicate_attribute="enabled"
; Checks that opEquals and toHash are both defined or neither are defined
opequals_tohash_check="enabled"
; Checks for subtraction from .length properties
length_subtraction_check="enabled"
; Checks for methods or properties whose names conflict with built-in propertie
; s
builtin_property_names_check="enabled"
; Checks for confusing code in inline asm statements
asm_style_check="enabled"
; Checks for confusing logical operator precedence
logical_precedence_check="enabled"
; Checks for undocumented public declarations
undocumented_declaration_check="enabled"
; Checks for poor placement of function attributes
function_attribute_check="enabled"
; Checks for use of the comma operator
comma_expression_check="enabled"
; Checks for local imports that are too broad. Only accurate when checking code
; used with D versions older than 2.071.0
local_import_check="disabled"
; Checks for variables that could be declared immutable
could_be_immutable_check="enabled"
; Checks for redundant expressions in if statements
redundant_if_check="enabled"
; Checks for redundant parenthesis
redundant_parens_check="enabled"
; Checks for mismatched argument and parameter names
mismatched_args_check="enabled"
; Checks for labels with the same name as variables
label_var_same_name_check="enabled"
; Checks for lines longer than `max_line_length` characters
long_line_check="enabled"
; The maximum line length used in `long_line_check`.
max_line_length="120"
; Checks for assignment to auto-ref function parameters
auto_ref_assignment_check="enabled"
; Checks for incorrect infinite range definitions
incorrect_infinite_range_check="enabled"
; Checks for asserts that are always true
useless_assert_check="enabled"
; Check for uses of the old-style alias syntax
alias_syntax_check="enabled"
; Checks for else if that should be else static if
static_if_else_check="enabled"
; Check for unclear lambda syntax
lambda_return_check="enabled"
; Check for auto function without return statement
auto_function_check="enabled"
; Check for sortedness of imports
imports_sortedness="disabled"
; Check for explicitly annotated unittests
explicitly_annotated_unittests="disabled"
; Check for properly documented public functions (Returns, Params)
properly_documented_public_functions="disabled"
; Check for useless usage of the final attribute
final_attribute_check="enabled"
; Check for virtual calls in the class constructors
vcall_in_ctor="enabled"
; Check for useless user defined initializers
useless_initializer="disabled"
; Check allman brace style
allman_braces_check="disabled"
; Check for redundant attributes
redundant_attributes_check="enabled"
; Check public declarations without a documented unittest
has_public_example="disabled"
; Check for asserts without an explanatory message
assert_without_msg="disabled"
; Check indent of if constraints
if_constraints_indent="disabled"
; Check for @trusted applied to a bigger scope than a single function
trust_too_much="enabled"
; Check for redundant storage classes on variable declarations
redundant_storage_classes="enabled"
; Check for unused function return values
unused_result="enabled"
; Enable cyclomatic complexity check
cyclomatic_complexity="disabled"
; Maximum cyclomatic complexity after which to issue warnings
max_cyclomatic_complexity="50"
; Check for function bodies on disabled functions
body_on_disabled_func_check="enabled"
; ModuleFilters for selectively enabling (+std) and disabling (-std.internal) individual checks
[analysis.config.ModuleFilters]

View File

@@ -1,23 +0,0 @@
{
"name": "extism",
"description": "The Universal Plug-in System. Extend anything with WebAssembly (wasm).",
"license": "BSD-3-Clause",
"authors": [
"Chance Snow <git@chancesnow.me>",
"Extism contributors"
],
"copyright": "Copyright © 2023, Extism contributors",
"toolchainRequirements": {
"frontend": ">=2.102"
},
"targetPath": "target",
"sourceFiles": ["d/extism.d"],
"importPaths": ["d"],
"systemDependencies": "extism >= 0.4.0",
"targetType": "sourceLibrary",
"subPackages": [
"d/examples/hello"
],
"dflags": ["-P-I$EXTISM_PACKAGE_DIR"],
"libs": ["extism"]
}

View File

@@ -29,7 +29,6 @@
(extism-manifest (= :version))
(ppx_inline_test (>= v0.15.0))
(cmdliner (>= 1.1.1))
(uuidm (>= 0.9.0))
)
(tags
(topics wasm plugin)))

View File

@@ -5,7 +5,7 @@ prepare:
mix compile
test: prepare
mix test -v
mix test
clean:
mix clean

View File

@@ -1,3 +1,73 @@
# Elixir Host SDK
# Extism
This contains the `0.x` version of the SDK. Development of this library has moved to [this repo](https://github.com/extism/elixir-sdk#readme).
Extism Host SDK for Elixir and Erlang
## Docs
Read the [docs on hexdocs.pm](https://hexdocs.pm/extism/).
## Installation
You can find this package on [hex.pm](https://hex.pm/packages/extism).
```elixir
def deps do
[
{:extism, "~> 0.1.0"}
]
end
```
## Getting Started
### Example
```elixir
# Create a context for which plugins can be allocated and cleaned
ctx = Extism.Context.new()
# point to some wasm code, this is the count_vowels example that ships with extism
manifest = %{ wasm: [ %{ path: "/Users/ben/code/extism/wasm/code.wasm" } ]}
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
# {:ok,
# %Extism.Plugin{
# resource: 0,
# reference: #Reference<0.520418104.1263009793.80956>
# }}
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
# {:ok, "{\"count\": 4}"}
{:ok, result} = JSON.decode(output)
# {:ok, %{"count" => 4}}
# free up the context and any plugins we allocated
Extism.Context.free(ctx)
```
### Modules
The two primary modules you should learn are:
* [Extism.Context](Extism.Context.html)
* [Extism.Plugin](Extism.Plugin.html)
#### Context
The [Context](Extism.Context.html) can be thought of as a session. You need a context to interact with the Extism runtime. The context holds your plugins and when you free the context, it frees your plugins. It's important to free up your context and plugins when you are done with them.
```elixir
ctx = Extism.Context.new()
# frees all the plugins
Extism.Context.reset(ctx)
# frees the context and all its plugins
Extism.Context.free(ctx)
```
#### Plugin
The [Plugin](Extism.Plugin.html) represents an instance of your WASM program from the given manifest.
The key method to know here is [Extism.Plugin#call](Extism.Plugin.html#call/3) which takes a function name to invoke and some input data, and returns the results from the plugin.
```elixir
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
```

View File

@@ -4,7 +4,7 @@ defmodule Extism.CancelHandle do
thread while running.
"""
defstruct [
# The actual NIF Resource
# The actual NIF Resource. PluginIndex and the context
handle: nil
]

View File

@@ -0,0 +1,64 @@
defmodule Extism.Context do
@moduledoc """
A Context is needed to create plugins. The Context is where your plugins
live. Freeing the context frees all of the plugins in its scope.
"""
defstruct [
# The actual NIF Resource. A pointer in this case
ptr: nil
]
def wrap_resource(ptr) do
%__MODULE__{
ptr: ptr
}
end
@doc """
Creates a new context.
"""
def new() do
ptr = Extism.Native.context_new()
Extism.Context.wrap_resource(ptr)
end
@doc """
Resets the context. This has the effect of freeing all the plugins created so far.
"""
def reset(ctx) do
Extism.Native.context_reset(ctx.ptr)
end
@doc """
Frees the context from memory and all of its plugins.
"""
def free(ctx) do
Extism.Native.context_free(ctx.ptr)
end
@doc """
Create a new plugin from a WASM module or manifest
## Examples:
iex> ctx = Extism.Context.new()
iex> manifest = %{ wasm: [ %{ path: "/Users/ben/code/extism/wasm/code.wasm" } ]}
iex> {:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
## Parameters
- ctx: The Context to manage this plugin
- manifest: The String or Map of the WASM module or [manifest](https://extism.org/docs/concepts/manifest)
- wasi: A bool you set to true if you want WASI support
"""
def new_plugin(ctx, manifest, wasi \\ false) do
{:ok, manifest_payload} = JSON.encode(manifest)
case Extism.Native.plugin_new_with_manifest(ctx.ptr, manifest_payload, wasi) do
{:error, err} -> {:error, err}
res -> {:ok, Extism.Plugin.wrap_resource(ctx, res)}
end
end
end

View File

@@ -7,12 +7,16 @@ defmodule Extism.Native do
otp_app: :extism,
crate: :extism_nif
def plugin_new_with_manifest(_manifest, _wasi), do: error()
def plugin_call(_plugin, _name, _input), do: error()
def plugin_has_function(_plugin, _function_name), do: error()
def plugin_free(_plugin), do: error()
def context_new(), do: error()
def context_reset(_ctx), do: error()
def context_free(_ctx), do: error()
def plugin_new_with_manifest(_ctx, _manifest, _wasi), do: error()
def plugin_call(_ctx, _plugin_id, _name, _input), do: error()
def plugin_update_manifest(_ctx, _plugin_id, _manifest, _wasi), do: error()
def plugin_has_function(_ctx, _plugin_id, _function_name), do: error()
def plugin_free(_ctx, _plugin_id), do: error()
def set_log_file(_filename, _level), do: error()
def plugin_cancel_handle(_plugin), do: error()
def plugin_cancel_handle(_ctx, _plugin_id), do: error()
def plugin_cancel(_handle), do: error()
defp error, do: :erlang.nif_error(:nif_not_loaded)

View File

@@ -3,25 +3,28 @@ defmodule Extism.Plugin do
A Plugin represents an instance of your WASM program from the given manifest.
"""
defstruct [
# The actual NIF Resource
plugin: nil,
# The actual NIF Resource. PluginIndex and the context
plugin_id: nil,
ctx: nil
]
def wrap_resource(plugin) do
def wrap_resource(ctx, plugin_id) do
%__MODULE__{
plugin: plugin
ctx: ctx,
plugin_id: plugin_id
}
end
@doc """
Creates a new plugin
"""
def new(manifest, wasi \\ false) do
def new(manifest, wasi \\ false, context \\ nil) do
ctx = context || Extism.Context.new()
{:ok, manifest_payload} = JSON.encode(manifest)
case Extism.Native.plugin_new_with_manifest(manifest_payload, wasi) do
case Extism.Native.plugin_new_with_manifest(ctx.ptr, manifest_payload, wasi) do
{:error, err} -> {:error, err}
res -> {:ok, Extism.Plugin.wrap_resource(res)}
res -> {:ok, Extism.Plugin.wrap_resource(ctx, res)}
end
end
@@ -46,24 +49,49 @@ defmodule Extism.Plugin do
"""
def call(plugin, name, input) do
case Extism.Native.plugin_call(plugin.plugin, name, input) do
case Extism.Native.plugin_call(plugin.ctx.ptr, plugin.plugin_id, name, input) do
{:error, err} -> {:error, err}
res -> {:ok, res}
end
end
@doc """
Updates the manifest of the given plugin
## Parameters
- ctx: The Context to manage this plugin
- manifest: The String or Map of the WASM module or [manifest](https://extism.org/docs/concepts/manifest)
- wasi: A bool you set to true if you want WASI support
"""
def update(plugin, manifest, wasi) when is_map(manifest) do
{:ok, manifest_payload} = JSON.encode(manifest)
case Extism.Native.plugin_update_manifest(
plugin.ctx.ptr,
plugin.plugin_id,
manifest_payload,
wasi
) do
{:error, err} -> {:error, err}
_ -> :ok
end
end
@doc """
Frees the plugin
"""
def free(plugin) do
Extism.Native.plugin_free(plugin.plugin)
Extism.Native.plugin_free(plugin.ctx.ptr, plugin.plugin_id)
end
@doc """
Returns true if the given plugin responds to the given function name
"""
def has_function(plugin, function_name) do
Extism.Native.plugin_has_function(plugin.plugin, function_name)
Extism.Native.plugin_has_function(plugin.ctx.ptr, plugin.plugin_id, function_name)
end
end
@@ -71,6 +99,6 @@ defimpl Inspect, for: Extim.Plugin do
import Inspect.Algebra
def inspect(dict, opts) do
concat(["#Extism.Plugin<", to_doc(dict.plugin, opts), ">"])
concat(["#Extism.Plugin<", to_doc(dict.plugin_id, opts), ">"])
end
end

View File

@@ -4,7 +4,7 @@ defmodule Extism.MixProject do
def project do
[
app: :extism,
version: "0.5.0",
version: "0.5.1",
elixir: "~> 1.12",
start_permanent: Mix.env() == :prod,
deps: deps(),

View File

@@ -1,6 +1,6 @@
%{
"earmark_parser": {:hex, :earmark_parser, "1.4.37", "2ad73550e27c8946648b06905a57e4d454e4d7229c2dafa72a0348c99d8be5f7", [:mix], [], "hexpm", "6b19783f2802f039806f375610faa22da130b8edc21209d0bff47918bb48360e"},
"ex_doc": {:hex, :ex_doc, "0.30.7", "dc7247091aec738ab781f71cbebfc07979d1040c98c7ee67dbde99b7829b743d", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "868ff1c7a44c462741853840d1e7ef19a07906e7467cb8da070c158ea6a42a51"},
"earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"},
"ex_doc": {:hex, :ex_doc, "0.30.5", "aa6da96a5c23389d7dc7c381eba862710e108cee9cfdc629b7ec021313900e9e", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "88a1e115dcb91cefeef7e22df4a6ebbe4634fbf98b38adcbc25c9607d6d9d8e6"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"json": {:hex, :json, "1.4.1", "8648f04a9439765ad449bc56a3ff7d8b11dd44ff08ffcdefc4329f7c93843dfa", [:mix], [], "hexpm", "9abf218dbe4ea4fcb875e087d5f904ef263d012ee5ed21d46e9dbca63f053d16"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},

View File

@@ -13,6 +13,6 @@ crate-type = ["cdylib"]
[workspace]
[dependencies]
rustler = "0.28.0"
extism = {path = "../../../runtime"} # "0.5.0"
rustler = "0.29.1"
extism = "0.5.2"
log = "0.4"

View File

@@ -1,5 +1,6 @@
use extism::Plugin;
use extism::{Context, Plugin};
use rustler::{Atom, Env, ResourceArc, Term};
use std::mem;
use std::path::Path;
use std::str;
use std::str::FromStr;
@@ -13,11 +14,11 @@ mod atoms {
}
}
struct ExtismPlugin {
plugin: RwLock<Option<Plugin>>,
struct ExtismContext {
ctx: RwLock<Context>,
}
unsafe impl Sync for ExtismPlugin {}
unsafe impl Send for ExtismPlugin {}
unsafe impl Sync for ExtismContext {}
unsafe impl Send for ExtismContext {}
struct ExtismCancelHandle {
handle: RwLock<extism::CancelHandle>,
@@ -27,7 +28,7 @@ unsafe impl Sync for ExtismCancelHandle {}
unsafe impl Send for ExtismCancelHandle {}
fn load(env: Env, _: Term) -> bool {
rustler::resource!(ExtismPlugin, env);
rustler::resource!(ExtismContext, env);
rustler::resource!(ExtismCancelHandle, env);
true
}
@@ -36,73 +37,111 @@ fn to_rustler_error(extism_error: extism::Error) -> rustler::Error {
rustler::Error::Term(Box::new(extism_error.to_string()))
}
fn freed_error() -> rustler::Error {
rustler::Error::Term(Box::new("Plugin has already been freed".to_string()))
#[rustler::nif]
fn context_new() -> ResourceArc<ExtismContext> {
ResourceArc::new(ExtismContext {
ctx: RwLock::new(Context::new()),
})
}
#[rustler::nif]
fn context_reset(ctx: ResourceArc<ExtismContext>) {
let context = &mut ctx.ctx.write().unwrap();
context.reset()
}
#[rustler::nif]
fn context_free(ctx: ResourceArc<ExtismContext>) {
let context = ctx.ctx.read().unwrap();
std::mem::drop(context)
}
#[rustler::nif]
fn plugin_new_with_manifest(
ctx: ResourceArc<ExtismContext>,
manifest_payload: String,
wasi: bool,
) -> Result<ResourceArc<ExtismPlugin>, rustler::Error> {
let result = match Plugin::new(manifest_payload, [], wasi) {
) -> Result<i32, rustler::Error> {
let context = ctx.ctx.write().unwrap();
let result = match Plugin::new(&context, manifest_payload, [], wasi) {
Err(e) => Err(to_rustler_error(e)),
Ok(plugin) => Ok(ResourceArc::new(ExtismPlugin {
plugin: RwLock::new(Some(plugin)),
})),
Ok(plugin) => {
let plugin_id = plugin.as_i32();
// this forget should be safe because the context will clean up
// all it's plugins when it is dropped
mem::forget(plugin);
Ok(plugin_id)
}
};
result
}
#[rustler::nif]
fn plugin_call(
plugin: ResourceArc<ExtismPlugin>,
ctx: ResourceArc<ExtismContext>,
plugin_id: i32,
name: String,
input: String,
) -> Result<String, rustler::Error> {
let mut plugin = plugin.plugin.write().unwrap();
if let Some(plugin) = &mut *plugin {
let result = match plugin.call(name, input) {
Err(e) => Err(to_rustler_error(e)),
Ok(result) => match str::from_utf8(result) {
Ok(output) => Ok(output.to_string()),
Err(_e) => Err(rustler::Error::Term(Box::new(
"Could not read output from plugin",
))),
},
};
result
} else {
Err(freed_error())
}
let context = &ctx.ctx.read().unwrap();
let mut plugin = unsafe { Plugin::from_id(plugin_id, context) };
let result = match plugin.call(name, input) {
Err(e) => Err(to_rustler_error(e)),
Ok(result) => match str::from_utf8(result) {
Ok(output) => Ok(output.to_string()),
Err(_e) => Err(rustler::Error::Term(Box::new(
"Could not read output from plugin",
))),
},
};
// this forget should be safe because the context will clean up
// all it's plugins when it is dropped
mem::forget(plugin);
result
}
#[rustler::nif]
fn plugin_update_manifest(
ctx: ResourceArc<ExtismContext>,
plugin_id: i32,
manifest_payload: String,
wasi: bool,
) -> Result<(), rustler::Error> {
let context = &ctx.ctx.read().unwrap();
let mut plugin = unsafe { Plugin::from_id(plugin_id, context) };
let result = match plugin.update(manifest_payload, [], wasi) {
Ok(()) => Ok(()),
Err(e) => Err(to_rustler_error(e)),
};
// this forget should be safe because the context will clean up
// all it's plugins when it is dropped
mem::forget(plugin);
result
}
#[rustler::nif]
fn plugin_cancel_handle(
plugin: ResourceArc<ExtismPlugin>,
ctx: ResourceArc<ExtismContext>,
plugin_id: i32,
) -> Result<ResourceArc<ExtismCancelHandle>, rustler::Error> {
let mut plugin = plugin.plugin.write().unwrap();
if let Some(plugin) = &mut *plugin {
let handle = plugin.cancel_handle();
Ok(ResourceArc::new(ExtismCancelHandle {
handle: RwLock::new(handle),
}))
} else {
Err(freed_error())
}
let context = &ctx.ctx.read().unwrap();
let plugin = unsafe { Plugin::from_id(plugin_id, context) };
let handle = plugin.cancel_handle();
Ok(ResourceArc::new(ExtismCancelHandle {
handle: RwLock::new(handle),
}))
}
#[rustler::nif]
fn plugin_cancel(handle: ResourceArc<ExtismCancelHandle>) -> bool {
handle.handle.read().unwrap().cancel().is_ok()
handle.handle.read().unwrap().cancel()
}
#[rustler::nif]
fn plugin_free(plugin: ResourceArc<ExtismPlugin>) -> Result<(), rustler::Error> {
let mut plugin = plugin.plugin.write().unwrap();
if let Some(plugin) = plugin.take() {
drop(plugin);
}
fn plugin_free(ctx: ResourceArc<ExtismContext>, plugin_id: i32) -> Result<(), rustler::Error> {
let context = &ctx.ctx.read().unwrap();
let plugin = unsafe { Plugin::from_id(plugin_id, context) };
std::mem::drop(plugin);
Ok(())
}
@@ -114,34 +153,42 @@ fn set_log_file(filename: String, log_level: String) -> Result<Atom, rustler::Er
"{} not a valid log level",
log_level
)))),
Ok(level) => match extism::set_log_file(path, level) {
Ok(()) => Ok(atoms::ok()),
Err(e) => Err(rustler::Error::Term(Box::new(format!(
"Did not set log file: {e:?}"
)))),
},
Ok(level) => {
if extism::set_log_file(path, Some(level)) {
Ok(atoms::ok())
} else {
Err(rustler::Error::Term(Box::new(
"Did not set log file, received false from the API.",
)))
}
}
}
}
#[rustler::nif]
fn plugin_has_function(
plugin: ResourceArc<ExtismPlugin>,
ctx: ResourceArc<ExtismContext>,
plugin_id: i32,
function_name: String,
) -> Result<bool, rustler::Error> {
let mut plugin = plugin.plugin.write().unwrap();
if let Some(plugin) = &mut *plugin {
let has_function = plugin.function_exists(function_name);
Ok(has_function)
} else {
Err(freed_error())
}
let context = &ctx.ctx.read().unwrap();
let plugin = unsafe { Plugin::from_id(plugin_id, context) };
let has_function = plugin.has_function(function_name);
// this forget should be safe because the context will clean up
// all it's plugins when it is dropped
mem::forget(plugin);
Ok(has_function)
}
rustler::init!(
"Elixir.Extism.Native",
[
context_new,
context_reset,
context_free,
plugin_new_with_manifest,
plugin_call,
plugin_update_manifest,
plugin_has_function,
plugin_cancel_handle,
plugin_cancel,

View File

@@ -2,21 +2,33 @@ defmodule ExtismTest do
use ExUnit.Case
doctest Extism
defp new_plugin() do
test "context create & reset" do
ctx = Extism.Context.new()
path = Path.join([__DIR__, "../../wasm/code.wasm"])
manifest = %{wasm: [%{path: path}]}
{:ok, plugin} = Extism.Plugin.new(manifest, false)
plugin
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
Extism.Context.reset(ctx)
# we should expect an error after resetting context
{:error, _err} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
end
defp new_plugin() do
ctx = Extism.Context.new()
path = Path.join([__DIR__, "../../wasm/code.wasm"])
manifest = %{wasm: [%{path: path}]}
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
{ctx, plugin}
end
test "counts vowels" do
plugin = new_plugin()
{ctx, plugin} = new_plugin()
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
assert JSON.decode(output) == {:ok, %{"count" => 4}}
Extism.Context.free(ctx)
end
test "can make multiple calls on a plugin" do
plugin = new_plugin()
{ctx, plugin} = new_plugin()
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
assert JSON.decode(output) == {:ok, %{"count" => 4}}
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test again")
@@ -25,24 +37,37 @@ defmodule ExtismTest do
assert JSON.decode(output) == {:ok, %{"count" => 6}}
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "🌎hello🌎world🌎")
assert JSON.decode(output) == {:ok, %{"count" => 3}}
Extism.Context.free(ctx)
end
test "can free a plugin" do
plugin = new_plugin()
{ctx, plugin} = new_plugin()
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
assert JSON.decode(output) == {:ok, %{"count" => 4}}
Extism.Plugin.free(plugin)
# Expect an error when calling a plugin that was freed
{:error, _err} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
Extism.Context.free(ctx)
end
test "can update manifest" do
{ctx, plugin} = new_plugin()
path = Path.join([__DIR__, "../../wasm/code.wasm"])
manifest = %{wasm: [%{path: path}]}
assert Extism.Plugin.update(plugin, manifest, true) == :ok
Extism.Context.free(ctx)
end
test "errors on bad manifest" do
{:error, _msg} = Extism.Plugin.new(%{"wasm" => 123}, false)
ctx = Extism.Context.new()
{:error, _msg} = Extism.Context.new_plugin(ctx, %{"wasm" => 123}, false)
Extism.Context.free(ctx)
end
test "errors on unknown function" do
plugin = new_plugin()
{ctx, plugin} = new_plugin()
{:error, _msg} = Extism.Plugin.call(plugin, "unknown", "this is a test")
Extism.Context.free(ctx)
end
test "set_log_file" do
@@ -50,8 +75,9 @@ defmodule ExtismTest do
end
test "has_function" do
plugin = new_plugin()
{ctx, plugin} = new_plugin()
assert Extism.Plugin.has_function(plugin, "count_vowels")
assert !Extism.Plugin.has_function(plugin, "unknown")
Extism.Context.free(ctx)
end
end

View File

@@ -1,14 +0,0 @@
[package]
name = "extism-sys"
version = "0.1.0"
edition = "2021"
[lib]
name = "extism_sys"
crate-type = ["cdylib"]
[dependencies]
extism = {path = "../runtime"}
[build-dependencies]
cc = "1"

View File

@@ -1,57 +0,0 @@
use std::borrow::Cow;
fn rewrite(line: &'_ str) -> Option<Cow<'_, str>> {
let line = line.trim();
if line.is_empty() {
return None;
}
if line.starts_with('#') {
return None;
}
if cfg!(target_os = "macos") {
if line.starts_with("typedef __builtin_va_list ") {
return None
}
} else if cfg!(target_os = "windows") {
if line.contains("__gnuc_va_list") ||
line.starts_with("__pragma") ||
line.contains("__attribute__") ||
line.contains("uintptr_t") ||
line.contains("intptr_t") ||
line.contains("size_t") ||
line.contains("ptrdiff_t") {
return None
}
return Some(Cow::Owned(line.replace("__attribute__((__cdecl__))", "")))
};
Some(Cow::Borrowed(line))
}
fn main() {
println!("cargo:rerun-if-changed=src/extism.c");
println!("cargo:rerun-if-changed=../runtime/extism.h");
std::fs::copy("../runtime/extism.h", "src/extism.h").unwrap();
let data = String::from_utf8(
cc::Build::new()
.file("src/extism.c")
.warnings(false)
.extra_warnings(false)
.expand(),
)
.unwrap();
let data: Vec<&str> = data.split('\n').collect();
let data: String = data
.into_iter()
.filter_map(rewrite)
.collect::<Vec<Cow<'_, str>>>()
.join("\n\n");
std::fs::write("../target/header.h", data).unwrap();
}

View File

@@ -1,17 +0,0 @@
[build-system]
requires = ["maturin>=1.1,<2.0"]
build-backend = "maturin"
[project]
name = "extism-sys"
version = "0.0.0+replaced-by-ci"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = ["cffi"]
[tool.maturin]
bindings = "cffi"

View File

@@ -1 +0,0 @@
#include "extism.h"

View File

@@ -1 +0,0 @@
pub use extism::sdk::*;

215
extism.go
View File

@@ -52,6 +52,11 @@ void extism_val_set_f64(ExtismValUnion* x, double f){
*/
import "C"
// Context is used to manage Plugins
type Context struct {
pointer *C.ExtismContext
}
type ValType = C.ExtismValType
type Val = C.ExtismVal
@@ -76,11 +81,9 @@ type Function struct {
// Free a function
func (f *Function) Free() {
if f.pointer != nil {
C.extism_function_free(f.pointer)
f.pointer = nil
f.userData.Delete()
}
C.extism_function_free(f.pointer)
f.pointer = nil
f.userData.Delete()
}
// NewFunction creates a new host function with the given name, input/outputs and optional user data, which can be an
@@ -133,32 +136,45 @@ func GetCurrentPlugin(ptr unsafe.Pointer) CurrentPlugin {
}
}
type MemoryHandle = uint
func (p *CurrentPlugin) Memory(offs MemoryHandle) []byte {
func (p *CurrentPlugin) Memory(offs uint) []byte {
length := C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs))
data := unsafe.Pointer(C.extism_current_plugin_memory(p.pointer))
return unsafe.Slice((*byte)(unsafe.Add(data, offs)), C.int(length))
}
// Alloc a new memory block of the given length, returning its offset
func (p *CurrentPlugin) Alloc(n uint) MemoryHandle {
func (p *CurrentPlugin) Alloc(n uint) uint {
return uint(C.extism_current_plugin_memory_alloc(p.pointer, C.uint64_t(n)))
}
// Free the memory block specified by the given offset
func (p *CurrentPlugin) Free(offs MemoryHandle) {
func (p *CurrentPlugin) Free(offs uint) {
C.extism_current_plugin_memory_free(p.pointer, C.uint64_t(offs))
}
// Length returns the number of bytes allocated at the specified offset
func (p *CurrentPlugin) Length(offs MemoryHandle) int {
return int(C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs)))
func (p *CurrentPlugin) Length(offs uint) uint {
return uint(C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs)))
}
// NewContext creates a new context, it should be freed using the `Free` method
func NewContext() Context {
p := C.extism_context_new()
return Context{
pointer: p,
}
}
// Free a context
func (ctx *Context) Free() {
C.extism_context_free(ctx.pointer)
ctx.pointer = nil
}
// Plugin is used to call WASM functions
type Plugin struct {
ptr *C.ExtismPlugin
ctx *Context
id int32
functions []Function
}
@@ -218,99 +234,175 @@ func ExtismVersion() string {
return C.GoString(C.extism_version())
}
func register(data []byte, functions []Function, wasi bool) (Plugin, error) {
func register(ctx *Context, data []byte, functions []Function, wasi bool) (Plugin, error) {
ptr := makePointer(data)
functionPointers := []*C.ExtismFunction{}
for _, f := range functions {
functionPointers = append(functionPointers, f.pointer)
}
plugin := C.int32_t(-1)
if len(functions) == 0 {
plugin = C.extism_plugin_new(
ctx.pointer,
(*C.uchar)(ptr),
C.uint64_t(len(data)),
nil,
0,
C._Bool(wasi))
} else {
plugin = C.extism_plugin_new(
ctx.pointer,
(*C.uchar)(ptr),
C.uint64_t(len(data)),
&functionPointers[0],
C.uint64_t(len(functions)),
C._Bool(wasi),
)
}
if plugin < 0 {
err := C.extism_error(ctx.pointer, C.int32_t(-1))
msg := "Unknown"
if err != nil {
msg = C.GoString(err)
}
return Plugin{id: -1}, errors.New(
fmt.Sprintf("Unable to load plugin: %s", msg),
)
}
return Plugin{id: int32(plugin), ctx: ctx, functions: functions}, nil
}
func update(ctx *Context, plugin int32, data []byte, functions []Function, wasi bool) error {
ptr := makePointer(data)
functionPointers := []*C.ExtismFunction{}
for _, f := range functions {
functionPointers = append(functionPointers, f.pointer)
}
var plugin *C.ExtismPlugin
errmsg := (*C.char)(nil)
if len(functions) == 0 {
plugin = C.extism_plugin_new(
b := bool(C.extism_plugin_update(
ctx.pointer,
C.int32_t(plugin),
(*C.uchar)(ptr),
C.uint64_t(len(data)),
nil,
0,
C._Bool(wasi),
&errmsg)
))
if b {
return nil
}
} else {
plugin = C.extism_plugin_new(
b := bool(C.extism_plugin_update(
ctx.pointer,
C.int32_t(plugin),
(*C.uchar)(ptr),
C.uint64_t(len(data)),
&functionPointers[0],
C.uint64_t(len(functions)),
C._Bool(wasi),
&errmsg,
)
))
if b {
return nil
}
}
if plugin == nil {
msg := C.GoString(errmsg)
C.extism_plugin_new_error_free(errmsg)
return Plugin{}, errors.New(
fmt.Sprintf("Unable to load plugin: %s", msg),
)
}
return Plugin{ptr: plugin, functions: functions}, nil
}
// NewPlugin creates a plugin
func NewPlugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
wasm, err := io.ReadAll(module)
err := C.extism_error(ctx.pointer, C.int32_t(-1))
msg := "Unknown"
if err != nil {
return Plugin{}, err
msg = C.GoString(err)
}
return register(wasm, functions, wasi)
return errors.New(
fmt.Sprintf("Unable to load plugin: %s", msg),
)
}
// NewPlugin creates a plugin from a manifest
// NewPlugin creates a plugin in its own context
func NewPlugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
ctx := NewContext()
return ctx.Plugin(module, functions, wasi)
}
// NewPlugin creates a plugin in its own context from a manifest
func NewPluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
ctx := NewContext()
return ctx.PluginFromManifest(manifest, functions, wasi)
}
// PluginFromManifest creates a plugin from a `Manifest`
func (ctx *Context) PluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
data, err := json.Marshal(manifest)
if err != nil {
return Plugin{}, err
return Plugin{id: -1}, err
}
return register(data, functions, wasi)
return register(ctx, data, functions, wasi)
}
// Plugin creates a plugin from a WASM module
func (ctx *Context) Plugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
wasm, err := io.ReadAll(module)
if err != nil {
return Plugin{id: -1}, err
}
return register(ctx, wasm, functions, wasi)
}
// Update a plugin with a new WASM module
func (p *Plugin) Update(module io.Reader, functions []Function, wasi bool) error {
wasm, err := io.ReadAll(module)
if err != nil {
return err
}
p.functions = functions
return update(p.ctx, p.id, wasm, functions, wasi)
}
// Update a plugin with a new Manifest
func (p *Plugin) UpdateManifest(manifest Manifest, functions []Function, wasi bool) error {
data, err := json.Marshal(manifest)
if err != nil {
return err
}
p.functions = functions
return update(p.ctx, p.id, data, functions, wasi)
}
// Set configuration values
func (plugin Plugin) SetConfig(data map[string][]byte) error {
if plugin.ptr == nil {
return errors.New("Cannot set config, Plugin already freed")
}
s, err := json.Marshal(data)
if err != nil {
return err
}
ptr := makePointer(s)
C.extism_plugin_config(plugin.ptr, (*C.uchar)(ptr), C.uint64_t(len(s)))
C.extism_plugin_config(plugin.ctx.pointer, C.int(plugin.id), (*C.uchar)(ptr), C.uint64_t(len(s)))
return nil
}
// FunctionExists returns true when the named function is present in the plugin
func (plugin Plugin) FunctionExists(functionName string) bool {
if plugin.ptr == nil {
return false
}
name := C.CString(functionName)
b := C.extism_plugin_function_exists(plugin.ptr, name)
b := C.extism_plugin_function_exists(plugin.ctx.pointer, C.int(plugin.id), name)
C.free(unsafe.Pointer(name))
return bool(b)
}
// Call a function by name with the given input, returning the output
func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
if plugin.ptr == nil {
return []byte{}, errors.New("Plugin has already been freed")
}
ptr := makePointer(input)
name := C.CString(functionName)
rc := C.extism_plugin_call(
plugin.ptr,
plugin.ctx.pointer,
C.int32_t(plugin.id),
name,
(*C.uchar)(ptr),
C.uint64_t(len(input)),
@@ -318,7 +410,7 @@ func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
C.free(unsafe.Pointer(name))
if rc != 0 {
err := C.extism_plugin_error(plugin.ptr)
err := C.extism_error(plugin.ctx.pointer, C.int32_t(plugin.id))
msg := "<unset by plugin>"
if err != nil {
msg = C.GoString(err)
@@ -329,10 +421,10 @@ func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
)
}
length := C.extism_plugin_output_length(plugin.ptr)
length := C.extism_plugin_output_length(plugin.ctx.pointer, C.int32_t(plugin.id))
if length > 0 {
x := C.extism_plugin_output_data(plugin.ptr)
x := C.extism_plugin_output_data(plugin.ctx.pointer, C.int32_t(plugin.id))
return unsafe.Slice((*byte)(x), C.int(length)), nil
}
@@ -341,11 +433,16 @@ func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
// Free a plugin
func (plugin *Plugin) Free() {
if plugin.ptr == nil {
if plugin.ctx.pointer == nil {
return
}
C.extism_plugin_free(plugin.ptr)
plugin.ptr = nil
C.extism_plugin_free(plugin.ctx.pointer, C.int32_t(plugin.id))
plugin.id = -1
}
// Reset removes all registered plugins in a Context
func (ctx Context) Reset() {
C.extism_context_reset(ctx.pointer)
}
// ValGetI64 returns an I64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
@@ -417,7 +514,7 @@ type CancelHandle struct {
}
func (p *Plugin) CancelHandle() CancelHandle {
pointer := C.extism_plugin_cancel_handle(p.ptr)
pointer := C.extism_plugin_cancel_handle(p.ctx.pointer, C.int(p.id))
return CancelHandle{pointer}
}

View File

@@ -19,7 +19,6 @@ depends: [
"extism-manifest" {= version}
"ppx_inline_test" {>= "v0.15.0"}
"cmdliner" {>= "1.1.1"}
"uuidm" {>= "0.9.0"}
"odoc" {with-doc}
]
build: [

View File

@@ -35,8 +35,16 @@ func expectVowelCount(plugin Plugin, input string, count int) error {
return nil
}
func TestCreateAndFreeContext(t *testing.T) {
ctx := NewContext()
ctx.Free()
}
func TestCallPlugin(t *testing.T) {
plugin, err := NewPluginFromManifest(manifest(false), []Function{}, false)
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
if err != nil {
t.Error(err)
}
@@ -53,7 +61,10 @@ func TestCallPlugin(t *testing.T) {
}
func TestFreePlugin(t *testing.T) {
plugin, err := NewPluginFromManifest(manifest(false), []Function{}, false)
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
if err != nil {
t.Error(err)
}
@@ -69,8 +80,52 @@ func TestFreePlugin(t *testing.T) {
}
}
func TestContextReset(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
if err != nil {
t.Error(err)
}
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
t.Error(err)
}
// reset the context dropping all plugins
ctx.Reset()
if err := expectVowelCount(plugin, "this is a test", 4); err == nil {
t.Fatal("Expected an error after plugin was freed")
}
}
func TestCanUpdateAManifest(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
if err != nil {
t.Error(err)
}
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
t.Error(err)
}
plugin.UpdateManifest(manifest(false), []Function{}, false)
// can still call the plugin
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
t.Error(err)
}
}
func TestFunctionExists(t *testing.T) {
plugin, err := NewPluginFromManifest(manifest(false), []Function{}, false)
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
if err != nil {
t.Error(err)
}
@@ -84,7 +139,10 @@ func TestFunctionExists(t *testing.T) {
}
func TestErrorsOnUnknownFunction(t *testing.T) {
plugin, err := NewPluginFromManifest(manifest(false), []Function{}, false)
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
if err != nil {
t.Error(err)
}
@@ -104,7 +162,10 @@ func TestCancel(t *testing.T) {
},
}
plugin, err := NewPluginFromManifest(manifest, []Function{}, false)
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest, []Function{}, false)
if err != nil {
t.Error(err)
}

View File

@@ -1,4 +0,0 @@
# Go Host SDK
This contains the `0.x` version of the SDK. Development of this library has moved to [this repo](https://github.com/extism/go-sdk#readme).

View File

@@ -1,9 +1,13 @@
module Main where
import Extism
import Extism.HostFunction
import Extism.CurrentPlugin
import Extism.Manifest(manifest, wasmFile)
unwrap (Right x) = x
unwrap (Left (ExtismError msg)) = do
error msg
hello plugin params msg = do
putStrLn "Hello from Haskell!"
putStrLn msg
@@ -11,11 +15,10 @@ hello plugin params msg = do
return [toI64 offs]
main = do
setLogFile "stdout" LogError
setLogFile "stdout" Error
let m = manifest [wasmFile "../wasm/code-functions.wasm"]
f <- hostFunction "hello_world" [I64] [I64] hello "Hello, again"
plugin <- unwrap <$> pluginFromManifest m [f] True
id <- pluginID plugin
print id
plugin <- unwrap <$> createPluginFromManifest m [f] True
res <- unwrap <$> call plugin "count_vowels" (toByteString "this is a test")
putStrLn (fromByteString res)
free plugin

View File

@@ -1,4 +0,0 @@
# Haskell Host SDK
This contains the `0.x` version of the SDK. Development of this library has moved to [this repo](https://github.com/extism/haskell-sdk#readme).

View File

@@ -11,7 +11,7 @@ category: Plugins, WebAssembly
extra-doc-files: CHANGELOG.md
library
exposed-modules: Extism Extism.HostFunction
exposed-modules: Extism Extism.CurrentPlugin
reexported-modules: Extism.Manifest
hs-source-dirs: src
other-modules: Extism.Bindings
@@ -22,8 +22,7 @@ library
base >= 4.16.1 && < 5,
bytestring >= 0.11.3 && <= 0.12,
json >= 0.10 && <= 0.11,
extism-manifest >= 0.0.0 && < 0.4.0,
uuid >= 1.3 && < 2
extism-manifest >= 0.0.0 && < 0.4.0
test-suite extism-example
type: exitcode-stdio-1.0

View File

@@ -9,7 +9,7 @@ import Data.ByteString.Internal (c2w, w2c)
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Char8 as BS (unpack)
data Nullable a = Null | NotNull a deriving Eq
data Nullable a = Null | NotNull a
makeArray x = JSArray [showJSON a | a <- x]
isNull JSNull = True
@@ -49,7 +49,7 @@ instance JSON a => JSON (Nullable a) where
readJSON x = readJSON x
newtype Base64 = Base64 B.ByteString deriving (Eq, Show)
newtype Base64 = Base64 B.ByteString
instance JSON Base64 where
showJSON (Base64 bs) = showJSON (BS.unpack $ B64.encode bs)

View File

@@ -9,7 +9,6 @@ newtype Memory = Memory
{
memoryMaxPages :: Nullable Int
}
deriving Eq
instance JSON Memory where
showJSON (Memory max) =
@@ -27,7 +26,6 @@ data HTTPRequest = HTTPRequest
, headers :: Nullable [(String, String)]
, method :: Nullable String
}
deriving Eq
makeKV x =
object [(k, showJSON v) | (k, v) <- x]
@@ -57,7 +55,6 @@ data WasmFile = WasmFile
, fileName :: Nullable String
, fileHash :: Nullable String
}
deriving Eq
instance JSON WasmFile where
showJSON (WasmFile path name hash) =
@@ -83,7 +80,9 @@ data WasmData = WasmData
, dataName :: Nullable String
, dataHash :: Nullable String
}
deriving Eq
instance JSON WasmData where
showJSON (WasmData bytes name hash) =
@@ -111,7 +110,6 @@ data WasmURL = WasmURL
, urlName :: Nullable String
, urlHash :: Nullable String
}
deriving Eq
instance JSON WasmURL where
@@ -129,7 +127,7 @@ instance JSON WasmURL where
Just req -> Ok (WasmURL req name hash)
-- | Specifies where to get WASM module data
data Wasm = File WasmFile | Data WasmData | URL WasmURL deriving Eq
data Wasm = File WasmFile | Data WasmData | URL WasmURL
instance JSON Wasm where
showJSON x =

View File

@@ -1,69 +1,60 @@
module Extism (
module Extism,
module Extism.Manifest,
Function(..),
Plugin(..),
CancelHandle(..),
LogLevel(..),
Error(..),
Result(..),
toByteString,
fromByteString,
extismVersion,
plugin,
pluginFromManifest,
isValid,
setConfig,
setLogFile,
functionExists,
call,
cancelHandle,
cancel,
pluginID,
unwrap
ValType(..),
Val(..)
) where
import Data.Int
import Data.Word
import Control.Monad (void)
import Foreign.ForeignPtr
import Foreign.C.String
import Foreign.Ptr
import Foreign.Marshal.Array
import Foreign.Marshal.Alloc
import Foreign.Storable
import Foreign.StablePtr
import Foreign.Concurrent
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import Foreign.Marshal.Utils (copyBytes, moveBytes)
import Data.ByteString as B
import Data.ByteString.Internal (c2w, w2c)
import Data.ByteString.Unsafe (unsafeUseAsCString)
import qualified Text.JSON (encode, toJSObject, showJSON)
import Data.Bifunctor (second)
import Text.JSON (encode, toJSObject, showJSON)
import Extism.Manifest (Manifest, toString)
import Extism.Bindings
import qualified Data.UUID (UUID, fromByteString)
-- | Host function, see 'Extism.HostFunction.hostFunction'
data Function = Function (ForeignPtr ExtismFunction) (StablePtr ()) deriving Eq
-- | Context for managing plugins
newtype Context = Context (ForeignPtr ExtismContext)
-- | Host function
data Function = Function (ForeignPtr ExtismFunction) (StablePtr ())
-- | Plugins can be used to call WASM function
newtype Plugin = Plugin (ForeignPtr ExtismPlugin) deriving Eq
data Plugin = Plugin Context Int32 [Function]
-- | Cancellation handle for Plugins
newtype CancelHandle = CancelHandle (Ptr ExtismCancelHandle)
-- | Access the plugin that is currently executing from inside a host function
type CurrentPlugin = Ptr ExtismCurrentPlugin
-- | Log level
data LogLevel = LogError | LogWarn | LogInfo | LogDebug | LogTrace deriving (Show, Eq)
data LogLevel = Error | Warn | Info | Debug | Trace deriving (Show)
-- | Extism error
newtype Error = ExtismError String deriving (Show, Eq)
newtype Error = ExtismError String deriving Show
-- | Result type
type Result a = Either Error a
-- | Helper function to convert a 'String' to a 'ByteString'
toByteString :: String -> B.ByteString
toByteString x = B.pack (map c2w x)
toByteString :: String -> ByteString
toByteString x = B.pack (Prelude.map c2w x)
-- | Helper function to convert a 'ByteString' to a 'String'
fromByteString :: B.ByteString -> String
fromByteString bs = map w2c $ B.unpack bs
fromByteString :: ByteString -> String
fromByteString bs = Prelude.map w2c $ B.unpack bs
-- | Get the Extism version string
extismVersion :: () -> IO String
@@ -71,55 +62,119 @@ extismVersion () = do
v <- extism_version
peekCString v
-- | Remove all registered plugins in a 'Context'
reset :: Context -> IO ()
reset (Context ctx) =
withForeignPtr ctx extism_context_reset
-- | Create a new 'Context'
newContext :: IO Context
newContext = do
ptr <- extism_context_new
fptr <- Foreign.ForeignPtr.newForeignPtr extism_context_free ptr
return (Context fptr)
-- | Execute a function with a new 'Context' that is destroyed when it returns
withContext :: (Context -> IO a) -> IO a
withContext f = do
ctx <- newContext
f ctx
-- | Execute a function with the provided 'Plugin' as a parameter, then frees the 'Plugin'
-- | before returning the result.
withPlugin :: (Plugin -> IO a) -> Plugin -> IO a
withPlugin f plugin = do
res <- f plugin
free plugin
return res
-- | Create a 'Plugin' from a WASM module, `useWasi` determines if WASI should
-- | be linked
plugin :: B.ByteString -> [Function] -> Bool -> IO (Result Plugin)
plugin wasm functions useWasi =
let nfunctions = fromIntegral (length functions) in
plugin :: Context -> B.ByteString -> [Function] -> Bool -> IO (Result Plugin)
plugin c wasm functions useWasi =
let nfunctions = fromIntegral (Prelude.length functions) in
let length = fromIntegral (B.length wasm) in
let wasi = fromInteger (if useWasi then 1 else 0) in
let Context ctx = c in
do
funcs <- Prelude.mapM (\(Function ptr _) -> withForeignPtr ptr (\x -> do return x)) functions
withForeignPtr ctx (\ctx -> do
p <- unsafeUseAsCString wasm (\s ->
withArray funcs (\funcs ->
extism_plugin_new ctx (castPtr s) length funcs nfunctions wasi ))
if p < 0 then do
err <- extism_error ctx (-1)
e <- peekCString err
return $ Left (ExtismError e)
else
return $ Right (Plugin c p functions))
-- | Create a 'Plugin' with its own 'Context'
createPlugin :: B.ByteString -> [Function] -> Bool -> IO (Result Plugin)
createPlugin c functions useWasi = do
ctx <- newContext
plugin ctx c functions useWasi
-- | Create a 'Plugin' from a 'Manifest'
pluginFromManifest :: Context -> Manifest -> [Function] -> Bool -> IO (Result Plugin)
pluginFromManifest ctx manifest functions useWasi =
let wasm = toByteString $ toString manifest in
plugin ctx wasm functions useWasi
-- | Create a 'Plugin' with its own 'Context' from a 'Manifest'
createPluginFromManifest :: Manifest -> [Function] -> Bool -> IO (Result Plugin)
createPluginFromManifest manifest functions useWasi = do
ctx <- newContext
pluginFromManifest ctx manifest functions useWasi
-- | Update a 'Plugin' with a new WASM module
update :: Plugin -> B.ByteString -> [Function] -> Bool -> IO (Result Plugin)
update (Plugin (Context ctx) id _) wasm functions useWasi =
let nfunctions = fromIntegral (Prelude.length functions) in
let length = fromIntegral (B.length wasm) in
let wasi = fromInteger (if useWasi then 1 else 0) in
do
funcs <- mapM (\(Function ptr _) -> withForeignPtr ptr (\x -> do return x)) functions
alloca (\e-> do
let errmsg = (e :: Ptr CString)
p <- unsafeUseAsCString wasm (\s ->
funcs <- Prelude.mapM (\(Function ptr _ ) -> withForeignPtr ptr (\x -> do return x)) functions
withForeignPtr ctx (\ctx' -> do
b <- unsafeUseAsCString wasm (\s ->
withArray funcs (\funcs ->
extism_plugin_new (castPtr s) length funcs nfunctions wasi errmsg ))
if p == nullPtr then do
err <- peek errmsg
e <- peekCString err
extism_plugin_new_error_free err
extism_plugin_update ctx' id (castPtr s) length funcs nfunctions wasi))
if b <= 0 then do
err <- extism_error ctx' (-1)
e <- peekCString err
return $ Left (ExtismError e)
else do
ptr <- Foreign.Concurrent.newForeignPtr p (extism_plugin_free p)
return $ Right (Plugin ptr))
else
return (Right (Plugin (Context ctx) id functions)))
-- | Create a 'Plugin' from a 'Manifest'
pluginFromManifest :: Manifest -> [Function] -> Bool -> IO (Result Plugin)
pluginFromManifest manifest functions useWasi =
-- | Update a 'Plugin' with a new 'Manifest'
updateManifest :: Plugin -> Manifest -> [Function] -> Bool -> IO (Result Plugin)
updateManifest plugin manifest functions useWasi =
let wasm = toByteString $ toString manifest in
plugin wasm functions useWasi
update plugin wasm functions useWasi
-- | Check if a 'Plugin' is valid
isValid :: Plugin -> IO Bool
isValid (Plugin p) = withForeignPtr p (\x -> return (x /= nullPtr))
isValid :: Plugin -> Bool
isValid (Plugin _ p _) = p >= 0
-- | Set configuration values for a plugin
setConfig :: Plugin -> [(String, Maybe String)] -> IO Bool
setConfig (Plugin plugin) x =
let obj = Text.JSON.toJSObject [(k, Text.JSON.showJSON v) | (k, v) <- x] in
let bs = toByteString (Text.JSON.encode obj) in
let length = fromIntegral (B.length bs) in
unsafeUseAsCString bs (\s -> do
withForeignPtr plugin (\plugin-> do
b <- extism_plugin_config plugin (castPtr s) length
return $ b /= 0))
setConfig (Plugin (Context ctx) plugin _) x =
if plugin < 0
then return False
else
let obj = toJSObject [(k, showJSON v) | (k, v) <- x] in
let bs = toByteString (encode obj) in
let length = fromIntegral (B.length bs) in
unsafeUseAsCString bs (\s -> do
withForeignPtr ctx (\ctx -> do
b <- extism_plugin_config ctx plugin (castPtr s) length
return $ b /= 0))
levelStr LogError = "error"
levelStr LogDebug = "debug"
levelStr LogWarn = "warn"
levelStr LogTrace = "trace"
levelStr LogInfo = "info"
levelStr Error = "error"
levelStr Debug = "debug"
levelStr Warn = "warn"
levelStr Trace = "trace"
levelStr Info = "info"
-- | Set the log file and level, this is a global configuration
setLogFile :: String -> LogLevel -> IO Bool
@@ -132,46 +187,43 @@ setLogFile filename level =
-- | Check if a function exists in the given plugin
functionExists :: Plugin -> String -> IO Bool
functionExists (Plugin plugin) name = do
withForeignPtr plugin (\plugin -> do
b <- withCString name (extism_plugin_function_exists plugin)
functionExists (Plugin (Context ctx) plugin _) name = do
withForeignPtr ctx (\ctx -> do
b <- withCString name (extism_plugin_function_exists ctx plugin)
if b == 1 then return True else return False)
--- | Call a function provided by the given plugin
call :: Plugin -> String -> B.ByteString -> IO (Result B.ByteString)
call (Plugin plugin) name input =
call (Plugin (Context ctx) plugin _) name input =
let length = fromIntegral (B.length input) in
do
withForeignPtr plugin (\plugin -> do
withForeignPtr ctx (\ctx -> do
rc <- withCString name (\name ->
unsafeUseAsCString input (\input ->
extism_plugin_call plugin name (castPtr input) length))
err <- extism_error plugin
extism_plugin_call ctx plugin name (castPtr input) length))
err <- extism_error ctx plugin
if err /= nullPtr
then do e <- peekCString err
return $ Left (ExtismError e)
else if rc == 0
then do
length <- extism_plugin_output_length plugin
ptr <- extism_plugin_output_data plugin
buf <- B.packCStringLen (castPtr ptr, fromIntegral length)
length <- extism_plugin_output_length ctx plugin
ptr <- extism_plugin_output_data ctx plugin
buf <- packCStringLen (castPtr ptr, fromIntegral length)
return $ Right buf
else return $ Left (ExtismError "Call failed"))
-- | Call a function with a string argument and return a string
callString :: Plugin -> String -> String -> IO (Result String)
callString p name input = do
res <- call p name (toByteString input)
case res of
Left x -> return $ Left x
Right x -> return $ Right (fromByteString x)
-- | Free a 'Plugin', this will automatically be called for every plugin
-- | associated with a 'Context' when that 'Context' is freed
free :: Plugin -> IO ()
free (Plugin (Context ctx) plugin _) =
withForeignPtr ctx (`extism_plugin_free` plugin)
-- | Create a new 'CancelHandle' that can be used to cancel a running plugin
-- | from another thread.
cancelHandle :: Plugin -> IO CancelHandle
cancelHandle (Plugin plugin) = do
handle <- withForeignPtr plugin extism_plugin_cancel_handle
cancelHandle (Plugin (Context ctx) plugin _) = do
handle <- withForeignPtr ctx (`extism_plugin_cancel_handle` plugin)
return (CancelHandle handle)
-- | Cancel a running plugin using a 'CancelHandle'
@@ -179,16 +231,58 @@ cancel :: CancelHandle -> IO Bool
cancel (CancelHandle handle) =
extism_plugin_cancel handle
pluginID :: Plugin -> IO Data.UUID.UUID
pluginID (Plugin plugin) =
withForeignPtr plugin (\plugin -> do
ptr <- extism_plugin_id plugin
buf <- B.packCStringLen (castPtr ptr, 16)
case Data.UUID.fromByteString (BL.fromStrict buf) of
Nothing -> error "Invalid Plugin ID"
Just x -> return x)
unwrap (Right x) = x
unwrap (Left (ExtismError msg)) = do
error msg
-- | Create a new 'Function' that can be called from a 'Plugin'
hostFunction :: String -> [ValType] -> [ValType] -> (CurrentPlugin -> [Val] -> a -> IO [Val]) -> a -> IO Function
hostFunction name params results f v =
let nparams = fromIntegral $ Prelude.length params in
let nresults = fromIntegral $ Prelude.length results in
do
cb <- callbackWrap (callback f :: CCallback)
free <- freePtrWrap freePtr
userData <- newStablePtr (v, free, cb)
let userDataPtr = castStablePtrToPtr userData
x <- withCString name (\name -> do
withArray params (\params ->
withArray results (\results -> do
extism_function_new name params nparams results nresults cb userDataPtr free)))
let freeFn = extism_function_free x
fptr <- Foreign.Concurrent.newForeignPtr x freeFn
return $ Function fptr (castPtrToStablePtr userDataPtr)
-- | Create a new I32 'Val'
toI32 :: Integral a => a -> Val
toI32 x = ValI32 (fromIntegral x)
-- | Create a new I64 'Val'
toI64 :: Integral a => a -> Val
toI64 x = ValI64 (fromIntegral x)
-- | Create a new F32 'Val'
toF32 :: Float -> Val
toF32 = ValF32
-- | Create a new F64 'Val'
toF64 :: Double -> Val
toF64 = ValF64
-- | Get I32 'Val'
fromI32 :: Integral a => Val -> Maybe a
fromI32 (ValI32 x) = Just (fromIntegral x)
fromI32 _ = Nothing
-- | Get I64 'Val'
fromI64 :: Integral a => Val -> Maybe a
fromI64 (ValI64 x) = Just (fromIntegral x)
fromI64 _ = Nothing
-- | Get F32 'Val'
fromF32 :: Val -> Maybe Float
fromF32 (ValF32 x) = Just x
fromF32 _ = Nothing
-- | Get F64 'Val'
fromF64 :: Val -> Maybe Double
fromF64 (ValF64 x) = Just x
fromF64 _ = Nothing

View File

@@ -13,7 +13,7 @@ import Foreign.StablePtr
type FreeCallback = Ptr () -> IO ()
newtype ExtismPlugin = ExtismPlugin () deriving Show
newtype ExtismContext = ExtismContext () deriving Show
newtype ExtismFunction = ExtismFunction () deriving Show
newtype ExtismCancelHandle = ExtismCancelHandle () deriving Show
newtype ExtismCurrentPlugin = ExtismCurrentPlugin () deriving Show
@@ -79,19 +79,21 @@ instance Storable ValType where
poke ptr x = do
pokeByteOff ptr 0 (intOfValType x)
foreign import ccall safe "extism.h extism_plugin_new" extism_plugin_new :: Ptr Word8 -> Word64 -> Ptr (Ptr ExtismFunction) -> Word64 -> CBool -> Ptr CString -> IO (Ptr ExtismPlugin)
foreign import ccall safe "extism.h extism_plugin_call" extism_plugin_call :: Ptr ExtismPlugin -> CString -> Ptr Word8 -> Word64 -> IO Int32
foreign import ccall safe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismPlugin -> CString -> IO CBool
foreign import ccall safe "extism.h extism_plugin_error" extism_error :: Ptr ExtismPlugin -> IO CString
foreign import ccall safe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismPlugin -> IO Word64
foreign import ccall safe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismPlugin -> IO (Ptr Word8)
foreign import ccall safe "extism.h extism_context_new" extism_context_new :: IO (Ptr ExtismContext)
foreign import ccall safe "extism.h &extism_context_free" extism_context_free :: FunPtr (Ptr ExtismContext -> IO ())
foreign import ccall safe "extism.h extism_plugin_new" extism_plugin_new :: Ptr ExtismContext -> Ptr Word8 -> Word64 -> Ptr (Ptr ExtismFunction) -> Word64 -> CBool -> IO Int32
foreign import ccall safe "extism.h extism_plugin_update" extism_plugin_update :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Word64 -> Ptr (Ptr ExtismFunction) -> Word64 -> CBool -> IO CBool
foreign import ccall safe "extism.h extism_plugin_call" extism_plugin_call :: Ptr ExtismContext -> Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
foreign import ccall safe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismContext -> Int32 -> CString -> IO CBool
foreign import ccall safe "extism.h extism_error" extism_error :: Ptr ExtismContext -> Int32 -> IO CString
foreign import ccall safe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismContext -> Int32 -> IO Word64
foreign import ccall safe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismContext -> Int32 -> IO (Ptr Word8)
foreign import ccall safe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
foreign import ccall safe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismPlugin -> Ptr Word8 -> Int64 -> IO CBool
foreign import ccall safe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismPlugin -> IO ()
foreign import ccall safe "extism.h extism_plugin_new_error_free" extism_plugin_new_error_free :: CString -> IO ()
foreign import ccall safe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Int64 -> IO CBool
foreign import ccall safe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismContext -> Int32 -> IO ()
foreign import ccall safe "extism.h extism_context_reset" extism_context_reset :: Ptr ExtismContext -> IO ()
foreign import ccall safe "extism.h extism_version" extism_version :: IO CString
foreign import ccall safe "extism.h extism_plugin_id" extism_plugin_id :: Ptr ExtismPlugin -> IO (Ptr Word8)
foreign import ccall safe "extism.h extism_plugin_cancel_handle" extism_plugin_cancel_handle :: Ptr ExtismPlugin -> IO (Ptr ExtismCancelHandle)
foreign import ccall safe "extism.h extism_plugin_cancel_handle" extism_plugin_cancel_handle :: Ptr ExtismContext -> Int32 -> IO (Ptr ExtismCancelHandle)
foreign import ccall safe "extism.h extism_plugin_cancel" extism_plugin_cancel :: Ptr ExtismCancelHandle -> IO Bool
foreign import ccall safe "extism.h extism_function_new" extism_function_new :: CString -> Ptr ValType -> Word64 -> Ptr ValType -> Word64 -> FunPtr CCallback -> Ptr () -> FunPtr FreeCallback -> IO (Ptr ExtismFunction)

Some files were not shown because too many files have changed in this diff Show More