Compare commits

..

1 Commits

Author SHA1 Message Date
Steve Manuel
ce67aecf61 ci: add multiple version test strategy for java sdk 2023-01-10 09:14:37 -07:00
269 changed files with 9557 additions and 18081 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
@@ -9,7 +17,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
rust:
- stable
steps:

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
@@ -22,6 +34,6 @@ jobs:
- name: Test Go Host SDK
run: |
go version
LD_LIBRARY_PATH=/usr/local/lib go test
cd go
LD_LIBRARY_PATH=/usr/local/lib go run main.go | grep "Hello from Go!"
LD_LIBRARY_PATH=/usr/local/lib go run main.go
LD_LIBRARY_PATH=/usr/local/lib go test

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
@@ -10,7 +18,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
version: [11, 17]
version: [8, 11, 17]
rust:
- stable
steps:
@@ -25,5 +33,10 @@ jobs:
- name: Test Java
run: |
cd java
cat pom.xml | sed 's/<java.version>17/<java.version>${{ matrix.version }}/' > updated.xml
mv updated.xml pom.xml
mvn --batch-mode -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn verify
#- name: Examine logs
# if: success() || failure()
# run: |
# cat /tmp/extism.log

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
@@ -10,7 +18,6 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
zig_version: ["master"] # eventually use multiple versions once stable
rust:
- stable
steps:
@@ -19,8 +26,6 @@ jobs:
- uses: ./.github/actions/extism
- name: Setup Zig env
uses: goto-bus-stop/setup-zig@v2
with:
version: ${{ matrix.zig_version }}
- name: Test Zig Host SDK
run: |

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,8 +10,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
fetch-tags: true
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v3.0.3
with:
@@ -22,25 +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: Publish 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: |
dotnet pack .\dotnet\nuget\Nuget.sln -o release-artifacts
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

@@ -1,7 +1,7 @@
on:
workflow_dispatch:
name: Release Haskell SDK
name: Release Rust SDK
jobs:
release-sdks:
@@ -10,6 +10,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: cachix/haskell-release-action@v1
with:
- hackage-token: "${{ secrets.HACKAGE_TOKEN }}"

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,17 @@ jobs:
override: true
target: ${{ matrix.target }}
- name: Release Runtime
- name: Release Rust Host SDK
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
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,60 +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: '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
@@ -79,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 }}
@@ -150,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'

11
.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
@@ -32,18 +30,9 @@ rust/test.log
duniverse
_build
php/Extism.php
python/docs
dist-newstyle
.stack-work
vendor
zig/zig-*
zig/example-out/
zig/*.log
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,9 +1,8 @@
[workspace]
members = [
"extism-maturin",
"manifest",
"runtime",
"rust",
"libextism",
"convert"
"elixir/native/extism_nif"
]
exclude = ["kernel"]

View File

@@ -1,6 +1,5 @@
DEST?=/usr/local
SOEXT=so
AEXT=a
FEATURES?=default
DEFAULT_FEATURES?=yes
@@ -19,34 +18,23 @@ else
FEATURE_FLAGS=--features $(FEATURES)
endif
build:
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
sed -e "s%PREFIX%$(DEST)%" libextism/extism.pc.in > libextism/extism.pc
sed -e "s%PREFIX%$(DEST)%" libextism/extism-static.pc.in > libextism/extism-static.pc
bench:
@(cargo criterion || echo 'For nicer output use cargo-criterion: `cargo install cargo-criterion` - using `cargo bench`') && cargo bench
.PHONY: kernel
kernel:
cd kernel && bash build.sh
.PHONY: build
lint:
cargo clippy --release --no-deps --manifest-path runtime/Cargo.toml
build:
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
debug:
RUSTFLAGS=-g $(MAKE) build
install:
mkdir -p $(DEST)/lib $(DEST)/include $(DEST)/lib/pkgconfig
install runtime/extism.h $(DEST)/include/extism.h
install target/release/libextism.$(SOEXT) $(DEST)/lib/libextism.$(SOEXT)
install 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 runtime/extism.h $(DEST)/include
install target/release/libextism.$(SOEXT) $(DEST)/lib
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,31 +1,24 @@
### _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.
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.
**Please note:** this project still under active development. It's usable, but expect some rough edges while work is underway. 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.
[![Discord](https://img.shields.io/discord/1011124058408112148?color=%23404eed&label=Community%20Chat&logo=Discord&logoColor=%23404eed)](https://discord.gg/cx3usBCWnc)
# [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"/>
@@ -69,4 +62,5 @@ Extism is an open-source product from the team at:
</p>
_Reach out and tell us what you're building! We'd love to help._

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

@@ -103,11 +103,7 @@
}
async loadFunctions(url) {
let helloWorld = function(index){
console.log("Hello, " + this.allocator.getString(index));
return index;
};
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": url } ] }, {"hello_world": helloWorld});
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": url } ] })
let functions = Object.keys(await plugin.getExports())
console.log("funcs ", functions)
this.setState({functions})
@@ -139,13 +135,7 @@
async handleOnRun(e) {
e && e.preventDefault && e.preventDefault();
let helloWorld = function(index){
console.log("Hello, " + this.allocator.getString(index));
return index;
};
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": this.state.url } ] }, {
"hello_world": helloWorld
});
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": this.state.url } ] })
let result = await plugin.call(this.state.func_name, this.state.input)
let output = result
this.setState({output})

View File

@@ -1,5 +1,5 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
testEnvironment: 'node',
};

5305
browser/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@extism/runtime-browser",
"version": "0.3.1",
"version": "0.2.2",
"description": "Extism runtime in the browser",
"scripts": {
"build": "node build.js && tsc --emitDeclarationOnly --outDir dist",
@@ -23,9 +23,7 @@
"devDependencies": {
"@types/jest": "^29.2.2",
"esbuild": "^0.15.13",
"esbuild-jest": "^0.5.0",
"jest": "^29.2.2",
"jest-environment-jsdom": "^29.3.1",
"prettier": "^2.7.1",
"ts-jest": "^29.0.3",
"tslint": "^6.1.3",
@@ -34,6 +32,6 @@
"typescript": "^4.8.4"
},
"dependencies": {
"@bjorn3/browser_wasi_shim": "^0.2.7"
"@bjorn3/browser_wasi_shim": "^0.2.1"
}
}
}

View File

@@ -1,11 +1,11 @@
import { Manifest, PluginConfig, ManifestWasmFile, ManifestWasmData } from './manifest';
import { ExtismPlugin } from './plugin';
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
@@ -20,9 +20,9 @@ export default class ExtismContext {
* @param config - Config details for the plugin
* @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) {
async newPlugin(manifest: ManifestData, config?: PluginConfig) {
let moduleData: ArrayBuffer | null = null;
if (manifest instanceof ArrayBuffer) {
moduleData = manifest;
} else if ((manifest as Manifest).wasm) {
const wasmData = (manifest as Manifest).wasm;
@@ -40,6 +40,6 @@ export default class ExtismContext {
throw Error(`Unsure how to interpret manifest ${manifest}`);
}
return new ExtismPlugin(moduleData, functions, config);
return new ExtismPlugin(moduleData, config);
}
}

View File

@@ -8,17 +8,16 @@ function parse(bytes: Uint8Array): any {
describe('', () => {
it('can load and call a plugin', async () => {
// const data = fs.readFileSync(path.join(__dirname, '..', 'data', 'code.wasm'));
// const ctx = new ExtismContext();
// const plugin = await ctx.newPlugin({ wasm: [{ data: data }] });
// const functions = await plugin.getExports();
// expect(Object.keys(functions).filter((x) => !x.startsWith('__') && x !== 'memory')).toEqual(['count_vowels']);
// let output = await plugin.call('count_vowels', 'this is a test');
// expect(parse(output)).toEqual({ count: 4 });
// output = await plugin.call('count_vowels', 'this is a test again');
// expect(parse(output)).toEqual({ count: 7 });
// output = await plugin.call('count_vowels', 'this is a test thrice');
// expect(parse(output)).toEqual({ count: 6 });
expect(true).toEqual(true);
const data = fs.readFileSync(path.join(__dirname, '..', 'data', 'code.wasm'));
const ctx = new ExtismContext();
const plugin = await ctx.newPlugin({ wasm: [{ data: data }] });
const functions = await plugin.getExports();
expect(Object.keys(functions).filter((x) => !x.startsWith('__') && x !== 'memory')).toEqual(['count_vowels']);
let output = await plugin.call('count_vowels', 'this is a test');
expect(parse(output)).toEqual({ count: 4 });
output = await plugin.call('count_vowels', 'this is a test again');
expect(parse(output)).toEqual({ count: 7 });
output = await plugin.call('count_vowels', 'this is a test thrice');
expect(parse(output)).toEqual({ count: 6 });
});
});

View File

@@ -1,4 +1,3 @@
import ExtismContext from './context';
import { ExtismFunction, ExtismPlugin } from './plugin';
export { ExtismContext, ExtismFunction, ExtismPlugin };
export { ExtismContext };

View File

@@ -1,27 +1,24 @@
import Allocator from './allocator';
import { PluginConfig } from './manifest';
import { WASI, Fd } from '@bjorn3/browser_wasi_shim';
//@ts-ignore TODO add types to this library
import { WASI, File } from "@bjorn3/browser_wasi_shim";
export type ExtismFunction = any;
export class ExtismPlugin {
moduleData: ArrayBuffer | WebAssembly.Module;
export default class ExtismPlugin {
moduleData: ArrayBuffer;
allocator: Allocator;
config?: PluginConfig;
vars: Record<string, Uint8Array>;
input: Uint8Array;
output: Uint8Array;
module?: WebAssembly.WebAssemblyInstantiatedSource;
functions: Record<string, ExtismFunction>;
constructor(moduleData: ArrayBuffer | WebAssembly.Module, functions: Record<string, ExtismFunction> = {}, config?: PluginConfig) {
constructor(moduleData: ArrayBuffer, config?: PluginConfig) {
this.moduleData = moduleData;
this.allocator = new Allocator(1024 * 1024);
this.config = config;
this.vars = {};
this.input = new Uint8Array();
this.output = new Uint8Array();
this.functions = functions;
}
async getExports(): Promise<WebAssembly.Exports> {
@@ -68,45 +65,23 @@ export class ExtismPlugin {
const environment = this.makeEnv();
const args: Array<string> = [];
const envVars: Array<string> = [];
let fds: Fd[] = [
// new XtermStdio(term), // stdin
// new XtermStdio(term), // stdout
// new XtermStdio(term), // stderr
let fds = [
new File([]), // stdin
new File([]), // stdout
new File([]), // stderr
];
let wasi = new WASI(args, envVars, fds);
let env = {
wasi_snapshot_preview1: wasi.wasiImport,
env: environment,
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");
// normally we would call wasi.start here but it doesn't respect when there is
// no _start function
if (this.module.instance.exports._start) {
//@ts-ignore
this.module.instance.exports._start();
}
this.module = await WebAssembly.instantiate(this.moduleData, env);
return this.module;
}
makeEnv(): any {
const plugin = this;
var env: any = {
return {
extism_alloc(n: bigint): bigint {
return plugin.allocator.alloc(n);
},
@@ -205,13 +180,5 @@ export class ExtismPlugin {
console.error(s);
},
};
for (const [name, func] of Object.entries(this.functions)) {
env[name] = function () {
return func.apply(plugin, arguments);
};
}
return env;
}
}

View File

@@ -1,13 +1,13 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"strict": true,
"skipLibCheck": true,
"allowJs": true
},
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"strict": true,
"skipLibCheck": true,
"allowJs": true
},
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}

View File

@@ -1,2 +1,2 @@
build:
clang -g -o main main.c -lextism -L .
clang -o main main.c -lextism -L .

61
c/main.c Normal file
View File

@@ -0,0 +1,61 @@
#include "../runtime/extism.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
uint8_t *read_file(const char *filename, size_t *len) {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
return NULL;
}
fseek(fp, 0, SEEK_END);
size_t length = ftell(fp);
fseek(fp, 0, SEEK_SET);
uint8_t *data = malloc(length);
if (data == NULL) {
fclose(fp);
return NULL;
}
assert(fread(data, 1, length, fp) == length);
fclose(fp);
*len = length;
return data;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fputs("Not enough arguments\n", stderr);
exit(1);
}
ExtismContext *ctx = extism_context_new();
size_t len = 0;
uint8_t *data = read_file("../wasm/code.wasm", &len);
ExtismPlugin plugin = extism_plugin_new(ctx, data, len, false);
free(data);
if (plugin < 0) {
exit(1);
}
assert(extism_plugin_call(ctx, plugin, "count_vowels", (uint8_t *)argv[1],
strlen(argv[1])) == 0);
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(ctx, plugin);
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,28 +0,0 @@
[package]
name = "extism-convert"
version = "0.3.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.3"
prost = { version = "0.12.0", optional = true }
protobuf = { version = "3.3.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", "prost"]
msgpack = ["rmp-serde"]
prost = ["prost"]
protobuf = ["protobuf"]

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,140 +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 = "prost")]
pub struct Protobuf<T: prost::Message>(pub T);
#[cfg(feature = "prost")]
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 = "prost")]
impl<T: Default + prost::Message> FromBytesOwned for Protobuf<T> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Protobuf(T::decode(data)?))
}
}
/// Protobuf encoding
///
/// Allows for `protobuf` Protobuf messages to be used as arguments to Extism plugin calls
#[cfg(feature = "protobuf")]
pub struct Protobuf<T: protobuf::Message>(pub T);
#[cfg(feature = "protobuf")]
impl<'a, T: protobuf::Message> ToBytes<'a> for Protobuf<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.0.write_to_bytes()?)
}
}
#[cfg(feature = "protobuf")]
impl<'a, T: Default + protobuf::Message> FromBytesOwned for Protobuf<T> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Protobuf(T::parse_from_bytes(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

@@ -1,16 +1,15 @@
FLAGS=`pkg-config --cflags --libs jsoncpp gtest` -lextism -lpthread
build-example:
$(CXX) -std=c++14 -o example -I. example.cpp $(FLAGS)
$(CXX) -std=c++11 -o example -I. example.cpp $(FLAGS)
.PHONY: example
example: build-example
./example
build-test:
$(CXX) -std=c++14 -o test/test -I. test/test.cpp $(FLAGS)
$(CXX) -std=c++11 -o test/test -I. test/test.cpp $(FLAGS)
.PHONY: test
test: build-test
cd test && ./test

View File

@@ -14,26 +14,10 @@ std::vector<uint8_t> read(const char *filename) {
}
int main(int argc, char *argv[]) {
auto wasm = read("../wasm/code-functions.wasm");
std::string tmp = "Testing";
auto wasm = read("../wasm/code.wasm");
Context context = Context();
// A lambda can be used as a host function
auto hello_world = [&tmp](CurrentPlugin plugin,
const std::vector<Val> &inputs,
std::vector<Val> &outputs, void *user_data) {
std::cout << "Hello from C++" << std::endl;
std::cout << (const char *)user_data << std::endl;
std::cout << tmp << std::endl;
outputs[0].v = inputs[0].v;
};
std::vector<Function> functions = {
Function("hello_world", {ValType::I64}, {ValType::I64}, hello_world,
(void *)"Hello again!",
[](void *x) { std::cout << "Free user data" << std::endl; }),
};
Plugin plugin(wasm, true, functions);
Plugin plugin = context.plugin(wasm);
const char *input = argc > 1 ? argv[1] : "this is a test";
ExtismSize length = strlen(input);

View File

@@ -1,10 +1,7 @@
#pragma once
#include <cstring>
#include <functional>
#include <map>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
@@ -23,62 +20,27 @@ extern "C" {
namespace extism {
typedef std::map<std::string, std::string> Config;
template <typename T> class ManifestKey {
bool is_set = false;
public:
T value;
ManifestKey(T x, bool is_set = false) : is_set(is_set) { value = x; }
void set(T x) {
value = x;
is_set = true;
}
bool empty() const { return is_set == false; }
};
class Wasm {
std::string _path;
std::string _url;
// TODO: add base64 encoded raw data
ManifestKey<std::string> _hash =
ManifestKey<std::string>(std::string(), false);
public:
// Create Wasm pointing to a path
static Wasm path(std::string s, std::string hash = std::string()) {
Wasm w;
w._path = s;
if (!hash.empty()) {
w._hash.set(hash);
}
return w;
}
// Create Wasm pointing to a URL
static Wasm url(std::string s, std::string hash = std::string()) {
Wasm w;
w._url = s;
if (!hash.empty()) {
w._hash.set(hash);
}
return w;
}
std::string path;
std::string url;
// TODO: add base64 encoded raw data
std::string hash;
#ifndef EXTISM_NO_JSON
Json::Value json() const {
Json::Value doc;
if (!this->_path.empty()) {
doc["path"] = this->_path;
} else if (!this->_url.empty()) {
doc["url"] = this->_url;
if (!this->path.empty()) {
doc["path"] = this->path;
}
if (!this->_hash.empty()) {
doc["hash"] = this->_hash.value;
if (!this->url.empty()) {
doc["url"] = this->url;
}
if (!this->hash.empty()) {
doc["hash"] = this->hash;
}
return doc;
@@ -90,23 +52,18 @@ class Manifest {
public:
Config config;
std::vector<Wasm> wasm;
ManifestKey<std::vector<std::string>> allowed_hosts;
ManifestKey<std::map<std::string, std::string>> allowed_paths;
ManifestKey<uint64_t> timeout_ms;
std::vector<std::string> allowed_hosts;
std::map<std::string, std::string> allowed_paths;
uint64_t timeout_ms;
// Empty manifest
Manifest()
: timeout_ms(0, false), allowed_hosts(std::vector<std::string>(), false),
allowed_paths(std::map<std::string, std::string>(), false) {}
Manifest() : timeout_ms(30000) {}
// Create manifest with a single Wasm from a path
static Manifest path(std::string s, std::string hash = std::string()) {
Manifest m;
m.add_wasm_path(s, hash);
return m;
}
// Create manifest with a single Wasm from a URL
static Manifest url(std::string s, std::string hash = std::string()) {
Manifest m;
m.add_wasm_url(s, hash);
@@ -135,7 +92,7 @@ public:
if (!this->allowed_hosts.empty()) {
Json::Value h;
for (auto s : this->allowed_hosts.value) {
for (auto s : this->allowed_hosts) {
h.append(s);
}
doc["allowed_hosts"] = h;
@@ -143,63 +100,54 @@ public:
if (!this->allowed_paths.empty()) {
Json::Value h;
for (auto k : this->allowed_paths.value) {
for (auto k : this->allowed_paths) {
h[k.first] = k.second;
}
doc["allowed_paths"] = h;
}
if (!this->timeout_ms.empty()) {
doc["timeout_ms"] = Json::Value(this->timeout_ms.value);
}
doc["timeout_ms"] = Json::Value(this->timeout_ms);
Json::FastWriter writer;
return writer.write(doc);
}
#endif
// Add Wasm from path
void add_wasm_path(std::string s, std::string hash = std::string()) {
Wasm w = Wasm::path(s, hash);
Wasm w;
w.path = s;
w.hash = hash;
this->wasm.push_back(w);
}
// Add Wasm from URL
void add_wasm_url(std::string u, std::string hash = std::string()) {
Wasm w = Wasm::url(u, hash);
Wasm w;
w.url = u;
w.hash = hash;
this->wasm.push_back(w);
}
// Add host to allowed hosts
void allow_host(std::string host) {
if (this->allowed_hosts.empty()) {
this->allowed_hosts.set(std::vector<std::string>{});
}
this->allowed_hosts.value.push_back(host);
}
void allow_host(std::string host) { this->allowed_hosts.push_back(host); }
// Add path to allowed paths
void allow_path(std::string src, std::string dest = std::string()) {
if (this->allowed_paths.empty()) {
this->allowed_paths.set(std::map<std::string, std::string>{});
}
if (dest.empty()) {
dest = src;
}
this->allowed_paths.value[src] = dest;
this->allowed_paths[src] = dest;
}
// Set timeout
void set_timeout_ms(uint64_t ms) { this->timeout_ms = ms; }
// Set config key/value
void set_config(std::string k, std::string v) { this->config[k] = v; }
};
class Error : public std::runtime_error {
class Error : public std::exception {
private:
std::string message;
public:
Error(std::string msg) : std::runtime_error(msg) {}
Error(std::string msg) : message(msg) {}
const char *what() { return message.c_str(); }
};
class Buffer {
@@ -218,172 +166,63 @@ public:
}
};
typedef ExtismValType ValType;
typedef ExtismValUnion ValUnion;
typedef ExtismVal Val;
typedef uint64_t MemoryHandle;
class CurrentPlugin {
ExtismCurrentPlugin *pointer;
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) {
return extism_current_plugin_memory_length(this->pointer, offs);
}
MemoryHandle 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 returnString(Val &output, const std::string &s) {
this->returnBytes(output, (const uint8_t *)s.c_str(), s.size());
}
void returnBytes(Val &output, const uint8_t *bytes, size_t len) {
auto offs = this->alloc(len);
memcpy(this->memory() + offs, bytes, len);
output.v.i64 = offs;
}
uint8_t *inputBytes(Val &inp, size_t *length = nullptr) {
if (inp.t != ValType::I64) {
return nullptr;
}
if (length != nullptr) {
*length = this->memoryLength(inp.v.i64);
}
return this->memory() + inp.v.i64;
}
std::string inputString(Val &inp) {
size_t length = 0;
char *buf = (char *)this->inputBytes(inp, &length);
return std::string(buf, length);
}
};
typedef std::function<void(CurrentPlugin, const std::vector<Val> &,
std::vector<Val> &, void *user_data)>
FunctionType;
struct UserData {
FunctionType func;
void *user_data = NULL;
std::function<void(void *)> free_user_data;
};
static void function_callback(ExtismCurrentPlugin *plugin,
const ExtismVal *inputs, ExtismSize n_inputs,
ExtismVal *outputs, ExtismSize n_outputs,
void *user_data) {
UserData *data = (UserData *)user_data;
const std::vector<Val> inp(inputs, inputs + n_inputs);
std::vector<Val> outp(outputs, outputs + n_outputs);
data->func(CurrentPlugin(plugin), inp, outp, data->user_data);
for (ExtismSize i = 0; i < n_outputs; i++) {
outputs[i] = outp[i];
}
}
static void free_user_data(void *user_data) {
UserData *data = (UserData *)user_data;
if (data->user_data != NULL && data->free_user_data != NULL) {
data->free_user_data(data->user_data);
}
}
class Function {
std::shared_ptr<ExtismFunction> func;
std::string name;
UserData user_data;
public:
Function(std::string name, const std::vector<ValType> inputs,
const std::vector<ValType> outputs, FunctionType f,
void *user_data = NULL, std::function<void(void *)> free = nullptr)
: name(name) {
this->user_data.func = f;
this->user_data.user_data = user_data;
this->user_data.free_user_data = free;
auto ptr = extism_function_new(
this->name.c_str(), inputs.data(), inputs.size(), outputs.data(),
outputs.size(), function_callback, &this->user_data, free_user_data);
this->func = std::shared_ptr<ExtismFunction>(ptr, extism_function_free);
}
void setNamespace(std::string s) {
extism_function_set_namespace(this->func.get(), s.c_str());
}
Function(const Function &f) { this->func = f.func; }
ExtismFunction *get() { return this->func.get(); }
};
class CancelHandle {
const ExtismCancelHandle *handle;
public:
CancelHandle(const ExtismCancelHandle *x) : handle(x){};
bool cancel() { return extism_plugin_cancel(this->handle); }
};
class Plugin {
std::vector<Function> functions;
std::shared_ptr<ExtismContext> context;
ExtismPlugin plugin;
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>())
: functions(functions) {
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
Plugin(std::shared_ptr<ExtismContext> ctx, const uint8_t *wasm,
ExtismSize length, bool with_wasi = false) {
this->plugin = extism_plugin_new(ctx.get(), wasm, length, with_wasi);
if (this->plugin < 0) {
const char *err = extism_error(ctx.get(), -1);
throw Error(err == nullptr ? "Unable to load plugin" : err);
}
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);
}
}
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) {
}
Plugin(const std::vector<uint8_t> &data, bool with_wasi = false,
std::vector<Function> functions = {})
: Plugin(data.data(), data.size(), with_wasi, functions) {}
CancelHandle cancelHandle() {
return CancelHandle(extism_plugin_cancel_handle(this->plugin));
this->context = ctx;
}
#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) {}
Plugin(std::shared_ptr<ExtismContext> ctx, const Manifest &manifest,
bool with_wasi = false) {
auto buffer = manifest.json();
this->plugin = extism_plugin_new(ctx.get(), (const uint8_t *)buffer.c_str(),
buffer.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) {
bool b = extism_plugin_update(this->context.get(), this->plugin, wasm,
length, 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) {
auto buffer = manifest.json();
bool b = extism_plugin_update(this->context.get(), this->plugin,
(const uint8_t *)buffer.c_str(),
buffer.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 +239,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);
}
}
@@ -411,13 +251,12 @@ public:
this->config(json.c_str(), json.size());
}
// 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,34 +264,64 @@ 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);
}
// Call a plugin function with std::vector<uint8_t> input
Buffer call(const std::string &func,
const std::vector<uint8_t> &input) const {
return this->call(func, input.data(), input.size());
}
// Call a plugin function with string input
Buffer call(const std::string &func,
const std::string &input = std::string()) const {
Buffer call(const std::string &func, const std::string &input) const {
return this->call(func, (const uint8_t *)input.c_str(), input.size());
}
// 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());
}
};
// Set global log file for plugins
inline bool setLogFile(const char *filename, const char *level) {
class Context {
public:
std::shared_ptr<ExtismContext> pointer;
Context() {
this->pointer = std::shared_ptr<ExtismContext>(extism_context_new(),
extism_context_free);
}
Plugin plugin(const uint8_t *wasm, size_t length,
bool with_wasi = false) const {
return Plugin(this->pointer, wasm, length, with_wasi);
}
Plugin plugin(const std::string &str, bool with_wasi = false) const {
return Plugin(this->pointer, (const uint8_t *)str.c_str(), str.size(),
with_wasi);
}
Plugin plugin(const std::vector<uint8_t> &data,
bool with_wasi = false) const {
return Plugin(this->pointer, data.data(), data.size(), with_wasi);
}
#ifndef EXTISM_NO_JSON
Plugin plugin(const Manifest &manifest, bool with_wasi = false) const {
return Plugin(this->pointer, manifest, with_wasi);
}
#endif
void reset() { extism_context_reset(this->pointer.get()); }
};
inline bool set_log_file(const char *filename, const char *level) {
return extism_log_file(filename, level);
}
// Get libextism version
inline std::string version() { return std::string(extism_version()); }
} // namespace extism

BIN
cpp/test/code.wasm Executable file

Binary file not shown.

View File

@@ -1,7 +1,6 @@
#include "../extism.hpp"
#include <fstream>
#include <thread>
#include <gtest/gtest.h>
@@ -11,38 +10,46 @@ std::vector<uint8_t> read(const char *filename) {
std::istreambuf_iterator<char>());
}
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);
Context context;
Manifest manifest = Manifest::path("code.wasm");
manifest.set_config("a", "1");
Plugin plugin(manifest);
ASSERT_NO_THROW(Plugin plugin = context.plugin(manifest));
Plugin plugin = context.plugin(manifest);
Buffer buf = plugin.call("count_vowels", "this is a test");
ASSERT_EQ((std::string)buf, "{\"count\": 4}");
}
TEST(Plugin, BadManifest) {
Context context;
Manifest manifest;
ASSERT_THROW(Plugin plugin(manifest), Error);
ASSERT_THROW(Plugin plugin = context.plugin(manifest), Error);
}
TEST(Plugin, Bytes) {
auto wasm = read(code.c_str());
ASSERT_NO_THROW(Plugin plugin(wasm));
Plugin plugin(wasm);
Context context;
auto wasm = read("code.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) {
auto wasm = read(code.c_str());
Plugin plugin(wasm);
Context context;
auto wasm = read("code.wasm");
Plugin plugin = context.plugin(wasm);
Config config;
config["abc"] = "123";
@@ -50,63 +57,12 @@ TEST(Plugin, UpdateConfig) {
}
TEST(Plugin, FunctionExists) {
auto wasm = read(code.c_str());
Plugin plugin(wasm);
Context context;
auto wasm = read("code.wasm");
Plugin plugin = context.plugin(wasm);
ASSERT_FALSE(plugin.functionExists("bad_function"));
ASSERT_TRUE(plugin.functionExists("count_vowels"));
}
TEST(Plugin, HostFunction) {
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(4);
memcpy(plugin.memory() + offs, "test", 4);
results[0].v.i64 = (int64_t)offs;
});
auto functions = std::vector<Function>{
hello_world,
};
Plugin plugin(wasm, true, functions);
auto buf = plugin.call("count_vowels", "aaa");
ASSERT_EQ(buf.length, 4);
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();
}
ASSERT_FALSE(plugin.function_exists("bad_function"));
ASSERT_TRUE(plugin.function_exists("count_vowels"));
}
}; // namespace

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,28 +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>
</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,16 +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>
<IsPackable>true</IsPackable>
</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.2.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,66 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.runtime.linux-arm64", "Extism.runtime.linux-arm64.csproj", "{F1CF6818-43C4-4CD6-A3AD-748EFC83C21B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.runtime.linux-musl-arm64", "Extism.runtime.linux-musl-arm64.csproj", "{2E563F73-9FD5-42B2-9368-12BBF5819148}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.runtime.linux-x64", "Extism.runtime.linux-x64.csproj", "{A2E24D65-8AE2-4C09-82CB-5AEBA0539973}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.runtime.osx-arm64", "Extism.runtime.osx-arm64.csproj", "{66461A9C-140C-48C1-B658-BE540220460D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.runtime.osx-x64", "Extism.runtime.osx-x64.csproj", "{BE23CF82-F668-4166-8579-0FF2B20EA095}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.runtime.win-x64", "Extism.runtime.win-x64.csproj", "{639F9EFE-AC2A-43A7-A9BB-EDAEB306455F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.runtime.all", "Extism.runtime.all.csproj", "{88DF54A7-845B-409D-93FE-64CF2864FFDE}"
ProjectSection(ProjectDependencies) = postProject
{2E563F73-9FD5-42B2-9368-12BBF5819148} = {2E563F73-9FD5-42B2-9368-12BBF5819148}
{639F9EFE-AC2A-43A7-A9BB-EDAEB306455F} = {639F9EFE-AC2A-43A7-A9BB-EDAEB306455F}
{66461A9C-140C-48C1-B658-BE540220460D} = {66461A9C-140C-48C1-B658-BE540220460D}
{A2E24D65-8AE2-4C09-82CB-5AEBA0539973} = {A2E24D65-8AE2-4C09-82CB-5AEBA0539973}
{BE23CF82-F668-4166-8579-0FF2B20EA095} = {BE23CF82-F668-4166-8579-0FF2B20EA095}
{F1CF6818-43C4-4CD6-A3AD-748EFC83C21B} = {F1CF6818-43C4-4CD6-A3AD-748EFC83C21B}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F1CF6818-43C4-4CD6-A3AD-748EFC83C21B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F1CF6818-43C4-4CD6-A3AD-748EFC83C21B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F1CF6818-43C4-4CD6-A3AD-748EFC83C21B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F1CF6818-43C4-4CD6-A3AD-748EFC83C21B}.Release|Any CPU.Build.0 = Release|Any CPU
{2E563F73-9FD5-42B2-9368-12BBF5819148}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E563F73-9FD5-42B2-9368-12BBF5819148}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E563F73-9FD5-42B2-9368-12BBF5819148}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E563F73-9FD5-42B2-9368-12BBF5819148}.Release|Any CPU.Build.0 = Release|Any CPU
{A2E24D65-8AE2-4C09-82CB-5AEBA0539973}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A2E24D65-8AE2-4C09-82CB-5AEBA0539973}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2E24D65-8AE2-4C09-82CB-5AEBA0539973}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2E24D65-8AE2-4C09-82CB-5AEBA0539973}.Release|Any CPU.Build.0 = Release|Any CPU
{66461A9C-140C-48C1-B658-BE540220460D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{66461A9C-140C-48C1-B658-BE540220460D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66461A9C-140C-48C1-B658-BE540220460D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66461A9C-140C-48C1-B658-BE540220460D}.Release|Any CPU.Build.0 = Release|Any CPU
{BE23CF82-F668-4166-8579-0FF2B20EA095}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BE23CF82-F668-4166-8579-0FF2B20EA095}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE23CF82-F668-4166-8579-0FF2B20EA095}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE23CF82-F668-4166-8579-0FF2B20EA095}.Release|Any CPU.Build.0 = Release|Any CPU
{639F9EFE-AC2A-43A7-A9BB-EDAEB306455F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{639F9EFE-AC2A-43A7-A9BB-EDAEB306455F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{639F9EFE-AC2A-43A7-A9BB-EDAEB306455F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{639F9EFE-AC2A-43A7-A9BB-EDAEB306455F}.Release|Any CPU.Build.0 = Release|Any CPU
{88DF54A7-845B-409D-93FE-64CF2864FFDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{88DF54A7-845B-409D-93FE-64CF2864FFDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88DF54A7-845B-409D-93FE-64CF2864FFDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88DF54A7-845B-409D-93FE-64CF2864FFDE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

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

View File

@@ -1,29 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\..\wasm\code.wasm" Link="code.wasm">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\..\..\wasm\code-functions.wasm" Link="code-functions.wasm">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="..\..\..\wasm\code.wasm" Link="code.wasm">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Extism.runtime.win-x64" Version="0.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1,38 +1,11 @@
using Extism.Sdk;
using Extism.Sdk.Native;
using System.Runtime.InteropServices;
using System.Text;
var userData = Marshal.StringToHGlobalAnsi("Hello again!");
using var helloWorld = new HostFunction(
"hello_world",
"env",
new[] { ExtismValType.I64 },
new[] { ExtismValType.I64 },
userData,
HelloWorld);
void HelloWorld(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> outputs, nint data)
{
Console.WriteLine("Hello from .NET!");
var text = Marshal.PtrToStringAnsi(data);
Console.WriteLine(text);
var input = plugin.ReadString(new nint(inputs[0].v.i64));
Console.WriteLine($"Input: {input}");
outputs[0].v.i64 = plugin.WriteString(input);
}
var wasm = File.ReadAllBytes("./code-functions.wasm");
using var plugin = new Plugin(wasm, new[] { helloWorld }, withWasi: true);
var context = new Context();
var wasm = await File.ReadAllBytesAsync("./code.wasm");
using var plugin = context.CreatePlugin(wasm, withWasi: true);
var output = Encoding.UTF8.GetString(
plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World!"))
);
Console.WriteLine($"Output: {output}");
Console.WriteLine(output); // prints {"count": 3}

View File

@@ -0,0 +1,184 @@
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 class Context : IDisposable
{
private const int DisposedMarker = 1;
private int _disposed;
/// <summary>
/// Initialize a new Extism Context.
/// </summary>
public Context()
{
NativeHandle = LibExtism.extism_context_new();
}
/// <summary>
/// Native pointer to the Extism Context.
/// </summary>
internal IntPtr 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="withWasi">Enable/Disable WASI.</param>
public Plugin CreatePlugin(ReadOnlySpan<byte> wasm, bool withWasi)
{
CheckNotDisposed();
unsafe
{
fixed (byte* wasmPtr = wasm)
{
var plugin = LibExtism.extism_plugin_new(NativeHandle, wasmPtr, wasm.Length, withWasi);
return new Plugin(this, plugin);
}
}
}
/// <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
}
// 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);
}
}
/// <summary>
/// Extism Log Levels
/// </summary>
public enum LogLevel
{
/// <summary>
/// Designates very serious errors.
/// </summary>
Error,
/// <summary>
/// Designates hazardous situations.
/// </summary>
Warning,
/// <summary>
/// Designates useful information.
/// </summary>
Info,
/// <summary>
/// Designates lower priority information.
/// </summary>
Debug,
/// <summary>
/// Designates very low priority, often extremely verbose, information.
/// </summary>
Trace
}

View File

@@ -1,138 +0,0 @@
using Extism.Sdk.Native;
using System.Text;
namespace Extism.Sdk
{
/// <summary>
/// Represents the current plugin. Can only be used within <see cref="HostFunction"/>s.
/// </summary>
public class CurrentPlugin
{
internal CurrentPlugin(nint nativeHandle)
{
NativeHandle = nativeHandle;
}
internal nint NativeHandle { get; }
/// <summary>
/// Returns a pointer to the memory of the currently running plugin.
/// NOTE: this should only be called from host functions.
/// </summary>
/// <returns></returns>
public nint GetMemory()
{
return LibExtism.extism_current_plugin_memory(NativeHandle);
}
/// <summary>
/// Reads a string from a memory block using UTF8.
/// </summary>
/// <param name="pointer"></param>
/// <returns></returns>
public string ReadString(nint pointer)
{
return ReadString(pointer, Encoding.UTF8);
}
/// <summary>
/// Reads a string form a memory block.
/// </summary>
/// <param name="pointer"></param>
/// <param name="encoding"></param>
/// <returns></returns>
public string ReadString(nint pointer, Encoding encoding)
{
var buffer = ReadBytes(pointer);
return encoding.GetString(buffer);
}
/// <summary>
/// Returns a span of bytes for a given block.
/// </summary>
/// <param name="pointer"></param>
/// <returns></returns>
public unsafe Span<byte> ReadBytes(nint pointer)
{
var mem = GetMemory();
var length = (int)BlockLength(pointer);
var ptr = (byte*)mem + pointer;
return new Span<byte>(ptr, length);
}
/// <summary>
/// Writes a string into the current plugin memory using UTF-8 encoding and returns the pointer of the block.
/// </summary>
/// <param name="value"></param>
public nint WriteString(string value)
=> WriteString(value, Encoding.UTF8);
/// <summary>
/// Writes a string into the current plugin memory and returns the pointer of the block.
/// </summary>
/// <param name="value"></param>
/// <param name="encoding"></param>
public nint WriteString(string value, Encoding encoding)
{
var bytes = encoding.GetBytes(value);
var pointer = AllocateBlock(bytes.Length);
WriteBytes(pointer, bytes);
return pointer;
}
/// <summary>
/// Writes a byte array into a block of memory.
/// </summary>
/// <param name="pointer"></param>
/// <param name="bytes"></param>
public unsafe void WriteBytes(nint pointer, Span<byte> bytes)
{
var length = BlockLength(pointer);
if (length < bytes.Length)
{
throw new InvalidOperationException("Destination block length is less than source block length.");
}
var mem = GetMemory();
var ptr = (void*)(mem + pointer);
var destination = new Span<byte>(ptr, bytes.Length);
bytes.CopyTo(destination);
}
/// <summary>
/// Frees a block of memory belonging to the current plugin.
/// </summary>
/// <param name="pointer"></param>
public void FreeBlock(nint pointer)
{
LibExtism.extism_current_plugin_memory_free(NativeHandle, pointer);
}
/// <summary>
/// Allocate a memory block in the currently running plugin.
///
/// </summary>
/// <param name="length"></param>
/// <returns></returns>
public nint AllocateBlock(long length)
{
return LibExtism.extism_current_plugin_memory_alloc(NativeHandle, length);
}
/// <summary>
/// Get the length of an allocated block.
/// NOTE: this should only be called from host functions.
/// </summary>
/// <param name="pointer"></param>
/// <returns></returns>
public long BlockLength(nint pointer)
{
return LibExtism.extism_current_plugin_memory_length(NativeHandle, pointer);
}
}
}

View File

@@ -28,7 +28,7 @@ public class ExtismException : Exception
/// with a specified error message and a reference to the inner exception
/// that is the cause of this exception.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="message">The message that describes the error .</param>
/// <param name="innerException">
/// The exception that is the cause of the current exception, or a null reference
/// (Nothing in Visual Basic) if no inner exception is specified.

View File

@@ -1,28 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<LangVersion>10</LangVersion>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<LangVersion>10</LangVersion>
</PropertyGroup>
<PropertyGroup>
<PackageId>Extism.Sdk</PackageId>
<Version>0.7.0</Version>
<Authors>Extism Contributors</Authors>
<Description>Extism SDK that allows hosting Extism plugins in .NET apps.</Description>
<Tags>extism, wasm, plugin</Tags>
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<PropertyGroup>
<PackageId>Extism.Sdk</PackageId>
<Version>0.2.0</Version>
<Authors>Extism Contributors</Authors>
<Description>Extism SDK that allows hosting Extism plugins in .NET apps.</Description>
<Tags>extism, wasm, plugin</Tags>
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,146 +0,0 @@
using Extism.Sdk.Native;
using System.Diagnostics.CodeAnalysis;
namespace Extism.Sdk
{
/// <summary>
/// A host function signature.
/// </summary>
/// <param name="plugin">Plugin Index</param>
/// <param name="inputs">Input parameters</param>
/// <param name="outputs">Output parameters, the host function can change this.</param>
/// <param name="userData">A data passed in during Host Function creation.</param>
public delegate void ExtismFunction(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> outputs, IntPtr userData);
/// <summary>
/// A function provided by the host that plugins can call.
/// </summary>
public class HostFunction : IDisposable
{
private const int DisposedMarker = 1;
private int _disposed;
/// <summary>
/// Registers a Host Function.
/// </summary>
/// <param name="functionName">The literal name of the function, how it would be called from a <see cref="Plugin"/>.</param>
/// <param name="inputTypes">The types of the input arguments/parameters the <see cref="Plugin"/> caller will provide.</param>
/// <param name="outputTypes">The types of the output returned from the host function to the <see cref="Plugin"/>.</param>
/// <param name="userData">An opaque pointer to an object from the host, accessible to the <see cref="Plugin"/>.
/// NOTE: it is the shared responsibility of the host and <see cref="Plugin"/> to cast/dereference this value properly.</param>
/// <param name="hostFunction"></param>
public HostFunction(
string functionName,
Span<ExtismValType> inputTypes,
Span<ExtismValType> outputTypes,
IntPtr userData,
ExtismFunction hostFunction) :
this(functionName, "", inputTypes, outputTypes, userData, hostFunction)
{
}
/// <summary>
/// Registers a Host Function.
/// </summary>
/// <param name="functionName">The literal name of the function, how it would be called from a <see cref="Plugin"/>.</param>
/// <param name="namespace">Function namespace.</param>
/// <param name="inputTypes">The types of the input arguments/parameters the <see cref="Plugin"/> caller will provide.</param>
/// <param name="outputTypes">The types of the output returned from the host function to the <see cref="Plugin"/>.</param>
/// <param name="userData">An opaque pointer to an object from the host, accessible to the <see cref="Plugin"/>.
/// NOTE: it is the shared responsibility of the host and <see cref="Plugin"/> to cast/dereference this value properly.</param>
/// <param name="hostFunction"></param>
unsafe public HostFunction(
string functionName,
string @namespace,
Span<ExtismValType> inputTypes,
Span<ExtismValType> outputTypes,
IntPtr userData,
ExtismFunction hostFunction)
{
fixed (ExtismValType* inputs = inputTypes)
fixed (ExtismValType* outputs = outputTypes)
{
NativeHandle = LibExtism.extism_function_new(functionName, inputs, inputTypes.Length, outputs, outputTypes.Length, CallbackImpl, userData, IntPtr.Zero);
}
if (!string.IsNullOrEmpty(@namespace))
{
LibExtism.extism_function_set_namespace(NativeHandle, @namespace);
}
void CallbackImpl(
nint plugin,
ExtismVal* inputsPtr,
uint n_inputs,
ExtismVal* outputsPtr,
uint n_outputs,
IntPtr data)
{
var outputs = new Span<ExtismVal>(outputsPtr, (int)n_outputs);
var inputs = new Span<ExtismVal>(inputsPtr, (int)n_inputs);
hostFunction(new CurrentPlugin(plugin), inputs, outputs, data);
}
}
internal IntPtr NativeHandle { get; }
/// <summary>
/// Frees all resources held by this Host Function.
/// </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 Host Function 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(HostFunction));
}
/// <summary>
/// Frees all resources held by this Host Function.
/// </summary>
unsafe protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Free up any managed resources here
}
// Free up unmanaged resources
LibExtism.extism_function_free(NativeHandle);
}
/// <summary>
/// Destructs the current Host Function and frees all resources used by it.
/// </summary>
~HostFunction()
{
Dispose(false);
}
}
}

View File

@@ -2,256 +2,124 @@ using System.Runtime.InteropServices;
namespace Extism.Sdk.Native;
/// <summary>
/// A union type for host function argument/return values.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct ExtismValUnion
{
/// <summary>
/// Set this for 32 bit integers
/// </summary>
[FieldOffset(0)]
public int i32;
/// <summary>
/// Set this for 64 bit integers
/// </summary>
[FieldOffset(0)]
public long i64;
/// <summary>
/// Set this for 32 bit floats
/// </summary>
[FieldOffset(0)]
public float f32;
/// <summary>
/// Set this for 64 bit floats
/// </summary>
[FieldOffset(0)]
public double f64;
}
/// <summary>
/// Represents Wasm data types that Extism can understand
/// </summary>
public enum ExtismValType : byte
{
/// <summary>
/// Signed 32 bit integer. Equivalent of <see cref="int"/> or <see cref="uint"/>
/// </summary>
I32,
/// <summary>
/// Signed 64 bit integer. Equivalent of <see cref="long"/> or <see cref="ulong"/>
/// </summary>
I64,
/// <summary>
/// Floating point 32 bit integer. Equivalent of <see cref="float"/>
/// </summary>
F32,
/// <summary>
/// Floating point 64 bit integer. Equivalent of <see cref="double"/>
/// </summary>
F64,
/// <summary>
/// A 128 bit number.
/// </summary>
V128,
/// <summary>
/// A reference to opaque data in the Wasm instance.
/// </summary>
FuncRef,
/// <summary>
/// A reference to opaque data in the Wasm instance.
/// </summary>
ExternRef
}
/// <summary>
/// `ExtismVal` holds the type and value of a function argument/return
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ExtismVal
{
/// <summary>
/// The type for the argument
/// </summary>
public ExtismValType t;
/// <summary>
/// The value for the argument
/// </summary>
public ExtismValUnion v;
}
/// <summary>
/// Functions exposed by the native Extism library.
/// </summary>
internal static class LibExtism
{
/// <summary>
/// An Extism Plugin
/// Create a new context.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct ExtismPlugin { }
/// <returns>A pointer to the newly created context.</returns>
[DllImport("extism")]
public static extern IntPtr extism_context_new();
/// <summary>
/// Host function signature
/// Remove a context from the registry and free associated memory.
/// </summary>
/// <param name="plugin"></param>
/// <param name="inputs"></param>
/// <param name="n_inputs"></param>
/// <param name="outputs"></param>
/// <param name="n_outputs"></param>
/// <param name="data"></param>
unsafe internal delegate void InternalExtismFunction(nint plugin, ExtismVal* inputs, uint n_inputs, ExtismVal* outputs, uint n_outputs, IntPtr data);
/// <summary>
/// Returns a pointer to the memory of the currently running plugin.
/// NOTE: this should only be called from host functions.
/// </summary>
/// <param name="plugin"></param>
/// <returns></returns>
[DllImport("extism", EntryPoint = "extism_current_plugin_memory")]
internal static extern IntPtr extism_current_plugin_memory(nint plugin);
/// <summary>
/// Allocate a memory block in the currently running plugin
/// </summary>
/// <param name="plugin"></param>
/// <param name="n"></param>
/// <returns></returns>
[DllImport("extism", EntryPoint = "extism_current_plugin_memory_alloc")]
internal static extern IntPtr extism_current_plugin_memory_alloc(nint plugin, long n);
/// <summary>
/// Get the length of an allocated block.
/// NOTE: this should only be called from host functions.
/// </summary>
/// <param name="plugin"></param>
/// <param name="n"></param>
/// <returns></returns>
[DllImport("extism", EntryPoint = "extism_current_plugin_memory_length")]
internal static extern long extism_current_plugin_memory_length(nint plugin, long n);
/// <summary>
/// Get the length of an allocated block.
/// NOTE: this should only be called from host functions.
/// </summary>
/// <param name="plugin"></param>
/// <param name="ptr"></param>
[DllImport("extism", EntryPoint = "extism_current_plugin_memory_free")]
internal static extern void extism_current_plugin_memory_free(nint plugin, IntPtr ptr);
/// <summary>
/// Create a new host function.
/// </summary>
/// <param name="name">function name, this should be valid UTF-8</param>
/// <param name="inputs">argument types</param>
/// <param name="nInputs">number of argument types</param>
/// <param name="outputs">return types</param>
/// <param name="nOutputs">number of return types</param>
/// <param name="func">the function to call</param>
/// <param name="userData">a pointer that will be passed to the function when it's called this value should live as long as the function exists</param>
/// <param name="freeUserData">a callback to release the `user_data` value when the resulting `ExtismFunction` is freed.</param>
/// <returns></returns>
[DllImport("extism", EntryPoint = "extism_function_new")]
unsafe internal static extern IntPtr extism_function_new(string name, ExtismValType* inputs, long nInputs, ExtismValType* outputs, long nOutputs, InternalExtismFunction func, IntPtr userData, IntPtr freeUserData);
/// <summary>
/// Set the namespace of an <see cref="ExtismFunction"/>
/// </summary>
/// <param name="ptr"></param>
/// <param name="namespace"></param>
[DllImport("extism", EntryPoint = "extism_function_set_namespace")]
internal static extern void extism_function_set_namespace(IntPtr ptr, string @namespace);
/// <summary>
/// Free an <see cref="ExtismFunction"/>
/// </summary>
/// <param name="ptr"></param>
[DllImport("extism", EntryPoint = "extism_function_free")]
internal static extern void extism_function_free(IntPtr ptr);
/// <param name="context"></param>
[DllImport("extism")]
public static extern void extism_context_free(IntPtr 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>
/// <param name="nFunctions">Number of host functions.</param>
/// <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 public static extern IntPtr extism_plugin_new(IntPtr context, byte* wasm, int wasmSize, 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="wasmLength">The length of the `wasm` parameter.</param>
/// <param name="withWasi">Enables/disables WASI.</param>
/// <returns></returns>
[DllImport("extism")]
unsafe public static extern bool extism_plugin_update(IntPtr context, IntPtr plugin, byte* wasm, int wasmLength, 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);
public static extern void extism_plugin_free(IntPtr context, IntPtr plugin);
/// <summary>
/// Remove all plugins from the registry.
/// </summary>
/// <param name="context"></param>
[DllImport("extism")]
public static extern void extism_context_reset(IntPtr 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 public static extern bool extism_plugin_config(IntPtr context, IntPtr 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);
public static extern bool extism_plugin_function_exists(IntPtr context, IntPtr 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 public static extern int extism_plugin_call(IntPtr context, IntPtr 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);
public static extern IntPtr extism_error(IntPtr 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);
public static extern long extism_plugin_output_length(IntPtr context, IntPtr 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);
public static extern IntPtr extism_plugin_output_data(IntPtr context, IntPtr plugin);
/// <summary>
/// Set log file and level.
@@ -260,43 +128,43 @@ internal static class LibExtism
/// <param name="logLevel"></param>
/// <returns></returns>
[DllImport("extism")]
internal static extern bool extism_log_file(string filename, string logLevel);
public 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")]
public static extern IntPtr extism_version();
/// <summary>
/// Extism Log Levels
/// </summary>
internal static class LogLevels
public static class LogLevels
{
/// <summary>
/// Designates very serious errors.
/// </summary>
internal const string Error = "Error";
public const string Error = "Error";
/// <summary>
/// Designates hazardous situations.
/// </summary>
internal const string Warn = "Warn";
public const string Warn = "Warn";
/// <summary>
/// Designates useful information.
/// </summary>
internal const string Info = "Info";
public const string Info = "Info";
/// <summary>
/// Designates lower priority information.
/// </summary>
internal const string Debug = "Debug";
public const string Debug = "Debug";
/// <summary>
/// Designates very low priority, often extremely verbose, information.
/// </summary>
internal const string Trace = "Trace";
public const string Trace = "Trace";
}
}
}

View File

@@ -1,32 +0,0 @@
namespace Extism.Sdk.Native;
/// <summary>
/// Extism Log Levels
/// </summary>
public enum LogLevel
{
/// <summary>
/// Designates very serious errors.
/// </summary>
Error,
/// <summary>
/// Designates hazardous situations.
/// </summary>
Warning,
/// <summary>
/// Designates useful information.
/// </summary>
Info,
/// <summary>
/// Designates lower priority information.
/// </summary>
Debug,
/// <summary>
/// Designates very low priority, often extremely verbose, information.
/// </summary>
Trace
}

View File

@@ -6,43 +6,36 @@ 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 HostFunction[] _functions;
private readonly Context _context;
private int _disposed;
/// <summary>
/// Native pointer to the Extism Plugin.
/// </summary>
internal LibExtism.ExtismPlugin* NativeHandle { get; }
internal Plugin(Context context, IntPtr handle)
{
_context = context;
NativeHandle = handle;
}
/// <summary>
/// Create a and load a plug-in
/// A pointer to the native Plugin struct.
/// </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>
internal IntPtr NativeHandle { 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>
public Plugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi) {
_functions = functions;
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
unsafe public bool Update(ReadOnlySpan<byte> wasm, bool withWasi)
{
CheckNotDisposed();
unsafe
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, NativeHandle, wasmPtr, wasm.Length, withWasi);
}
}
@@ -56,18 +49,18 @@ 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, NativeHandle, jsonPtr, json.Length);
}
}
/// <summary>
/// Checks if a specific function exists in the current plugin.
/// </summary>
unsafe public bool FunctionExists(string name)
public bool FunctionExists(string name)
{
CheckNotDisposed();
return LibExtism.extism_plugin_function_exists(NativeHandle, name);
return LibExtism.extism_plugin_function_exists(_context.NativeHandle, NativeHandle, name);
}
/// <summary>
@@ -85,20 +78,14 @@ public unsafe class Plugin : IDisposable
fixed (byte* dataPtr = data)
{
int response = LibExtism.extism_plugin_call(NativeHandle, functionName, dataPtr, data.Length);
if (response == 0)
{
int response = LibExtism.extism_plugin_call(_context.NativeHandle, NativeHandle, functionName, dataPtr, data.Length);
if (response == 0) {
return OutputData();
}
else
{
} else {
var errorMsg = GetError();
if (errorMsg != null)
{
if (errorMsg != null) {
throw new ExtismException(errorMsg);
}
else
{
} else {
throw new ExtismException("Call to Extism failed");
}
}
@@ -109,11 +96,11 @@ public unsafe class Plugin : IDisposable
/// Get the length of a plugin's output data.
/// </summary>
/// <returns></returns>
unsafe internal int OutputLength()
internal int OutputLength()
{
CheckNotDisposed();
return (int)LibExtism.extism_plugin_output_length(NativeHandle);
return (int)LibExtism.extism_plugin_output_length(_context.NativeHandle, NativeHandle);
}
/// <summary>
@@ -127,7 +114,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, NativeHandle).ToPointer();
return new Span<byte>(ptr, length);
}
}
@@ -136,11 +123,11 @@ public unsafe class Plugin : IDisposable
/// Get the error associated with the current plugin.
/// </summary>
/// <returns></returns>
unsafe internal string? GetError()
internal string? GetError()
{
CheckNotDisposed();
var result = LibExtism.extism_plugin_error(NativeHandle);
var result = LibExtism.extism_error(_context.NativeHandle, NativeHandle);
return Marshal.PtrToStringUTF8(result);
}
@@ -181,7 +168,7 @@ public unsafe class Plugin : IDisposable
/// <summary>
/// Frees all resources held by this Plugin.
/// </summary>
unsafe protected virtual void Dispose(bool disposing)
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
@@ -189,7 +176,7 @@ public unsafe class Plugin : IDisposable
}
// Free up unmanaged resources
LibExtism.extism_plugin_free(NativeHandle);
LibExtism.extism_plugin_free(_context.NativeHandle, NativeHandle);
}
/// <summary>
@@ -199,4 +186,4 @@ public unsafe class Plugin : IDisposable
{
Dispose(false);
}
}
}

View File

@@ -1,7 +1,6 @@
using Extism.Sdk.Native;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Xunit;
@@ -13,46 +12,13 @@ public class BasicTests
[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 = new Plugin(wasm, Array.Empty<HostFunction>(), withWasi: true);
using var plugin = context.CreatePlugin(wasm, withWasi: true);
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
}
[Fact]
public void CountVowelsHostFunctions()
{
var userData = Marshal.StringToHGlobalAnsi("Hello again!");
using var helloWorld = new HostFunction(
"hello_world",
"env",
new[] { ExtismValType.I64 },
new[] { ExtismValType.I64 },
userData,
HelloWorld);
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);
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
void HelloWorld(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> outputs, nint data)
{
Console.WriteLine("Hello from .NET!");
var text = Marshal.PtrToStringAnsi(data);
Console.WriteLine(text);
var input = plugin.ReadString(new nint(inputs[0].v.i64));
Console.WriteLine($"Input: {input}");
var output = new string(input); // clone the string
outputs[0].v.i64 = plugin.WriteString(output);
}
}
}
}

View File

@@ -25,9 +25,6 @@
<None Include="..\..\..\wasm\code.wasm" Link="code.wasm">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\..\..\wasm\code-functions.wasm" Link="code-functions.wasm">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
@@ -35,8 +32,4 @@
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Extism.runtime.win-x64" Version="0.4.0" />
</ItemGroup>
</Project>

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

@@ -21,15 +21,13 @@
(description "Bindings to Extism, the universal plugin system")
(depends
(ocaml (>= 4.14.1))
dune
(ctypes (>= 0.18.0))
(dune (>= 3.2))
(ctypes-foreign (>= 0.18.0))
(bigstringaf (>= 0.9.0))
(ppx_yojson_conv (>= v0.15.0))
(extism-manifest (= :version))
(ppx_inline_test (>= v0.15.0))
(ppx_yojson_conv (>= 0.15.0))
extism-manifest
(ppx_inline_test (>= 0.15.0))
(cmdliner (>= 1.1.1))
(uuidm (>= 0.9.0))
)
(tags
(topics wasm plugin)))
@@ -37,12 +35,11 @@
(package
(name extism-manifest)
(synopsis "Extism manifest bindings")
(description "Bindings to the Extism manifest format")
(description "Bindings to Extism, the universal plugin system")
(depends
(ocaml (>= 4.14.1))
dune
(ppx_yojson_conv (>= v0.15.0))
(ppx_inline_test (>= v0.15.0))
(dune (>= 3.2))
(ppx_yojson_conv (>= 0.15.0))
(base64 (>= 3.5.0))
)
(tags

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

@@ -1,31 +0,0 @@
defmodule Extism.CancelHandle do
@moduledoc """
A CancelHandle is a handle generated by a plugin that allows it to be cancelled from another
thread while running.
"""
defstruct [
# The actual NIF Resource
handle: nil
]
def wrap_resource(handle) do
%__MODULE__{
handle: handle
}
end
@doc """
Cancel plugin execution
"""
def cancel(handle) do
Extism.Native.plugin_cancel(handle.handle)
end
end
defimpl Inspect, for: Extim.CancelHandle do
import Inspect.Algebra
def inspect(dict, opts) do
concat(["#Extism.CancelHandle<", to_doc(dict.handle, opts), ">"])
end
end

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,13 +7,15 @@ 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), do: error()
defp error, do: :erlang.nif_error(:nif_not_loaded)
end

View File

@@ -3,34 +3,24 @@ 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
{:ok, manifest_payload} = JSON.encode(manifest)
case Extism.Native.plugin_new_with_manifest(manifest_payload, wasi) do
{:error, err} -> {:error, err}
res -> {:ok, Extism.Plugin.wrap_resource(res)}
end
end
@doc """
Call a plugin's function by name
## Examples
iex> {:ok, plugin} = Extism.Plugin.new(manifest, false)
iex> {:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
iex> {:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
# {:ok, "{\"count\": 4}"}
@@ -46,24 +36,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 +86,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.1.0",
elixir: "~> 1.12",
start_permanent: Mix.env() == :prod,
deps: deps(),
@@ -23,7 +23,7 @@ defmodule Extism.MixProject do
defp deps do
[
{:rustler, "~> 0.29.1"},
{:rustler, "~> 0.26.0"},
{:json, "~> 1.4"},
{:ex_doc, "~> 0.21", only: :dev, runtime: false}
]

View File

@@ -1,12 +1,12 @@
%{
"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.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
"ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [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", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"},
"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"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
"rustler": {:hex, :rustler, "0.29.1", "880f20ae3027bd7945def6cea767f5257bc926f33ff50c0d5d5a5315883c084d", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "109497d701861bfcd26eb8f5801fe327a8eef304f56a5b63ef61151ff44ac9b6"},
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"rustler": {:hex, :rustler, "0.26.0", "06a2773d453ee3e9109efda643cf2ae633dedea709e2455ac42b83637c9249bf", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "42961e9d2083d004d5a53e111ad1f0c347efd9a05cb2eb2ffa1d037cdc74db91"},
"toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"},
}

View File

@@ -1,6 +1,6 @@
[package]
name = "extism_nif"
version = "0.3.0"
version = "0.1.0"
edition = "2021"
authors = ["Benjamin Eckel <bhelx@simst.im>"]
@@ -9,10 +9,7 @@ name = "extism_nif"
path = "src/lib.rs"
crate-type = ["cdylib"]
# need this to be here and be empty
[workspace]
[dependencies]
rustler = "0.28.0"
extism = {path = "../../../runtime"} # "0.5.0"
rustler = "0.26.0"
extism = { version = "0.1.0", path = "../../../rust" }
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,96 +14,114 @@ mod atoms {
}
}
struct ExtismPlugin {
plugin: RwLock<Option<Plugin>>,
}
unsafe impl Sync for ExtismPlugin {}
unsafe impl Send for ExtismPlugin {}
struct ExtismCancelHandle {
handle: RwLock<extism::CancelHandle>,
struct ExtismContext {
ctx: RwLock<Context>,
}
unsafe impl Sync for ExtismCancelHandle {}
unsafe impl Send for ExtismCancelHandle {}
unsafe impl Sync for ExtismContext {}
unsafe impl Send for ExtismContext {}
fn load(env: Env, _: Term) -> bool {
rustler::resource!(ExtismPlugin, env);
rustler::resource!(ExtismCancelHandle, env);
rustler::resource!(ExtismContext, env);
true
}
fn to_rustler_error(extism_error: extism::Error) -> rustler::Error {
rustler::Error::Term(Box::new(extism_error.to_string()))
match extism_error {
extism::Error::UnableToLoadPlugin(msg) => rustler::Error::Term(Box::new(msg)),
extism::Error::Message(msg) => rustler::Error::Term(Box::new(msg)),
extism::Error::Json(json_err) => rustler::Error::Term(Box::new(json_err.to_string())),
extism::Error::Runtime(e) => rustler::Error::Term(Box::new(e.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_cancel_handle(
plugin: ResourceArc<ExtismPlugin>,
) -> 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())
}
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: ResourceArc<ExtismCancelHandle>) -> bool {
handle.handle.read().unwrap().cancel().is_ok()
}
#[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,37 +133,43 @@ 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,
plugin_free,
set_log_file,
],

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