mirror of
https://github.com/extism/extism.git
synced 2026-01-11 23:08:06 -05:00
Compare commits
5 Commits
fix-dotnet
...
fix-releas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea1c153af4 | ||
|
|
9e30cd1932 | ||
|
|
3dccf9b5cf | ||
|
|
af090dbe4d | ||
|
|
832a644e11 |
22
.github/actions/extism/action.yml
vendored
22
.github/actions/extism/action.yml
vendored
@@ -7,24 +7,12 @@ runs:
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
- name: Download libextism
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Cache Rust environment
|
||||
uses: Swatinem/rust-cache@v1
|
||||
- name: Cache libextism
|
||||
id: cache-libextism
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/**
|
||||
key: ${{ runner.os }}-libextism-${{ hashFiles('runtime/**') }}-${{ hashFiles('manifest/**') }}-${{ hashFiles('libextism/**') }}
|
||||
- name: Build
|
||||
if: steps.cache-libextism.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: cargo build --release -p libextism
|
||||
name: libextism-${{ matrix.os }}
|
||||
- name: Install extism shared library
|
||||
shell: bash
|
||||
run: |
|
||||
sudo make install
|
||||
sudo cp libextism.* /usr/local/lib
|
||||
sudo cp runtime/extism.h /usr/local/include
|
||||
36
.github/workflows/browser-ci.yml
vendored
36
.github/workflows/browser-ci.yml
vendored
@@ -1,36 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-node.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- browser/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Browser CI
|
||||
|
||||
jobs:
|
||||
node:
|
||||
name: Browser
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Test Browser Runtime
|
||||
run: |
|
||||
cd browser
|
||||
npm i
|
||||
npm run test
|
||||
39
.github/workflows/ci-cpp.yml
vendored
39
.github/workflows/ci-cpp.yml
vendored
@@ -1,39 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-cpp.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- cpp/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: C++ CI
|
||||
|
||||
jobs:
|
||||
cpp:
|
||||
name: C++
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Install C++ SDK deps
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
run: |
|
||||
brew install jsoncpp googletest pkg-config
|
||||
- name: Install C++ SDK deps
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: |
|
||||
sudo apt-get install g++ libjsoncpp-dev libgtest-dev pkg-config
|
||||
- name: Run C++ tests
|
||||
run: |
|
||||
cd cpp
|
||||
LD_LIBRARY_PATH=/usr/local/lib make example
|
||||
LD_LIBRARY_PATH=/usr/local/lib make test
|
||||
34
.github/workflows/ci-dotnet.yml
vendored
34
.github/workflows/ci-dotnet.yml
vendored
@@ -1,34 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-dotnet.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- dotnet/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: .NET CI
|
||||
|
||||
jobs:
|
||||
dotnet:
|
||||
name: .NET
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup .NET Core SDK
|
||||
uses: actions/setup-dotnet@v3.0.3
|
||||
with:
|
||||
dotnet-version: 7.x
|
||||
- name: Test .NET Sdk
|
||||
run: |
|
||||
cd dotnet
|
||||
LD_LIBRARY_PATH=/usr/local/lib dotnet test ./Extism.sln
|
||||
41
.github/workflows/ci-elixir.yml
vendored
41
.github/workflows/ci-elixir.yml
vendored
@@ -1,41 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-elixir.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- rust/**
|
||||
- elixir/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Elixir CI
|
||||
|
||||
jobs:
|
||||
elixir:
|
||||
name: Elixir
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
MIX_ENV: test
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Elixir Host SDK
|
||||
if: ${{ runner.os != 'macOS' }}
|
||||
uses: erlef/setup-beam@v1
|
||||
with:
|
||||
experimental-otp: true
|
||||
otp-version: '25.0.4'
|
||||
elixir-version: '1.14.0'
|
||||
|
||||
- name: Test Elixir Host SDK
|
||||
if: ${{ runner.os != 'macOS' }}
|
||||
run: |
|
||||
cd elixir
|
||||
LD_LIBRARY_PATH=/usr/local/lib mix do deps.get, test
|
||||
39
.github/workflows/ci-go.yml
vendored
39
.github/workflows/ci-go.yml
vendored
@@ -1,39 +0,0 @@
|
||||
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
|
||||
|
||||
jobs:
|
||||
go:
|
||||
name: Go
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Go env
|
||||
uses: actions/setup-go@v3
|
||||
|
||||
- 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!"
|
||||
47
.github/workflows/ci-haskell.yml
vendored
47
.github/workflows/ci-haskell.yml
vendored
@@ -1,47 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-haskell.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- haskell/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Haskell CI
|
||||
|
||||
jobs:
|
||||
haskell:
|
||||
name: Haskell
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Haskell env
|
||||
uses: haskell/actions/setup@v2
|
||||
with:
|
||||
enable-stack: false
|
||||
- name: Cache Haskell
|
||||
id: cache-haskell
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./haskell/dist-newstyle
|
||||
key: ${{ runner.os }}-haskell-${{ hashFiles('haskell/**') }}
|
||||
- name: Build Haskell Host SDK
|
||||
if: steps.cache-haskell.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd haskell
|
||||
cabal update
|
||||
LD_LIBRARY_PATH=/usr/local/lib cabal build
|
||||
- name: Test Haskell SDK
|
||||
run: |
|
||||
cd haskell
|
||||
cabal update
|
||||
LD_LIBRARY_PATH=/usr/local/lib cabal test
|
||||
37
.github/workflows/ci-java.yml
vendored
37
.github/workflows/ci-java.yml
vendored
@@ -1,37 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-java.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- java/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Java CI
|
||||
|
||||
jobs:
|
||||
java:
|
||||
name: Java
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
version: [11, 17]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '${{ matrix.version }}'
|
||||
- name: Test Java
|
||||
run: |
|
||||
cd java
|
||||
mvn --batch-mode -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn verify
|
||||
|
||||
38
.github/workflows/ci-node.yml
vendored
38
.github/workflows/ci-node.yml
vendored
@@ -1,38 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-node.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- node/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Node CI
|
||||
|
||||
jobs:
|
||||
node:
|
||||
name: Node
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Test Node Host SDK
|
||||
run: |
|
||||
cd node
|
||||
npm i
|
||||
LD_LIBRARY_PATH=/usr/local/lib npm run build
|
||||
LD_LIBRARY_PATH=/usr/local/lib npm run example
|
||||
LD_LIBRARY_PATH=/usr/local/lib npm run test
|
||||
50
.github/workflows/ci-ocaml.yml
vendored
50
.github/workflows/ci-ocaml.yml
vendored
@@ -1,50 +0,0 @@
|
||||
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
|
||||
|
||||
jobs:
|
||||
ocaml:
|
||||
name: OCaml
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup OCaml env
|
||||
uses: ocaml/setup-ocaml@v2
|
||||
with:
|
||||
ocaml-compiler: ocaml-base-compiler.5.0.0
|
||||
- name: Cache OCaml
|
||||
id: cache-ocaml
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: _build
|
||||
key: ${{ runner.os }}-ocaml-${{ hashFiles('ocaml/**.ml') }}-${{ hashFiles('dune-project') }}
|
||||
- name: Build OCaml Host SDK
|
||||
if: steps.cache-ocaml.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
opam install -y --deps-only .
|
||||
cd ocaml
|
||||
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune build
|
||||
- name: Test OCaml Host SDK
|
||||
run: |
|
||||
opam install -y --deps-only .
|
||||
cd ocaml
|
||||
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune exec ./bin/main.exe ../wasm/code.wasm count_vowels -- --input "qwertyuiop"
|
||||
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune runtest
|
||||
42
.github/workflows/ci-php.yml
vendored
42
.github/workflows/ci-php.yml
vendored
@@ -1,42 +0,0 @@
|
||||
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
|
||||
|
||||
jobs:
|
||||
php:
|
||||
name: PHP
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup PHP env
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.1"
|
||||
extensions: ffi
|
||||
tools: composer
|
||||
env:
|
||||
fail-fast: true
|
||||
|
||||
- name: Test PHP SDK
|
||||
run: |
|
||||
cd php/example
|
||||
composer install
|
||||
php index.php
|
||||
40
.github/workflows/ci-python.yml
vendored
40
.github/workflows/ci-python.yml
vendored
@@ -1,40 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-python.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- python/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Python CI
|
||||
|
||||
jobs:
|
||||
python:
|
||||
name: Python
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Python env
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
check-latest: true
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
- name: Test Python Host SDK
|
||||
run: |
|
||||
cd python
|
||||
cp ../README.md .
|
||||
poetry install --no-dev
|
||||
poetry run python example.py
|
||||
poetry run python -m unittest discover
|
||||
38
.github/workflows/ci-ruby.yml
vendored
38
.github/workflows/ci-ruby.yml
vendored
@@ -1,38 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-ruby.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- ruby/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Ruby CI
|
||||
|
||||
jobs:
|
||||
ruby:
|
||||
name: Ruby
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Ruby env
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: "3.0"
|
||||
|
||||
- name: Test Ruby Host SDK
|
||||
run: |
|
||||
cd ruby
|
||||
bundle install
|
||||
ruby example.rb
|
||||
rake test
|
||||
|
||||
93
.github/workflows/ci-rust.yml
vendored
93
.github/workflows/ci-rust.yml
vendored
@@ -1,93 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-rust.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- rust/**
|
||||
- libextism/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Rust CI
|
||||
|
||||
env:
|
||||
RUNTIME_CRATE: extism
|
||||
LIBEXTISM_CRATE: libextism
|
||||
|
||||
jobs:
|
||||
lib:
|
||||
name: Extism runtime lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
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 libextism
|
||||
id: cache-libextism
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/release/libextism.*
|
||||
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-${{ env.GITHUB_SHA }}
|
||||
- name: Build
|
||||
if: steps.cache-libextism.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: cargo build --release -p ${{ env.LIBEXTISM_CRATE }}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: libextism-${{ matrix.os }}
|
||||
path: |
|
||||
target/release/libextism.*
|
||||
lint_and_test:
|
||||
name: Extism runtime lint and test
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
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-${{ 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 }}
|
||||
|
||||
37
.github/workflows/ci-zig.yml
vendored
37
.github/workflows/ci-zig.yml
vendored
@@ -1,37 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-zig.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- zig/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Zig CI
|
||||
|
||||
jobs:
|
||||
zig:
|
||||
name: Zig
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
zig_version: ["master"] # eventually use multiple versions once stable
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- 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: |
|
||||
zig version
|
||||
cd zig
|
||||
LD_LIBRARY_PATH=/usr/local/lib zig build test
|
||||
355
.github/workflows/ci.yml
vendored
355
.github/workflows/ci.yml
vendored
@@ -2,7 +2,361 @@ on: [pull_request, workflow_dispatch]
|
||||
|
||||
name: CI
|
||||
|
||||
env:
|
||||
RUNTIME_CRATE: extism-runtime
|
||||
LIBEXTISM_CRATE: libextism
|
||||
RUST_SDK_CRATE: extism
|
||||
|
||||
jobs:
|
||||
lib:
|
||||
name: Extism runtime lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
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 libextism
|
||||
id: cache-libextism
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/release/libextism.*
|
||||
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-${{ env.GITHUB_SHA }}
|
||||
- name: Build
|
||||
if: steps.cache-libextism.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: cargo build --release -p ${{ env.LIBEXTISM_CRATE }}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: libextism-${{ matrix.os }}
|
||||
path: |
|
||||
target/release/libextism.*
|
||||
lint_and_test:
|
||||
name: Extism runtime lint and test
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
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-${{ 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 --all-features --release -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
rust:
|
||||
name: Rust
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Test Rust Host SDK
|
||||
run: LD_LIBRARY_PATH=/usr/local/lib cargo test --release -p ${{ env.RUST_SDK_CRATE }}
|
||||
|
||||
elixir:
|
||||
name: Elixir
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
MIX_ENV: test
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Elixir Host SDK
|
||||
if: ${{ runner.os != 'macOS' }}
|
||||
uses: erlef/setup-beam@v1
|
||||
with:
|
||||
experimental-otp: true
|
||||
otp-version: '25.0.4'
|
||||
elixir-version: '1.14.0'
|
||||
|
||||
- name: Test Elixir Host SDK
|
||||
if: ${{ runner.os != 'macOS' }}
|
||||
run: |
|
||||
cd elixir
|
||||
LD_LIBRARY_PATH=/usr/local/lib mix do deps.get, test
|
||||
|
||||
go:
|
||||
name: Go
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Go env
|
||||
uses: actions/setup-go@v3
|
||||
|
||||
- name: Test Go Host SDK
|
||||
run: |
|
||||
go version
|
||||
cd go
|
||||
LD_LIBRARY_PATH=/usr/local/lib go run main.go
|
||||
LD_LIBRARY_PATH=/usr/local/lib go test
|
||||
|
||||
python:
|
||||
name: Python
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Python env
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
check-latest: true
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
- name: Test Python Host SDK
|
||||
run: |
|
||||
cd python
|
||||
cp ../README.md .
|
||||
poetry install
|
||||
poetry run python example.py
|
||||
poetry run python -m unittest discover
|
||||
|
||||
ruby:
|
||||
name: Ruby
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Ruby env
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: "3.0"
|
||||
|
||||
- name: Test Ruby Host SDK
|
||||
run: |
|
||||
cd ruby
|
||||
bundle install
|
||||
ruby example.rb
|
||||
rake test
|
||||
|
||||
node:
|
||||
name: Node
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Test Node Host SDK
|
||||
run: |
|
||||
cd node
|
||||
npm i
|
||||
LD_LIBRARY_PATH=/usr/local/lib npm run build
|
||||
LD_LIBRARY_PATH=/usr/local/lib npm run example
|
||||
LD_LIBRARY_PATH=/usr/local/lib npm run test
|
||||
|
||||
- name: Test Browser Runtime
|
||||
run: |
|
||||
cd browser
|
||||
npm i
|
||||
npm run test
|
||||
|
||||
ocaml:
|
||||
name: OCaml
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup OCaml env
|
||||
uses: ocaml/setup-ocaml@v2
|
||||
with:
|
||||
ocaml-compiler: ocaml-base-compiler.5.0.0~beta1
|
||||
- name: Cache OCaml
|
||||
id: cache-ocaml
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: _build
|
||||
key: ${{ runner.os }}-ocaml-${{ hashFiles('ocaml/lib/**') }}-${{ hashFiles('ocaml/bin/**') }}-${{ hashFiles('dune-project') }}
|
||||
- name: Build OCaml Host SDK
|
||||
if: steps.cache-ocaml.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
opam install -y --deps-only .
|
||||
cd ocaml
|
||||
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune build
|
||||
- name: Test OCaml Host SDK
|
||||
run: |
|
||||
opam install -y --deps-only .
|
||||
cd ocaml
|
||||
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune exec ./bin/main.exe
|
||||
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune runtest
|
||||
|
||||
haskell:
|
||||
name: Haskell
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Haskell env
|
||||
uses: haskell/actions/setup@v2
|
||||
with:
|
||||
enable-stack: true
|
||||
stack-version: "latest"
|
||||
- name: Cache Haskell
|
||||
id: cache-haskell
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .stack-work
|
||||
key: ${{ runner.os }}-haskell-${{ hashFiles('haskell/**') }}
|
||||
- name: Build Haskell Host SDK
|
||||
if: steps.cache-haskell.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd haskell
|
||||
LD_LIBRARY_PATH=/usr/local/lib stack build
|
||||
- name: Test Haskell SDK
|
||||
run: |
|
||||
cd haskell
|
||||
LD_LIBRARY_PATH=/usr/local/lib stack test
|
||||
|
||||
php:
|
||||
name: PHP
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup PHP env
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.1"
|
||||
extensions: ffi
|
||||
tools: composer
|
||||
env:
|
||||
fail-fast: true
|
||||
|
||||
- name: Test PHP SDK
|
||||
run: |
|
||||
cd php/example
|
||||
composer install
|
||||
php index.php
|
||||
|
||||
cpp:
|
||||
name: C++
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Install C++ SDK deps
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
run: |
|
||||
brew install jsoncpp googletest pkg-config
|
||||
- name: Install C++ SDK deps
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: |
|
||||
sudo apt-get install g++ libjsoncpp-dev libgtest-dev pkg-config
|
||||
- name: Run C++ tests
|
||||
run: |
|
||||
cd cpp
|
||||
LD_LIBRARY_PATH=/usr/local/lib make example
|
||||
LD_LIBRARY_PATH=/usr/local/lib make test
|
||||
|
||||
sdk_api_coverage:
|
||||
name: SDK API Coverage Report
|
||||
runs-on: ubuntu-latest
|
||||
@@ -22,4 +376,3 @@ jobs:
|
||||
id: coverage
|
||||
run: |
|
||||
python scripts/sdk_coverage.py
|
||||
|
||||
|
||||
29
.github/workflows/release-dotnet-native.yaml
vendored
29
.github/workflows/release-dotnet-native.yaml
vendored
@@ -1,29 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release .NET Native NuGet Packages
|
||||
|
||||
jobs:
|
||||
release-runtimes:
|
||||
name: release-dotnet-native
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup .NET Core SDK
|
||||
uses: actions/setup-dotnet@v3.0.3
|
||||
with:
|
||||
dotnet-version: 7.x
|
||||
- uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: release.yml
|
||||
name: release-artifacts
|
||||
- name: Extract Archive
|
||||
run: |
|
||||
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: |
|
||||
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 }}
|
||||
32
.github/workflows/release-dotnet.yaml
vendored
32
.github/workflows/release-dotnet.yaml
vendored
@@ -1,32 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release .NET SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-dotnet
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Install extism shared library
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p /home/runner/.local/bin/
|
||||
export PATH="/home/runner/.local/bin/:$PATH"
|
||||
curl https://raw.githubusercontent.com/extism/cli/main/install.sh | sh
|
||||
extism --sudo --prefix /usr/local install
|
||||
- name: Setup .NET Core SDK
|
||||
uses: actions/setup-dotnet@v3.0.3
|
||||
with:
|
||||
dotnet-version: 7.x
|
||||
- name: Test .NET Sdk
|
||||
run: |
|
||||
cd dotnet
|
||||
LD_LIBRARY_PATH=/usr/local/lib dotnet test ./Extism.sln
|
||||
- name: Publish .NET Sdk
|
||||
run: |
|
||||
cd dotnet/src/Extism.Sdk/
|
||||
dotnet pack -c Release
|
||||
dotnet nuget push --source https://api.nuget.org/v3/index.json ./bin/Release/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }}
|
||||
8
.github/workflows/release-elixir.yaml
vendored
8
.github/workflows/release-elixir.yaml
vendored
@@ -10,13 +10,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Install extism shared library
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p /home/runner/.local/bin/
|
||||
export PATH="/home/runner/.local/bin/:$PATH"
|
||||
curl https://raw.githubusercontent.com/extism/cli/main/install.sh | sh
|
||||
extism --sudo --prefix /usr/local install
|
||||
|
||||
- name: Setup Elixir Host SDK
|
||||
uses: erlef/setup-beam@v1
|
||||
with:
|
||||
|
||||
16
.github/workflows/release-haskell.yaml
vendored
16
.github/workflows/release-haskell.yaml
vendored
@@ -1,16 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Haskell SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-rust
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- uses: cachix/haskell-release-action@v1
|
||||
with:
|
||||
- hackage-token: "${{ secrets.HACKAGE_TOKEN }}"
|
||||
- work-dir: ./haskell
|
||||
22
.github/workflows/release-java.yml
vendored
22
.github/workflows/release-java.yml
vendored
@@ -1,22 +0,0 @@
|
||||
name: Publish package to the Maven Central Repository
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
- name: Publish package
|
||||
env:
|
||||
JRELEASER_NEXUS2_USERNAME: ${{ secrets.JRELEASER_NEXUS2_USERNAME }}
|
||||
JRELEASER_NEXUS2_PASSWORD: ${{ secrets.JRELEASER_NEXUS2_PASSWORD }}
|
||||
JRELEASER_GPG_PASSPHRASE: ${{ secrets.JRELEASER_GPG_PASSPHRASE }}
|
||||
JRELEASER_GPG_SECRET_KEY: ${{ secrets.JRELEASER_GPG_SECRET_KEY }}
|
||||
JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.JRELEASER_GPG_PUBLIC_KEY }}
|
||||
JRELEASER_GITHUB_TOKEN: "dummy"
|
||||
run: mvn -Prelease clean jreleaser:prepare deploy jreleaser:deploy -DaltDeploymentRepository=local::default::file:./target/staging-deploy
|
||||
3
.github/workflows/release-python.yaml
vendored
3
.github/workflows/release-python.yaml
vendored
@@ -23,8 +23,9 @@ jobs:
|
||||
run: |
|
||||
cd python
|
||||
cp ../LICENSE .
|
||||
cp ../README.md .
|
||||
make clean
|
||||
poetry install --no-dev
|
||||
make prepare
|
||||
poetry build
|
||||
|
||||
- name: Release Python Host SDK
|
||||
|
||||
2
.github/workflows/release-ruby.yaml
vendored
2
.github/workflows/release-ruby.yaml
vendored
@@ -21,5 +21,5 @@ jobs:
|
||||
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_TOKEN }}
|
||||
run: |
|
||||
cd ruby
|
||||
make publish RUBYGEMS_API_KEY=$RUBYGEMS_API_KEY
|
||||
make publish
|
||||
|
||||
10
.github/workflows/release-rust.yaml
vendored
10
.github/workflows/release-rust.yaml
vendored
@@ -19,19 +19,17 @@ jobs:
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Release Rust Manifest Crate
|
||||
- name: Release Rust Host SDK
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
|
||||
run: |
|
||||
# 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 10
|
||||
sleep 5
|
||||
|
||||
- name: Release Runtime
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
|
||||
run: |
|
||||
cargo publish --manifest-path runtime/Cargo.toml --no-verify
|
||||
cargo publish --manifest-path rust/Cargo.toml
|
||||
|
||||
|
||||
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -27,18 +27,9 @@ ruby/tmp/
|
||||
ruby/Gemfile.lock
|
||||
rust/target
|
||||
rust/test.log
|
||||
duniverse
|
||||
_build
|
||||
ocaml/duniverse
|
||||
ocaml/_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
|
||||
|
||||
vendor
|
||||
@@ -1 +0,0 @@
|
||||
version = 0.26.0
|
||||
@@ -2,6 +2,9 @@
|
||||
members = [
|
||||
"manifest",
|
||||
"runtime",
|
||||
"libextism",
|
||||
"rust",
|
||||
"libextism"
|
||||
]
|
||||
exclude = [
|
||||
"elixir/native/extism_nif"
|
||||
]
|
||||
exclude = ["kernel"]
|
||||
|
||||
15
Makefile
15
Makefile
@@ -18,23 +18,18 @@ else
|
||||
FEATURE_FLAGS=--features $(FEATURES)
|
||||
endif
|
||||
|
||||
build:
|
||||
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
|
||||
|
||||
.PHONY: kernel
|
||||
kernel:
|
||||
cd kernel && bash build.sh
|
||||
.PHONY: build
|
||||
|
||||
lint:
|
||||
cargo clippy --release --no-deps --manifest-path runtime/Cargo.toml
|
||||
|
||||
debug:
|
||||
RUSTFLAGS=-g $(MAKE) build
|
||||
build:
|
||||
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
|
||||
|
||||
install:
|
||||
mkdir -p $(DEST)/lib $(DEST)/include
|
||||
install runtime/extism.h $(DEST)/include/extism.h
|
||||
install target/release/libextism.$(SOEXT) $(DEST)/lib/libextism.$(SOEXT)
|
||||
install runtime/extism.h $(DEST)/include
|
||||
install target/release/libextism.$(SOEXT) $(DEST)/lib
|
||||
|
||||
uninstall:
|
||||
rm -f $(DEST)/include/extism.h $(DEST)/lib/libextism.$(SOEXT)
|
||||
|
||||
18
README.md
18
README.md
@@ -1,8 +1,6 @@
|
||||
### _Welcome!_
|
||||
|
||||
**Please note:** This project still under active development and APIs may change until we hit v1.0.
|
||||
|
||||
If you're interested in working on or building with Extism, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know - we are happy to help get you started.
|
||||
**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.
|
||||
|
||||
[](https://discord.gg/cx3usBCWnc)
|
||||
|
||||
@@ -12,21 +10,16 @@ The universal plug-in system. Run WebAssembly extensions inside your app. Use id
|
||||
[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) & more (others coming soon).
|
||||
[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) & more (others coming soon).
|
||||
|
||||
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).
|
||||
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).
|
||||
|
||||
<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"/>
|
||||
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/200043015-ddfe5833-0252-43a8-bc9e-5b3f829c37d1.png" alt="Extism embedded SDK language support"/>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
Add a flexible, secure, and _bLaZiNg FaSt_ plug-in system to your project. Server, desktop, mobile, web, database -- you name it. Enable users to write and execute safe extensions to your software in **3 easy steps:**
|
||||
|
||||
### 1. Import
|
||||
@@ -64,4 +57,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._
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
.PHONY: test
|
||||
|
||||
prepare:
|
||||
npm install
|
||||
|
||||
build:
|
||||
npm run build
|
||||
|
||||
test: prepare
|
||||
npm run test
|
||||
|
||||
clean:
|
||||
echo "No clean implemented"
|
||||
|
||||
publish: clean prepare build
|
||||
npm publish
|
||||
|
||||
format:
|
||||
npx prettier --write src
|
||||
|
||||
lint:
|
||||
npx prettier --check src
|
||||
|
||||
docs:
|
||||
npx typedoc --out doc src
|
||||
|
||||
show-docs: docs
|
||||
open doc/index.html
|
||||
@@ -1,12 +1,12 @@
|
||||
const { build } = require("esbuild");
|
||||
const { peerDependencies } = require('./package.json')
|
||||
const { dependencies, peerDependencies } = require('./package.json')
|
||||
|
||||
const sharedConfig = {
|
||||
entryPoints: ["src/index.ts"],
|
||||
bundle: true,
|
||||
minify: false,
|
||||
drop: [], // preseve debugger statements
|
||||
external: Object.keys(peerDependencies || {}),
|
||||
external: Object.keys(dependencies || {}).concat(Object.keys(peerDependencies || {})),
|
||||
};
|
||||
|
||||
build({
|
||||
|
||||
@@ -103,12 +103,8 @@
|
||||
}
|
||||
|
||||
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 functions = Object.keys(await plugin.getExports())
|
||||
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": url } ] })
|
||||
let functions = await plugin.getExportedFunctions()
|
||||
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})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'jsdom',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
|
||||
5723
browser/package-lock.json
generated
5723
browser/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@extism/runtime-browser",
|
||||
"version": "0.3.0",
|
||||
"version": "0.0.1",
|
||||
"description": "Extism runtime in the browser",
|
||||
"scripts": {
|
||||
"build": "node build.js && tsc --emitDeclarationOnly --outDir dist",
|
||||
@@ -17,23 +17,18 @@
|
||||
],
|
||||
"module": "dist/index.esm.js",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/src/index.d.ts",
|
||||
"typings": "dist/index.d.ts",
|
||||
"author": "The Extism Authors <oss@extism.org>",
|
||||
"license": "BSD-3-Clause",
|
||||
"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",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typedoc": "^0.23.20",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bjorn3/browser_wasi_shim": "^0.2.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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.
|
||||
@@ -20,7 +20,7 @@ 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) {
|
||||
async newPlugin(manifest: ManifestData, config?: PluginConfig) {
|
||||
let moduleData: ArrayBuffer | null = null;
|
||||
if (manifest instanceof ArrayBuffer) {
|
||||
moduleData = manifest;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ExtismContext from './context';
|
||||
import { ExtismFunction, ExtismPlugin } from './plugin';
|
||||
|
||||
export { ExtismContext, ExtismFunction, ExtismPlugin };
|
||||
export { ExtismContext };
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import Allocator from './allocator';
|
||||
import { PluginConfig } from './manifest';
|
||||
import { WASI, Fd } from '@bjorn3/browser_wasi_shim';
|
||||
|
||||
export type ExtismFunction = any;
|
||||
|
||||
export class ExtismPlugin {
|
||||
export default class ExtismPlugin {
|
||||
moduleData: ArrayBuffer;
|
||||
allocator: Allocator;
|
||||
config?: PluginConfig;
|
||||
@@ -12,16 +9,14 @@ export class ExtismPlugin {
|
||||
input: Uint8Array;
|
||||
output: Uint8Array;
|
||||
module?: WebAssembly.WebAssemblyInstantiatedSource;
|
||||
functions: Record<string, ExtismFunction>;
|
||||
|
||||
constructor(moduleData: ArrayBuffer, 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> {
|
||||
@@ -66,33 +61,13 @@ export class ExtismPlugin {
|
||||
return this.module;
|
||||
}
|
||||
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 wasi = new WASI(args, envVars, fds);
|
||||
let env = {
|
||||
wasi_snapshot_preview1: wasi.wasiImport,
|
||||
env: environment,
|
||||
};
|
||||
this.module = await WebAssembly.instantiate(this.moduleData, env);
|
||||
// normally we would call wasi.start here but it doesn't respect when there is
|
||||
// no _start function
|
||||
//@ts-ignore
|
||||
wasi.inst = this.module.instance;
|
||||
if (this.module.instance.exports._start) {
|
||||
//@ts-ignore
|
||||
this.module.instance.exports._start();
|
||||
}
|
||||
this.module = await WebAssembly.instantiate(this.moduleData, { env: environment });
|
||||
return this.module;
|
||||
}
|
||||
|
||||
makeEnv(): any {
|
||||
const plugin = this;
|
||||
var env: any = {
|
||||
return {
|
||||
extism_alloc(n: bigint): bigint {
|
||||
return plugin.allocator.alloc(n);
|
||||
},
|
||||
@@ -191,13 +166,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
{
|
||||
"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
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
build:
|
||||
clang -g -o main main.c -lextism -L .
|
||||
clang -o main main.c -lextism -L .
|
||||
43
c/main.c
43
c/main.c
@@ -9,21 +9,6 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void hello_world(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
|
||||
uint64_t n_inputs, ExtismVal *outputs, uint64_t n_outputs,
|
||||
void *data) {
|
||||
puts("Hello from C!");
|
||||
puts(data);
|
||||
|
||||
ExtismSize ptr_offs = inputs[0].v.i64;
|
||||
|
||||
uint8_t *buf = extism_current_plugin_memory(plugin) + ptr_offs;
|
||||
uint64_t length = extism_current_plugin_memory_length(plugin, ptr_offs);
|
||||
fwrite(buf, length, 1, stdout);
|
||||
fputc('\n', stdout);
|
||||
outputs[0].v.i64 = inputs[0].v.i64;
|
||||
}
|
||||
|
||||
uint8_t *read_file(const char *filename, size_t *len) {
|
||||
|
||||
FILE *fp = fopen(filename, "rb");
|
||||
@@ -36,7 +21,6 @@ uint8_t *read_file(const char *filename, size_t *len) {
|
||||
|
||||
uint8_t *data = malloc(length);
|
||||
if (data == NULL) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -53,29 +37,24 @@ int main(int argc, char *argv[]) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
uint8_t *data = read_file("../wasm/code-functions.wasm", &len);
|
||||
ExtismValType inputs[] = {I64};
|
||||
ExtismValType outputs[] = {I64};
|
||||
ExtismFunction *f = extism_function_new("hello_world", inputs, 1, outputs, 1,
|
||||
hello_world, "Hello, again!", NULL);
|
||||
ExtismContext *ctx = extism_context_new();
|
||||
|
||||
char *errmsg = NULL;
|
||||
ExtismPlugin *plugin = extism_plugin_new(
|
||||
data, len, (const ExtismFunction **)&f, 1, true, &errmsg);
|
||||
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 == NULL) {
|
||||
puts(errmsg);
|
||||
extism_plugin_new_error_free(errmsg);
|
||||
if (plugin < 0) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
assert(extism_plugin_call(plugin, "count_vowels", (uint8_t *)argv[1],
|
||||
assert(extism_plugin_call(ctx, plugin, "count_vowels", (uint8_t *)argv[1],
|
||||
strlen(argv[1])) == 0);
|
||||
ExtismSize out_len = extism_plugin_output_length(plugin);
|
||||
const uint8_t *output = extism_plugin_output_data(plugin);
|
||||
ExtismSize out_len = extism_plugin_output_length(ctx, plugin);
|
||||
const uint8_t *output = extism_plugin_output_data(ctx, plugin);
|
||||
write(STDOUT_FILENO, output, out_len);
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
extism_plugin_free(plugin);
|
||||
|
||||
extism_plugin_free(ctx, plugin);
|
||||
extism_context_free(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
},
|
||||
"files": [
|
||||
"php/src/Context.php",
|
||||
"php/src/Plugin.php"
|
||||
"php/src/Plugin.php",
|
||||
"php/src/extism.h"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
399
cpp/extism.hpp
399
cpp/extism.hpp
@@ -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,14 @@ 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;
|
||||
|
||||
// Empty manifest
|
||||
Manifest()
|
||||
: timeout_ms(0, false), allowed_hosts(std::vector<std::string>(), false),
|
||||
allowed_paths(std::map<std::string, std::string>(), false) {}
|
||||
|
||||
// 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,71 +88,43 @@ 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;
|
||||
}
|
||||
|
||||
if (!this->allowed_paths.empty()) {
|
||||
Json::Value h;
|
||||
for (auto k : this->allowed_paths.value) {
|
||||
h[k.first] = k.second;
|
||||
}
|
||||
doc["allowed_paths"] = h;
|
||||
}
|
||||
|
||||
if (!this->timeout_ms.empty()) {
|
||||
doc["timeout_ms"] = Json::Value(this->timeout_ms.value);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 +143,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 +216,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 +228,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 +241,63 @@ 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
|
||||
|
||||
@@ -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> ¶ms,
|
||||
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> ¶ms,
|
||||
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
|
||||
|
||||
479
dotnet/.gitignore
vendored
479
dotnet/.gitignore
vendored
@@ -1,479 +0,0 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Tye
|
||||
.tye/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
##
|
||||
## Visual studio for Mac
|
||||
##
|
||||
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
|
||||
# Mac bundle stuff
|
||||
*.dmg
|
||||
*.app
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
nuget/runtimes/win-x64.dll
|
||||
@@ -1,37 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.4.33110.190
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.Sdk", "src\Extism.Sdk\Extism.Sdk.csproj", "{1FAA7B6E-249C-4E4C-AE7A-A493A9D24475}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.Sdk.Tests", "test\Extism.Sdk\Extism.Sdk.Tests.csproj", "{DB440D61-C781-4C59-9223-9A79CC9FB4E7}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.Sdk.Sample", "samples\Extism.Sdk.Sample\Extism.Sdk.Sample.csproj", "{2232E572-E8BA-46A1-AF31-E4168960DB75}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1FAA7B6E-249C-4E4C-AE7A-A493A9D24475}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1FAA7B6E-249C-4E4C-AE7A-A493A9D24475}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1FAA7B6E-249C-4E4C-AE7A-A493A9D24475}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1FAA7B6E-249C-4E4C-AE7A-A493A9D24475}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DB440D61-C781-4C59-9223-9A79CC9FB4E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DB440D61-C781-4C59-9223-9A79CC9FB4E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DB440D61-C781-4C59-9223-9A79CC9FB4E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DB440D61-C781-4C59-9223-9A79CC9FB4E7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2232E572-E8BA-46A1-AF31-E4168960DB75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2232E572-E8BA-46A1-AF31-E4168960DB75}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2232E572-E8BA-46A1-AF31-E4168960DB75}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2232E572-E8BA-46A1-AF31-E4168960DB75}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {2B6BF267-F2A5-4CB5-8DFD-F11CC8787E6B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,24 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<NoBuild>true</NoBuild>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>Extism.runtime.win-x64</PackageId>
|
||||
<Version>0.7.0</Version>
|
||||
<Authors>Extism Contributors</Authors>
|
||||
<Description>Internal implementation package for Extism to work on Windows x64</Description>
|
||||
<Tags>extism, wasm, plugin</Tags>
|
||||
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="runtimes/win-x64.dll"
|
||||
CopyToOutputDirectory="Always"
|
||||
Pack="true"
|
||||
PackagePath="runtimes\win-x64\native\extism.dll" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1 +0,0 @@
|
||||
win-x64.dll
|
||||
@@ -1,29 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</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>
|
||||
<!-- <PackageReference Include="Extism.runtime.win-x64" Version="0.7.0" /> -->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -1,49 +0,0 @@
|
||||
using Extism.Sdk;
|
||||
using Extism.Sdk.Native;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
Console.WriteLine($"Version: {Plugin.ExtismVersion()}");
|
||||
|
||||
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 manifest = new Manifest(new PathWasmSource("./code-functions.wasm"))
|
||||
{
|
||||
Config = new Dictionary<string, string>
|
||||
{
|
||||
{ "my-key", "some cool value" }
|
||||
},
|
||||
};
|
||||
|
||||
using var plugin = new Plugin(manifest, new[] { helloWorld }, withWasi: true);
|
||||
|
||||
Console.WriteLine("Plugin creatd!!!");
|
||||
|
||||
var output = Encoding.UTF8.GetString(
|
||||
plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World!"))
|
||||
);
|
||||
|
||||
Console.WriteLine($"Output: {output}");
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
## Example 1
|
||||
|
||||
This example shows how you can use the library in the most basic way.
|
||||
It loads up the sample wasm plugin and lets you to pass inputs to it and show the ouput.
|
||||
**Please note that on Windows you have to manually copy the `extism.dll` file to the ouput directory.**
|
||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
||||
<!-- Recommended practices for publishing nuget packages from: https://devblogs.microsoft.com/dotnet/producing-packages-with-source-link/ -->
|
||||
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!-- Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
||||
<!-- Embed source files that are not tracked by the source control manager in the PDB -->
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
|
||||
<!-- Recommended: Embed symbols containing Source Link in the main file (exe/dll) -->
|
||||
<DebugType>embedded</DebugType>
|
||||
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<LangVersion>11</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>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="7.0.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,40 +0,0 @@
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Represents errors that occur during calling Extism functions.
|
||||
/// </summary>
|
||||
public class ExtismException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExtismException"/> class.
|
||||
/// </summary>
|
||||
public ExtismException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExtismException"/> class with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error .</param>
|
||||
public ExtismException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExtismException"/> class
|
||||
/// 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="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.
|
||||
/// </param>
|
||||
public ExtismException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
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
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct ExtismPlugin { }
|
||||
|
||||
/// <summary>
|
||||
/// Host function signature
|
||||
/// </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);
|
||||
|
||||
/// <summary>
|
||||
/// Load a WASM plugin.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <param name="errmsg"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern ExtismPlugin* extism_plugin_new(byte* wasm, ulong wasmSize, IntPtr* functions, ulong nFunctions, [MarshalAs(UnmanagedType.I1)] bool withWasi, out char** errmsg);
|
||||
|
||||
/// <summary>
|
||||
/// Frees a plugin error message.
|
||||
/// </summary>
|
||||
/// <param name="errorMessage"></param>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern void extism_plugin_new_error_free(IntPtr errorMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Remove a plugin from the registry and free associated memory.
|
||||
/// </summary>
|
||||
/// <param name="plugin">Pointer to the plugin you want to free.</param>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern void extism_plugin_free(ExtismPlugin* plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Update plugin config values, this will merge with the existing values.
|
||||
/// </summary>
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if funcName exists.
|
||||
/// </summary>
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
/// Call a function.
|
||||
/// </summary>
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
/// Get the error associated with a Plugin
|
||||
/// </summary>
|
||||
/// <param name="plugin">A plugin pointer</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern IntPtr extism_plugin_error(ExtismPlugin* plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Get the length of a plugin's output data.
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern long extism_plugin_output_length(ExtismPlugin* plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Get the plugin's output data.
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern IntPtr extism_plugin_output_data(ExtismPlugin* plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Set log file and level.
|
||||
/// </summary>
|
||||
/// <param name="filename"></param>
|
||||
/// <param name="logLevel"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
internal static extern bool extism_log_file(string filename, string logLevel);
|
||||
|
||||
/// <summary>
|
||||
/// Get Extism Runtime version.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
internal static extern IntPtr extism_version();
|
||||
|
||||
/// <summary>
|
||||
/// Extism Log Levels
|
||||
/// </summary>
|
||||
internal static class LogLevels
|
||||
{
|
||||
/// <summary>
|
||||
/// Designates very serious errors.
|
||||
/// </summary>
|
||||
internal const string Error = "Error";
|
||||
|
||||
/// <summary>
|
||||
/// Designates hazardous situations.
|
||||
/// </summary>
|
||||
internal const string Warn = "Warn";
|
||||
|
||||
/// <summary>
|
||||
/// Designates useful information.
|
||||
/// </summary>
|
||||
internal const string Info = "Info";
|
||||
|
||||
/// <summary>
|
||||
/// Designates lower priority information.
|
||||
/// </summary>
|
||||
internal const string Debug = "Debug";
|
||||
|
||||
/// <summary>
|
||||
/// Designates very low priority, often extremely verbose, information.
|
||||
/// </summary>
|
||||
internal const string Trace = "Trace";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Extism.Sdk
|
||||
{
|
||||
/// <summary>
|
||||
/// The manifest is a description of your plugin and some of the runtime constraints to apply to it.
|
||||
/// You can think of it as a blueprint to build your plugin.
|
||||
/// </summary>
|
||||
public class Manifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an empty manifest.
|
||||
/// </summary>
|
||||
public Manifest()
|
||||
{
|
||||
AllowedPaths = new Dictionary<string, string>
|
||||
{
|
||||
{ "/usr/plugins/1/data", "/data" }, // src, dest
|
||||
{ "d:/plugins/1/data", "/data" } // src, dest
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a manifest from one or more Wasm sources.
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
public Manifest(params WasmSource[] sources)
|
||||
{
|
||||
Sources.AddRange(sources);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of Wasm sources. See <see cref="PathWasmSource"/> and <see cref="ByteArrayWasmSource"/>.
|
||||
/// </summary>
|
||||
[JsonPropertyName("wasm")]
|
||||
public List<WasmSource> Sources { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Configures memory for the Wasm runtime.
|
||||
/// Memory is described in units of pages (64KB) and represent contiguous chunks of addressable memory.
|
||||
/// </summary>
|
||||
[JsonPropertyName("memory")]
|
||||
public MemoryOptions? MemoryOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of host names the plugins can access. Example:
|
||||
/// <code>
|
||||
/// AllowedHosts = new List<string> {
|
||||
/// "www.example.com",
|
||||
/// "api.*.com",
|
||||
/// "example.*",
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
[JsonPropertyName("allowed_hosts")]
|
||||
public List<string> AllowedHosts { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of directories that can be accessed by the plugins. Examples:
|
||||
/// <code>
|
||||
/// AllowedPaths = new Dictionary<string, string>
|
||||
/// {
|
||||
/// { "/usr/plugins/1/data", "/data" }, // src, dest
|
||||
/// { "d:/plugins/1/data", "/data" } // src, dest
|
||||
/// };
|
||||
/// </code>
|
||||
/// </summary>
|
||||
[JsonPropertyName("allowed_paths")]
|
||||
public Dictionary<string, string> AllowedPaths { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Configurations available to the plugins. Examples:
|
||||
/// <code>
|
||||
/// Config = new Dictionary<string, string>
|
||||
/// {
|
||||
/// { "userId", "55" }, // key, value
|
||||
/// { "mySecret", "super-secret-key" } // key, value
|
||||
/// };
|
||||
/// </code>
|
||||
/// </summary>
|
||||
[JsonPropertyName("config")]
|
||||
public Dictionary<string, string> Config { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures memory for the Wasm runtime.
|
||||
/// Memory is described in units of pages (64KB) and represent contiguous chunks of addressable memory.
|
||||
/// </summary>
|
||||
public class MemoryOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Max number of pages. Each page is 64KB.
|
||||
/// </summary>
|
||||
[JsonPropertyName("max")]
|
||||
public int MaxPages { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A named Wasm source.
|
||||
/// </summary>
|
||||
public abstract class WasmSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Logical name of the Wasm source
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hash of the WASM source
|
||||
/// </summary>
|
||||
[JsonPropertyName("hash")]
|
||||
public string? Hash { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wasm Source represented by a file referenced by a path.
|
||||
/// </summary>
|
||||
public class PathWasmSource : WasmSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="path">path to wasm plugin.</param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="hash"></param>
|
||||
public PathWasmSource(string path, string? name = null, string? hash = null)
|
||||
{
|
||||
Path = System.IO.Path.GetFullPath(path);
|
||||
Name = name ?? System.IO.Path.GetFileNameWithoutExtension(path);
|
||||
Hash = hash;
|
||||
|
||||
if (Hash is null)
|
||||
{
|
||||
using var file = File.OpenRead(Path);
|
||||
Hash = Helpers.ComputeSha256Hash(file);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path to wasm plugin.
|
||||
/// </summary>
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wasm Source represented by raw bytes.
|
||||
/// </summary>
|
||||
public class ByteArrayWasmSource : WasmSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="data">the byte array representing the Wasm code</param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="hash"></param>
|
||||
public ByteArrayWasmSource(byte[] data, string? name, string? hash = null)
|
||||
{
|
||||
Data = data;
|
||||
Name = name;
|
||||
Hash = hash;
|
||||
|
||||
if (Hash is null)
|
||||
{
|
||||
using var memory = new MemoryStream(data);
|
||||
Hash = Helpers.ComputeSha256Hash(memory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The byte array representing the Wasm code
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
[JsonConverter(typeof(Base64EncodedStringConverter))]
|
||||
public byte[] Data { get; }
|
||||
}
|
||||
|
||||
static class Helpers
|
||||
{
|
||||
public static string ComputeSha256Hash(Stream stream)
|
||||
{
|
||||
using (SHA256 sha256 = SHA256.Create())
|
||||
{
|
||||
byte[] hashBytes = sha256.ComputeHash(stream);
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Base64EncodedStringConverter : JsonConverter<string>
|
||||
{
|
||||
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
|
||||
Encoding.UTF8.GetString(reader.GetBytesFromBase64());
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) =>
|
||||
writer.WriteBase64StringValue(Encoding.UTF8.GetBytes(value));
|
||||
}
|
||||
|
||||
class WasmSourceConverter : JsonConverter<WasmSource>
|
||||
{
|
||||
public override WasmSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, WasmSource value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value is PathWasmSource path)
|
||||
JsonSerializer.Serialize(writer, path, typeof(PathWasmSource), options);
|
||||
else if (value is ByteArrayWasmSource bytes)
|
||||
JsonSerializer.Serialize(writer, bytes, typeof(ByteArrayWasmSource), options);
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "Unknown Wasm Source");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a WASM Extism plugin.
|
||||
/// </summary>
|
||||
public unsafe class Plugin : IDisposable
|
||||
{
|
||||
private const int DisposedMarker = 1;
|
||||
|
||||
private readonly HostFunction[] _functions;
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Native pointer to the Extism Plugin.
|
||||
/// </summary>
|
||||
internal LibExtism.ExtismPlugin* NativeHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a plugin from a Manifest.
|
||||
/// </summary>
|
||||
/// <param name="manifest"></param>
|
||||
/// <param name="functions"></param>
|
||||
/// <param name="withWasi"></param>
|
||||
public Plugin(Manifest manifest, HostFunction[] functions, bool withWasi)
|
||||
{
|
||||
_functions = functions;
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
options.Converters.Add(new WasmSourceConverter());
|
||||
var json = JsonSerializer.Serialize(manifest, options);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
|
||||
fixed (byte* wasmPtr = bytes)
|
||||
fixed (IntPtr* functionsPtr = functionHandles)
|
||||
{
|
||||
NativeHandle = Initialize(wasmPtr, bytes.Length, functions, withWasi, functionsPtr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create and load a plugin from a byte array.
|
||||
/// </summary>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="functions">List of host functions expected by the plugin.</param>
|
||||
/// <param name="withWasi">Enable/Disable WASI.</param>
|
||||
public Plugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi)
|
||||
{
|
||||
_functions = functions;
|
||||
|
||||
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
|
||||
fixed (byte* wasmPtr = wasm)
|
||||
fixed (IntPtr* functionsPtr = functionHandles)
|
||||
{
|
||||
NativeHandle = Initialize(wasmPtr, wasm.Length, functions, withWasi, functionsPtr);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe LibExtism.ExtismPlugin* Initialize(byte* wasmPtr, int wasmLength, HostFunction[] functions, bool withWasi, IntPtr* functionsPtr)
|
||||
{
|
||||
char** errorMsgPtr;
|
||||
|
||||
var handle = LibExtism.extism_plugin_new(wasmPtr, (ulong)wasmLength, functionsPtr, (ulong)functions.Length, withWasi, out errorMsgPtr);
|
||||
if (handle == null)
|
||||
{
|
||||
var msg = "Unable to create plugin";
|
||||
|
||||
if (errorMsgPtr is not null)
|
||||
{
|
||||
msg = Marshal.PtrToStringAnsi(new IntPtr(errorMsgPtr));
|
||||
}
|
||||
|
||||
throw new ExtismException(msg);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update plugin config values, this will merge with the existing values.
|
||||
/// </summary>
|
||||
/// <param name="json">The configuration JSON encoded in UTF8.</param>
|
||||
unsafe public bool SetConfig(ReadOnlySpan<byte> json)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
fixed (byte* jsonPtr = json)
|
||||
{
|
||||
return LibExtism.extism_plugin_config(NativeHandle, jsonPtr, json.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a specific function exists in the current plugin.
|
||||
/// </summary>
|
||||
unsafe public bool FunctionExists(string name)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
return LibExtism.extism_plugin_function_exists(NativeHandle, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls a function in the current plugin and returns a status.
|
||||
/// If the status represents an error, call <see cref="GetError"/> to get the error.
|
||||
/// Othewise, call <see cref="OutputData"/> to get the function's output data.
|
||||
/// </summary>
|
||||
/// <param name="functionName">Name of the function in the plugin to invoke.</param>
|
||||
/// <param name="data">A buffer to provide as input to the function.</param>
|
||||
/// <returns>The exit code of the function.</returns>
|
||||
/// <exception cref="ExtismException"></exception>
|
||||
unsafe public ReadOnlySpan<byte> CallFunction(string functionName, ReadOnlySpan<byte> data)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
fixed (byte* dataPtr = data)
|
||||
{
|
||||
int response = LibExtism.extism_plugin_call(NativeHandle, functionName, dataPtr, data.Length);
|
||||
if (response == 0)
|
||||
{
|
||||
return OutputData();
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMsg = GetError();
|
||||
if (errorMsg != null)
|
||||
{
|
||||
throw new ExtismException(errorMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExtismException("Call to Extism failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the length of a plugin's output data.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
unsafe internal int OutputLength()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
return (int)LibExtism.extism_plugin_output_length(NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the plugin's output data.
|
||||
/// </summary>
|
||||
internal ReadOnlySpan<byte> OutputData()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
var length = OutputLength();
|
||||
|
||||
unsafe
|
||||
{
|
||||
var ptr = LibExtism.extism_plugin_output_data(NativeHandle).ToPointer();
|
||||
return new Span<byte>(ptr, length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the error associated with the current plugin.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
unsafe internal string? GetError()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
var result = LibExtism.extism_plugin_error(NativeHandle);
|
||||
return Marshal.PtrToStringUTF8(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Plugin.
|
||||
/// </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(Plugin));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Plugin.
|
||||
/// </summary>
|
||||
unsafe protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Free up any managed resources here
|
||||
}
|
||||
|
||||
// Free up unmanaged resources
|
||||
LibExtism.extism_plugin_free(NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructs the current Plugin and frees all resources used by it.
|
||||
/// </summary>
|
||||
~Plugin()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Extism Runtime version.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string ExtismVersion()
|
||||
{
|
||||
var version = LibExtism.extism_version();
|
||||
return Marshal.PtrToStringAnsi(version);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
## Extism.Sdk
|
||||
Extism SDK that allows hosting Extism plugins in .NET apps.
|
||||
@@ -1,58 +0,0 @@
|
||||
using Extism.Sdk.Native;
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Extism.Sdk.Tests;
|
||||
|
||||
public class BasicTests
|
||||
{
|
||||
[Fact]
|
||||
public void CountHelloWorldVowels()
|
||||
{
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<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>
|
||||
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Extism.runtime.win-x64" Version="0.7.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
27
dune-project
27
dune-project
@@ -19,31 +19,6 @@
|
||||
(name extism)
|
||||
(synopsis "Extism bindings")
|
||||
(description "Bindings to Extism, the universal plugin system")
|
||||
(depends
|
||||
(ocaml (>= 4.14.1))
|
||||
dune
|
||||
(ctypes (>= 0.18.0))
|
||||
(ctypes-foreign (>= 0.18.0))
|
||||
(bigstringaf (>= 0.9.0))
|
||||
(ppx_yojson_conv (>= v0.15.0))
|
||||
(extism-manifest (= :version))
|
||||
(ppx_inline_test (>= v0.15.0))
|
||||
(cmdliner (>= 1.1.1))
|
||||
(uuidm (>= 0.9.0))
|
||||
)
|
||||
(tags
|
||||
(topics wasm plugin)))
|
||||
|
||||
(package
|
||||
(name extism-manifest)
|
||||
(synopsis "Extism manifest bindings")
|
||||
(description "Bindings to the Extism manifest format")
|
||||
(depends
|
||||
(ocaml (>= 4.14.1))
|
||||
dune
|
||||
(ppx_yojson_conv (>= v0.15.0))
|
||||
(ppx_inline_test (>= v0.15.0))
|
||||
(base64 (>= 3.5.0))
|
||||
)
|
||||
(depends ocaml dune ctypes-foreign bigstringaf ppx_yojson_conv base64 ppx_inline_test)
|
||||
(tags
|
||||
(topics wasm plugin)))
|
||||
|
||||
@@ -5,7 +5,7 @@ prepare:
|
||||
mix compile
|
||||
|
||||
test: prepare
|
||||
mix test -v
|
||||
mix test
|
||||
|
||||
clean:
|
||||
mix clean
|
||||
|
||||
@@ -13,7 +13,7 @@ You can find this package on [hex.pm](https://hex.pm/packages/extism).
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:extism, "~> 0.1.0"}
|
||||
{:extism, "~> 0.0.1-rc.6"}
|
||||
]
|
||||
end
|
||||
```
|
||||
@@ -23,9 +23,12 @@ end
|
||||
### 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.Plugin.new(manifest, false)
|
||||
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
|
||||
# {:ok,
|
||||
# %Extism.Plugin{
|
||||
# resource: 0,
|
||||
@@ -35,20 +38,36 @@ manifest = %{ wasm: [ %{ path: "/Users/ben/code/extism/wasm/code.wasm" } ]}
|
||||
# {: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 primary modules you should learn is:
|
||||
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.Plugin.new(manifest, false)
|
||||
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
|
||||
```
|
||||
```
|
||||
@@ -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
|
||||
64
elixir/lib/extism/context.ex
Normal file
64
elixir/lib/extism/context.ex
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,8 +4,8 @@ defmodule Extism.MixProject do
|
||||
def project do
|
||||
[
|
||||
app: :extism,
|
||||
version: "0.5.0",
|
||||
elixir: "~> 1.12",
|
||||
version: "0.0.1",
|
||||
elixir: "~> 1.14",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps(),
|
||||
package: package(),
|
||||
@@ -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}
|
||||
]
|
||||
@@ -43,7 +43,7 @@ defmodule Extism.MixProject do
|
||||
licenses: ["BSD-3-Clause"],
|
||||
description: "Extism Host SDK for Elixir and Erlang",
|
||||
name: "extism",
|
||||
files: ~w(lib native .formatter.exs mix.exs README.md LICENSE),
|
||||
files: ~w(lib native priv .formatter.exs mix.exs README.md LICENSE),
|
||||
links: %{"GitHub" => "https://github.com/extism/extism"}
|
||||
]
|
||||
end
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
%{
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [: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", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"},
|
||||
"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"},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "extism_nif"
|
||||
version = "0.3.0"
|
||||
version = "0.0.1-rc.6"
|
||||
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.0.1-rc.6" }
|
||||
log = "0.4"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use extism::Plugin;
|
||||
use rustler::{Atom, Env, ResourceArc, Term};
|
||||
use std::path::Path;
|
||||
use rustler::{Atom, Env, Term, ResourceArc};
|
||||
use extism::{Plugin, Context};
|
||||
use std::str;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::sync::RwLock;
|
||||
use std::mem;
|
||||
|
||||
mod atoms {
|
||||
rustler::atoms! {
|
||||
@@ -13,96 +14,98 @@ mod atoms {
|
||||
}
|
||||
}
|
||||
|
||||
struct ExtismPlugin {
|
||||
plugin: RwLock<Option<Plugin>>,
|
||||
struct ExtismContext {
|
||||
ctx: RwLock<Context>
|
||||
}
|
||||
unsafe impl Sync for ExtismPlugin {}
|
||||
unsafe impl Send for ExtismPlugin {}
|
||||
|
||||
struct ExtismCancelHandle {
|
||||
handle: RwLock<extism::CancelHandle>,
|
||||
}
|
||||
|
||||
unsafe impl Sync for ExtismCancelHandle {}
|
||||
unsafe impl Send for ExtismCancelHandle {}
|
||||
|
||||
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()))
|
||||
}
|
||||
|
||||
fn freed_error() -> rustler::Error {
|
||||
rustler::Error::Term(Box::new("Plugin has already been freed".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()))
|
||||
}
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
fn plugin_new_with_manifest(
|
||||
manifest_payload: String,
|
||||
wasi: bool,
|
||||
) -> Result<ResourceArc<ExtismPlugin>, rustler::Error> {
|
||||
let result = match Plugin::new(manifest_payload, [], wasi) {
|
||||
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<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>,
|
||||
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) {
|
||||
fn plugin_call(ctx: ResourceArc<ExtismContext>, plugin_id: i32, name: String, input: String) -> Result<String, rustler::Error> {
|
||||
let context = &ctx.ctx.read().unwrap();
|
||||
let 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",
|
||||
))),
|
||||
},
|
||||
};
|
||||
result
|
||||
} else {
|
||||
Err(freed_error())
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -110,41 +113,38 @@ fn plugin_free(plugin: ResourceArc<ExtismPlugin>) -> Result<(), rustler::Error>
|
||||
fn set_log_file(filename: String, log_level: String) -> Result<Atom, rustler::Error> {
|
||||
let path = Path::new(&filename);
|
||||
match log::Level::from_str(&log_level) {
|
||||
Err(_e) => Err(rustler::Error::Term(Box::new(format!(
|
||||
"{} 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:?}"
|
||||
)))),
|
||||
},
|
||||
Err(_e) => Err(rustler::Error::Term(Box::new(format!("{} not a valid log level", log_level)))),
|
||||
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>,
|
||||
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())
|
||||
}
|
||||
fn plugin_has_function(ctx: ResourceArc<ExtismContext>, plugin_id: i32, function_name: String) -> Result<bool, rustler::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,
|
||||
],
|
||||
|
||||
@@ -2,21 +2,33 @@ defmodule ExtismTest do
|
||||
use ExUnit.Case
|
||||
doctest Extism
|
||||
|
||||
defp new_plugin() do
|
||||
test "context create & reset" do
|
||||
ctx = Extism.Context.new()
|
||||
path = Path.join([__DIR__, "../../wasm/code.wasm"])
|
||||
manifest = %{wasm: [%{path: path}]}
|
||||
{:ok, plugin} = Extism.Plugin.new(manifest, false)
|
||||
plugin
|
||||
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
|
||||
Extism.Context.reset(ctx)
|
||||
# we should expect an error after resetting context
|
||||
{:error, _err} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
|
||||
end
|
||||
|
||||
defp new_plugin() do
|
||||
ctx = Extism.Context.new()
|
||||
path = Path.join([__DIR__, "../../wasm/code.wasm"])
|
||||
manifest = %{wasm: [%{path: path}]}
|
||||
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
|
||||
{ctx, plugin}
|
||||
end
|
||||
|
||||
test "counts vowels" do
|
||||
plugin = new_plugin()
|
||||
{ctx, plugin} = new_plugin()
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
|
||||
assert JSON.decode(output) == {:ok, %{"count" => 4}}
|
||||
Extism.Context.free(ctx)
|
||||
end
|
||||
|
||||
test "can make multiple calls on a plugin" do
|
||||
plugin = new_plugin()
|
||||
{ctx, plugin} = new_plugin()
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
|
||||
assert JSON.decode(output) == {:ok, %{"count" => 4}}
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test again")
|
||||
@@ -25,24 +37,37 @@ defmodule ExtismTest do
|
||||
assert JSON.decode(output) == {:ok, %{"count" => 6}}
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "🌎hello🌎world🌎")
|
||||
assert JSON.decode(output) == {:ok, %{"count" => 3}}
|
||||
Extism.Context.free(ctx)
|
||||
end
|
||||
|
||||
test "can free a plugin" do
|
||||
plugin = new_plugin()
|
||||
{ctx, plugin} = new_plugin()
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
|
||||
assert JSON.decode(output) == {:ok, %{"count" => 4}}
|
||||
Extism.Plugin.free(plugin)
|
||||
# Expect an error when calling a plugin that was freed
|
||||
{:error, _err} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
|
||||
Extism.Context.free(ctx)
|
||||
end
|
||||
|
||||
test "can update manifest" do
|
||||
{ctx, plugin} = new_plugin()
|
||||
path = Path.join([__DIR__, "../../wasm/code.wasm"])
|
||||
manifest = %{wasm: [%{path: path}]}
|
||||
assert Extism.Plugin.update(plugin, manifest, true) == :ok
|
||||
Extism.Context.free(ctx)
|
||||
end
|
||||
|
||||
test "errors on bad manifest" do
|
||||
{:error, _msg} = Extism.Plugin.new(%{"wasm" => 123}, false)
|
||||
ctx = Extism.Context.new()
|
||||
{:error, _msg} = Extism.Context.new_plugin(ctx, %{"wasm" => 123}, false)
|
||||
Extism.Context.free(ctx)
|
||||
end
|
||||
|
||||
test "errors on unknown function" do
|
||||
plugin = new_plugin()
|
||||
{ctx, plugin} = new_plugin()
|
||||
{:error, _msg} = Extism.Plugin.call(plugin, "unknown", "this is a test")
|
||||
Extism.Context.free(ctx)
|
||||
end
|
||||
|
||||
test "set_log_file" do
|
||||
@@ -50,8 +75,9 @@ defmodule ExtismTest do
|
||||
end
|
||||
|
||||
test "has_function" do
|
||||
plugin = new_plugin()
|
||||
{ctx, plugin} = new_plugin()
|
||||
assert Extism.Plugin.has_function(plugin, "count_vowels")
|
||||
assert !Extism.Plugin.has_function(plugin, "unknown")
|
||||
Extism.Context.free(ctx)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
synopsis: "Extism manifest bindings"
|
||||
description: "Bindings to the Extism manifest format"
|
||||
maintainer: ["Extism Authors <oss@extism.org>"]
|
||||
authors: ["Extism Authors <oss@extism.org>"]
|
||||
license: "BSD-3-Clause"
|
||||
tags: ["topics" "wasm" "plugin"]
|
||||
homepage: "https://github.com/extism/extism"
|
||||
doc: "https://github.com/extism/extism"
|
||||
bug-reports: "https://github.com/extism/extism/issues"
|
||||
depends: [
|
||||
"ocaml" {>= "4.14.1"}
|
||||
"dune" {>= "3.2"}
|
||||
"ppx_yojson_conv" {>= "v0.15.0"}
|
||||
"ppx_inline_test" {>= "v0.15.0"}
|
||||
"base64" {>= "3.5.0"}
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
["dune" "subst"] {dev}
|
||||
[
|
||||
"dune"
|
||||
"build"
|
||||
"-p"
|
||||
name
|
||||
"-j"
|
||||
jobs
|
||||
"@install"
|
||||
"@runtest" {with-test}
|
||||
"@doc" {with-doc}
|
||||
]
|
||||
]
|
||||
dev-repo: "git+https://github.com/extism/extism.git"
|
||||
385
extism.go
385
extism.go
@@ -5,161 +5,39 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime/cgo"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I/usr/local/include
|
||||
#cgo LDFLAGS: -L/usr/local/lib -lextism
|
||||
#cgo pkg-config: libextism.pc
|
||||
#include <extism.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int64_t extism_val_i64(ExtismValUnion* x){
|
||||
return x->i64;
|
||||
}
|
||||
|
||||
int32_t extism_val_i32(ExtismValUnion* x){
|
||||
return x->i32;
|
||||
}
|
||||
|
||||
float extism_val_f32(ExtismValUnion* x){
|
||||
return x->f32;
|
||||
}
|
||||
|
||||
double extism_val_f64(ExtismValUnion* x){
|
||||
return x->f64;
|
||||
}
|
||||
|
||||
|
||||
void extism_val_set_i64(ExtismValUnion* x, int64_t i){
|
||||
x->i64 = i;
|
||||
}
|
||||
|
||||
|
||||
void extism_val_set_i32(ExtismValUnion* x, int32_t i){
|
||||
x->i32 = i;
|
||||
}
|
||||
|
||||
void extism_val_set_f32(ExtismValUnion* x, float f){
|
||||
x->f32 = f;
|
||||
}
|
||||
|
||||
void extism_val_set_f64(ExtismValUnion* x, double f){
|
||||
x->f64 = f;
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type ValType = C.ExtismValType
|
||||
|
||||
type Val = C.ExtismVal
|
||||
|
||||
type Size = C.ExtismSize
|
||||
|
||||
var (
|
||||
I32 ValType = C.I32
|
||||
I64 ValType = C.I64
|
||||
F32 ValType = C.F32
|
||||
F64 ValType = C.F64
|
||||
V128 ValType = C.V128
|
||||
FuncRef ValType = C.FuncRef
|
||||
ExternRef ValType = C.ExternRef
|
||||
)
|
||||
|
||||
// Function is used to define host functions
|
||||
type Function struct {
|
||||
pointer *C.ExtismFunction
|
||||
userData cgo.Handle
|
||||
// Context is used to manage Plugins
|
||||
type Context struct {
|
||||
pointer *C.ExtismContext
|
||||
}
|
||||
|
||||
// Free a function
|
||||
func (f *Function) Free() {
|
||||
if f.pointer != nil {
|
||||
C.extism_function_free(f.pointer)
|
||||
f.pointer = nil
|
||||
f.userData.Delete()
|
||||
// NewContext creates a new context, it should be freed using the `Free` method
|
||||
func NewContext() Context {
|
||||
p := C.extism_context_new()
|
||||
return Context{
|
||||
pointer: p,
|
||||
}
|
||||
}
|
||||
|
||||
// NewFunction creates a new host function with the given name, input/outputs and optional user data, which can be an
|
||||
// arbitrary `interface{}`
|
||||
func NewFunction(name string, inputs []ValType, outputs []ValType, f unsafe.Pointer, userData interface{}) Function {
|
||||
var function Function
|
||||
function.userData = cgo.NewHandle(userData)
|
||||
cname := C.CString(name)
|
||||
ptr := unsafe.Pointer(function.userData)
|
||||
var inputsPtr *C.ExtismValType = nil
|
||||
if len(inputs) > 0 {
|
||||
inputsPtr = (*C.ExtismValType)(&inputs[0])
|
||||
}
|
||||
var outputsPtr *C.ExtismValType = nil
|
||||
if len(outputs) > 0 {
|
||||
outputsPtr = (*C.ExtismValType)(&outputs[0])
|
||||
}
|
||||
function.pointer = C.extism_function_new(
|
||||
cname,
|
||||
inputsPtr,
|
||||
C.uint64_t(len(inputs)),
|
||||
outputsPtr,
|
||||
C.uint64_t(len(outputs)),
|
||||
(*[0]byte)(f),
|
||||
ptr,
|
||||
nil,
|
||||
)
|
||||
C.free(unsafe.Pointer(cname))
|
||||
return function
|
||||
}
|
||||
|
||||
func (f *Function) SetNamespace(s string) {
|
||||
cstr := C.CString(s)
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
C.extism_function_set_namespace(f.pointer, cstr)
|
||||
}
|
||||
|
||||
func (f Function) WithNamespace(s string) Function {
|
||||
f.SetNamespace(s)
|
||||
return f
|
||||
}
|
||||
|
||||
type CurrentPlugin struct {
|
||||
pointer *C.ExtismCurrentPlugin
|
||||
}
|
||||
|
||||
func GetCurrentPlugin(ptr unsafe.Pointer) CurrentPlugin {
|
||||
return CurrentPlugin{
|
||||
pointer: (*C.ExtismCurrentPlugin)(ptr),
|
||||
}
|
||||
}
|
||||
|
||||
type MemoryHandle = uint
|
||||
|
||||
func (p *CurrentPlugin) Memory(offs MemoryHandle) []byte {
|
||||
length := C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs))
|
||||
data := unsafe.Pointer(C.extism_current_plugin_memory(p.pointer))
|
||||
return unsafe.Slice((*byte)(unsafe.Add(data, offs)), C.int(length))
|
||||
}
|
||||
|
||||
// Alloc a new memory block of the given length, returning its offset
|
||||
func (p *CurrentPlugin) Alloc(n uint) MemoryHandle {
|
||||
return uint(C.extism_current_plugin_memory_alloc(p.pointer, C.uint64_t(n)))
|
||||
}
|
||||
|
||||
// Free the memory block specified by the given offset
|
||||
func (p *CurrentPlugin) Free(offs MemoryHandle) {
|
||||
C.extism_current_plugin_memory_free(p.pointer, C.uint64_t(offs))
|
||||
}
|
||||
|
||||
// Length returns the number of bytes allocated at the specified offset
|
||||
func (p *CurrentPlugin) Length(offs MemoryHandle) int {
|
||||
return int(C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs)))
|
||||
// Free a context
|
||||
func (ctx *Context) Free() {
|
||||
C.extism_context_free(ctx.pointer)
|
||||
ctx.pointer = nil
|
||||
}
|
||||
|
||||
// Plugin is used to call WASM functions
|
||||
type Plugin struct {
|
||||
ptr *C.ExtismPlugin
|
||||
functions []Function
|
||||
ctx *Context
|
||||
id int32
|
||||
}
|
||||
|
||||
type WasmData struct {
|
||||
@@ -175,11 +53,11 @@ type WasmFile struct {
|
||||
}
|
||||
|
||||
type WasmUrl struct {
|
||||
Url string `json:"url"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Url string `json:"url"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Header map[string]string `json:"header,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
}
|
||||
|
||||
type Wasm interface{}
|
||||
@@ -187,12 +65,10 @@ type Wasm interface{}
|
||||
type Manifest struct {
|
||||
Wasm []Wasm `json:"wasm"`
|
||||
Memory struct {
|
||||
MaxPages uint32 `json:"max_pages,omitempty"`
|
||||
Max uint32 `json:"max,omitempty"`
|
||||
} `json:"memory,omitempty"`
|
||||
Config map[string]string `json:"config,omitempty"`
|
||||
AllowedHosts []string `json:"allowed_hosts,omitempty"`
|
||||
AllowedPaths map[string]string `json:"allowed_paths,omitempty"`
|
||||
Timeout uint `json:"timeout_ms,omitempty"`
|
||||
}
|
||||
|
||||
func makePointer(data []byte) unsafe.Pointer {
|
||||
@@ -218,99 +94,121 @@ func ExtismVersion() string {
|
||||
return C.GoString(C.extism_version())
|
||||
}
|
||||
|
||||
func register(data []byte, functions []Function, wasi bool) (Plugin, error) {
|
||||
func register(ctx *Context, data []byte, wasi bool) (Plugin, error) {
|
||||
ptr := makePointer(data)
|
||||
functionPointers := []*C.ExtismFunction{}
|
||||
for _, f := range functions {
|
||||
functionPointers = append(functionPointers, f.pointer)
|
||||
}
|
||||
plugin := C.extism_plugin_new(
|
||||
ctx.pointer,
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
C._Bool(wasi),
|
||||
)
|
||||
|
||||
var plugin *C.ExtismPlugin
|
||||
errmsg := (*C.char)(nil)
|
||||
if len(functions) == 0 {
|
||||
plugin = C.extism_plugin_new(
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
nil,
|
||||
0,
|
||||
C._Bool(wasi),
|
||||
&errmsg)
|
||||
} else {
|
||||
plugin = C.extism_plugin_new(
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
&functionPointers[0],
|
||||
C.uint64_t(len(functions)),
|
||||
C._Bool(wasi),
|
||||
&errmsg,
|
||||
)
|
||||
}
|
||||
if plugin < 0 {
|
||||
err := C.extism_error(ctx.pointer, C.int32_t(-1))
|
||||
msg := "Unknown"
|
||||
if err != nil {
|
||||
msg = C.GoString(err)
|
||||
}
|
||||
|
||||
if plugin == nil {
|
||||
msg := C.GoString(errmsg)
|
||||
C.extism_plugin_new_error_free(errmsg)
|
||||
return Plugin{}, errors.New(
|
||||
return Plugin{id: -1}, errors.New(
|
||||
fmt.Sprintf("Unable to load plugin: %s", msg),
|
||||
)
|
||||
}
|
||||
|
||||
return Plugin{ptr: plugin, functions: functions}, nil
|
||||
return Plugin{id: int32(plugin), ctx: ctx}, nil
|
||||
}
|
||||
|
||||
// NewPlugin creates a plugin
|
||||
func NewPlugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
|
||||
wasm, err := io.ReadAll(module)
|
||||
if err != nil {
|
||||
return Plugin{}, err
|
||||
func update(ctx *Context, plugin int32, data []byte, wasi bool) error {
|
||||
ptr := makePointer(data)
|
||||
b := bool(C.extism_plugin_update(
|
||||
ctx.pointer,
|
||||
C.int32_t(plugin),
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
C._Bool(wasi),
|
||||
))
|
||||
|
||||
if b {
|
||||
return nil
|
||||
}
|
||||
|
||||
return register(wasm, functions, wasi)
|
||||
err := C.extism_error(ctx.pointer, C.int32_t(-1))
|
||||
msg := "Unknown"
|
||||
if err != nil {
|
||||
msg = C.GoString(err)
|
||||
}
|
||||
|
||||
return errors.New(
|
||||
fmt.Sprintf("Unable to load plugin: %s", msg),
|
||||
)
|
||||
}
|
||||
|
||||
// NewPlugin creates a plugin from a manifest
|
||||
func NewPluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
|
||||
// PluginFromManifest creates a plugin from a `Manifest`
|
||||
func (ctx *Context) PluginFromManifest(manifest Manifest, wasi bool) (Plugin, error) {
|
||||
data, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return Plugin{}, err
|
||||
return Plugin{id: -1}, err
|
||||
}
|
||||
|
||||
return register(data, functions, wasi)
|
||||
return register(ctx, data, wasi)
|
||||
}
|
||||
|
||||
// Plugin creates a plugin from a WASM module
|
||||
func (ctx *Context) Plugin(module io.Reader, wasi bool) (Plugin, error) {
|
||||
wasm, err := io.ReadAll(module)
|
||||
if err != nil {
|
||||
return Plugin{id: -1}, err
|
||||
}
|
||||
|
||||
return register(ctx, wasm, wasi)
|
||||
}
|
||||
|
||||
// Update a plugin with a new WASM module
|
||||
func (p *Plugin) Update(module io.Reader, wasi bool) error {
|
||||
wasm, err := io.ReadAll(module)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return update(p.ctx, p.id, wasm, wasi)
|
||||
}
|
||||
|
||||
// Update a plugin with a new Manifest
|
||||
func (p *Plugin) UpdateManifest(manifest Manifest, wasi bool) error {
|
||||
data, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return update(p.ctx, p.id, data, wasi)
|
||||
}
|
||||
|
||||
// Set configuration values
|
||||
func (plugin Plugin) SetConfig(data map[string][]byte) error {
|
||||
if plugin.ptr == nil {
|
||||
return errors.New("Cannot set config, Plugin already freed")
|
||||
}
|
||||
s, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptr := makePointer(s)
|
||||
C.extism_plugin_config(plugin.ptr, (*C.uchar)(ptr), C.uint64_t(len(s)))
|
||||
C.extism_plugin_config(plugin.ctx.pointer, C.int(plugin.id), (*C.uchar)(ptr), C.uint64_t(len(s)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// FunctionExists returns true when the named function is present in the plugin
|
||||
func (plugin Plugin) FunctionExists(functionName string) bool {
|
||||
if plugin.ptr == nil {
|
||||
return false
|
||||
}
|
||||
name := C.CString(functionName)
|
||||
b := C.extism_plugin_function_exists(plugin.ptr, name)
|
||||
b := C.extism_plugin_function_exists(plugin.ctx.pointer, C.int(plugin.id), name)
|
||||
C.free(unsafe.Pointer(name))
|
||||
return bool(b)
|
||||
}
|
||||
|
||||
// Call a function by name with the given input, returning the output
|
||||
func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
|
||||
if plugin.ptr == nil {
|
||||
return []byte{}, errors.New("Plugin has already been freed")
|
||||
}
|
||||
ptr := makePointer(input)
|
||||
name := C.CString(functionName)
|
||||
rc := C.extism_plugin_call(
|
||||
plugin.ptr,
|
||||
plugin.ctx.pointer,
|
||||
C.int32_t(plugin.id),
|
||||
name,
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(input)),
|
||||
@@ -318,7 +216,7 @@ func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
|
||||
C.free(unsafe.Pointer(name))
|
||||
|
||||
if rc != 0 {
|
||||
err := C.extism_plugin_error(plugin.ptr)
|
||||
err := C.extism_error(plugin.ctx.pointer, C.int32_t(plugin.id))
|
||||
msg := "<unset by plugin>"
|
||||
if err != nil {
|
||||
msg = C.GoString(err)
|
||||
@@ -329,11 +227,12 @@ func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
|
||||
)
|
||||
}
|
||||
|
||||
length := C.extism_plugin_output_length(plugin.ptr)
|
||||
length := C.extism_plugin_output_length(plugin.ctx.pointer, C.int32_t(plugin.id))
|
||||
|
||||
if length > 0 {
|
||||
x := C.extism_plugin_output_data(plugin.ptr)
|
||||
return unsafe.Slice((*byte)(x), C.int(length)), nil
|
||||
x := C.extism_plugin_output_data(plugin.ctx.pointer, C.int32_t(plugin.id))
|
||||
y := (*[]byte)(unsafe.Pointer(&x))
|
||||
return []byte((*y)[0:length]), nil
|
||||
}
|
||||
|
||||
return []byte{}, nil
|
||||
@@ -341,86 +240,14 @@ func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
|
||||
|
||||
// Free a plugin
|
||||
func (plugin *Plugin) Free() {
|
||||
if plugin.ptr == nil {
|
||||
if plugin.ctx.pointer == nil {
|
||||
return
|
||||
}
|
||||
C.extism_plugin_free(plugin.ptr)
|
||||
plugin.ptr = nil
|
||||
C.extism_plugin_free(plugin.ctx.pointer, C.int32_t(plugin.id))
|
||||
plugin.id = -1
|
||||
}
|
||||
|
||||
// ValGetI64 returns an I64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetI64(v unsafe.Pointer) int64 {
|
||||
return int64(C.extism_val_i64(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValGetUInt returns a uint from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetUInt(v unsafe.Pointer) uint {
|
||||
return uint(C.extism_val_i64(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValGetI32 returns an int32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetI32(v unsafe.Pointer) int32 {
|
||||
return int32(C.extism_val_i32(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValGetF32 returns a float32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetF32(v unsafe.Pointer) float32 {
|
||||
return float32(C.extism_val_f32(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValGetF32 returns a float64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetF64(v unsafe.Pointer) float64 {
|
||||
return float64(C.extism_val_i64(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValSetI64 stores an int64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
||||
func ValSetI64(v unsafe.Pointer, i int64) {
|
||||
C.extism_val_set_i64(&(*Val)(v).v, C.int64_t(i))
|
||||
}
|
||||
|
||||
// ValSetI32 stores an int32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
||||
func ValSetI32(v unsafe.Pointer, i int32) {
|
||||
C.extism_val_set_i32(&(*Val)(v).v, C.int32_t(i))
|
||||
}
|
||||
|
||||
// ValSetF32 stores a float32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
||||
func ValSetF32(v unsafe.Pointer, i float32) {
|
||||
C.extism_val_set_f32(&(*Val)(v).v, C.float(i))
|
||||
}
|
||||
|
||||
// ValSetF64 stores a float64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
||||
func ValSetF64(v unsafe.Pointer, f float64) {
|
||||
C.extism_val_set_f64(&(*Val)(v).v, C.double(f))
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) ReturnBytes(v unsafe.Pointer, b []byte) {
|
||||
mem := p.Alloc(uint(len(b)))
|
||||
ptr := p.Memory(mem)
|
||||
copy(ptr, b)
|
||||
ValSetI64(v, int64(mem))
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) ReturnString(v unsafe.Pointer, s string) {
|
||||
p.ReturnBytes(v, []byte(s))
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) InputBytes(v unsafe.Pointer) []byte {
|
||||
return p.Memory(ValGetUInt(v))
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) InputString(v unsafe.Pointer) string {
|
||||
return string(p.InputBytes(v))
|
||||
}
|
||||
|
||||
type CancelHandle struct {
|
||||
pointer *C.ExtismCancelHandle
|
||||
}
|
||||
|
||||
func (p *Plugin) CancelHandle() CancelHandle {
|
||||
pointer := C.extism_plugin_cancel_handle(p.ptr)
|
||||
return CancelHandle{pointer}
|
||||
}
|
||||
|
||||
func (c *CancelHandle) Cancel() bool {
|
||||
return bool(C.extism_plugin_cancel(c.pointer))
|
||||
// Reset removes all registered plugins in a Context
|
||||
func (ctx Context) Reset() {
|
||||
C.extism_context_reset(ctx.pointer)
|
||||
}
|
||||
|
||||
17
extism.opam
17
extism.opam
@@ -10,16 +10,13 @@ homepage: "https://github.com/extism/extism"
|
||||
doc: "https://github.com/extism/extism"
|
||||
bug-reports: "https://github.com/extism/extism/issues"
|
||||
depends: [
|
||||
"ocaml" {>= "4.14.1"}
|
||||
"ocaml"
|
||||
"dune" {>= "3.2"}
|
||||
"ctypes" {>= "0.18.0"}
|
||||
"ctypes-foreign" {>= "0.18.0"}
|
||||
"bigstringaf" {>= "0.9.0"}
|
||||
"ppx_yojson_conv" {>= "v0.15.0"}
|
||||
"extism-manifest" {= version}
|
||||
"ppx_inline_test" {>= "v0.15.0"}
|
||||
"cmdliner" {>= "1.1.1"}
|
||||
"uuidm" {>= "0.9.0"}
|
||||
"ctypes-foreign"
|
||||
"bigstringaf"
|
||||
"ppx_yojson_conv"
|
||||
"base64"
|
||||
"ppx_inline_test"
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
@@ -37,5 +34,3 @@ build: [
|
||||
]
|
||||
]
|
||||
dev-repo: "git+https://github.com/extism/extism.git"
|
||||
build-env: [EXTISM_TEST_NO_LIB = ""]
|
||||
post-messages: ["See https://extism.org/docs/install/ for information about installing libextism"]
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
build-env: [EXTISM_TEST_NO_LIB = ""]
|
||||
post-messages: ["See https://extism.org/docs/install/ for information about installing libextism"]
|
||||
103
extism_test.go
103
extism_test.go
@@ -4,19 +4,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func manifest(functions bool) Manifest {
|
||||
path := "./wasm/code.wasm"
|
||||
if functions {
|
||||
path = "./wasm/code-functions.wasm"
|
||||
}
|
||||
|
||||
func manifest() Manifest {
|
||||
return Manifest{
|
||||
Wasm: []Wasm{
|
||||
WasmFile{
|
||||
Path: path,
|
||||
Path: "./wasm/code.wasm",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -35,8 +29,16 @@ func expectVowelCount(plugin Plugin, input string, count int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCreateAndFreeContext(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
ctx.Free()
|
||||
}
|
||||
|
||||
func TestCallPlugin(t *testing.T) {
|
||||
plugin, err := NewPluginFromManifest(manifest(false), []Function{}, false)
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -53,7 +55,10 @@ func TestCallPlugin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFreePlugin(t *testing.T) {
|
||||
plugin, err := NewPluginFromManifest(manifest(false), []Function{}, false)
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -69,8 +74,52 @@ func TestFreePlugin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextReset(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// reset the context dropping all plugins
|
||||
ctx.Reset()
|
||||
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err == nil {
|
||||
t.Fatal("Expected an error after plugin was freed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanUpdateAManifest(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
plugin.UpdateManifest(manifest(), false)
|
||||
|
||||
// can still call the plugin
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionExists(t *testing.T) {
|
||||
plugin, err := NewPluginFromManifest(manifest(false), []Function{}, false)
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -84,7 +133,10 @@ func TestFunctionExists(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestErrorsOnUnknownFunction(t *testing.T) {
|
||||
plugin, err := NewPluginFromManifest(manifest(false), []Function{}, false)
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -94,30 +146,3 @@ func TestErrorsOnUnknownFunction(t *testing.T) {
|
||||
t.Fatal("Was expecting call to unknown function to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancel(t *testing.T) {
|
||||
manifest := Manifest{
|
||||
Wasm: []Wasm{
|
||||
WasmFile{
|
||||
Path: "./wasm/loop.wasm",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
plugin, err := NewPluginFromManifest(manifest, []Function{}, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cancelHandle := plugin.CancelHandle()
|
||||
|
||||
go func(handle CancelHandle) {
|
||||
time.Sleep(time.Second * 1)
|
||||
handle.Cancel()
|
||||
}(cancelHandle)
|
||||
|
||||
_, err = plugin.Call("infinite_loop", []byte(""))
|
||||
if err == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
34
go/main.go
34
go/main.go
@@ -4,38 +4,17 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/cgo"
|
||||
"unsafe"
|
||||
|
||||
"github.com/extism/extism"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <extism.h>
|
||||
EXTISM_GO_FUNCTION(hello_world);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
//export hello_world
|
||||
func hello_world(plugin unsafe.Pointer, inputs *C.ExtismVal, nInputs C.ExtismSize, outputs *C.ExtismVal, nOutputs C.ExtismSize, userData uintptr) {
|
||||
fmt.Println("Hello from Go!")
|
||||
s := cgo.Handle(userData)
|
||||
fmt.Println(s.Value().(string))
|
||||
inputSlice := unsafe.Slice(inputs, nInputs)
|
||||
outputSlice := unsafe.Slice(outputs, nOutputs)
|
||||
|
||||
// Get memory pointed to by first element of input slice
|
||||
p := extism.GetCurrentPlugin(plugin)
|
||||
str := p.InputString(unsafe.Pointer(&inputSlice[0]))
|
||||
fmt.Println(str)
|
||||
|
||||
outputSlice[0] = inputSlice[0]
|
||||
}
|
||||
|
||||
func main() {
|
||||
version := extism.ExtismVersion()
|
||||
fmt.Println("Extism Version: ", version)
|
||||
|
||||
ctx := extism.NewContext()
|
||||
defer ctx.Free() // this will free the context and all associated plugins
|
||||
|
||||
// set some input data to provide to the plugin module
|
||||
var data []byte
|
||||
if len(os.Args) > 1 {
|
||||
@@ -43,10 +22,9 @@ func main() {
|
||||
} else {
|
||||
data = []byte("testing from go -> wasm shared memory...")
|
||||
}
|
||||
manifest := extism.Manifest{Wasm: []extism.Wasm{extism.WasmFile{Path: "../wasm/code-functions.wasm"}}}
|
||||
f := extism.NewFunction("hello_world", []extism.ValType{extism.I64}, []extism.ValType{extism.I64}, C.hello_world, "Hello again!")
|
||||
defer f.Free()
|
||||
plugin, err := extism.NewPluginFromManifest(manifest, []extism.Function{f}, true)
|
||||
|
||||
manifest := extism.Manifest{Wasm: []extism.Wasm{extism.WasmFile{Path: "../wasm/code.wasm"}}}
|
||||
plugin, err := ctx.PluginFromManifest(manifest, false)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Revision history for extism
|
||||
|
||||
## 0.2.0.0 -- 2023-01-16
|
||||
## 0.1.0.0 -- YYYY-mm-dd
|
||||
|
||||
* First version. Released on an unsuspecting world.
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
module Main where
|
||||
|
||||
import System.Exit (exitFailure, exitSuccess)
|
||||
import qualified Data.ByteString as B
|
||||
import Extism
|
||||
import Extism.HostFunction
|
||||
import Extism.Manifest(manifest, wasmFile)
|
||||
import Extism.Manifest
|
||||
|
||||
hello plugin params msg = do
|
||||
putStrLn "Hello from Haskell!"
|
||||
putStrLn msg
|
||||
offs <- allocBytes plugin (toByteString "{\"count\": 999}")
|
||||
return [toI64 offs]
|
||||
try f (Right x) = f x
|
||||
try f (Left (ErrorMessage msg)) = do
|
||||
_ <- putStrLn msg
|
||||
exitFailure
|
||||
|
||||
handlePlugin plugin = do
|
||||
res <- Extism.call plugin "count_vowels" (Extism.toByteString "this is a test")
|
||||
try (\bs -> do
|
||||
_ <- putStrLn (Extism.fromByteString bs)
|
||||
_ <- Extism.free plugin
|
||||
exitSuccess) res
|
||||
|
||||
main = do
|
||||
setLogFile "stdout" LogError
|
||||
let m = manifest [wasmFile "../wasm/code-functions.wasm"]
|
||||
f <- hostFunction "hello_world" [I64] [I64] hello "Hello, again"
|
||||
plugin <- unwrap <$> pluginFromManifest m [f] True
|
||||
id <- pluginID plugin
|
||||
print id
|
||||
res <- unwrap <$> call plugin "count_vowels" (toByteString "this is a test")
|
||||
putStrLn (fromByteString res)
|
||||
context <- Extism.newContext ()
|
||||
plugin <- Extism.pluginFromManifest context (manifest [wasmFile "../wasm/code.wasm"]) False
|
||||
try handlePlugin plugin
|
||||
@@ -1,33 +0,0 @@
|
||||
.PHONY: test
|
||||
|
||||
prepare:
|
||||
cabal update
|
||||
|
||||
build: prepare
|
||||
cabal build
|
||||
|
||||
test: prepare
|
||||
cabal test
|
||||
|
||||
clean:
|
||||
cabal clean
|
||||
|
||||
publish: clean prepare
|
||||
cabal v2-haddock --haddock-for-hackage ./manifest/extism-manifest.cabal
|
||||
cabal v2-haddock --haddock-for-hackage
|
||||
cabal sdist ./manifest/extism-manifest.cabal
|
||||
cabal sdist
|
||||
# TODO: upload
|
||||
|
||||
format:
|
||||
# TODO
|
||||
|
||||
lint:
|
||||
cabal check
|
||||
hlint src manifest
|
||||
|
||||
docs:
|
||||
# TODO
|
||||
|
||||
show-docs: docs
|
||||
# TODO
|
||||
@@ -1 +0,0 @@
|
||||
packages: extism.cabal manifest/extism-manifest.cabal
|
||||
@@ -1,46 +1,54 @@
|
||||
cabal-version: 3.0
|
||||
cabal-version: 2.4
|
||||
name: extism
|
||||
version: 0.5.0
|
||||
license: BSD-3-Clause
|
||||
maintainer: oss@extism.org
|
||||
version: 0.0.1
|
||||
|
||||
-- A short (one-line) description of the package.
|
||||
synopsis: Extism bindings
|
||||
|
||||
-- A longer description of the package.
|
||||
description: Bindings to Extism, the universal plugin system
|
||||
|
||||
-- A URL where users can report bugs.
|
||||
bug-reports: https://github.com/extism/extism
|
||||
|
||||
-- The license under which the package is released.
|
||||
license: BSD-3-Clause
|
||||
|
||||
author: Extism authors
|
||||
bug-reports: https://github.com/extism/extism
|
||||
synopsis: Extism bindings
|
||||
description: Bindings to Extism, the universal plugin system
|
||||
category: Plugins, WebAssembly
|
||||
extra-doc-files: CHANGELOG.md
|
||||
maintainer: oss@extism.org
|
||||
|
||||
-- A copyright notice.
|
||||
-- copyright:
|
||||
category: Plugins, WebAssembly
|
||||
extra-source-files: CHANGELOG.md
|
||||
|
||||
library
|
||||
exposed-modules: Extism Extism.HostFunction
|
||||
reexported-modules: Extism.Manifest
|
||||
hs-source-dirs: src
|
||||
other-modules: Extism.Bindings
|
||||
default-language: Haskell2010
|
||||
extra-libraries: extism
|
||||
extra-lib-dirs: /usr/local/lib
|
||||
build-depends:
|
||||
base >= 4.16.1 && < 5,
|
||||
bytestring >= 0.11.3 && <= 0.12,
|
||||
json >= 0.10 && <= 0.11,
|
||||
extism-manifest >= 0.0.0 && < 0.4.0,
|
||||
uuid >= 1.3 && < 2
|
||||
exposed-modules: Extism Extism.Manifest
|
||||
|
||||
test-suite extism-example
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Example.hs
|
||||
-- Modules included in this library but not exported.
|
||||
other-modules:
|
||||
|
||||
-- LANGUAGE extensions used by modules in this package.
|
||||
-- other-extensions:
|
||||
build-depends:
|
||||
base ^>=4.16.1.0
|
||||
, bytestring
|
||||
, base64-bytestring
|
||||
, json
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
||||
build-depends:
|
||||
base,
|
||||
extism,
|
||||
bytestring
|
||||
|
||||
test-suite extism-test
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Test.hs
|
||||
extra-libraries: extism
|
||||
extra-lib-dirs: /usr/local/lib
|
||||
|
||||
Test-Suite extism-example
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Example.hs
|
||||
build-depends: base, extism, bytestring
|
||||
default-language: Haskell2010
|
||||
|
||||
Test-Suite extism-test
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Test.hs
|
||||
hs-source-dirs: test
|
||||
build-depends: base, extism, bytestring, HUnit
|
||||
default-language: Haskell2010
|
||||
build-depends:
|
||||
base,
|
||||
extism,
|
||||
bytestring,
|
||||
HUnit
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Revision history for extism-manifest
|
||||
|
||||
## 0.1.0.0 -- YYYY-mm-dd
|
||||
|
||||
* First version. Released on an unsuspecting world.
|
||||
@@ -1,60 +0,0 @@
|
||||
module Extism.JSON (
|
||||
module Extism.JSON,
|
||||
module Text.JSON
|
||||
) where
|
||||
|
||||
import Text.JSON
|
||||
import qualified Data.ByteString as B
|
||||
import Data.ByteString.Internal (c2w, w2c)
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import qualified Data.ByteString.Char8 as BS (unpack)
|
||||
|
||||
data Nullable a = Null | NotNull a deriving Eq
|
||||
|
||||
makeArray x = JSArray [showJSON a | a <- x]
|
||||
isNull JSNull = True
|
||||
isNull _ = False
|
||||
filterNulls obj = [(a, b) | (a, b) <- obj, not (isNull b)]
|
||||
object x = makeObj $ filterNulls x
|
||||
objectWithNulls = makeObj
|
||||
nonNull = NotNull
|
||||
null' = Null
|
||||
(.=) a b = (a, showJSON b)
|
||||
toNullable (Just x) = NotNull x
|
||||
toNullable Nothing = Null
|
||||
fromNullable (NotNull x) = Just x
|
||||
fromNullable Null = Nothing
|
||||
fromNotNull (NotNull x) = x
|
||||
fromNotNull Null = error "Value is Null"
|
||||
mapNullable f Null = Null
|
||||
mapNullable f (NotNull x) = NotNull (f x)
|
||||
|
||||
(.?) (JSObject a) k =
|
||||
case valFromObj k a of
|
||||
Ok x -> NotNull x
|
||||
Error _ -> Null
|
||||
(.?) _ _ = Null
|
||||
(.??) a k = toNullable $ lookup k a
|
||||
|
||||
find :: JSON a => String -> JSValue -> Nullable a
|
||||
find k obj = obj .? k
|
||||
|
||||
update :: JSON a => String -> a -> JSValue -> JSValue
|
||||
update k v (JSObject obj) = object $ fromJSObject obj ++ [k .= v]
|
||||
|
||||
instance JSON a => JSON (Nullable a) where
|
||||
showJSON (NotNull x) = showJSON x
|
||||
showJSON Null = JSNull
|
||||
readJSON JSNull = Ok Null
|
||||
readJSON x = readJSON x
|
||||
|
||||
|
||||
newtype Base64 = Base64 B.ByteString deriving (Eq, Show)
|
||||
|
||||
instance JSON Base64 where
|
||||
showJSON (Base64 bs) = showJSON (BS.unpack $ B64.encode bs)
|
||||
readJSON (JSString s) =
|
||||
let toByteString x = B.pack (Prelude.map c2w x) in
|
||||
case B64.decode (toByteString (fromJSString s)) of
|
||||
Left msg -> Error msg
|
||||
Right d -> Ok (Base64 d)
|
||||
@@ -1,249 +0,0 @@
|
||||
module Extism.Manifest where
|
||||
|
||||
import Extism.JSON
|
||||
import qualified Data.ByteString as B
|
||||
import qualified Data.ByteString.Char8 as BS (unpack)
|
||||
|
||||
-- | Memory options
|
||||
newtype Memory = Memory
|
||||
{
|
||||
memoryMaxPages :: Nullable Int
|
||||
}
|
||||
deriving Eq
|
||||
|
||||
instance JSON Memory where
|
||||
showJSON (Memory max) =
|
||||
object [
|
||||
"max_pages" .= max
|
||||
]
|
||||
readJSON obj =
|
||||
let max = obj .? "max_pages" in
|
||||
Ok (Memory max)
|
||||
|
||||
-- | HTTP request
|
||||
data HTTPRequest = HTTPRequest
|
||||
{
|
||||
url :: String
|
||||
, headers :: Nullable [(String, String)]
|
||||
, method :: Nullable String
|
||||
}
|
||||
deriving Eq
|
||||
|
||||
makeKV x =
|
||||
object [(k, showJSON v) | (k, v) <- x]
|
||||
|
||||
requestObj (HTTPRequest url headers method) =
|
||||
[
|
||||
"url" .= url,
|
||||
"headers" .= mapNullable makeKV headers,
|
||||
"method" .= method
|
||||
]
|
||||
|
||||
instance JSON HTTPRequest where
|
||||
showJSON req = object $ requestObj req
|
||||
readJSON x =
|
||||
let url = x .? "url" in
|
||||
let headers = x .? "headers" in
|
||||
let method = x .? "method" in
|
||||
case url of
|
||||
Null -> Error "Missing 'url' field"
|
||||
NotNull url -> Ok (HTTPRequest url headers method)
|
||||
|
||||
|
||||
-- | WASM from file
|
||||
data WasmFile = WasmFile
|
||||
{
|
||||
filePath :: String
|
||||
, fileName :: Nullable String
|
||||
, fileHash :: Nullable String
|
||||
}
|
||||
deriving Eq
|
||||
|
||||
instance JSON WasmFile where
|
||||
showJSON (WasmFile path name hash) =
|
||||
object [
|
||||
"path" .= path,
|
||||
"name" .= name,
|
||||
"hash" .= hash
|
||||
]
|
||||
readJSON x =
|
||||
let path = x .? "url" in
|
||||
let name = x .? "name" in
|
||||
let hash = x .? "hash" in
|
||||
case path of
|
||||
Null -> Error "Missing 'path' field"
|
||||
NotNull path -> Ok (WasmFile path name hash)
|
||||
|
||||
|
||||
|
||||
-- | WASM from raw bytes
|
||||
data WasmData = WasmData
|
||||
{
|
||||
dataBytes :: Base64
|
||||
, dataName :: Nullable String
|
||||
, dataHash :: Nullable String
|
||||
}
|
||||
deriving Eq
|
||||
|
||||
instance JSON WasmData where
|
||||
showJSON (WasmData bytes name hash) =
|
||||
object [
|
||||
"data" .= bytes,
|
||||
"name" .= name,
|
||||
"hash" .= hash
|
||||
]
|
||||
readJSON x =
|
||||
let d = x .? "data" in
|
||||
let name = x .? "name" in
|
||||
let hash = x .? "hash" in
|
||||
case d of
|
||||
Null -> Error "Missing 'path' field"
|
||||
NotNull d ->
|
||||
case readJSON d of
|
||||
Error msg -> Error msg
|
||||
Ok d' -> Ok (WasmData d' name hash)
|
||||
|
||||
|
||||
-- | WASM from a URL
|
||||
data WasmURL = WasmURL
|
||||
{
|
||||
req :: HTTPRequest
|
||||
, urlName :: Nullable String
|
||||
, urlHash :: Nullable String
|
||||
}
|
||||
deriving Eq
|
||||
|
||||
|
||||
instance JSON WasmURL where
|
||||
showJSON (WasmURL req name hash) =
|
||||
object (
|
||||
"name" .= name :
|
||||
"hash" .= hash :
|
||||
requestObj req)
|
||||
readJSON x =
|
||||
let req = x .? "req" in
|
||||
let name = x .? "name" in
|
||||
let hash = x .? "hash" in
|
||||
case fromNullable req of
|
||||
Nothing -> Error "Missing 'req' field"
|
||||
Just req -> Ok (WasmURL req name hash)
|
||||
|
||||
-- | Specifies where to get WASM module data
|
||||
data Wasm = File WasmFile | Data WasmData | URL WasmURL deriving Eq
|
||||
|
||||
instance JSON Wasm where
|
||||
showJSON x =
|
||||
case x of
|
||||
File f -> showJSON f
|
||||
Data d -> showJSON d
|
||||
URL u -> showJSON u
|
||||
readJSON x =
|
||||
let file = (readJSON x :: Result WasmFile) in
|
||||
case file of
|
||||
Ok x -> Ok (File x)
|
||||
Error _ ->
|
||||
let data' = (readJSON x :: Result WasmData) in
|
||||
case data' of
|
||||
Ok x -> Ok (Data x)
|
||||
Error _ ->
|
||||
let url = (readJSON x :: Result WasmURL) in
|
||||
case url of
|
||||
Ok x -> Ok (URL x)
|
||||
Error _ -> Error "JSON does not match any of the Wasm types"
|
||||
|
||||
wasmFile :: String -> Wasm
|
||||
wasmFile path =
|
||||
File WasmFile { filePath = path, fileName = null', fileHash = null'}
|
||||
|
||||
wasmURL :: String -> String -> Wasm
|
||||
wasmURL method url =
|
||||
let r = HTTPRequest { url = url, headers = null', method = nonNull method } in
|
||||
URL WasmURL { req = r, urlName = null', urlHash = null' }
|
||||
|
||||
wasmData :: B.ByteString -> Wasm
|
||||
wasmData d =
|
||||
Data WasmData { dataBytes = Base64 d, dataName = null', dataHash = null' }
|
||||
|
||||
withName :: Wasm -> String -> Wasm
|
||||
withName (Data d) name = Data d { dataName = nonNull name }
|
||||
withName (URL url) name = URL url { urlName = nonNull name }
|
||||
withName (File f) name = File f { fileName = nonNull name }
|
||||
|
||||
|
||||
withHash :: Wasm -> String -> Wasm
|
||||
withHash (Data d) hash = Data d { dataHash = nonNull hash }
|
||||
withHash (URL url) hash = URL url { urlHash = nonNull hash }
|
||||
withHash (File f) hash = File f { fileHash = nonNull hash }
|
||||
|
||||
-- | The 'Manifest' type is used to provide WASM data and configuration to the
|
||||
-- | Extism runtime
|
||||
data Manifest = Manifest
|
||||
{
|
||||
wasm :: [Wasm]
|
||||
, memory :: Nullable Memory
|
||||
, config :: Nullable [(String, String)]
|
||||
, allowedHosts :: Nullable [String]
|
||||
, allowedPaths :: Nullable [(String, String)]
|
||||
, timeout :: Nullable Int
|
||||
}
|
||||
|
||||
|
||||
instance JSON Manifest where
|
||||
showJSON (Manifest wasm memory config hosts paths timeout) =
|
||||
let w = makeArray wasm in
|
||||
object [
|
||||
"wasm" .= w,
|
||||
"memory" .= memory,
|
||||
"config" .= mapNullable makeKV config,
|
||||
"allowed_hosts" .= hosts,
|
||||
"allowed_paths" .= mapNullable makeKV paths,
|
||||
"timeout_ms" .= timeout
|
||||
]
|
||||
readJSON x =
|
||||
let wasm = x .? "wasm" in
|
||||
let memory = x .? "memory" in
|
||||
let config = x .? "config" in
|
||||
let hosts = x .? "allowed_hosts" in
|
||||
let paths = x .? "allowed_paths" in
|
||||
let timeout = x .? "timeout_ms" in
|
||||
case fromNullable wasm of
|
||||
Nothing -> Error "Missing 'wasm' field"
|
||||
Just wasm -> Ok (Manifest wasm memory config hosts paths timeout)
|
||||
|
||||
-- | Create a new 'Manifest' from a list of 'Wasm'
|
||||
manifest :: [Wasm] -> Manifest
|
||||
manifest wasm =
|
||||
Manifest {
|
||||
wasm = wasm,
|
||||
memory = null',
|
||||
config = null',
|
||||
allowedHosts = null',
|
||||
allowedPaths = null',
|
||||
timeout = null'
|
||||
}
|
||||
|
||||
-- | Update the config values
|
||||
withConfig :: Manifest -> [(String, String)] -> Manifest
|
||||
withConfig m config =
|
||||
m { config = nonNull config }
|
||||
|
||||
|
||||
-- | Update allowed hosts for `extism_http_request`
|
||||
withHosts :: Manifest -> [String] -> Manifest
|
||||
withHosts m hosts =
|
||||
m { allowedHosts = nonNull hosts }
|
||||
|
||||
|
||||
-- | Update allowed paths
|
||||
withPaths :: Manifest -> [(String, String)] -> Manifest
|
||||
withPaths m p =
|
||||
m { allowedPaths = nonNull p }
|
||||
|
||||
-- | Update plugin timeout (in milliseconds)
|
||||
withTimeout :: Manifest -> Int -> Manifest
|
||||
withTimeout m t =
|
||||
m { timeout = nonNull t }
|
||||
|
||||
toString :: (JSON a) => a -> String
|
||||
toString v =
|
||||
encode (showJSON v)
|
||||
@@ -1,21 +0,0 @@
|
||||
cabal-version: 3.0
|
||||
name: extism-manifest
|
||||
version: 0.3.0
|
||||
license: BSD-3-Clause
|
||||
maintainer: oss@extism.org
|
||||
author: Extism authors
|
||||
bug-reports: https://github.com/extism/extism
|
||||
synopsis: Extism manifest bindings
|
||||
description: Bindings to Extism WebAssembly manifest
|
||||
category: Plugins, WebAssembly
|
||||
extra-doc-files: CHANGELOG.md
|
||||
|
||||
library
|
||||
exposed-modules: Extism.Manifest Extism.JSON
|
||||
hs-source-dirs: .
|
||||
default-language: Haskell2010
|
||||
build-depends:
|
||||
base >= 4.16.1 && < 5,
|
||||
bytestring >= 0.11.3 && <= 0.12,
|
||||
json >= 0.10 && <= 0.11,
|
||||
base64-bytestring >= 1.2.1 && < 1.3,
|
||||
@@ -1,127 +1,155 @@
|
||||
module Extism (
|
||||
module Extism.Manifest,
|
||||
Function(..),
|
||||
Plugin(..),
|
||||
CancelHandle(..),
|
||||
LogLevel(..),
|
||||
Error(..),
|
||||
Result(..),
|
||||
toByteString,
|
||||
fromByteString,
|
||||
extismVersion,
|
||||
plugin,
|
||||
pluginFromManifest,
|
||||
isValid,
|
||||
setConfig,
|
||||
setLogFile,
|
||||
functionExists,
|
||||
call,
|
||||
cancelHandle,
|
||||
cancel,
|
||||
pluginID,
|
||||
unwrap
|
||||
) where
|
||||
{-# LANGUAGE ForeignFunctionInterface #-}
|
||||
|
||||
module Extism (module Extism, module Extism.Manifest) where
|
||||
import GHC.Int
|
||||
import GHC.Word
|
||||
import Foreign.C.Types
|
||||
import Foreign.Ptr
|
||||
import Foreign.ForeignPtr
|
||||
import Foreign.C.String
|
||||
import Foreign.Ptr
|
||||
import Foreign.Marshal.Array
|
||||
import Foreign.Marshal.Alloc
|
||||
import Foreign.Storable
|
||||
import Foreign.StablePtr
|
||||
import Foreign.Concurrent
|
||||
import qualified Data.ByteString as B
|
||||
import qualified Data.ByteString.Lazy as BL
|
||||
import Control.Monad (void)
|
||||
import Data.ByteString as B
|
||||
import Data.ByteString.Internal (c2w, w2c)
|
||||
import Data.ByteString.Unsafe (unsafeUseAsCString)
|
||||
import qualified Text.JSON (encode, toJSObject, showJSON)
|
||||
import Data.Bifunctor (second)
|
||||
import Text.JSON (JSON, toJSObject, toJSString, encode, JSValue(JSNull, JSString))
|
||||
import Extism.Manifest (Manifest, toString)
|
||||
import Extism.Bindings
|
||||
import qualified Data.UUID (UUID, fromByteString)
|
||||
|
||||
-- | Host function, see 'Extism.HostFunction.hostFunction'
|
||||
data Function = Function (ForeignPtr ExtismFunction) (StablePtr ()) deriving Eq
|
||||
newtype ExtismContext = ExtismContext () deriving Show
|
||||
|
||||
-- | Plugins can be used to call WASM function
|
||||
newtype Plugin = Plugin (ForeignPtr ExtismPlugin) deriving Eq
|
||||
foreign import ccall unsafe "extism.h extism_context_new" extism_context_new :: IO (Ptr ExtismContext)
|
||||
foreign import ccall unsafe "extism.h &extism_context_free" extism_context_free :: FunPtr (Ptr ExtismContext -> IO ())
|
||||
foreign import ccall unsafe "extism.h extism_plugin_new" extism_plugin_new :: Ptr ExtismContext -> Ptr Word8 -> Word64 -> CBool -> IO Int32
|
||||
foreign import ccall unsafe "extism.h extism_plugin_update" extism_plugin_update :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Word64 -> CBool -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_plugin_call" extism_plugin_call :: Ptr ExtismContext -> Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
|
||||
foreign import ccall unsafe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismContext -> Int32 -> CString -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_error" extism_error :: Ptr ExtismContext -> Int32 -> IO CString
|
||||
foreign import ccall unsafe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismContext -> Int32 -> IO Word64
|
||||
foreign import ccall unsafe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismContext -> Int32 -> IO (Ptr Word8)
|
||||
foreign import ccall unsafe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Int64 -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismContext -> Int32 -> IO ()
|
||||
foreign import ccall unsafe "extism.h extism_context_reset" extism_context_reset :: Ptr ExtismContext -> IO ()
|
||||
foreign import ccall unsafe "extism.h extism_version" extism_version :: IO CString
|
||||
|
||||
-- | Cancellation handle for Plugins
|
||||
newtype CancelHandle = CancelHandle (Ptr ExtismCancelHandle)
|
||||
-- Context manages plugins
|
||||
newtype Context = Context (ForeignPtr ExtismContext)
|
||||
|
||||
-- | Log level
|
||||
data LogLevel = LogError | LogWarn | LogInfo | LogDebug | LogTrace deriving (Show, Eq)
|
||||
-- Plugins can be used to call WASM function
|
||||
data Plugin = Plugin Context Int32
|
||||
|
||||
-- | Extism error
|
||||
newtype Error = ExtismError String deriving (Show, Eq)
|
||||
-- Log level
|
||||
data LogLevel = Error | Warn | Info | Debug | Trace deriving (Show)
|
||||
|
||||
-- | Result type
|
||||
type Result a = Either Error a
|
||||
-- Extism error
|
||||
newtype Error = ErrorMessage String deriving Show
|
||||
|
||||
-- | Helper function to convert a 'String' to a 'ByteString'
|
||||
toByteString :: String -> B.ByteString
|
||||
toByteString x = B.pack (map c2w x)
|
||||
-- Helper function to convert a string to a bytestring
|
||||
toByteString :: String -> ByteString
|
||||
toByteString x = B.pack (Prelude.map c2w x)
|
||||
|
||||
-- | Helper function to convert a 'ByteString' to a 'String'
|
||||
fromByteString :: B.ByteString -> String
|
||||
fromByteString bs = map w2c $ B.unpack bs
|
||||
-- Helper function to convert a bytestring to a string
|
||||
fromByteString :: ByteString -> String
|
||||
fromByteString bs = Prelude.map w2c $ B.unpack bs
|
||||
|
||||
-- | Get the Extism version string
|
||||
-- Get the Extism version string
|
||||
extismVersion :: () -> IO String
|
||||
extismVersion () = do
|
||||
v <- extism_version
|
||||
peekCString v
|
||||
|
||||
-- | Create a 'Plugin' from a WASM module, `useWasi` determines if WASI should
|
||||
-- | be linked
|
||||
plugin :: B.ByteString -> [Function] -> Bool -> IO (Result Plugin)
|
||||
plugin wasm functions useWasi =
|
||||
let nfunctions = fromIntegral (length functions) in
|
||||
-- Remove all registered plugins in a Context
|
||||
reset :: Context -> IO ()
|
||||
reset (Context ctx) =
|
||||
withForeignPtr ctx extism_context_reset
|
||||
|
||||
-- Create a new context
|
||||
newContext :: () -> IO Context
|
||||
newContext () = do
|
||||
ptr <- extism_context_new
|
||||
fptr <- newForeignPtr extism_context_free ptr
|
||||
return (Context fptr)
|
||||
|
||||
-- Execute a function with a new context that is destroyed when it returns
|
||||
withContext :: (Context -> IO a) -> IO a
|
||||
withContext f = do
|
||||
ctx <- newContext ()
|
||||
f ctx
|
||||
|
||||
-- Create a plugin from a WASM module, `useWasi` determines if WASI should
|
||||
-- be linked
|
||||
plugin :: Context -> B.ByteString -> Bool -> IO (Either Error Plugin)
|
||||
plugin c wasm useWasi =
|
||||
let length = fromIntegral (B.length wasm) in
|
||||
let wasi = fromInteger (if useWasi then 1 else 0) in
|
||||
let Context ctx = c in
|
||||
do
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
p <- unsafeUseAsCString wasm (\s ->
|
||||
extism_plugin_new ctx (castPtr s) length wasi)
|
||||
if p < 0 then do
|
||||
err <- extism_error ctx (-1)
|
||||
e <- peekCString err
|
||||
return $ Left (ErrorMessage e)
|
||||
else
|
||||
return $ Right (Plugin c p))
|
||||
|
||||
-- Create a plugin from a Manifest
|
||||
pluginFromManifest :: Context -> Manifest -> Bool -> IO (Either Error Plugin)
|
||||
pluginFromManifest ctx manifest useWasi =
|
||||
let wasm = toByteString $ toString manifest in
|
||||
plugin ctx wasm useWasi
|
||||
|
||||
-- Update a plugin with a new WASM module
|
||||
update :: Plugin -> B.ByteString -> Bool -> IO (Either Error ())
|
||||
update (Plugin (Context ctx) id) wasm useWasi =
|
||||
let length = fromIntegral (B.length wasm) in
|
||||
let wasi = fromInteger (if useWasi then 1 else 0) in
|
||||
do
|
||||
funcs <- mapM (\(Function ptr _) -> withForeignPtr ptr (\x -> do return x)) functions
|
||||
alloca (\e-> do
|
||||
let errmsg = (e :: Ptr CString)
|
||||
p <- unsafeUseAsCString wasm (\s ->
|
||||
withArray funcs (\funcs ->
|
||||
extism_plugin_new (castPtr s) length funcs nfunctions wasi errmsg ))
|
||||
if p == nullPtr then do
|
||||
err <- peek errmsg
|
||||
e <- peekCString err
|
||||
extism_plugin_new_error_free err
|
||||
return $ Left (ExtismError e)
|
||||
else do
|
||||
ptr <- Foreign.Concurrent.newForeignPtr p (extism_plugin_free p)
|
||||
return $ Right (Plugin ptr))
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
b <- unsafeUseAsCString wasm (\s ->
|
||||
extism_plugin_update ctx id (castPtr s) length wasi)
|
||||
if b <= 0 then do
|
||||
err <- extism_error ctx (-1)
|
||||
e <- peekCString err
|
||||
return $ Left (ErrorMessage e)
|
||||
else
|
||||
return (Right ()))
|
||||
|
||||
-- | Create a 'Plugin' from a 'Manifest'
|
||||
pluginFromManifest :: Manifest -> [Function] -> Bool -> IO (Result Plugin)
|
||||
pluginFromManifest manifest functions useWasi =
|
||||
-- Update a plugin with a new Manifest
|
||||
updateManifest :: Plugin -> Manifest -> Bool -> IO (Either Error ())
|
||||
updateManifest plugin manifest useWasi =
|
||||
let wasm = toByteString $ toString manifest in
|
||||
plugin wasm functions useWasi
|
||||
update plugin wasm useWasi
|
||||
|
||||
-- | Check if a 'Plugin' is valid
|
||||
isValid :: Plugin -> IO Bool
|
||||
isValid (Plugin p) = withForeignPtr p (\x -> return (x /= nullPtr))
|
||||
-- Check if a plugin is value
|
||||
isValid :: Plugin -> Bool
|
||||
isValid (Plugin _ p) = p >= 0
|
||||
|
||||
-- | Set configuration values for a plugin
|
||||
convertMaybeString Nothing = JSNull
|
||||
convertMaybeString (Just s) = JSString (toJSString s)
|
||||
|
||||
-- Set configuration values for a plugin
|
||||
setConfig :: Plugin -> [(String, Maybe String)] -> IO Bool
|
||||
setConfig (Plugin plugin) x =
|
||||
let obj = Text.JSON.toJSObject [(k, Text.JSON.showJSON v) | (k, v) <- x] in
|
||||
let bs = toByteString (Text.JSON.encode obj) in
|
||||
let length = fromIntegral (B.length bs) in
|
||||
unsafeUseAsCString bs (\s -> do
|
||||
withForeignPtr plugin (\plugin-> do
|
||||
b <- extism_plugin_config plugin (castPtr s) length
|
||||
return $ b /= 0))
|
||||
setConfig (Plugin (Context ctx) plugin) x =
|
||||
if plugin < 0
|
||||
then return False
|
||||
else
|
||||
let obj = toJSObject [(k, convertMaybeString v) | (k, v) <- x] in
|
||||
let bs = toByteString (encode obj) in
|
||||
let length = fromIntegral (B.length bs) in
|
||||
unsafeUseAsCString bs (\s -> do
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
b <- extism_plugin_config ctx plugin (castPtr s) length
|
||||
return $ b /= 0))
|
||||
|
||||
levelStr LogError = "error"
|
||||
levelStr LogDebug = "debug"
|
||||
levelStr LogWarn = "warn"
|
||||
levelStr LogTrace = "trace"
|
||||
levelStr LogInfo = "info"
|
||||
levelStr Error = "error"
|
||||
levelStr Debug = "debug"
|
||||
levelStr Warn = "warn"
|
||||
levelStr Trace = "trace"
|
||||
levelStr Info = "info"
|
||||
|
||||
-- | Set the log file and level, this is a global configuration
|
||||
-- Set the log file and level, this is a global configuration
|
||||
setLogFile :: String -> LogLevel -> IO Bool
|
||||
setLogFile filename level =
|
||||
let s = levelStr level in
|
||||
@@ -130,65 +158,35 @@ setLogFile filename level =
|
||||
b <- extism_log_file f l
|
||||
return $ b /= 0))
|
||||
|
||||
-- | Check if a function exists in the given plugin
|
||||
-- Check if a function exists in the given plugin
|
||||
functionExists :: Plugin -> String -> IO Bool
|
||||
functionExists (Plugin plugin) name = do
|
||||
withForeignPtr plugin (\plugin -> do
|
||||
b <- withCString name (extism_plugin_function_exists plugin)
|
||||
functionExists (Plugin (Context ctx) plugin) name = do
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
b <- withCString name (extism_plugin_function_exists ctx plugin)
|
||||
if b == 1 then return True else return False)
|
||||
|
||||
--- | Call a function provided by the given plugin
|
||||
call :: Plugin -> String -> B.ByteString -> IO (Result B.ByteString)
|
||||
call (Plugin plugin) name input =
|
||||
--- Call a function provided by the given plugin
|
||||
call :: Plugin -> String -> B.ByteString -> IO (Either Error B.ByteString)
|
||||
call (Plugin (Context ctx) plugin) name input =
|
||||
let length = fromIntegral (B.length input) in
|
||||
do
|
||||
withForeignPtr plugin (\plugin -> do
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
rc <- withCString name (\name ->
|
||||
unsafeUseAsCString input (\input ->
|
||||
extism_plugin_call plugin name (castPtr input) length))
|
||||
err <- extism_error plugin
|
||||
extism_plugin_call ctx plugin name (castPtr input) length))
|
||||
err <- extism_error ctx plugin
|
||||
if err /= nullPtr
|
||||
then do e <- peekCString err
|
||||
return $ Left (ExtismError e)
|
||||
return $ Left (ErrorMessage e)
|
||||
else if rc == 0
|
||||
then do
|
||||
length <- extism_plugin_output_length plugin
|
||||
ptr <- extism_plugin_output_data plugin
|
||||
buf <- B.packCStringLen (castPtr ptr, fromIntegral length)
|
||||
length <- extism_plugin_output_length ctx plugin
|
||||
ptr <- extism_plugin_output_data ctx plugin
|
||||
buf <- packCStringLen (castPtr ptr, fromIntegral length)
|
||||
return $ Right buf
|
||||
else return $ Left (ExtismError "Call failed"))
|
||||
else return $ Left (ErrorMessage "Call failed"))
|
||||
|
||||
-- | Call a function with a string argument and return a string
|
||||
callString :: Plugin -> String -> String -> IO (Result String)
|
||||
callString p name input = do
|
||||
res <- call p name (toByteString input)
|
||||
case res of
|
||||
Left x -> return $ Left x
|
||||
Right x -> return $ Right (fromByteString x)
|
||||
|
||||
|
||||
-- | Create a new 'CancelHandle' that can be used to cancel a running plugin
|
||||
-- | from another thread.
|
||||
cancelHandle :: Plugin -> IO CancelHandle
|
||||
cancelHandle (Plugin plugin) = do
|
||||
handle <- withForeignPtr plugin extism_plugin_cancel_handle
|
||||
return (CancelHandle handle)
|
||||
|
||||
-- | Cancel a running plugin using a 'CancelHandle'
|
||||
cancel :: CancelHandle -> IO Bool
|
||||
cancel (CancelHandle handle) =
|
||||
extism_plugin_cancel handle
|
||||
|
||||
pluginID :: Plugin -> IO Data.UUID.UUID
|
||||
pluginID (Plugin plugin) =
|
||||
withForeignPtr plugin (\plugin -> do
|
||||
ptr <- extism_plugin_id plugin
|
||||
buf <- B.packCStringLen (castPtr ptr, 16)
|
||||
case Data.UUID.fromByteString (BL.fromStrict buf) of
|
||||
Nothing -> error "Invalid Plugin ID"
|
||||
Just x -> return x)
|
||||
|
||||
|
||||
unwrap (Right x) = x
|
||||
unwrap (Left (ExtismError msg)) = do
|
||||
error msg
|
||||
-- Free a plugin
|
||||
free :: Plugin -> IO ()
|
||||
free (Plugin (Context ctx) plugin) =
|
||||
withForeignPtr ctx (`extism_plugin_free` plugin)
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
{-# LANGUAGE ForeignFunctionInterface #-}
|
||||
|
||||
module Extism.Bindings where
|
||||
|
||||
import Foreign.C.Types
|
||||
import Foreign.Ptr
|
||||
import Foreign.C.String
|
||||
import Data.Int
|
||||
import Data.Word
|
||||
import Foreign.Storable
|
||||
import Foreign.Marshal.Array
|
||||
import Foreign.StablePtr
|
||||
|
||||
type FreeCallback = Ptr () -> IO ()
|
||||
|
||||
newtype ExtismPlugin = ExtismPlugin () deriving Show
|
||||
newtype ExtismFunction = ExtismFunction () deriving Show
|
||||
newtype ExtismCancelHandle = ExtismCancelHandle () deriving Show
|
||||
newtype ExtismCurrentPlugin = ExtismCurrentPlugin () deriving Show
|
||||
data ValType = I32 | I64 | F32 | F64 | V128 | FuncRef | ExternRef deriving (Show, Eq)
|
||||
data Val = ValI32 Int32 | ValI64 Int64 | ValF32 Float | ValF64 Double deriving (Show, Eq)
|
||||
|
||||
typeOfVal (ValI32 _) = I32
|
||||
typeOfVal (ValI64 _) = I64
|
||||
typeOfVal (ValF32 _) = F32
|
||||
typeOfVal (ValF64 _) = F64
|
||||
|
||||
type CCallback = Ptr ExtismCurrentPlugin -> Ptr Val -> Word64 -> Ptr Val -> Word64 -> Ptr () -> IO ()
|
||||
|
||||
_32Bit = sizeOf (undefined :: Int) == 4
|
||||
|
||||
instance Storable Val where
|
||||
sizeOf _ =
|
||||
if _32Bit then 12 else 16
|
||||
alignment _ = 1
|
||||
peek ptr = do
|
||||
let offs = if _32Bit then 4 else 8
|
||||
t <- valTypeOfInt <$> peekByteOff ptr 0
|
||||
case t of
|
||||
I32 -> ValI32 <$> peekByteOff ptr offs
|
||||
I64 -> ValI64 <$> peekByteOff ptr offs
|
||||
F32 -> ValF32 <$> peekByteOff ptr offs
|
||||
F64 -> ValF64 <$> peekByteOff ptr offs
|
||||
poke ptr x = do
|
||||
let offs = if _32Bit then 4 else 8
|
||||
pokeByteOff ptr 0 (typeOfVal x)
|
||||
case x of
|
||||
ValI32 x -> pokeByteOff ptr offs x
|
||||
ValI64 x -> pokeByteOff ptr offs x
|
||||
ValF32 x -> pokeByteOff ptr offs x
|
||||
ValF64 x -> pokeByteOff ptr offs x
|
||||
|
||||
|
||||
intOfValType :: ValType -> CInt
|
||||
intOfValType I32 = 0
|
||||
intOfValType I64 = 1
|
||||
intOfValType F32 = 2
|
||||
intOfValType F64 = 3
|
||||
intOfValType V128 = 4
|
||||
intOfValType FuncRef = 5
|
||||
intOfValType ExternRef = 6
|
||||
|
||||
valTypeOfInt :: CInt -> ValType
|
||||
valTypeOfInt 0 = I32
|
||||
valTypeOfInt 1 = I64
|
||||
valTypeOfInt 2 = F32
|
||||
valTypeOfInt 3 = F64
|
||||
valTypeOfInt 4 = V128
|
||||
valTypeOfInt 5 = FuncRef
|
||||
valTypeOfInt 6 = ExternRef
|
||||
valTypeOfInt _ = error "Invalid ValType"
|
||||
|
||||
instance Storable ValType where
|
||||
sizeOf _ = 4
|
||||
alignment _ = 1
|
||||
peek ptr = do
|
||||
x <- peekByteOff ptr 0
|
||||
return $ valTypeOfInt (x :: CInt)
|
||||
poke ptr x = do
|
||||
pokeByteOff ptr 0 (intOfValType x)
|
||||
|
||||
foreign import ccall safe "extism.h extism_plugin_new" extism_plugin_new :: Ptr Word8 -> Word64 -> Ptr (Ptr ExtismFunction) -> Word64 -> CBool -> Ptr CString -> IO (Ptr ExtismPlugin)
|
||||
foreign import ccall safe "extism.h extism_plugin_call" extism_plugin_call :: Ptr ExtismPlugin -> CString -> Ptr Word8 -> Word64 -> IO Int32
|
||||
foreign import ccall safe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismPlugin -> CString -> IO CBool
|
||||
foreign import ccall safe "extism.h extism_plugin_error" extism_error :: Ptr ExtismPlugin -> IO CString
|
||||
foreign import ccall safe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismPlugin -> IO Word64
|
||||
foreign import ccall safe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismPlugin -> IO (Ptr Word8)
|
||||
foreign import ccall safe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
|
||||
foreign import ccall safe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismPlugin -> Ptr Word8 -> Int64 -> IO CBool
|
||||
foreign import ccall safe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismPlugin -> IO ()
|
||||
foreign import ccall safe "extism.h extism_plugin_new_error_free" extism_plugin_new_error_free :: CString -> IO ()
|
||||
foreign import ccall safe "extism.h extism_version" extism_version :: IO CString
|
||||
foreign import ccall safe "extism.h extism_plugin_id" extism_plugin_id :: Ptr ExtismPlugin -> IO (Ptr Word8)
|
||||
foreign import ccall safe "extism.h extism_plugin_cancel_handle" extism_plugin_cancel_handle :: Ptr ExtismPlugin -> IO (Ptr ExtismCancelHandle)
|
||||
foreign import ccall safe "extism.h extism_plugin_cancel" extism_plugin_cancel :: Ptr ExtismCancelHandle -> IO Bool
|
||||
|
||||
foreign import ccall safe "extism.h extism_function_new" extism_function_new :: CString -> Ptr ValType -> Word64 -> Ptr ValType -> Word64 -> FunPtr CCallback -> Ptr () -> FunPtr FreeCallback -> IO (Ptr ExtismFunction)
|
||||
foreign import ccall safe "extism.h extism_function_free" extism_function_free :: Ptr ExtismFunction -> IO ()
|
||||
foreign import ccall safe "extism.h extism_current_plugin_memory" extism_current_plugin_memory :: Ptr ExtismCurrentPlugin -> IO (Ptr Word8)
|
||||
foreign import ccall safe "extism.h extism_current_plugin_memory_alloc" extism_current_plugin_memory_alloc :: Ptr ExtismCurrentPlugin -> Word64 -> IO Word64
|
||||
foreign import ccall safe "extism.h extism_current_plugin_memory_length" extism_current_plugin_memory_length :: Ptr ExtismCurrentPlugin -> Word64 -> IO Word64
|
||||
foreign import ccall safe "extism.h extism_current_plugin_memory_free" extism_current_plugin_memory_free :: Ptr ExtismCurrentPlugin -> Word64 -> IO ()
|
||||
|
||||
freePtr ptr = do
|
||||
let s = castPtrToStablePtr ptr
|
||||
(a, b, c) <- deRefStablePtr s
|
||||
freeHaskellFunPtr b
|
||||
freeHaskellFunPtr c
|
||||
freeStablePtr s
|
||||
|
||||
foreign import ccall "wrapper" freePtrWrap :: FreeCallback -> IO (FunPtr FreeCallback)
|
||||
|
||||
foreign import ccall "wrapper" callbackWrap :: CCallback -> IO (FunPtr CCallback)
|
||||
|
||||
callback :: (Ptr ExtismCurrentPlugin -> [Val] -> a -> IO [Val]) -> (Ptr ExtismCurrentPlugin -> Ptr Val -> Word64 -> Ptr Val -> Word64 -> Ptr () -> IO ())
|
||||
callback f plugin params nparams results nresults ptr = do
|
||||
p <- peekArray (fromIntegral nparams) params
|
||||
(userData, _, _) <- deRefStablePtr (castPtrToStablePtr ptr)
|
||||
res <- f plugin p userData
|
||||
pokeArray results res
|
||||
@@ -1,157 +0,0 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
|
||||
module Extism.HostFunction(
|
||||
CurrentPlugin(..),
|
||||
ValType(..),
|
||||
Val(..),
|
||||
MemoryHandle,
|
||||
memoryAlloc,
|
||||
memoryLength,
|
||||
memoryFree,
|
||||
memory,
|
||||
memoryOffset,
|
||||
memoryBytes,
|
||||
memoryString,
|
||||
allocBytes,
|
||||
allocString,
|
||||
toI32,
|
||||
toI64,
|
||||
toF32,
|
||||
toF64,
|
||||
fromI32,
|
||||
fromI64,
|
||||
fromF32,
|
||||
fromF64,
|
||||
hostFunction
|
||||
) where
|
||||
|
||||
import Extism
|
||||
import Extism.Bindings
|
||||
import Data.Word
|
||||
import qualified Data.ByteString as B
|
||||
import Foreign.Ptr
|
||||
import Foreign.ForeignPtr
|
||||
import Foreign.C.String
|
||||
import Foreign.StablePtr
|
||||
import Foreign.Concurrent
|
||||
import Foreign.Marshal.Array
|
||||
import qualified Data.ByteString.Internal as BS (c2w)
|
||||
|
||||
-- | Access the plugin that is currently executing from inside a host function
|
||||
type CurrentPlugin = Ptr ExtismCurrentPlugin
|
||||
|
||||
-- | A memory handle represents an allocated block of Extism memory
|
||||
newtype MemoryHandle = MemoryHandle Word64 deriving (Num, Enum, Eq, Ord, Real, Integral, Show)
|
||||
|
||||
-- | Allocate a new handle of the given size
|
||||
memoryAlloc :: CurrentPlugin -> Word64 -> IO MemoryHandle
|
||||
memoryAlloc p n = MemoryHandle <$> extism_current_plugin_memory_alloc p n
|
||||
|
||||
-- | Get the length of a handle, returns 0 if the handle is invalid
|
||||
memoryLength :: CurrentPlugin -> MemoryHandle -> IO Word64
|
||||
memoryLength p (MemoryHandle offs) = extism_current_plugin_memory_length p offs
|
||||
|
||||
-- | Free allocated memory
|
||||
memoryFree :: CurrentPlugin -> MemoryHandle -> IO ()
|
||||
memoryFree p (MemoryHandle offs) = extism_current_plugin_memory_free p offs
|
||||
|
||||
-- | Access a pointer to the entire memory region
|
||||
memory :: CurrentPlugin -> IO (Ptr Word8)
|
||||
memory = extism_current_plugin_memory
|
||||
|
||||
-- | Access the pointer for the given 'MemoryHandle'
|
||||
memoryOffset :: CurrentPlugin -> MemoryHandle -> IO (Ptr Word8)
|
||||
memoryOffset plugin (MemoryHandle offs) = do
|
||||
x <- extism_current_plugin_memory plugin
|
||||
return $ plusPtr x (fromIntegral offs)
|
||||
|
||||
-- | Access the data associated with a handle as a 'ByteString'
|
||||
memoryBytes :: CurrentPlugin -> MemoryHandle -> IO B.ByteString
|
||||
memoryBytes plugin offs = do
|
||||
ptr <- memoryOffset plugin offs
|
||||
len <- memoryLength plugin offs
|
||||
arr <- peekArray (fromIntegral len) ptr
|
||||
return $ B.pack arr
|
||||
|
||||
|
||||
-- | Access the data associated with a handle as a 'String'
|
||||
memoryString :: CurrentPlugin -> MemoryHandle -> IO String
|
||||
memoryString plugin offs = do
|
||||
ptr <- memoryOffset plugin offs
|
||||
len <- memoryLength plugin offs
|
||||
arr <- peekArray (fromIntegral len) ptr
|
||||
return $ fromByteString $ B.pack arr
|
||||
|
||||
-- | Allocate memory and copy an existing 'ByteString' into it
|
||||
allocBytes :: CurrentPlugin -> B.ByteString -> IO MemoryHandle
|
||||
allocBytes plugin s = do
|
||||
let length = B.length s
|
||||
offs <- memoryAlloc plugin (fromIntegral length)
|
||||
ptr <- memoryOffset plugin offs
|
||||
pokeArray ptr (B.unpack s)
|
||||
return offs
|
||||
|
||||
|
||||
-- | Allocate memory and copy an existing 'String' into it
|
||||
allocString :: CurrentPlugin -> String -> IO MemoryHandle
|
||||
allocString plugin s = do
|
||||
let length = Prelude.length s
|
||||
offs <- memoryAlloc plugin (fromIntegral length)
|
||||
ptr <- memoryOffset plugin offs
|
||||
pokeArray ptr (Prelude.map BS.c2w s)
|
||||
return offs
|
||||
|
||||
-- | Create a new I32 'Val'
|
||||
toI32 :: Integral a => a -> Val
|
||||
toI32 x = ValI32 (fromIntegral x)
|
||||
|
||||
-- | Create a new I64 'Val'
|
||||
toI64 :: Integral a => a -> Val
|
||||
toI64 x = ValI64 (fromIntegral x)
|
||||
|
||||
-- | Create a new F32 'Val'
|
||||
toF32 :: Float -> Val
|
||||
toF32 = ValF32
|
||||
|
||||
-- | Create a new F64 'Val'
|
||||
toF64 :: Double -> Val
|
||||
toF64 = ValF64
|
||||
|
||||
-- | Get I32 'Val'
|
||||
fromI32 :: Integral a => Val -> Maybe a
|
||||
fromI32 (ValI32 x) = Just (fromIntegral x)
|
||||
fromI32 _ = Nothing
|
||||
|
||||
-- | Get I64 'Val'
|
||||
fromI64 :: Integral a => Val -> Maybe a
|
||||
fromI64 (ValI64 x) = Just (fromIntegral x)
|
||||
fromI64 _ = Nothing
|
||||
|
||||
-- | Get F32 'Val'
|
||||
fromF32 :: Val -> Maybe Float
|
||||
fromF32 (ValF32 x) = Just x
|
||||
fromF32 _ = Nothing
|
||||
|
||||
-- | Get F64 'Val'
|
||||
fromF64 :: Val -> Maybe Double
|
||||
fromF64 (ValF64 x) = Just x
|
||||
fromF64 _ = Nothing
|
||||
|
||||
-- | Create a new 'Function' that can be called from a 'Plugin'
|
||||
hostFunction :: String -> [ValType] -> [ValType] -> (CurrentPlugin -> [Val] -> a -> IO [Val]) -> a -> IO Function
|
||||
hostFunction name params results f v =
|
||||
let nparams = fromIntegral $ length params in
|
||||
let nresults = fromIntegral $ length results in
|
||||
do
|
||||
cb <- callbackWrap (callback f :: CCallback)
|
||||
free <- freePtrWrap freePtr
|
||||
userData <- newStablePtr (v, free, cb)
|
||||
let userDataPtr = castStablePtrToPtr userData
|
||||
x <- withCString name (\name -> do
|
||||
withArray params (\params ->
|
||||
withArray results (\results -> do
|
||||
extism_function_new name params nparams results nresults cb userDataPtr free)))
|
||||
let freeFn = extism_function_free x
|
||||
fptr <- Foreign.Concurrent.newForeignPtr x freeFn
|
||||
return $ Function fptr (castPtrToStablePtr userDataPtr)
|
||||
|
||||
183
haskell/src/Extism/Manifest.hs
Normal file
183
haskell/src/Extism/Manifest.hs
Normal file
@@ -0,0 +1,183 @@
|
||||
module Extism.Manifest where
|
||||
|
||||
import Text.JSON
|
||||
(
|
||||
JSValue(JSNull, JSString, JSArray),
|
||||
toJSString, showJSON, makeObj, encode
|
||||
)
|
||||
import qualified Data.ByteString as B
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import qualified Data.ByteString.Char8 as BS (unpack)
|
||||
|
||||
valueOrNull f Nothing = JSNull
|
||||
valueOrNull f (Just x) = f x
|
||||
makeString s = JSString (toJSString s)
|
||||
stringOrNull = valueOrNull makeString
|
||||
makeArray f [] = JSNull
|
||||
makeArray f x = JSArray [f a | a <- x]
|
||||
filterNulls obj = [(a, b) | (a, b) <- obj, not (isNull b)]
|
||||
mapObj f x = makeObj (filterNulls [(a, f b) | (a, b) <- x])
|
||||
isNull JSNull = True
|
||||
isNull _ = False
|
||||
|
||||
newtype Memory = Memory
|
||||
{
|
||||
memoryMax :: Maybe Int
|
||||
}
|
||||
|
||||
class JSONValue a where
|
||||
toJSONValue :: a -> JSValue
|
||||
|
||||
instance JSONValue Memory where
|
||||
toJSONValue x =
|
||||
case memoryMax x of
|
||||
Nothing -> makeObj []
|
||||
Just max -> makeObj [("max", showJSON max)]
|
||||
|
||||
data HttpRequest = HttpRequest
|
||||
{
|
||||
url :: String
|
||||
, header :: [(String, String)]
|
||||
, method :: Maybe String
|
||||
}
|
||||
|
||||
requestObj x =
|
||||
let meth = stringOrNull $ method x in
|
||||
let h = mapObj makeString $ header x in
|
||||
filterNulls [
|
||||
("url", makeString $ url x),
|
||||
("header", h),
|
||||
("method", meth)
|
||||
]
|
||||
|
||||
instance JSONValue HttpRequest where
|
||||
toJSONValue x =
|
||||
makeObj $ requestObj x
|
||||
|
||||
data WasmFile = WasmFile
|
||||
{
|
||||
filePath :: String
|
||||
, fileName :: Maybe String
|
||||
, fileHash :: Maybe String
|
||||
}
|
||||
|
||||
instance JSONValue WasmFile where
|
||||
toJSONValue x =
|
||||
let path = makeString $ filePath x in
|
||||
let name = stringOrNull $ fileName x in
|
||||
let hash = stringOrNull $ fileHash x in
|
||||
makeObj $ filterNulls [
|
||||
("path", path),
|
||||
("name", name),
|
||||
("hash", hash)
|
||||
]
|
||||
|
||||
data WasmCode = WasmCode
|
||||
{
|
||||
codeBytes :: B.ByteString
|
||||
, codeName :: Maybe String
|
||||
, codeHash :: Maybe String
|
||||
}
|
||||
|
||||
|
||||
instance JSONValue WasmCode where
|
||||
toJSONValue x =
|
||||
let bytes = makeString $ BS.unpack $ B64.encode $ codeBytes x in
|
||||
let name = stringOrNull $ codeName x in
|
||||
let hash = stringOrNull $ codeHash x in
|
||||
makeObj $ filterNulls [
|
||||
("data", bytes),
|
||||
("name", name),
|
||||
("hash", hash)
|
||||
]
|
||||
|
||||
data WasmURL = WasmURL
|
||||
{
|
||||
req :: HttpRequest
|
||||
, urlName :: Maybe String
|
||||
, urlHash :: Maybe String
|
||||
}
|
||||
|
||||
|
||||
instance JSONValue WasmURL where
|
||||
toJSONValue x =
|
||||
let request = requestObj $ req x in
|
||||
let name = stringOrNull $ urlName x in
|
||||
let hash = stringOrNull $ urlHash x in
|
||||
makeObj $ filterNulls $ ("name", name) : ("hash", hash) : request
|
||||
|
||||
data Wasm = File WasmFile | Code WasmCode | URL WasmURL
|
||||
|
||||
instance JSONValue Wasm where
|
||||
toJSONValue x =
|
||||
case x of
|
||||
File f -> toJSONValue f
|
||||
Code d -> toJSONValue d
|
||||
URL u -> toJSONValue u
|
||||
|
||||
wasmFile :: String -> Wasm
|
||||
wasmFile path =
|
||||
File WasmFile { filePath = path, fileName = Nothing, fileHash = Nothing}
|
||||
|
||||
wasmURL :: String -> String -> Wasm
|
||||
wasmURL method url =
|
||||
let r = HttpRequest { url = url, header = [], method = Just method } in
|
||||
URL WasmURL { req = r, urlName = Nothing, urlHash = Nothing }
|
||||
|
||||
wasmCode :: B.ByteString -> Wasm
|
||||
wasmCode code =
|
||||
Code WasmCode { codeBytes = code, codeName = Nothing, codeHash = Nothing }
|
||||
|
||||
withName :: Wasm -> String -> Wasm
|
||||
withName (Code code) name = Code code { codeName = Just name }
|
||||
withName (URL url) name = URL url { urlName = Just name }
|
||||
withName (File f) name = File f { fileName = Just name }
|
||||
|
||||
|
||||
withHash :: Wasm -> String -> Wasm
|
||||
withHash (Code code) hash = Code code { codeHash = Just hash }
|
||||
withHash (URL url) hash = URL url { urlHash = Just hash }
|
||||
withHash (File f) hash = File f { fileHash = Just hash }
|
||||
|
||||
data Manifest = Manifest
|
||||
{
|
||||
wasm :: [Wasm]
|
||||
, memory :: Maybe Memory
|
||||
, config :: [(String, String)]
|
||||
, allowed_hosts :: [String]
|
||||
}
|
||||
|
||||
manifest :: [Wasm] -> Manifest
|
||||
manifest wasm =
|
||||
Manifest {
|
||||
wasm = wasm,
|
||||
memory = Nothing,
|
||||
config = [],
|
||||
allowed_hosts = []
|
||||
}
|
||||
|
||||
withConfig :: Manifest -> [(String, String)] -> Manifest
|
||||
withConfig m config =
|
||||
m { config = config }
|
||||
|
||||
|
||||
withHosts :: Manifest -> [String] -> Manifest
|
||||
withHosts m hosts =
|
||||
m { allowed_hosts = hosts }
|
||||
|
||||
instance JSONValue Manifest where
|
||||
toJSONValue x =
|
||||
let w = makeArray toJSONValue $ wasm x in
|
||||
let mem = valueOrNull toJSONValue $ memory x in
|
||||
let c = mapObj makeString $ config x in
|
||||
let hosts = makeArray makeString $ allowed_hosts x in
|
||||
makeObj $ filterNulls [
|
||||
("wasm", w),
|
||||
("memory", mem),
|
||||
("config", c),
|
||||
("allowed_hosts", hosts)
|
||||
]
|
||||
|
||||
toString :: Manifest -> String
|
||||
toString manifest =
|
||||
encode (toJSONValue manifest)
|
||||
67
haskell/stack.yaml
Normal file
67
haskell/stack.yaml
Normal file
@@ -0,0 +1,67 @@
|
||||
# This file was automatically generated by 'stack init'
|
||||
#
|
||||
# Some commonly used options have been documented as comments in this file.
|
||||
# For advanced use and comprehensive documentation of the format, please see:
|
||||
# https://docs.haskellstack.org/en/stable/yaml_configuration/
|
||||
|
||||
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
|
||||
# A snapshot resolver dictates the compiler version and the set of packages
|
||||
# to be used for project dependencies. For example:
|
||||
#
|
||||
# resolver: lts-3.5
|
||||
# resolver: nightly-2015-09-21
|
||||
# resolver: ghc-7.10.2
|
||||
#
|
||||
# The location of a snapshot can be provided as a file or url. Stack assumes
|
||||
# a snapshot provided as a file might change, whereas a url resource does not.
|
||||
#
|
||||
# resolver: ./custom-snapshot.yaml
|
||||
# resolver: https://example.com/snapshots/2018-01-01.yaml
|
||||
resolver:
|
||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml
|
||||
|
||||
# User packages to be built.
|
||||
# Various formats can be used as shown in the example below.
|
||||
#
|
||||
# packages:
|
||||
# - some-directory
|
||||
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
|
||||
# subdirs:
|
||||
# - auto-update
|
||||
# - wai
|
||||
packages:
|
||||
- .
|
||||
# Dependency packages to be pulled from upstream that are not in the resolver.
|
||||
# These entries can reference officially published versions as well as
|
||||
# forks / in-progress versions pinned to a git hash. For example:
|
||||
#
|
||||
# extra-deps:
|
||||
# - acme-missiles-0.3
|
||||
# - git: https://github.com/commercialhaskell/stack.git
|
||||
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
|
||||
#
|
||||
# extra-deps: []
|
||||
|
||||
# Override default flag values for local packages and extra-deps
|
||||
# flags: {}
|
||||
|
||||
# Extra package databases containing global packages
|
||||
# extra-package-dbs: []
|
||||
|
||||
# Control whether we use the GHC we find on the path
|
||||
# system-ghc: true
|
||||
#
|
||||
# Require a specific version of stack, using version ranges
|
||||
# require-stack-version: -any # Default
|
||||
# require-stack-version: ">=2.7"
|
||||
#
|
||||
# Override the architecture used by stack, especially useful on Windows
|
||||
# arch: i386
|
||||
# arch: x86_64
|
||||
#
|
||||
# Extra directories used by stack for building
|
||||
# extra-include-dirs: [/path/to/dir]
|
||||
# extra-lib-dirs: [/path/to/dir]
|
||||
#
|
||||
# Allow a newer minor version of GHC than the snapshot specifies
|
||||
# compiler-check: newer-minor
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user