mirror of
https://github.com/extism/extism.git
synced 2026-01-11 23:08:06 -05:00
Compare commits
2 Commits
test-java-
...
add-elixir
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c31215ce7 | ||
|
|
fc95a99e40 |
30
.github/actions/extism/action.yml
vendored
30
.github/actions/extism/action.yml
vendored
@@ -1,30 +0,0 @@
|
||||
on: [workflow_call]
|
||||
|
||||
name: libextism
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
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/**
|
||||
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: Install extism shared library
|
||||
shell: bash
|
||||
run: |
|
||||
sudo make install
|
||||
41
.github/dependabot.yml
vendored
41
.github/dependabot.yml
vendored
@@ -1,41 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directory: "python"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "mix"
|
||||
directory: "elixir"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "node"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "bundler"
|
||||
directory: "ruby"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
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, macos-latest, windows-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
|
||||
cd go
|
||||
LD_LIBRARY_PATH=/usr/local/lib go run main.go
|
||||
LD_LIBRARY_PATH=/usr/local/lib go test
|
||||
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
|
||||
42
.github/workflows/ci-java.yml
vendored
42
.github/workflows/ci-java.yml
vendored
@@ -1,42 +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: [8, 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
|
||||
cat pom.xml | sed 's/<java.version>17/<java.version>${{ matrix.version }}/' > updated.xml
|
||||
mv updated.xml pom.xml
|
||||
mvn --batch-mode -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn verify
|
||||
#- name: Examine logs
|
||||
# if: success() || failure()
|
||||
# run: |
|
||||
# cat /tmp/extism.log
|
||||
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
|
||||
|
||||
105
.github/workflows/ci-rust.yml
vendored
105
.github/workflows/ci-rust.yml
vendored
@@ -1,105 +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-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 }}
|
||||
34
.github/workflows/ci-zig.yml
vendored
34
.github/workflows/ci-zig.yml
vendored
@@ -1,34 +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]
|
||||
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
|
||||
|
||||
- name: Test Zig Host SDK
|
||||
run: |
|
||||
zig version
|
||||
cd zig
|
||||
LD_LIBRARY_PATH=/usr/local/lib zig build test
|
||||
150
.github/workflows/ci.yml
vendored
150
.github/workflows/ci.yml
vendored
@@ -1,25 +1,171 @@
|
||||
on: [pull_request, workflow_dispatch]
|
||||
on: [push, pull_request]
|
||||
|
||||
name: CI
|
||||
|
||||
env:
|
||||
RUNTIME_MANIFEST: runtime/Cargo.toml
|
||||
RUNTIME_CRATE: extism-runtime
|
||||
RUST_SDK_CRATE: extism
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build & Test
|
||||
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
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Cache Rust environment
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Format
|
||||
run: cargo fmt --check -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Lint
|
||||
run: cargo clippy --release --no-deps -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Test
|
||||
run: cargo test --all-features --release -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Install extism shared library
|
||||
shell: bash
|
||||
run: sudo make install
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
poetry install
|
||||
poetry run python example.py
|
||||
poetry run python -m unittest discover
|
||||
|
||||
- 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
|
||||
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Test Node Host SDK
|
||||
run: |
|
||||
cd node
|
||||
npm i
|
||||
npm run build
|
||||
LD_LIBRARY_PATH=/usr/local/lib node example.js
|
||||
|
||||
- name: Test Rust Host SDK
|
||||
run: LD_LIBRARY_PATH=/usr/local/lib cargo test --release -p ${{ env.RUST_SDK_CRATE }}
|
||||
|
||||
# - name: Setup OCaml env
|
||||
# uses: ocaml/setup-ocaml@v2
|
||||
|
||||
# - name: Test OCaml Host SDK
|
||||
# run: |
|
||||
# opam install -y .
|
||||
# cd ocaml
|
||||
# opam exec -- dune exec extism
|
||||
|
||||
- name: Setup Haskell env
|
||||
uses: haskell/actions/setup@v2
|
||||
with:
|
||||
enable-stack: true
|
||||
stack-version: "latest"
|
||||
|
||||
- name: Test Haskell SDK
|
||||
run: |
|
||||
cd haskell
|
||||
LD_LIBRARY_PATH=/usr/local/lib stack test
|
||||
|
||||
- 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
|
||||
|
||||
sdk_api_coverage:
|
||||
name: SDK API Coverage Report
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python env
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
check-latest: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get install ripgrep
|
||||
pip3 install pycparser
|
||||
|
||||
- name: Run coverage script
|
||||
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 }}
|
||||
34
.github/workflows/release-elixir.yaml
vendored
34
.github/workflows/release-elixir.yaml
vendored
@@ -1,34 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Elixir SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-elixir
|
||||
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 Elixir Host SDK
|
||||
uses: erlef/setup-beam@v1
|
||||
with:
|
||||
experimental-otp: true
|
||||
otp-version: '25.0.4'
|
||||
elixir-version: '1.14.0'
|
||||
|
||||
- name: Publish Elixir Host SDK to hex.pm
|
||||
env:
|
||||
HEX_API_KEY: ${{ secrets.HEX_PM_API_TOKEN }}
|
||||
run: |
|
||||
cd elixir
|
||||
cp ../LICENSE .
|
||||
make publish
|
||||
|
||||
17
.github/workflows/release-haskell.yaml
vendored
17
.github/workflows/release-haskell.yaml
vendored
@@ -1,17 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Rust 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
|
||||
30
.github/workflows/release-node.yaml
vendored
30
.github/workflows/release-node.yaml
vendored
@@ -1,30 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Node SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-node
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
|
||||
CI: true
|
||||
|
||||
- name: Release Node Host SDK
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
|
||||
CI: true
|
||||
run: |
|
||||
cd node
|
||||
make publish
|
||||
|
||||
36
.github/workflows/release-python.yaml
vendored
36
.github/workflows/release-python.yaml
vendored
@@ -1,36 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Python SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-python
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python env
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
check-latest: true
|
||||
- name: Run image
|
||||
uses: abatilo/actions-poetry@v2
|
||||
|
||||
- name: Build Python Host SDK
|
||||
run: |
|
||||
cd python
|
||||
cp ../LICENSE .
|
||||
make clean
|
||||
poetry install --no-dev
|
||||
poetry build
|
||||
|
||||
- name: Release Python Host SDK
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: ${{ secrets.PYPI_API_USER }}
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: python/dist/
|
||||
|
||||
25
.github/workflows/release-ruby.yaml
vendored
25
.github/workflows/release-ruby.yaml
vendored
@@ -1,25 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Ruby SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-ruby
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.1' # Version range or exact version of a Ruby version to use, using semvers version range syntax.
|
||||
|
||||
- name: Publish Ruby Gem
|
||||
env:
|
||||
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_TOKEN }}
|
||||
run: |
|
||||
cd ruby
|
||||
make publish RUBYGEMS_API_KEY=$RUBYGEMS_API_KEY
|
||||
|
||||
35
.github/workflows/release-rust.yaml
vendored
35
.github/workflows/release-rust.yaml
vendored
@@ -1,35 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Rust SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-rust
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust env
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- 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 5
|
||||
|
||||
cargo publish --manifest-path runtime/Cargo.toml --no-verify
|
||||
cargo publish --manifest-path rust/Cargo.toml
|
||||
|
||||
|
||||
112
.github/workflows/release.yml
vendored
112
.github/workflows/release.yml
vendored
@@ -1,13 +1,12 @@
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release
|
||||
|
||||
env:
|
||||
RUNTIME_MANIFEST: runtime/Cargo.toml
|
||||
RUNTIME_CRATE: libextism
|
||||
RUNTIME_CRATE: extism-runtime
|
||||
RUSTFLAGS: -C target-feature=-crt-static
|
||||
ARTIFACT_DIR: release-artifacts
|
||||
|
||||
@@ -214,3 +213,112 @@ jobs:
|
||||
files: |
|
||||
*.tar.gz
|
||||
*.txt
|
||||
|
||||
release-sdks:
|
||||
needs: [release-linux, release-macos] # release-windows
|
||||
name: publish-sdks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
|
||||
CI: true
|
||||
|
||||
- name: Release Node Host SDK
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
|
||||
CI: true
|
||||
run: |
|
||||
cd node
|
||||
npm publish
|
||||
|
||||
- name: Setup Rust env
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- 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 5
|
||||
|
||||
cargo publish --manifest-path runtime/Cargo.toml --no-verify
|
||||
cargo publish --manifest-path rust/Cargo.toml
|
||||
|
||||
- name: Setup Elixir Host SDK
|
||||
uses: erlef/setup-beam@v1
|
||||
with:
|
||||
experimental-otp: true
|
||||
otp-version: '25.0.4'
|
||||
elixir-version: '1.14.0'
|
||||
|
||||
- name: Publish Elixir Host SDK to hex.pm
|
||||
env:
|
||||
HEX_API_KEY: ${{ secrets.HEX_PM_API_TOKEN }}
|
||||
run: |
|
||||
cd elixir
|
||||
cp ../LICENSE .
|
||||
mix do deps.get, deps.compile
|
||||
mix hex.build
|
||||
mix hex.publish --yes
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.1' # Version range or exact version of a Ruby version to use, using semvers version range syntax.
|
||||
|
||||
- name: Publish Ruby Gem
|
||||
env:
|
||||
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_TOKEN }}
|
||||
run: |
|
||||
cd ruby
|
||||
gem build extism.gemspec
|
||||
gem push extism*.gem -k $RUBYGEMS_API_KEY
|
||||
|
||||
- name: Publish Elixir Host SDK to hex.pm
|
||||
env:
|
||||
HEX_API_KEY: ${{ secrets.HEX_PM_API_TOKEN }}
|
||||
run: |
|
||||
cd elixir
|
||||
mix do deps.get, deps.compile
|
||||
mix hex.build
|
||||
mix hex.publish --yes
|
||||
|
||||
- name: Setup Python env
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
check-latest: true
|
||||
|
||||
- name: Build Python Host SDK
|
||||
run: |
|
||||
pushd python
|
||||
python3 -m pip install --upgrade build
|
||||
cp ../LICENSE .
|
||||
cp ../README.md .
|
||||
python3 -m poetry build
|
||||
popd
|
||||
|
||||
- name: Release Python Host SDK
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
env:
|
||||
INPUT_VERIFY_METADATA: false
|
||||
with:
|
||||
user: ${{ secrets.PYPI_API_USER }}
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: python/dist/
|
||||
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -13,8 +13,6 @@ __pycache__
|
||||
python/dist
|
||||
python/poetry.lock
|
||||
c/main
|
||||
cpp/test/test
|
||||
cpp/example
|
||||
go/main
|
||||
ruby/.bundle/
|
||||
ruby/.yardoc
|
||||
@@ -25,14 +23,12 @@ ruby/pkg/
|
||||
ruby/spec/reports/
|
||||
ruby/tmp/
|
||||
ruby/Gemfile.lock
|
||||
cpp/example
|
||||
rust/target
|
||||
rust/test.log
|
||||
duniverse
|
||||
_build
|
||||
ocaml/duniverse
|
||||
ocaml/_build
|
||||
php/Extism.php
|
||||
dist-newstyle
|
||||
.stack-work
|
||||
vendor
|
||||
zig/zig-*
|
||||
zig/example-out/
|
||||
zig/*.log
|
||||
vendor
|
||||
@@ -1 +0,0 @@
|
||||
version = 0.24.1
|
||||
@@ -3,6 +3,8 @@ members = [
|
||||
"manifest",
|
||||
"runtime",
|
||||
"rust",
|
||||
"libextism",
|
||||
]
|
||||
exclude = [
|
||||
"elixir/native/extism_nif"
|
||||
]
|
||||
|
||||
|
||||
5
Makefile
5
Makefile
@@ -25,10 +25,7 @@ lint:
|
||||
cargo clippy --release --no-deps --manifest-path runtime/Cargo.toml
|
||||
|
||||
build:
|
||||
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
|
||||
|
||||
debug:
|
||||
RUSTFLAGS=-g $(MAKE) build
|
||||
cargo build --release $(FEATURE_FLAGS) --manifest-path runtime/Cargo.toml
|
||||
|
||||
install:
|
||||
install runtime/extism.h $(DEST)/include
|
||||
|
||||
18
README.md
18
README.md
@@ -10,21 +10,14 @@ 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) & 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/191437220-4030840d-1a9e-47b9-a44a-39a004885308.png"/>
|
||||
</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
|
||||
@@ -58,9 +51,8 @@ The easiest way to start would be to join the [Discord](https://discord.gg/cx3us
|
||||
Extism is an open-source product from the team at:
|
||||
|
||||
<p align="left">
|
||||
<a href="https://dylib.so" _target="blanks"><img width="200px" src="https://user-images.githubusercontent.com/7517515/198204119-5afdebb9-a5d8-4322-bd2a-46179c8d7b24.svg"/></a>
|
||||
</p>
|
||||
|
||||
<a href="https://dylib.so" _target="blanks"><img width="200px" src="https://user-images.githubusercontent.com/7517515/195408048-0f199b26-cba3-4635-b683-f43b1e610c82.svg"/></a>
|
||||
</p>![dylibso-logo-outline]
|
||||
|
||||
|
||||
_Reach out and tell us what you're building! We'd love to help._
|
||||
|
||||
130
browser/.gitignore
vendored
130
browser/.gitignore
vendored
@@ -1,130 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -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,23 +0,0 @@
|
||||
const { build } = require("esbuild");
|
||||
const { peerDependencies } = require('./package.json')
|
||||
|
||||
const sharedConfig = {
|
||||
entryPoints: ["src/index.ts"],
|
||||
bundle: true,
|
||||
minify: false,
|
||||
drop: [], // preseve debugger statements
|
||||
external: Object.keys(peerDependencies || {}),
|
||||
};
|
||||
|
||||
build({
|
||||
...sharedConfig,
|
||||
platform: 'node', // for CJS
|
||||
outfile: "dist/index.js",
|
||||
});
|
||||
|
||||
build({
|
||||
...sharedConfig,
|
||||
outfile: "dist/index.esm.js",
|
||||
platform: 'neutral', // for ESM
|
||||
format: "esm",
|
||||
});
|
||||
Binary file not shown.
@@ -1,238 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
|
||||
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
|
||||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
|
||||
<style>
|
||||
#main {
|
||||
width: 100%;
|
||||
}
|
||||
.manifest {
|
||||
display: flex; /* or inline-flex */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
.urlInput {
|
||||
width: 600px;
|
||||
}
|
||||
.funcName {
|
||||
width: 150px;
|
||||
}
|
||||
.textAreas {
|
||||
display: flex; /* or inline-flex */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
.inputBox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.inputBox > textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.outputBox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.outputBox > textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.space {
|
||||
height: 80px;
|
||||
}
|
||||
.dragAreas {
|
||||
display: flex; /* or inline-flex */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
.dragInput {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-style: dotted;
|
||||
border-color: #000;
|
||||
}
|
||||
.dragOutput {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.dropZone {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.outputImage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script type="text/babel">
|
||||
function getBase64(file, cb) {
|
||||
var reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = function () {
|
||||
cb(reader.result)
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
console.log("error")
|
||||
};
|
||||
}
|
||||
|
||||
function arrayTob64(buffer) {
|
||||
var binary = '';
|
||||
var bytes = [].slice.call(buffer);
|
||||
bytes.forEach((b) => binary += String.fromCharCode(b));
|
||||
return window.btoa(binary);
|
||||
}
|
||||
|
||||
|
||||
class App extends React.Component {
|
||||
state = {
|
||||
url: "https://raw.githubusercontent.com/extism/extism/main/wasm/code.wasm",
|
||||
input: new Uint8Array(),
|
||||
output: new Uint8Array(),
|
||||
func_name: "count_vowels",
|
||||
functions: []
|
||||
}
|
||||
|
||||
async loadFunctions(url) {
|
||||
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": url } ] })
|
||||
let functions = Object.keys(await plugin.getExports())
|
||||
console.log("funcs ", functions)
|
||||
this.setState({functions})
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadFunctions(this.state.url)
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.extismContext = props.extismContext
|
||||
}
|
||||
|
||||
handleInputChange(e) {
|
||||
e.preventDefault();
|
||||
this.setState({ [e.target.name]: e.target.value })
|
||||
if (e.target.name === "url") {
|
||||
this.loadFunctions(e.target.value)
|
||||
}
|
||||
}
|
||||
|
||||
onInputKeyPress(e) {
|
||||
if (e.keyCode == 13 && e.shiftKey == true) {
|
||||
e.preventDefault()
|
||||
this.handleOnRun()
|
||||
}
|
||||
}
|
||||
|
||||
async handleOnRun(e) {
|
||||
e && e.preventDefault && e.preventDefault();
|
||||
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})
|
||||
}
|
||||
|
||||
nop = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
handleDrop = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let files = [...e.dataTransfer.files];
|
||||
if (files && files.length == 1) {
|
||||
let file = files[0]
|
||||
console.log(file)
|
||||
file.arrayBuffer().then(b => {
|
||||
this.setState({input: new Uint8Array(b)})
|
||||
this.handleOnRun()
|
||||
})
|
||||
} else {
|
||||
throw Error("Only one file please")
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const funcOptions = this.state.functions.map(f => <option value={f}>{f}</option>)
|
||||
let image = null
|
||||
if (this.state.output) {
|
||||
image = <img src={`data:image/png;base64,${arrayTob64(this.state.output)}`}/>
|
||||
}
|
||||
|
||||
return <div className="app">
|
||||
<div className="manifest">
|
||||
<div>
|
||||
<label>WASM Url: </label>
|
||||
<input type="text" name="url" className="urlInput" value={this.state.url} onChange={this.handleInputChange.bind(this)} />
|
||||
</div>
|
||||
<div>
|
||||
<label>Function: </label>
|
||||
<select type="text" name="func_name" className="funcName" value={this.state.func_name} onChange={this.handleInputChange.bind(this)}>
|
||||
{funcOptions}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={this.handleOnRun.bind(this)}>Run</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="textAreas">
|
||||
<div className="inputBox">
|
||||
<h3>Text Input</h3>
|
||||
<textarea name="input" value={this.state.input} onChange={this.handleInputChange.bind(this)} onKeyDown={this.onInputKeyPress.bind(this)}></textarea>
|
||||
</div>
|
||||
<div className="outputBox">
|
||||
<h3>Text Output</h3>
|
||||
<textarea name="output" value={new TextDecoder().decode(this.state.output)} ></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space" />
|
||||
<div className="dragAreas">
|
||||
<div className="dragInput">
|
||||
<h3>Image Input</h3>
|
||||
<div className="dropZone"
|
||||
onDrop={this.handleDrop.bind(this)}
|
||||
onDragOver={this.nop.bind(this)}
|
||||
onDragEnter={this.nop.bind(this)}
|
||||
onDragLeave={this.nop.bind(this)}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dragOutput">
|
||||
<h3>Image Output</h3>
|
||||
<div className="outputImage">
|
||||
{image}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
window.App = App
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import {ExtismContext} from './dist/index.esm.js'
|
||||
const e = React.createElement;
|
||||
|
||||
window.onload = () => {
|
||||
const domContainer = document.getElementById('main');
|
||||
console.log(domContainer)
|
||||
const root = ReactDOM.createRoot(domContainer);
|
||||
const extismContext = new ExtismContext()
|
||||
root.render(e(App, {extismContext}));
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +0,0 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
9567
browser/package-lock.json
generated
9567
browser/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"name": "@extism/runtime-browser",
|
||||
"version": "0.2.2",
|
||||
"description": "Extism runtime in the browser",
|
||||
"scripts": {
|
||||
"build": "node build.js && tsc --emitDeclarationOnly --outDir dist",
|
||||
"format": "prettier --write \"src/**/*.ts\"",
|
||||
"lint": "tslint -p tsconfig.json",
|
||||
"test": "jest --config jest.config.js"
|
||||
},
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist/*"
|
||||
],
|
||||
"module": "dist/index.esm.js",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/src/index.d.ts",
|
||||
"author": "The Extism Authors <oss@extism.org>",
|
||||
"license": "BSD-3-Clause",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.2",
|
||||
"esbuild": "^0.15.13",
|
||||
"jest": "^29.2.2",
|
||||
"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.1"
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
type MemoryBlock = { offset: bigint; length: bigint };
|
||||
|
||||
export default class Allocator {
|
||||
currentIndex: bigint;
|
||||
active: Record<number, MemoryBlock>;
|
||||
freed: MemoryBlock[];
|
||||
memory: Uint8Array;
|
||||
|
||||
constructor(n: number) {
|
||||
this.currentIndex = BigInt(1);
|
||||
this.active = {};
|
||||
this.freed = [];
|
||||
this.memory = new Uint8Array(n);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.currentIndex = BigInt(1);
|
||||
this.active = {};
|
||||
this.freed = [];
|
||||
}
|
||||
|
||||
alloc(length: bigint): bigint {
|
||||
for (var i = 0; i < this.freed.length; i++) {
|
||||
let block = this.freed[i];
|
||||
if (block.length === length) {
|
||||
this.active[Number(block.offset)] = block;
|
||||
this.freed.splice(i, 1);
|
||||
return block.offset;
|
||||
} else if (block.length > length + BigInt(64)) {
|
||||
const newBlock = { offset: block.offset, length };
|
||||
block.offset += length;
|
||||
block.length -= length;
|
||||
return newBlock.offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Resize memory if needed
|
||||
// TODO: put a limit on the memory size
|
||||
if (BigInt(this.memory.length) < this.currentIndex + length) {
|
||||
const tmp = new Uint8Array(Number(this.currentIndex + length + BigInt(64)));
|
||||
tmp.set(this.memory);
|
||||
this.memory = tmp;
|
||||
}
|
||||
|
||||
const offset = this.currentIndex;
|
||||
this.currentIndex += length;
|
||||
this.active[Number(offset)] = { offset, length };
|
||||
return offset;
|
||||
}
|
||||
|
||||
getBytes(offset: bigint): Uint8Array | null {
|
||||
const block = this.active[Number(offset)];
|
||||
if (!block) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Uint8Array(this.memory.buffer, Number(offset), Number(block.length));
|
||||
}
|
||||
|
||||
getString(offset: bigint): string | null {
|
||||
const bytes = this.getBytes(offset);
|
||||
if (bytes === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TextDecoder().decode(bytes);
|
||||
}
|
||||
|
||||
allocBytes(data: Uint8Array): bigint {
|
||||
const offs = this.alloc(BigInt(data.length));
|
||||
const bytes = this.getBytes(offs);
|
||||
if (bytes === null) {
|
||||
this.free(offs);
|
||||
return BigInt(0);
|
||||
}
|
||||
|
||||
bytes.set(data);
|
||||
return offs;
|
||||
}
|
||||
|
||||
allocString(data: string): bigint {
|
||||
const bytes = new TextEncoder().encode(data);
|
||||
return this.allocBytes(bytes);
|
||||
}
|
||||
|
||||
getLength(offset: bigint): bigint {
|
||||
const block = this.active[Number(offset)];
|
||||
if (!block) {
|
||||
return BigInt(0);
|
||||
}
|
||||
|
||||
return block.length;
|
||||
}
|
||||
|
||||
free(offset: bigint) {
|
||||
const block = this.active[Number(offset)];
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete this.active[Number(offset)];
|
||||
this.freed.push(block);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { Manifest, PluginConfig, ManifestWasmFile, ManifestWasmData } from './manifest';
|
||||
import ExtismPlugin from './plugin';
|
||||
|
||||
/**
|
||||
* Can be a {@link Manifest} or just the raw bytes of the WASM module as an ArrayBuffer.
|
||||
* We recommend using {@link Manifest}
|
||||
*/
|
||||
type ManifestData = Manifest | ArrayBuffer;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export default class ExtismContext {
|
||||
/**
|
||||
* Create a plugin managed by this context
|
||||
*
|
||||
* @param manifest - The {@link ManifestData} describing the plugin code and config
|
||||
* @param config - Config details for the plugin
|
||||
* @returns A new Plugin scoped to this Context
|
||||
*/
|
||||
async newPlugin(manifest: ManifestData, config?: PluginConfig) {
|
||||
let moduleData: ArrayBuffer | null = null;
|
||||
if (manifest instanceof ArrayBuffer) {
|
||||
moduleData = manifest;
|
||||
} else if ((manifest as Manifest).wasm) {
|
||||
const wasmData = (manifest as Manifest).wasm;
|
||||
if (wasmData.length > 1) throw Error('This runtime only supports one module in Manifest.wasm');
|
||||
const wasm = wasmData[0];
|
||||
if ((wasm as ManifestWasmData).data) {
|
||||
moduleData = (wasm as ManifestWasmData).data;
|
||||
} else if ((wasm as ManifestWasmFile).path) {
|
||||
const response = await fetch((wasm as ManifestWasmFile).path);
|
||||
moduleData = await response.arrayBuffer();
|
||||
console.dir(moduleData);
|
||||
}
|
||||
}
|
||||
if (!moduleData) {
|
||||
throw Error(`Unsure how to interpret manifest ${manifest}`);
|
||||
}
|
||||
|
||||
return new ExtismPlugin(moduleData, config);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ExtismContext } from './';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
function parse(bytes: Uint8Array): any {
|
||||
return JSON.parse(new TextDecoder().decode(bytes));
|
||||
}
|
||||
|
||||
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 });
|
||||
});
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
import ExtismContext from './context';
|
||||
|
||||
export { ExtismContext };
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Represents a path or url to a WASM module
|
||||
*/
|
||||
export type ManifestWasmFile = {
|
||||
path: string;
|
||||
name?: string;
|
||||
hash?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the raw bytes of a WASM file loaded into memory
|
||||
*/
|
||||
export type ManifestWasmData = {
|
||||
data: Uint8Array;
|
||||
name?: string;
|
||||
hash?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* {@link ExtismPlugin} Config
|
||||
*/
|
||||
export type PluginConfig = Map<string, string>;
|
||||
|
||||
/**
|
||||
* The WASM to load as bytes or a path
|
||||
*/
|
||||
export type ManifestWasm = ManifestWasmFile | ManifestWasmData;
|
||||
|
||||
/**
|
||||
* The manifest which describes the {@link ExtismPlugin} code and
|
||||
* runtime constraints.
|
||||
*
|
||||
* @see [Extism > Concepts > Manifest](https://extism.org/docs/concepts/manifest)
|
||||
*/
|
||||
export type Manifest = {
|
||||
wasm: Array<ManifestWasm>;
|
||||
//memory?: ManifestMemory;
|
||||
config?: PluginConfig;
|
||||
allowed_hosts?: Array<string>;
|
||||
};
|
||||
@@ -1,184 +0,0 @@
|
||||
import Allocator from './allocator';
|
||||
import { PluginConfig } from './manifest';
|
||||
//@ts-ignore TODO add types to this library
|
||||
import { WASI, File } from "@bjorn3/browser_wasi_shim";
|
||||
|
||||
export default class ExtismPlugin {
|
||||
moduleData: ArrayBuffer;
|
||||
allocator: Allocator;
|
||||
config?: PluginConfig;
|
||||
vars: Record<string, Uint8Array>;
|
||||
input: Uint8Array;
|
||||
output: Uint8Array;
|
||||
module?: WebAssembly.WebAssemblyInstantiatedSource;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
async getExports(): Promise<WebAssembly.Exports> {
|
||||
const module = await this._instantiateModule();
|
||||
return module.instance.exports;
|
||||
}
|
||||
|
||||
async getImports(): Promise<WebAssembly.ModuleImportDescriptor[]> {
|
||||
const module = await this._instantiateModule();
|
||||
return WebAssembly.Module.imports(module.module);
|
||||
}
|
||||
|
||||
async getInstance(): Promise<WebAssembly.Instance> {
|
||||
const module = await this._instantiateModule();
|
||||
return module.instance;
|
||||
}
|
||||
|
||||
async call(func_name: string, input: Uint8Array | string): Promise<Uint8Array> {
|
||||
const module = await this._instantiateModule();
|
||||
|
||||
if (typeof input === 'string') {
|
||||
this.input = new TextEncoder().encode(input);
|
||||
} else if (input instanceof Uint8Array) {
|
||||
this.input = input;
|
||||
} else {
|
||||
throw new Error('input should be string or Uint8Array');
|
||||
}
|
||||
|
||||
this.allocator.reset();
|
||||
|
||||
let func = module.instance.exports[func_name];
|
||||
if (!func) {
|
||||
throw Error(`function does not exist ${func_name}`);
|
||||
}
|
||||
//@ts-ignore
|
||||
func();
|
||||
return this.output;
|
||||
}
|
||||
|
||||
async _instantiateModule(): Promise<WebAssembly.WebAssemblyInstantiatedSource> {
|
||||
if (this.module) {
|
||||
return this.module;
|
||||
}
|
||||
const environment = this.makeEnv();
|
||||
const args: Array<string> = [];
|
||||
const envVars: Array<string> = [];
|
||||
let fds = [
|
||||
new File([]), // stdin
|
||||
new File([]), // stdout
|
||||
new File([]), // stderr
|
||||
];
|
||||
let wasi = new WASI(args, envVars, fds);
|
||||
let env = {
|
||||
wasi_snapshot_preview1: wasi.wasiImport,
|
||||
env: environment
|
||||
};
|
||||
this.module = await WebAssembly.instantiate(this.moduleData, env);
|
||||
return this.module;
|
||||
}
|
||||
|
||||
makeEnv(): any {
|
||||
const plugin = this;
|
||||
return {
|
||||
extism_alloc(n: bigint): bigint {
|
||||
return plugin.allocator.alloc(n);
|
||||
},
|
||||
extism_free(n: bigint) {
|
||||
plugin.allocator.free(n);
|
||||
},
|
||||
extism_load_u8(n: bigint): number {
|
||||
return plugin.allocator.memory[Number(n)];
|
||||
},
|
||||
extism_load_u64(n: bigint): bigint {
|
||||
let cast = new DataView(plugin.allocator.memory.buffer, Number(n));
|
||||
return cast.getBigUint64(0, true);
|
||||
},
|
||||
extism_store_u8(offset: bigint, n: number) {
|
||||
plugin.allocator.memory[Number(offset)] = Number(n);
|
||||
},
|
||||
extism_store_u64(offset: bigint, n: bigint) {
|
||||
const tmp = new DataView(plugin.allocator.memory.buffer, Number(offset));
|
||||
tmp.setBigUint64(0, n, true);
|
||||
},
|
||||
extism_input_length(): bigint {
|
||||
return BigInt(plugin.input.length);
|
||||
},
|
||||
extism_input_load_u8(i: bigint): number {
|
||||
return plugin.input[Number(i)];
|
||||
},
|
||||
extism_input_load_u64(idx: bigint): bigint {
|
||||
let cast = new DataView(plugin.input.buffer, Number(idx));
|
||||
return cast.getBigUint64(0, true);
|
||||
},
|
||||
extism_output_set(offset: bigint, length: bigint) {
|
||||
const offs = Number(offset);
|
||||
const len = Number(length);
|
||||
plugin.output = plugin.allocator.memory.slice(offs, offs + len);
|
||||
},
|
||||
extism_error_set(i: bigint) {
|
||||
throw plugin.allocator.getString(i);
|
||||
},
|
||||
extism_config_get(i: bigint): bigint {
|
||||
if (typeof plugin.config === 'undefined') {
|
||||
return BigInt(0);
|
||||
}
|
||||
const key = plugin.allocator.getString(i);
|
||||
if (key === null) {
|
||||
return BigInt(0);
|
||||
}
|
||||
const value = plugin.config.get(key);
|
||||
if (typeof value === 'undefined') {
|
||||
return BigInt(0);
|
||||
}
|
||||
return plugin.allocator.allocString(value);
|
||||
},
|
||||
extism_var_get(i: bigint): bigint {
|
||||
const key = plugin.allocator.getString(i);
|
||||
if (key === null) {
|
||||
return BigInt(0);
|
||||
}
|
||||
const value = plugin.vars[key];
|
||||
if (typeof value === 'undefined') {
|
||||
return BigInt(0);
|
||||
}
|
||||
return plugin.allocator.allocBytes(value);
|
||||
},
|
||||
extism_var_set(n: bigint, i: bigint) {
|
||||
const key = plugin.allocator.getString(n);
|
||||
if (key === null) {
|
||||
return;
|
||||
}
|
||||
const value = plugin.allocator.getBytes(i);
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
plugin.vars[key] = value;
|
||||
},
|
||||
extism_http_request(n: bigint, i: bigint): number {
|
||||
debugger;
|
||||
return 0;
|
||||
},
|
||||
extism_length(i: bigint): bigint {
|
||||
return plugin.allocator.getLength(i);
|
||||
},
|
||||
extism_log_warn(i: bigint) {
|
||||
const s = plugin.allocator.getString(i);
|
||||
console.warn(s);
|
||||
},
|
||||
extism_log_info(i: bigint) {
|
||||
const s = plugin.allocator.getString(i);
|
||||
console.log(s);
|
||||
},
|
||||
extism_log_debug(i: bigint) {
|
||||
const s = plugin.allocator.getString(i);
|
||||
console.debug(s);
|
||||
},
|
||||
extism_log_error(i: bigint) {
|
||||
const s = plugin.allocator.getString(i);
|
||||
console.error(s);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"tslint:recommended",
|
||||
"tslint-config-prettier"
|
||||
]
|
||||
}
|
||||
1
c/main.c
1
c/main.c
@@ -21,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +1,50 @@
|
||||
{
|
||||
"name": "extism/extism",
|
||||
"description": "Make your software programmable. Run WebAssembly extensions in your app using the first off-the-shelf, universal plug-in system.",
|
||||
"license": "BSD-3-Clause",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"WebAssembly",
|
||||
"plugin-system",
|
||||
"runtime",
|
||||
"plug-in"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The Extism Authors",
|
||||
"email": "oss@extism.org",
|
||||
"homepage": "https://extism.org"
|
||||
"name": "extism/extism",
|
||||
"description": "Make your software programmable. Run WebAssembly extensions in your app using the first off-the-shelf, universal plug-in system.",
|
||||
"license": "BSD-3-Clause",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"WebAssembly",
|
||||
"plugin-system",
|
||||
"runtime",
|
||||
"plug-in"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The Extism Authors",
|
||||
"email": "oss@extism.org",
|
||||
"homepage": "https://extism.org"
|
||||
},
|
||||
{
|
||||
"name": "Dylibso, Inc.",
|
||||
"email": "oss@dylib.so",
|
||||
"homepage": "https://dylib.so"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.4 || ^8",
|
||||
"ircmaxell/ffime": "dev-master"
|
||||
},
|
||||
{
|
||||
"name": "Dylibso, Inc.",
|
||||
"email": "oss@dylib.so",
|
||||
"homepage": "https://dylib.so"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.4 || ^8",
|
||||
"ircmaxell/ffime": "dev-master"
|
||||
},
|
||||
"suggest": {},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Extism\\": "php/src/"
|
||||
"suggest": {},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Extism\\": "php/src/"
|
||||
},
|
||||
"files": [
|
||||
"php/src/Plugin.php",
|
||||
"php/src/generate.php",
|
||||
"php/src/extism.h"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"php/src/Context.php",
|
||||
"php/src/Plugin.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {},
|
||||
"scripts": {},
|
||||
"scripts-descriptions": {}
|
||||
"autoload-dev": {
|
||||
"psr-4": {}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {},
|
||||
"scripts": {},
|
||||
"scripts-descriptions": {}
|
||||
}
|
||||
16
cpp/Makefile
16
cpp/Makefile
@@ -1,15 +1,3 @@
|
||||
FLAGS=`pkg-config --cflags --libs jsoncpp gtest` -lextism -lpthread
|
||||
build:
|
||||
clang++ -std=c++11 -o example example.cpp -lextism -L .
|
||||
|
||||
build-example:
|
||||
$(CXX) -std=c++11 -o example -I. example.cpp $(FLAGS)
|
||||
|
||||
.PHONY: example
|
||||
example: build-example
|
||||
./example
|
||||
|
||||
build-test:
|
||||
$(CXX) -std=c++11 -o test/test -I. test/test.cpp $(FLAGS)
|
||||
|
||||
.PHONY: test
|
||||
test: build-test
|
||||
cd test && ./test
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#define EXTISM_NO_JSON
|
||||
#include "extism.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
227
cpp/extism.hpp
227
cpp/extism.hpp
@@ -1,146 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
#if __has_include(<jsoncpp/json/json.h>)
|
||||
#include <jsoncpp/json/json.h>
|
||||
#else
|
||||
#include <json/json.h>
|
||||
#endif
|
||||
#endif // EXTISM_NO_JSON
|
||||
|
||||
extern "C" {
|
||||
#include <extism.h>
|
||||
}
|
||||
|
||||
namespace extism {
|
||||
|
||||
typedef std::map<std::string, std::string> Config;
|
||||
class Wasm {
|
||||
public:
|
||||
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;
|
||||
}
|
||||
|
||||
if (!this->url.empty()) {
|
||||
doc["url"] = this->url;
|
||||
}
|
||||
|
||||
if (!this->hash.empty()) {
|
||||
doc["hash"] = this->hash;
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
class Manifest {
|
||||
public:
|
||||
Config config;
|
||||
std::vector<Wasm> wasm;
|
||||
std::vector<std::string> allowed_hosts;
|
||||
std::map<std::string, std::string> allowed_paths;
|
||||
uint64_t timeout_ms;
|
||||
|
||||
Manifest() : timeout_ms(30000) {}
|
||||
|
||||
static Manifest path(std::string s, std::string hash = std::string()) {
|
||||
Manifest m;
|
||||
m.add_wasm_path(s, hash);
|
||||
return m;
|
||||
}
|
||||
|
||||
static Manifest url(std::string s, std::string hash = std::string()) {
|
||||
Manifest m;
|
||||
m.add_wasm_url(s, hash);
|
||||
return m;
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
std::string json() const {
|
||||
Json::Value doc;
|
||||
Json::Value wasm;
|
||||
for (auto w : this->wasm) {
|
||||
wasm.append(w.json());
|
||||
}
|
||||
|
||||
doc["wasm"] = wasm;
|
||||
|
||||
if (!this->config.empty()) {
|
||||
Json::Value conf;
|
||||
|
||||
for (auto k : this->config) {
|
||||
conf[k.first] = k.second;
|
||||
}
|
||||
doc["config"] = conf;
|
||||
}
|
||||
|
||||
if (!this->allowed_hosts.empty()) {
|
||||
Json::Value h;
|
||||
|
||||
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) {
|
||||
h[k.first] = k.second;
|
||||
}
|
||||
doc["allowed_paths"] = h;
|
||||
}
|
||||
|
||||
doc["timeout_ms"] = Json::Value(this->timeout_ms);
|
||||
|
||||
Json::FastWriter writer;
|
||||
return writer.write(doc);
|
||||
}
|
||||
#endif
|
||||
|
||||
void add_wasm_path(std::string s, std::string hash = std::string()) {
|
||||
Wasm w;
|
||||
w.path = s;
|
||||
w.hash = hash;
|
||||
this->wasm.push_back(w);
|
||||
}
|
||||
|
||||
void add_wasm_url(std::string u, std::string hash = std::string()) {
|
||||
Wasm w;
|
||||
w.url = u;
|
||||
w.hash = hash;
|
||||
this->wasm.push_back(w);
|
||||
}
|
||||
|
||||
void allow_host(std::string host) { this->allowed_hosts.push_back(host); }
|
||||
|
||||
void allow_path(std::string src, std::string dest = std::string()) {
|
||||
if (dest.empty()) {
|
||||
dest = src;
|
||||
}
|
||||
this->allowed_paths[src] = dest;
|
||||
}
|
||||
|
||||
void set_timeout_ms(uint64_t ms) { this->timeout_ms = ms; }
|
||||
|
||||
void set_config(std::string k, std::string v) { this->config[k] = v; }
|
||||
};
|
||||
|
||||
class Error : public std::exception {
|
||||
private:
|
||||
std::string message;
|
||||
@@ -156,10 +24,6 @@ public:
|
||||
const uint8_t *data;
|
||||
ExtismSize length;
|
||||
|
||||
std::string string() { return (std::string)(*this); }
|
||||
|
||||
std::vector<uint8_t> vector() { return (std::vector<uint8_t>)(*this); }
|
||||
|
||||
operator std::string() { return std::string((const char *)data, length); }
|
||||
operator std::vector<uint8_t>() {
|
||||
return std::vector<uint8_t>(data, data + length);
|
||||
@@ -181,29 +45,11 @@ public:
|
||||
this->context = ctx;
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
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->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);
|
||||
@@ -213,46 +59,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
#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) {
|
||||
Json::Value conf;
|
||||
|
||||
for (auto k : data) {
|
||||
conf[k.first] = k.second;
|
||||
}
|
||||
|
||||
Json::FastWriter writer;
|
||||
auto s = writer.write(conf);
|
||||
this->config(s);
|
||||
}
|
||||
#endif
|
||||
|
||||
void config(const char *json, size_t length) {
|
||||
bool b = extism_plugin_config(this->context.get(), this->plugin,
|
||||
(const uint8_t *)json, length);
|
||||
if (!b) {
|
||||
const char *err = extism_error(this->context.get(), this->plugin);
|
||||
throw Error(err == nullptr ? "Unable to update plugin config" : err);
|
||||
}
|
||||
}
|
||||
|
||||
void config(const std::string &json) {
|
||||
this->config(json.c_str(), json.size());
|
||||
}
|
||||
|
||||
Buffer call(const std::string &func, const uint8_t *input,
|
||||
ExtismSize input_length) const {
|
||||
ExtismSize input_length) {
|
||||
|
||||
int32_t rc = extism_plugin_call(this->context.get(), this->plugin,
|
||||
func.c_str(), input, input_length);
|
||||
if (rc != 0) {
|
||||
@@ -271,57 +80,35 @@ public:
|
||||
return Buffer(ptr, length);
|
||||
}
|
||||
|
||||
Buffer call(const std::string &func,
|
||||
const std::vector<uint8_t> &input) const {
|
||||
Buffer call(const std::string &func, const std::vector<uint8_t> &input) {
|
||||
return this->call(func, input.data(), input.size());
|
||||
}
|
||||
|
||||
Buffer call(const std::string &func, const std::string &input) const {
|
||||
Buffer call(const std::string &func, const std::string &input) {
|
||||
return this->call(func, (const uint8_t *)input.c_str(), input.size());
|
||||
}
|
||||
|
||||
bool function_exists(const std::string &func) const {
|
||||
return extism_plugin_function_exists(this->context.get(), this->plugin,
|
||||
func.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
class Context {
|
||||
public:
|
||||
std::shared_ptr<ExtismContext> pointer;
|
||||
|
||||
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 {
|
||||
Plugin plugin(const uint8_t *wasm, size_t length, bool with_wasi = false) {
|
||||
return Plugin(this->pointer, wasm, length, with_wasi);
|
||||
}
|
||||
|
||||
Plugin plugin(const std::string &str, bool with_wasi = false) const {
|
||||
Plugin plugin(const std::string &str, bool with_wasi = false) {
|
||||
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 {
|
||||
Plugin plugin(const std::vector<uint8_t> &data, bool with_wasi = false) {
|
||||
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);
|
||||
}
|
||||
|
||||
inline std::string version() { return std::string(extism_version()); }
|
||||
} // namespace extism
|
||||
|
||||
Binary file not shown.
@@ -1,73 +0,0 @@
|
||||
#include "../extism.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
std::vector<uint8_t> read(const char *filename) {
|
||||
std::ifstream file(filename, std::ios::binary);
|
||||
return std::vector<uint8_t>((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
namespace {
|
||||
using namespace extism;
|
||||
|
||||
TEST(Context, Basic) {
|
||||
Context context;
|
||||
ASSERT_NE(context.pointer, nullptr);
|
||||
}
|
||||
|
||||
TEST(Plugin, Manifest) {
|
||||
Context context;
|
||||
Manifest manifest = Manifest::path("code.wasm");
|
||||
manifest.set_config("a", "1");
|
||||
|
||||
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 = context.plugin(manifest), Error);
|
||||
}
|
||||
|
||||
TEST(Plugin, Bytes) {
|
||||
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) {
|
||||
Context context;
|
||||
auto wasm = read("code.wasm");
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
Config config;
|
||||
config["abc"] = "123";
|
||||
ASSERT_NO_THROW(plugin.config(config));
|
||||
}
|
||||
|
||||
TEST(Plugin, FunctionExists) {
|
||||
Context context;
|
||||
auto wasm = read("code.wasm");
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
ASSERT_FALSE(plugin.function_exists("bad_function"));
|
||||
ASSERT_TRUE(plugin.function_exists("count_vowels"));
|
||||
}
|
||||
|
||||
}; // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
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.2.0</Version>
|
||||
<Authors>Extism Contributors</Authors>
|
||||
<Description>Internal implementation package for Extism to work on Windows x64</Description>
|
||||
<Tags>extism, wasm, plugin</Tags>
|
||||
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="runtimes/win-x64.dll"
|
||||
CopyToOutputDirectory="Always"
|
||||
Pack="true"
|
||||
PackagePath="runtimes\win-x64\native\extism.dll" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1 +0,0 @@
|
||||
win-x64.dll
|
||||
@@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\..\wasm\code.wasm" Link="code.wasm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -1,11 +0,0 @@
|
||||
using Extism.Sdk.Native;
|
||||
using System.Text;
|
||||
|
||||
var context = new Context();
|
||||
var wasm = await File.ReadAllBytesAsync("./code.wasm");
|
||||
using var plugin = context.CreatePlugin(wasm, withWasi: true);
|
||||
|
||||
var output = Encoding.UTF8.GetString(
|
||||
plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World!"))
|
||||
);
|
||||
Console.WriteLine(output); // prints {"count": 3}
|
||||
@@ -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.**
|
||||
@@ -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,184 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Extism context through which you can load <see cref="Plugin"/>s.
|
||||
/// </summary>
|
||||
public class Context : IDisposable
|
||||
{
|
||||
private const int DisposedMarker = 1;
|
||||
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new Extism Context.
|
||||
/// </summary>
|
||||
public Context()
|
||||
{
|
||||
NativeHandle = LibExtism.extism_context_new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Native pointer to the Extism Context.
|
||||
/// </summary>
|
||||
internal IntPtr NativeHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads an Extism <see cref="Plugin"/>.
|
||||
/// </summary>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="withWasi">Enable/Disable WASI.</param>
|
||||
public Plugin CreatePlugin(ReadOnlySpan<byte> wasm, bool withWasi)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* wasmPtr = wasm)
|
||||
{
|
||||
var plugin = LibExtism.extism_plugin_new(NativeHandle, wasmPtr, wasm.Length, withWasi);
|
||||
return new Plugin(this, plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all plugins from this <see cref="Context"/>'s registry.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
LibExtism.extism_context_reset(NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get this this <see cref="Context"/>'s last error.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal string? GetError()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
var result = LibExtism.extism_error(NativeHandle, -1);
|
||||
return Marshal.PtrToStringUTF8(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Context.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _disposed, DisposedMarker) == DisposedMarker)
|
||||
{
|
||||
// Already disposed.
|
||||
return;
|
||||
}
|
||||
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throw an appropriate exception if the plugin has been disposed.
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
protected void CheckNotDisposed()
|
||||
{
|
||||
Interlocked.MemoryBarrier();
|
||||
if (_disposed == DisposedMarker)
|
||||
{
|
||||
ThrowDisposedException();
|
||||
}
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
private static void ThrowDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Context));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Context.
|
||||
/// </summary>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Free up any managed resources here
|
||||
}
|
||||
|
||||
// Free up unmanaged resources
|
||||
LibExtism.extism_context_free(NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructs the current Context and frees all resources used by it.
|
||||
/// </summary>
|
||||
~Context()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Extism version string.
|
||||
/// </summary>
|
||||
public static string GetExtismVersion()
|
||||
{
|
||||
var pointer = LibExtism.extism_version();
|
||||
return Marshal.PtrToStringUTF8(pointer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set Extism's log file and level. This is applied for all <see cref="Context"/>s.
|
||||
/// </summary>
|
||||
/// <param name="logPath">Log file; can be 'stdout' or 'stderr' to write logs to the console.</param>
|
||||
/// <param name="level">The log level to write at.</param>
|
||||
public static bool SetExtismLogFile(string logPath, LogLevel level)
|
||||
{
|
||||
var logLevel = level switch
|
||||
{
|
||||
LogLevel.Error => LibExtism.LogLevels.Error,
|
||||
LogLevel.Warning => LibExtism.LogLevels.Warn,
|
||||
LogLevel.Info => LibExtism.LogLevels.Info,
|
||||
LogLevel.Debug => LibExtism.LogLevels.Debug,
|
||||
LogLevel.Trace => LibExtism.LogLevels.Trace,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
return LibExtism.extism_log_file(logPath, logLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extism Log Levels
|
||||
/// </summary>
|
||||
public enum LogLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Designates very serious errors.
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// Designates hazardous situations.
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// Designates useful information.
|
||||
/// </summary>
|
||||
Info,
|
||||
|
||||
/// <summary>
|
||||
/// Designates lower priority information.
|
||||
/// </summary>
|
||||
Debug,
|
||||
|
||||
/// <summary>
|
||||
/// Designates very low priority, often extremely verbose, information.
|
||||
/// </summary>
|
||||
Trace
|
||||
}
|
||||
@@ -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,28 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<LangVersion>10</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>Extism.Sdk</PackageId>
|
||||
<Version>0.2.0</Version>
|
||||
<Authors>Extism Contributors</Authors>
|
||||
<Description>Extism SDK that allows hosting Extism plugins in .NET apps.</Description>
|
||||
<Tags>extism, wasm, plugin</Tags>
|
||||
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="\"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,170 +0,0 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
/// <summary>
|
||||
/// Functions exposed by the native Extism library.
|
||||
/// </summary>
|
||||
internal static class LibExtism
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new context.
|
||||
/// </summary>
|
||||
/// <returns>A pointer to the newly created context.</returns>
|
||||
[DllImport("extism")]
|
||||
public static extern IntPtr extism_context_new();
|
||||
|
||||
/// <summary>
|
||||
/// Remove a context from the registry and free associated memory.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
[DllImport("extism")]
|
||||
public static extern void extism_context_free(IntPtr context);
|
||||
|
||||
/// <summary>
|
||||
/// Load a WASM plugin.
|
||||
/// </summary>
|
||||
/// <param name="context">Pointer to the context the plugin will be associated with.</param>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="wasmSize">The length of the `wasm` parameter.</param>
|
||||
/// <param name="withWasi">Enables/disables WASI.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe public static extern IntPtr extism_plugin_new(IntPtr context, byte* wasm, int wasmSize, bool withWasi);
|
||||
|
||||
/// <summary>
|
||||
/// Update a plugin, keeping the existing ID.
|
||||
/// Similar to <see cref="extism_plugin_new"/> but takes an `plugin` argument to specify which plugin to update.
|
||||
/// Memory for this plugin will be reset upon update.
|
||||
/// </summary>
|
||||
/// <param name="context">Pointer to the context the plugin is associated with.</param>
|
||||
/// <param name="plugin">Pointer to the plugin you want to update.</param>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="wasmLength">The length of the `wasm` parameter.</param>
|
||||
/// <param name="withWasi">Enables/disables WASI.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe public static extern bool extism_plugin_update(IntPtr context, IntPtr plugin, byte* wasm, int wasmLength, bool withWasi);
|
||||
|
||||
/// <summary>
|
||||
/// Remove a plugin from the registry and free associated memory.
|
||||
/// </summary>
|
||||
/// <param name="context">Pointer to the context the plugin is associated with.</param>
|
||||
/// <param name="plugin">Pointer to the plugin you want to free.</param>
|
||||
[DllImport("extism")]
|
||||
public static extern void extism_plugin_free(IntPtr context, IntPtr plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Remove all plugins from the registry.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
[DllImport("extism")]
|
||||
public static extern void extism_context_reset(IntPtr context);
|
||||
|
||||
/// <summary>
|
||||
/// Update plugin config values, this will merge with the existing values.
|
||||
/// </summary>
|
||||
/// <param name="context">Pointer to the context the plugin is associated with.</param>
|
||||
/// <param name="plugin">Pointer to the plugin you want to update the configurations for.</param>
|
||||
/// <param name="json">The configuration JSON encoded in UTF8.</param>
|
||||
/// <param name="jsonLength">The length of the `json` parameter.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe public static extern bool extism_plugin_config(IntPtr context, IntPtr plugin, byte* json, int jsonLength);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if funcName exists.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="plugin"></param>
|
||||
/// <param name="funcName"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
public static extern bool extism_plugin_function_exists(IntPtr context, IntPtr plugin, string funcName);
|
||||
|
||||
/// <summary>
|
||||
/// Call a function.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="plugin"></param>
|
||||
/// <param name="funcName">The function to call.</param>
|
||||
/// <param name="data">Input data.</param>
|
||||
/// <param name="dataLen">The length of the `data` parameter.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe public static extern int extism_plugin_call(IntPtr context, IntPtr plugin, string funcName, byte* data, int dataLen);
|
||||
|
||||
/// <summary>
|
||||
/// Get the error associated with a Context or Plugin, if plugin is -1 then the context error will be returned.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="plugin">A plugin pointer, or -1 for the context error.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
public static extern IntPtr extism_error(IntPtr context, nint plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Get the length of a plugin's output data.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="plugin"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
public static extern long extism_plugin_output_length(IntPtr context, IntPtr plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Get the plugin's output data.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="plugin"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
public static extern IntPtr extism_plugin_output_data(IntPtr context, IntPtr plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Set log file and level.
|
||||
/// </summary>
|
||||
/// <param name="filename"></param>
|
||||
/// <param name="logLevel"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
public static extern bool extism_log_file(string filename, string logLevel);
|
||||
|
||||
/// <summary>
|
||||
/// Get the Extism version string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism", EntryPoint = "extism_version")]
|
||||
public static extern IntPtr extism_version();
|
||||
|
||||
/// <summary>
|
||||
/// Extism Log Levels
|
||||
/// </summary>
|
||||
public static class LogLevels
|
||||
{
|
||||
/// <summary>
|
||||
/// Designates very serious errors.
|
||||
/// </summary>
|
||||
public const string Error = "Error";
|
||||
|
||||
/// <summary>
|
||||
/// Designates hazardous situations.
|
||||
/// </summary>
|
||||
public const string Warn = "Warn";
|
||||
|
||||
/// <summary>
|
||||
/// Designates useful information.
|
||||
/// </summary>
|
||||
public const string Info = "Info";
|
||||
|
||||
/// <summary>
|
||||
/// Designates lower priority information.
|
||||
/// </summary>
|
||||
public const string Debug = "Debug";
|
||||
|
||||
/// <summary>
|
||||
/// Designates very low priority, often extremely verbose, information.
|
||||
/// </summary>
|
||||
public const string Trace = "Trace";
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a WASM Extism plugin.
|
||||
/// </summary>
|
||||
public class Plugin : IDisposable
|
||||
{
|
||||
private const int DisposedMarker = 1;
|
||||
|
||||
private readonly Context _context;
|
||||
private int _disposed;
|
||||
|
||||
internal Plugin(Context context, IntPtr handle)
|
||||
{
|
||||
_context = context;
|
||||
NativeHandle = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the native Plugin struct.
|
||||
/// </summary>
|
||||
internal IntPtr NativeHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Update a plugin, keeping the existing ID.
|
||||
/// </summary>
|
||||
/// <param name="wasm">The plugin WASM bytes.</param>
|
||||
/// <param name="withWasi">Enable/Disable WASI.</param>
|
||||
unsafe public bool Update(ReadOnlySpan<byte> wasm, bool withWasi)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
fixed (byte* wasmPtr = wasm)
|
||||
{
|
||||
return LibExtism.extism_plugin_update(_context.NativeHandle, NativeHandle, wasmPtr, wasm.Length, withWasi);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(_context.NativeHandle, NativeHandle, jsonPtr, json.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a specific function exists in the current plugin.
|
||||
/// </summary>
|
||||
public bool FunctionExists(string name)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
return LibExtism.extism_plugin_function_exists(_context.NativeHandle, 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(_context.NativeHandle, 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>
|
||||
internal int OutputLength()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
return (int)LibExtism.extism_plugin_output_length(_context.NativeHandle, 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(_context.NativeHandle, NativeHandle).ToPointer();
|
||||
return new Span<byte>(ptr, length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the error associated with the current plugin.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal string? GetError()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
var result = LibExtism.extism_error(_context.NativeHandle, 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>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Free up any managed resources here
|
||||
}
|
||||
|
||||
// Free up unmanaged resources
|
||||
LibExtism.extism_plugin_free(_context.NativeHandle, NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructs the current Plugin and frees all resources used by it.
|
||||
/// </summary>
|
||||
~Plugin()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
## Extism.Sdk
|
||||
Extism SDK that allows hosting Extism plugins in .NET apps.
|
||||
@@ -1,24 +0,0 @@
|
||||
using Extism.Sdk.Native;
|
||||
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Extism.Sdk.Tests;
|
||||
|
||||
public class BasicTests
|
||||
{
|
||||
[Fact]
|
||||
public void CountHelloWorldVowels()
|
||||
{
|
||||
using var context = new Context();
|
||||
|
||||
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
|
||||
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code.wasm"));
|
||||
using var plugin = context.CreatePlugin(wasm, withWasi: true);
|
||||
|
||||
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
|
||||
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
|
||||
}
|
||||
}
|
||||
@@ -1,35 +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>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
24
dune-project
24
dune-project
@@ -19,28 +19,6 @@
|
||||
(name extism)
|
||||
(synopsis "Extism bindings")
|
||||
(description "Bindings to Extism, the universal plugin system")
|
||||
(depends
|
||||
(ocaml (>= 4.14.1))
|
||||
(dune (>= 3.2))
|
||||
(ctypes-foreign (>= 0.18.0))
|
||||
(bigstringaf (>= 0.9.0))
|
||||
(ppx_yojson_conv (>= 0.15.0))
|
||||
extism-manifest
|
||||
(ppx_inline_test (>= 0.15.0))
|
||||
(cmdliner (>= 1.1.1))
|
||||
)
|
||||
(tags
|
||||
(topics wasm plugin)))
|
||||
|
||||
(package
|
||||
(name extism-manifest)
|
||||
(synopsis "Extism manifest bindings")
|
||||
(description "Bindings to Extism, the universal plugin system")
|
||||
(depends
|
||||
(ocaml (>= 4.14.1))
|
||||
(dune (>= 3.2))
|
||||
(ppx_yojson_conv (>= 0.15.0))
|
||||
(base64 (>= 3.5.0))
|
||||
)
|
||||
(depends ocaml dune ctypes-foreign bigstringaf ppx_yojson_conv base64)
|
||||
(tags
|
||||
(topics wasm plugin)))
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
.PHONY: test
|
||||
|
||||
prepare:
|
||||
mix deps.get
|
||||
mix compile
|
||||
|
||||
test: prepare
|
||||
mix test
|
||||
|
||||
clean:
|
||||
mix clean
|
||||
|
||||
publish: clean prepare
|
||||
mix hex.build
|
||||
mix hex.publish --yes
|
||||
|
||||
format:
|
||||
mix format
|
||||
|
||||
lint:
|
||||
mix format --check-formatted
|
||||
|
||||
docs:
|
||||
mix docs
|
||||
|
||||
show-docs: docs
|
||||
open doc/index.html
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
Extism Host SDK for Elixir and Erlang
|
||||
|
||||
## Docs
|
||||
|
||||
Read the [docs on hexdocs.pm](https://hexdocs.pm/extism/).
|
||||
|
||||
## Installation
|
||||
|
||||
You can find this package on [hex.pm](https://hex.pm/packages/extism).
|
||||
@@ -13,14 +9,12 @@ 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.5"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Example
|
||||
## Usage
|
||||
|
||||
```elixir
|
||||
# Create a context for which plugins can be allocated and cleaned
|
||||
@@ -42,32 +36,3 @@ manifest = %{ wasm: [ %{ path: "/Users/ben/code/extism/wasm/code.wasm" } ]}
|
||||
# free up the context and any plugins we allocated
|
||||
Extism.Context.free(ctx)
|
||||
```
|
||||
|
||||
### Modules
|
||||
|
||||
The two primary modules you should learn are:
|
||||
|
||||
* [Extism.Context](Extism.Context.html)
|
||||
* [Extism.Plugin](Extism.Plugin.html)
|
||||
|
||||
#### Context
|
||||
|
||||
The [Context](Extism.Context.html) can be thought of as a session. You need a context to interact with the Extism runtime. The context holds your plugins and when you free the context, it frees your plugins. It's important to free up your context and plugins when you are done with them.
|
||||
|
||||
```elixir
|
||||
ctx = Extism.Context.new()
|
||||
# frees all the plugins
|
||||
Extism.Context.reset(ctx)
|
||||
# frees the context and all its plugins
|
||||
Extism.Context.free(ctx)
|
||||
```
|
||||
|
||||
#### Plugin
|
||||
|
||||
The [Plugin](Extism.Plugin.html) represents an instance of your WASM program from the given manifest.
|
||||
The key method to know here is [Extism.Plugin#call](Extism.Plugin.html#call/3) which takes a function name to invoke and some input data, and returns the results from the plugin.
|
||||
|
||||
```elixir
|
||||
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
|
||||
```
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
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
|
||||
@@ -15,47 +10,21 @@ defmodule Extism.Context do
|
||||
}
|
||||
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
|
||||
def new_plugin(ctx, manifest, wasi) 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)}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
defmodule Extism.Native do
|
||||
@moduledoc """
|
||||
This module represents the Native Extism runtime API and is for internal use.
|
||||
Do not use or rely on this this module.
|
||||
"""
|
||||
use Rustler,
|
||||
otp_app: :extism,
|
||||
crate: :extism_nif
|
||||
otp_app: :extism,
|
||||
crate: :extism_nif
|
||||
|
||||
def context_new(), do: error()
|
||||
def context_reset(_ctx), do: error()
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
defmodule Extism.Plugin do
|
||||
@moduledoc """
|
||||
A Plugin represents an instance of your WASM program from the given manifest.
|
||||
"""
|
||||
defstruct [
|
||||
# The actual NIF Resource. PluginIndex and the context
|
||||
plugin_id: nil,
|
||||
@@ -15,26 +12,6 @@ defmodule Extism.Plugin do
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Call a plugin's function by name
|
||||
|
||||
## Examples
|
||||
|
||||
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}"}
|
||||
|
||||
## Parameters
|
||||
|
||||
- plugin: The plugin
|
||||
- name: The name of the function as a string
|
||||
- input: The input data as a string
|
||||
|
||||
## Returns
|
||||
|
||||
A string representation of the functions output
|
||||
|
||||
"""
|
||||
def call(plugin, name, input) do
|
||||
case Extism.Native.plugin_call(plugin.ctx.ptr, plugin.plugin_id, name, input) do
|
||||
{:error, err} -> {:error, err}
|
||||
@@ -42,44 +19,21 @@ defmodule Extism.Plugin do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the manifest of the given plugin
|
||||
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}
|
||||
res -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
## Parameters
|
||||
def free(plugin) do
|
||||
Extism.Native.plugin_free(plugin.ctx.ptr, plugin.plugin_id)
|
||||
end
|
||||
|
||||
- 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.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.ctx.ptr, plugin.plugin_id, function_name)
|
||||
end
|
||||
def has_function(plugin, function_name) do
|
||||
Extism.Native.plugin_has_function(plugin.ctx.ptr, plugin.plugin_id, function_name)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Extim.Plugin do
|
||||
|
||||
BIN
elixir/logo.png
BIN
elixir/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
@@ -4,13 +4,12 @@ defmodule Extism.MixProject do
|
||||
def project do
|
||||
[
|
||||
app: :extism,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.12",
|
||||
version: "0.0.1-rc.5",
|
||||
elixir: "~> 1.14",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps(),
|
||||
package: package(),
|
||||
aliases: aliases(),
|
||||
docs: docs()
|
||||
aliases: aliases()
|
||||
]
|
||||
end
|
||||
|
||||
@@ -25,7 +24,7 @@ defmodule Extism.MixProject do
|
||||
[
|
||||
{:rustler, "~> 0.26.0"},
|
||||
{:json, "~> 1.4"},
|
||||
{:ex_doc, "~> 0.21", only: :dev, runtime: false}
|
||||
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
|
||||
]
|
||||
end
|
||||
|
||||
@@ -43,17 +42,8 @@ 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),
|
||||
links: %{"GitHub" => "https://github.com/extism/extism"}
|
||||
]
|
||||
end
|
||||
|
||||
defp docs do
|
||||
[
|
||||
main: "Extism",
|
||||
logo: "./logo.png",
|
||||
main: "readme",
|
||||
extras: ["README.md"]
|
||||
files: ~w(lib native priv .formatter.exs mix.exs README.md LICENSE),
|
||||
links: %{ "GitHub" => "https://github.com/extism/extism" },
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%{
|
||||
"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"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.29.0", "4a1cb903ce746aceef9c1f9ae8a6c12b742a5461e6959b9d3b24d813ffbea146", [: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", "f096adb8bbca677d35d278223361c7792d496b3fc0d0224c9d4bc2f651af5db1"},
|
||||
"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"},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "extism_nif"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1-rc.5"
|
||||
edition = "2021"
|
||||
authors = ["Benjamin Eckel <bhelx@simst.im>"]
|
||||
|
||||
@@ -11,5 +11,5 @@ crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
rustler = "0.26.0"
|
||||
extism = { version = "0.1.0", path = "../../../rust" }
|
||||
extism = { version = "0.0.1-rc.5" }
|
||||
log = "0.4"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use extism::{Context, Plugin};
|
||||
use rustler::{Atom, Env, ResourceArc, Term};
|
||||
use std::mem;
|
||||
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! {
|
||||
@@ -15,12 +15,9 @@ mod atoms {
|
||||
}
|
||||
|
||||
struct ExtismContext {
|
||||
ctx: RwLock<Context>,
|
||||
ctx: RwLock<Context>
|
||||
}
|
||||
|
||||
unsafe impl Sync for ExtismContext {}
|
||||
unsafe impl Send for ExtismContext {}
|
||||
|
||||
fn load(env: Env, _: Term) -> bool {
|
||||
rustler::resource!(ExtismContext, env);
|
||||
true
|
||||
@@ -30,16 +27,15 @@ fn to_rustler_error(extism_error: extism::Error) -> rustler::Error {
|
||||
match extism_error {
|
||||
extism::Error::UnableToLoadPlugin(msg) => rustler::Error::Term(Box::new(msg)),
|
||||
extism::Error::Message(msg) => rustler::Error::Term(Box::new(msg)),
|
||||
extism::Error::Json(json_err) => rustler::Error::Term(Box::new(json_err.to_string())),
|
||||
extism::Error::Runtime(e) => rustler::Error::Term(Box::new(e.to_string())),
|
||||
extism::Error::Json(json_err) => rustler::Error::Term(Box::new(json_err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
fn context_new() -> ResourceArc<ExtismContext> {
|
||||
ResourceArc::new(ExtismContext {
|
||||
ctx: RwLock::new(Context::new()),
|
||||
})
|
||||
ResourceArc::new(
|
||||
ExtismContext { ctx: RwLock::new(Context::new()) }
|
||||
)
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
@@ -55,11 +51,7 @@ fn context_free(ctx: ResourceArc<ExtismContext>) {
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
fn plugin_new_with_manifest(
|
||||
ctx: ResourceArc<ExtismContext>,
|
||||
manifest_payload: String,
|
||||
wasi: bool,
|
||||
) -> Result<i32, rustler::Error> {
|
||||
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)),
|
||||
@@ -75,22 +67,17 @@ fn plugin_new_with_manifest(
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
fn plugin_call(
|
||||
ctx: ResourceArc<ExtismContext>,
|
||||
plugin_id: i32,
|
||||
name: String,
|
||||
input: String,
|
||||
) -> Result<String, rustler::Error> {
|
||||
fn plugin_call(ctx: ResourceArc<ExtismContext>, plugin_id: i32, name: String, input: String) -> Result<String, rustler::Error> {
|
||||
let context = &ctx.ctx.read().unwrap();
|
||||
let mut plugin = unsafe { Plugin::from_id(plugin_id, context) };
|
||||
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",
|
||||
))),
|
||||
},
|
||||
Ok(result) => {
|
||||
match str::from_utf8(&result) {
|
||||
Ok(output) => Ok(output.to_string()),
|
||||
Err(_e) => Err(rustler::Error::Term(Box::new("Could not read output from plugin")))
|
||||
}
|
||||
}
|
||||
};
|
||||
// this forget should be safe because the context will clean up
|
||||
// all it's plugins when it is dropped
|
||||
@@ -99,17 +86,14 @@ fn plugin_call(
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
fn plugin_update_manifest(
|
||||
ctx: ResourceArc<ExtismContext>,
|
||||
plugin_id: i32,
|
||||
manifest_payload: String,
|
||||
wasi: bool,
|
||||
) -> Result<(), rustler::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)),
|
||||
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
|
||||
@@ -129,28 +113,16 @@ fn plugin_free(ctx: ResourceArc<ExtismContext>, plugin_id: i32) -> Result<(), ru
|
||||
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
|
||||
)))),
|
||||
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.",
|
||||
)))
|
||||
}
|
||||
extism::set_log_file(path, Some(level));
|
||||
Ok(atoms::ok())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
fn plugin_has_function(
|
||||
ctx: ResourceArc<ExtismContext>,
|
||||
plugin_id: i32,
|
||||
function_name: String,
|
||||
) -> Result<bool, rustler::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);
|
||||
|
||||
@@ -35,8 +35,6 @@ defmodule ExtismTest do
|
||||
assert JSON.decode(output) == {:ok, %{"count" => 7}}
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test thrice")
|
||||
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
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
synopsis: "Extism manifest bindings"
|
||||
description: "Bindings to Extism, the universal plugin system"
|
||||
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" & >= "3.2"}
|
||||
"ppx_yojson_conv" {>= "0.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"
|
||||
18
extism.go
18
extism.go
@@ -53,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{}
|
||||
@@ -65,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 {
|
||||
@@ -196,7 +194,7 @@ func (plugin Plugin) SetConfig(data map[string][]byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FunctionExists returns true when the named function is present in the plugin
|
||||
/// FunctionExists returns true when the name function is present in the plugin
|
||||
func (plugin Plugin) FunctionExists(functionName string) bool {
|
||||
name := C.CString(functionName)
|
||||
b := C.extism_plugin_function_exists(plugin.ctx.pointer, C.int(plugin.id), name)
|
||||
@@ -204,7 +202,7 @@ func (plugin Plugin) FunctionExists(functionName string) bool {
|
||||
return bool(b)
|
||||
}
|
||||
|
||||
// Call a function by name with the given input, returning the output
|
||||
/// Call a function by name with the given input, returning the output
|
||||
func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
|
||||
ptr := makePointer(input)
|
||||
name := C.CString(functionName)
|
||||
|
||||
14
extism.opam
14
extism.opam
@@ -10,14 +10,12 @@ 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" & >= "3.2"}
|
||||
"ctypes-foreign" {>= "0.18.0"}
|
||||
"bigstringaf" {>= "0.9.0"}
|
||||
"ppx_yojson_conv" {>= "0.15.0"}
|
||||
"extism-manifest"
|
||||
"ppx_inline_test" {>= "0.15.0"}
|
||||
"cmdliner" {>= "1.1.1"}
|
||||
"ocaml"
|
||||
"dune" {>= "3.2"}
|
||||
"ctypes-foreign"
|
||||
"bigstringaf"
|
||||
"ppx_yojson_conv"
|
||||
"base64"
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
|
||||
148
extism_test.go
148
extism_test.go
@@ -1,148 +0,0 @@
|
||||
package extism
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func manifest() Manifest {
|
||||
return Manifest{
|
||||
Wasm: []Wasm{
|
||||
WasmFile{
|
||||
Path: "./wasm/code.wasm",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func expectVowelCount(plugin Plugin, input string, count int) error {
|
||||
out, err := plugin.Call("count_vowels", []byte(input))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var result map[string]int
|
||||
json.Unmarshal(out, &result)
|
||||
if result["count"] != count {
|
||||
return fmt.Errorf("Got count %d but expected %d", result["count"], count)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCreateAndFreeContext(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
ctx.Free()
|
||||
}
|
||||
|
||||
func TestCallPlugin(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)
|
||||
}
|
||||
if err := expectVowelCount(plugin, "this is a test again", 7); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := expectVowelCount(plugin, "this is a test thrice", 6); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFreePlugin(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)
|
||||
}
|
||||
|
||||
// free this specific plugin
|
||||
plugin.Free()
|
||||
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err == nil {
|
||||
t.Fatal("Expected an error after plugin was freed")
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !plugin.FunctionExists("count_vowels") {
|
||||
t.Fatal("Was expecting to find the function count_vowels")
|
||||
}
|
||||
if plugin.FunctionExists("i_dont_exist") {
|
||||
t.Fatal("Was not expecting to find the function i_dont_exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorsOnUnknownFunction(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = plugin.Call("i_dont_exist", []byte("someinput"))
|
||||
if err == nil {
|
||||
t.Fatal("Was expecting call to unknown function to fail")
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,23 @@
|
||||
module Main where
|
||||
|
||||
import System.Exit (exitFailure, exitSuccess)
|
||||
import qualified Data.ByteString as B
|
||||
import Extism
|
||||
import Extism.Manifest(manifest, wasmFile)
|
||||
import Extism.Manifest
|
||||
|
||||
unwrap (Right x) = x
|
||||
unwrap (Left (ExtismError msg)) = do
|
||||
error msg
|
||||
try f (Right x) = f x
|
||||
try f (Left (Error 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
|
||||
let m = manifest [wasmFile "../wasm/code.wasm"]
|
||||
context <- Extism.newContext
|
||||
plugin <- unwrap <$> Extism.pluginFromManifest context m False
|
||||
res <- unwrap <$> Extism.call plugin "count_vowels" (Extism.toByteString "this is a test")
|
||||
putStrLn (Extism.fromByteString res)
|
||||
Extism.free plugin
|
||||
context <- Extism.newContext ()
|
||||
plugin <- Extism.pluginFromManifest context (manifest [wasmFile "../wasm/code.wasm"]) False
|
||||
try handlePlugin plugin
|
||||
@@ -1,30 +0,0 @@
|
||||
.PHONY: test
|
||||
|
||||
prepare:
|
||||
cabal update
|
||||
|
||||
build: prepare
|
||||
cabal build
|
||||
|
||||
test: prepare
|
||||
cabal test
|
||||
|
||||
clean:
|
||||
cabal clean
|
||||
|
||||
publish: clean prepare
|
||||
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,45 +1,47 @@
|
||||
cabal-version: 3.0
|
||||
cabal-version: 2.4
|
||||
name: extism
|
||||
version: 0.0.1
|
||||
license: BSD-3-Clause
|
||||
maintainer: oss@extism.org
|
||||
version: 0.0.1.0
|
||||
|
||||
-- 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
|
||||
maintainer: oss@extism.org
|
||||
|
||||
-- A copyright notice.
|
||||
-- copyright:
|
||||
category: Plugins
|
||||
extra-source-files: CHANGELOG.md
|
||||
|
||||
library
|
||||
exposed-modules: Extism
|
||||
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 && < 4.18.0,
|
||||
bytestring >= 0.11.3 && < 0.12,
|
||||
json >= 0.10 && < 0.11,
|
||||
extism-manifest >= 0.0.0 && < 0.1.0
|
||||
exposed-modules: Extism Extism.Manifest
|
||||
|
||||
test-suite extism-example
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Example.hs
|
||||
default-language: Haskell2010
|
||||
build-depends:
|
||||
base,
|
||||
extism,
|
||||
bytestring
|
||||
-- Modules included in this library but not exported.
|
||||
other-modules:
|
||||
|
||||
test-suite extism-test
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Test.hs
|
||||
hs-source-dirs: test
|
||||
-- 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
|
||||
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
|
||||
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,58 +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
|
||||
|
||||
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 x = makeObj x
|
||||
nonNull x = NotNull x
|
||||
null' = Null
|
||||
(.=) a b = (a, showJSON b)
|
||||
toNullable (Just x) = NotNull x
|
||||
toNullable Nothing = Null
|
||||
fromNullable (NotNull x) = Just x
|
||||
fromNullable Null = Nothing
|
||||
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
|
||||
|
||||
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,247 +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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
||||
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.0.1
|
||||
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-source-files: CHANGELOG.md
|
||||
|
||||
library
|
||||
exposed-modules: Extism.Manifest Extism.JSON
|
||||
hs-source-dirs: .
|
||||
default-language: Haskell2010
|
||||
build-depends:
|
||||
base >= 4.16.1 && < 4.18.0,
|
||||
bytestring >= 0.11.3 && < 0.12,
|
||||
json >= 0.10 && < 0.11,
|
||||
base64-bytestring >= 1.2.1 && < 1.3,
|
||||
@@ -1,68 +1,75 @@
|
||||
{-# LANGUAGE ForeignFunctionInterface #-}
|
||||
|
||||
module Extism (module Extism, module Extism.Manifest) where
|
||||
import Data.Int
|
||||
import Data.Word
|
||||
import Control.Monad (void)
|
||||
import GHC.Int
|
||||
import GHC.Word
|
||||
import Foreign.C.Types
|
||||
import Foreign.Ptr
|
||||
import Foreign.ForeignPtr
|
||||
import Foreign.C.String
|
||||
import Foreign.Ptr
|
||||
import Control.Monad (void)
|
||||
import Data.ByteString as B
|
||||
import Data.ByteString.Internal (c2w, w2c)
|
||||
import Data.ByteString.Unsafe (unsafeUseAsCString)
|
||||
import Data.Bifunctor (second)
|
||||
import Text.JSON (encode, toJSObject, showJSON)
|
||||
import Text.JSON (JSON, toJSObject, encode)
|
||||
import Extism.Manifest (Manifest, toString)
|
||||
import Extism.Bindings
|
||||
|
||||
-- | Context for managing plugins
|
||||
newtype ExtismContext = ExtismContext () deriving Show
|
||||
|
||||
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
|
||||
|
||||
-- Context manages plugins
|
||||
newtype Context = Context (ForeignPtr ExtismContext)
|
||||
|
||||
-- | Plugins can be used to call WASM function
|
||||
-- Plugins can be used to call WASM function
|
||||
data Plugin = Plugin Context Int32
|
||||
|
||||
-- | Log level
|
||||
data LogLevel = Error | Warn | Info | Debug | Trace deriving (Show)
|
||||
-- Extism error
|
||||
newtype Error = Error String deriving Show
|
||||
|
||||
-- | Extism error
|
||||
newtype Error = ExtismError String deriving Show
|
||||
|
||||
-- | Result type
|
||||
type Result a = Either Error a
|
||||
|
||||
-- | Helper function to convert a 'String' to a 'ByteString'
|
||||
-- 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'
|
||||
-- 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
|
||||
|
||||
-- | Remove all registered plugins in a 'Context'
|
||||
-- Remove all registered plugins in a Context
|
||||
reset :: Context -> IO ()
|
||||
reset (Context ctx) =
|
||||
withForeignPtr ctx extism_context_reset
|
||||
withForeignPtr ctx (\ctx ->
|
||||
extism_context_reset ctx)
|
||||
|
||||
-- | Create a new 'Context'
|
||||
newContext :: IO Context
|
||||
newContext = do
|
||||
-- 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 (Result Plugin)
|
||||
-- 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
|
||||
@@ -74,18 +81,18 @@ plugin c wasm useWasi =
|
||||
if p < 0 then do
|
||||
err <- extism_error ctx (-1)
|
||||
e <- peekCString err
|
||||
return $ Left (ExtismError e)
|
||||
return $ Left (Error e)
|
||||
else
|
||||
return $ Right (Plugin c p))
|
||||
|
||||
-- | Create a 'Plugin' from a 'Manifest'
|
||||
pluginFromManifest :: Context -> Manifest -> Bool -> IO (Result Plugin)
|
||||
-- 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 (Result ())
|
||||
-- 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
|
||||
@@ -96,58 +103,49 @@ update (Plugin (Context ctx) id) wasm useWasi =
|
||||
if b <= 0 then do
|
||||
err <- extism_error ctx (-1)
|
||||
e <- peekCString err
|
||||
return $ Left (ExtismError e)
|
||||
return $ Left (Error e)
|
||||
else
|
||||
return (Right ()))
|
||||
|
||||
-- | Update a 'Plugin' with a new 'Manifest'
|
||||
updateManifest :: Plugin -> Manifest -> Bool -> IO (Result ())
|
||||
-- Update a plugin with a new Manifest
|
||||
updateManifest :: Plugin -> Manifest -> Bool -> IO (Either Error ())
|
||||
updateManifest plugin manifest useWasi =
|
||||
let wasm = toByteString $ toString manifest in
|
||||
update plugin wasm useWasi
|
||||
|
||||
-- | Check if a 'Plugin' is valid
|
||||
-- Check if a plugin is value
|
||||
isValid :: Plugin -> Bool
|
||||
isValid (Plugin _ p) = p >= 0
|
||||
|
||||
-- | Set configuration values for a plugin
|
||||
setConfig :: Plugin -> [(String, Maybe String)] -> IO Bool
|
||||
-- Set configuration values for a plugin
|
||||
setConfig :: Plugin -> [(String, Maybe String)] -> IO ()
|
||||
setConfig (Plugin (Context ctx) plugin) x =
|
||||
if plugin < 0
|
||||
then return False
|
||||
then return ()
|
||||
else
|
||||
let obj = toJSObject [(k, showJSON v) | (k, v) <- x] in
|
||||
let obj = toJSObject 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))
|
||||
withForeignPtr ctx (\ctx ->
|
||||
void $ extism_plugin_config ctx plugin (castPtr s) length))
|
||||
|
||||
levelStr Error = "error"
|
||||
levelStr Debug = "debug"
|
||||
levelStr Warn = "warn"
|
||||
levelStr Trace = "trace"
|
||||
levelStr Info = "info"
|
||||
|
||||
-- | Set the log file and level, this is a global configuration
|
||||
setLogFile :: String -> LogLevel -> IO Bool
|
||||
-- Set the log file and level, this is a global configuration
|
||||
setLogFile :: String -> String -> IO ()
|
||||
setLogFile filename level =
|
||||
let s = levelStr level in
|
||||
withCString filename (\f ->
|
||||
withCString s (\l -> do
|
||||
b <- extism_log_file f l
|
||||
return $ b /= 0))
|
||||
withCString level (\l -> do
|
||||
void $ extism_log_file f l))
|
||||
|
||||
-- | 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 (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 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
|
||||
@@ -158,17 +156,17 @@ call (Plugin (Context ctx) plugin) name input =
|
||||
err <- extism_error ctx plugin
|
||||
if err /= nullPtr
|
||||
then do e <- peekCString err
|
||||
return $ Left (ExtismError e)
|
||||
return $ Left (Error e)
|
||||
else if rc == 0
|
||||
then do
|
||||
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 (Error "Call failed"))
|
||||
|
||||
-- | Free a 'Plugin', this will automatically be called for every plugin
|
||||
-- | associated with a 'Context' when that 'Context' is freed
|
||||
-- Free a plugin
|
||||
free :: Plugin -> IO ()
|
||||
free (Plugin (Context ctx) plugin) =
|
||||
withForeignPtr ctx (`extism_plugin_free` plugin)
|
||||
withForeignPtr ctx (\ctx ->
|
||||
extism_plugin_free ctx plugin)
|
||||
|
||||
@@ -1,26 +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
|
||||
|
||||
newtype ExtismContext = ExtismContext () deriving Show
|
||||
|
||||
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
|
||||
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