Compare commits

..

1 Commits

Author SHA1 Message Date
Benjamin Eckel
2a73564e8a docs: GH pages for client docs 2022-10-28 10:53:07 -05:00
234 changed files with 6574 additions and 28870 deletions

View File

@@ -7,24 +7,12 @@ runs:
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
- name: Download libextism
uses: actions/download-artifact@v3
with:
toolchain: stable
override: true
- name: Cache Rust environment
uses: Swatinem/rust-cache@v1
- name: Cache libextism
id: cache-libextism
uses: actions/cache@v3
with:
path: target/**
key: ${{ runner.os }}-libextism-${{ hashFiles('runtime/**') }}-${{ hashFiles('manifest/**') }}-${{ hashFiles('libextism/**') }}
- name: Build
if: steps.cache-libextism.outputs.cache-hit != 'true'
shell: bash
run: cargo build --release -p libextism
name: libextism-${{ matrix.os }}
- name: Install extism shared library
shell: bash
run: |
sudo make install
sudo cp libextism.* /usr/local/lib
sudo cp runtime/extism.h /usr/local/include

View File

@@ -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"

View File

@@ -1,36 +0,0 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-node.yml
- manifest/**
- runtime/**
- libextism/**
- browser/**
workflow_dispatch:
name: Browser CI
jobs:
node:
name: Browser
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Setup Node env
uses: actions/setup-node@v3
with:
node-version: 18
- name: Test Browser Runtime
run: |
cd browser
npm i
npm run test

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,39 +0,0 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-go.yml
- manifest/**
- runtime/**
- libextism/**
- extism.go
- extism_test.go
- go.mod
- libextism.pc
- go/**
workflow_dispatch:
name: Go CI
jobs:
go:
name: Go
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Setup Go env
uses: actions/setup-go@v3
- name: Test Go Host SDK
run: |
go version
LD_LIBRARY_PATH=/usr/local/lib go test
cd go
LD_LIBRARY_PATH=/usr/local/lib go run main.go | grep "Hello from Go!"

View File

@@ -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

View File

@@ -1,37 +0,0 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-java.yml
- manifest/**
- runtime/**
- libextism/**
- java/**
workflow_dispatch:
name: Java CI
jobs:
java:
name: Java
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
version: [11, 17]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Set up Java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '${{ matrix.version }}'
- name: Test Java
run: |
cd java
mvn --batch-mode -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn verify

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 }}

View File

@@ -1,37 +0,0 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-zig.yml
- manifest/**
- runtime/**
- libextism/**
- zig/**
workflow_dispatch:
name: Zig CI
jobs:
zig:
name: Zig
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
zig_version: ["master"] # eventually use multiple versions once stable
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Setup Zig env
uses: goto-bus-stop/setup-zig@v2
with:
version: ${{ matrix.zig_version }}
- name: Test Zig Host SDK
run: |
zig version
cd zig
LD_LIBRARY_PATH=/usr/local/lib zig build test

View File

@@ -2,7 +2,353 @@ on: [pull_request, workflow_dispatch]
name: CI
env:
RUNTIME_MANIFEST: runtime/Cargo.toml
RUNTIME_CRATE: extism-runtime
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.RUNTIME_CRATE }}
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: libextism-${{ matrix.os }}
path: |
target/release/libextism.*
lint_and_test:
name: Extism runtime lint and test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Cache Rust environment
uses: Swatinem/rust-cache@v1
- name: Cache target
id: cache-target
uses: actions/cache@v3
with:
path: target/**
key: ${{ runner.os }}-target-${{ env.GITHUB_SHA }}
- name: Format
run: cargo fmt --check -p ${{ env.RUNTIME_CRATE }}
- name: Lint
run: cargo clippy --release --all-features --no-deps -p ${{ env.RUNTIME_CRATE }}
- name: Test
run: cargo test --all-features --release -p ${{ env.RUNTIME_CRATE }}
rust:
name: Rust
needs: lib
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Test Rust Host SDK
run: LD_LIBRARY_PATH=/usr/local/lib cargo test --release -p ${{ env.RUST_SDK_CRATE }}
elixir:
name: Elixir
needs: lib
runs-on: ${{ matrix.os }}
env:
MIX_ENV: test
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Setup Elixir Host SDK
if: ${{ runner.os != 'macOS' }}
uses: erlef/setup-beam@v1
with:
experimental-otp: true
otp-version: '25.0.4'
elixir-version: '1.14.0'
- name: Test Elixir Host SDK
if: ${{ runner.os != 'macOS' }}
run: |
cd elixir
LD_LIBRARY_PATH=/usr/local/lib mix do deps.get, test
go:
name: Go
needs: lib
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Setup Go env
uses: actions/setup-go@v3
- name: Test Go Host SDK
run: |
go version
cd go
LD_LIBRARY_PATH=/usr/local/lib go run main.go
LD_LIBRARY_PATH=/usr/local/lib go test
python:
name: Python
needs: lib
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Setup Python env
uses: actions/setup-python@v4
with:
python-version: "3.9"
check-latest: true
- name: Install Poetry
uses: snok/install-poetry@v1
- name: Test Python Host SDK
run: |
cd python
poetry install
poetry run python example.py
poetry run python -m unittest discover
ruby:
name: Ruby
needs: lib
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Setup Ruby env
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.0"
- name: Test Ruby Host SDK
run: |
cd ruby
bundle install
ruby example.rb
rake test
node:
name: Node
needs: lib
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Setup Node env
uses: actions/setup-node@v3
with:
node-version: 18
- name: Test Node Host SDK
run: |
cd node
npm i
LD_LIBRARY_PATH=/usr/local/lib npm run build
LD_LIBRARY_PATH=/usr/local/lib npm run example
LD_LIBRARY_PATH=/usr/local/lib npm run test
ocaml:
name: OCaml
needs: lib
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Setup OCaml env
uses: ocaml/setup-ocaml@v2
with:
ocaml-compiler: ocaml-base-compiler.5.0.0~beta1
- name: Cache OCaml
id: cache-ocaml
uses: actions/cache@v3
with:
path: _build
key: ${{ runner.os }}-ocaml-${{ hashFiles('ocaml/**') }}-${{ hashFiles('dune-project') }}
- name: Build OCaml Host SDK
if: steps.cache-ocaml.outputs.cache-hit != 'true'
run: |
cd ocaml
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune build
- name: Test OCaml Host SDK
run: |
opam install -y --deps-only .
cd ocaml
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune exec ./bin/main.exe
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune runtest
haskell:
name: Haskell
needs: lib
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Setup Haskell env
uses: haskell/actions/setup@v2
with:
enable-stack: true
stack-version: "latest"
- name: Cache Haskell
id: cache-haskell
uses: actions/cache@v3
with:
path: .stack-work
key: ${{ runner.os }}-haskell-${{ hashFiles('haskell/**') }}
- name: Build Haskell Host SDK
if: steps.cache-haskell.outputs.cache-hit != 'true'
run: |
cd haskell
LD_LIBRARY_PATH=/usr/local/lib stack build
- name: Test Haskell SDK
run: |
cd haskell
LD_LIBRARY_PATH=/usr/local/lib stack test
php:
name: PHP
needs: lib
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Setup PHP env
uses: shivammathur/setup-php@v2
with:
php-version: "8.1"
extensions: ffi
tools: composer
env:
fail-fast: true
- name: Test PHP SDK
run: |
cd php/example
composer install
php index.php
cpp:
name: C++
needs: lib
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- uses: ./.github/actions/extism
- name: Install C++ SDK deps
if: ${{ matrix.os == 'macos-latest' }}
run: |
brew install jsoncpp googletest pkg-config
- name: Install C++ SDK deps
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
sudo apt-get install g++ libjsoncpp-dev libgtest-dev pkg-config
- name: Run C++ tests
run: |
cd cpp
LD_LIBRARY_PATH=/usr/local/lib make example
LD_LIBRARY_PATH=/usr/local/lib make test
sdk_api_coverage:
name: SDK API Coverage Report
runs-on: ubuntu-latest
@@ -22,4 +368,3 @@ jobs:
id: coverage
run: |
python scripts/sdk_coverage.py

34
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Docs
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the "main" branch
push:
branches: [ "main" ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
- name: Setup Python env
uses: actions/setup-python@v4
with:
python-version: "3.9"
check-latest: true
- name: Build Python Docs
run: |
cd python
make prepare
make docs

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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

View File

@@ -1,16 +0,0 @@
on:
workflow_dispatch:
name: Release Haskell SDK
jobs:
release-sdks:
name: release-rust
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: cachix/haskell-release-action@v1
with:
- hackage-token: "${{ secrets.HACKAGE_TOKEN }}"
- work-dir: ./haskell

View File

@@ -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

View File

@@ -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

View File

@@ -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/

View File

@@ -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

View File

@@ -1,39 +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 Manifest Crate
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
- name: Release Rust Host SDK
if: always()
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |
#cargo publish --manifest-path runtime/Cargo.toml --no-verify
cargo publish --manifest-path rust/Cargo.toml

View File

@@ -1,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/

15
.gitignore vendored
View File

@@ -27,18 +27,9 @@ ruby/tmp/
ruby/Gemfile.lock
rust/target
rust/test.log
duniverse
_build
ocaml/duniverse
ocaml/_build
php/Extism.php
python/docs
dist-newstyle
.stack-work
vendor
zig/zig-*
zig/example-out/
zig/*.log
java/*.iml
java/*.log
java/.idea
java/.DS_Store
vendor

View File

@@ -1 +0,0 @@
version = 0.24.1

View File

@@ -3,6 +3,8 @@ members = [
"manifest",
"runtime",
"rust",
"libextism",
]
exclude = ["kernel"]
exclude = [
"elixir/native/extism_nif"
]

View File

@@ -18,23 +18,18 @@ else
FEATURE_FLAGS=--features $(FEATURES)
endif
build:
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
.PHONY: kernel
kernel:
cd kernel && bash build.sh
.PHONY: build
lint:
cargo clippy --release --no-deps --manifest-path runtime/Cargo.toml
debug:
RUSTFLAGS=-g $(MAKE) build
build:
cargo build --release $(FEATURE_FLAGS) --manifest-path runtime/Cargo.toml
install:
mkdir -p $(DEST)/lib $(DEST)/include
install runtime/extism.h $(DEST)/include/extism.h
install target/release/libextism.$(SOEXT) $(DEST)/lib/libextism.$(SOEXT)
install runtime/extism.h $(DEST)/include
install target/release/libextism.$(SOEXT) $(DEST)/lib
uninstall:
rm -f $(DEST)/include/extism.h $(DEST)/lib/libextism.$(SOEXT)

View File

@@ -1,8 +1,6 @@
### _Welcome!_
**Please note:** This project still under active development and APIs may change until we hit v1.0.
If you're interested in working on or building with Extism, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know - we are happy to help get you started.
**Please note:** this project still under active development. It's usable, but expect some rough edges while work is underway. If you're interested in working on or building with Extism, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know - we are happy to help get you started.
[![Discord](https://img.shields.io/discord/1011124058408112148?color=%23404eed&label=Community%20Chat&logo=Discord&logoColor=%23404eed)](https://discord.gg/cx3usBCWnc)
@@ -12,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), [Elixir/Erlang](https://extism.org/docs/integrate-into-your-codebase/elixir-or-erlang-host-sdk) & more (others coming soon).
Plug-in development kits (PDK) for plug-in authors supported in [Rust](https://github.com/extism/rust-pdk), [AssemblyScript](https://github.com/extism/assemblyscript-pdk), [Go](https://github.com/extism/go-pdk), [C/C++](https://github.com/extism/c-pdk), [Haskell](https://github.com/extism/haskell-pdk), and [Zig](https://github.com/extism/zig-pdk).
Plug-in development kits (PDK) for plug-in authors supported in [Rust](https://github.com/extism/rust-pdk), [AssemblyScript](https://github.com/extism/assemblyscript-pdk), [Go](https://github.com/extism/go-pdk), [C/C++](https://github.com/extism/c-pdk).
<p align="center">
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/210286900-39b144fd-1b26-4dd0-b7a9-2b5755bc174d.png" alt="Extism embedded SDK language support"/>
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/198499438-f3de06e5-71b4-439d-ab31-a3672acc6ede.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
@@ -64,4 +55,5 @@ Extism is an open-source product from the team at:
</p>
_Reach out and tell us what you're building! We'd love to help._

130
browser/.gitignore vendored
View File

@@ -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.*

View File

@@ -1,5 +0,0 @@
{
"printWidth": 120,
"trailingComma": "all",
"singleQuote": true
}

View File

@@ -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

View File

@@ -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",
});

View File

@@ -1,248 +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 helloWorld = function(index){
console.log("Hello, " + this.allocator.getString(index));
return index;
};
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": url } ] }, {"hello_world": helloWorld});
let functions = Object.keys(await plugin.getExports())
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 helloWorld = function(index){
console.log("Hello, " + this.allocator.getString(index));
return index;
};
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": this.state.url } ] }, {
"hello_world": helloWorld
});
let 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>

View File

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

14454
browser/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,39 +0,0 @@
{
"name": "@extism/runtime-browser",
"version": "0.3.0",
"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",
"esbuild-jest": "^0.5.0",
"jest": "^29.2.2",
"jest-environment-jsdom": "^29.3.1",
"prettier": "^2.7.1",
"ts-jest": "^29.0.3",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typedoc": "^0.23.20",
"typescript": "^4.8.4"
},
"dependencies": {
"@bjorn3/browser_wasi_shim": "^0.2.7"
}
}

View File

@@ -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);
}
}

View File

@@ -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, functions: Record<string, any> = {}, 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, functions, config);
}
}

View File

@@ -1,24 +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 });
expect(true).toEqual(true);
});
});

View File

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

View File

@@ -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>;
};

View File

@@ -1,203 +0,0 @@
import Allocator from './allocator';
import { PluginConfig } from './manifest';
import { WASI, Fd } from '@bjorn3/browser_wasi_shim';
export type ExtismFunction = any;
export class ExtismPlugin {
moduleData: ArrayBuffer;
allocator: Allocator;
config?: PluginConfig;
vars: Record<string, Uint8Array>;
input: Uint8Array;
output: Uint8Array;
module?: WebAssembly.WebAssemblyInstantiatedSource;
functions: Record<string, ExtismFunction>;
constructor(moduleData: ArrayBuffer, functions: Record<string, ExtismFunction> = {}, config?: PluginConfig) {
this.moduleData = moduleData;
this.allocator = new Allocator(1024 * 1024);
this.config = config;
this.vars = {};
this.input = new Uint8Array();
this.output = new Uint8Array();
this.functions = functions;
}
async getExports(): Promise<WebAssembly.Exports> {
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: Fd[] = [
// new XtermStdio(term), // stdin
// new XtermStdio(term), // stdout
// new XtermStdio(term), // stderr
];
let wasi = new WASI(args, envVars, fds);
let env = {
wasi_snapshot_preview1: wasi.wasiImport,
env: environment,
};
this.module = await WebAssembly.instantiate(this.moduleData, env);
// normally we would call wasi.start here but it doesn't respect when there is
// no _start function
//@ts-ignore
wasi.inst = this.module.instance;
if (this.module.instance.exports._start) {
//@ts-ignore
this.module.instance.exports._start();
}
return this.module;
}
makeEnv(): any {
const plugin = this;
var env: any = {
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);
},
};
for (const [name, func] of Object.entries(this.functions)) {
env[name] = function () {
return func.apply(plugin, arguments);
};
}
return env;
}
}

View File

@@ -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"]
}

View File

@@ -1,6 +0,0 @@
{
"extends": [
"tslint:recommended",
"tslint-config-prettier"
]
}

View File

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

View File

@@ -9,21 +9,6 @@
#include <sys/stat.h>
#include <unistd.h>
void hello_world(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
uint64_t n_inputs, ExtismVal *outputs, uint64_t n_outputs,
void *data) {
puts("Hello from C!");
puts(data);
ExtismSize ptr_offs = inputs[0].v.i64;
uint8_t *buf = extism_current_plugin_memory(plugin) + ptr_offs;
uint64_t length = extism_current_plugin_memory_length(plugin, ptr_offs);
fwrite(buf, length, 1, stdout);
fputc('\n', stdout);
outputs[0].v.i64 = inputs[0].v.i64;
}
uint8_t *read_file(const char *filename, size_t *len) {
FILE *fp = fopen(filename, "rb");
@@ -36,7 +21,6 @@ uint8_t *read_file(const char *filename, size_t *len) {
uint8_t *data = malloc(length);
if (data == NULL) {
fclose(fp);
return NULL;
}
@@ -56,18 +40,13 @@ int main(int argc, char *argv[]) {
ExtismContext *ctx = extism_context_new();
size_t len = 0;
uint8_t *data = read_file("../wasm/code-functions.wasm", &len);
ExtismValType inputs[] = {I64};
ExtismValType outputs[] = {I64};
ExtismFunction *f = extism_function_new("hello_world", inputs, 1, outputs, 1,
hello_world, "Hello, again!", NULL);
ExtismPlugin plugin =
extism_plugin_new(ctx, data, len, (const ExtismFunction **)&f, 1, true);
uint8_t *data = read_file("../wasm/code.wasm", &len);
ExtismPlugin plugin = extism_plugin_new(ctx, data, len, false);
free(data);
if (plugin < 0) {
puts(extism_error(ctx, -1));
exit(1);
}
assert(extism_plugin_call(ctx, plugin, "count_vowels", (uint8_t *)argv[1],
strlen(argv[1])) == 0);
ExtismSize out_len = extism_plugin_output_length(ctx, plugin);
@@ -76,7 +55,6 @@ int main(int argc, char *argv[]) {
write(STDOUT_FILENO, "\n", 1);
extism_plugin_free(ctx, plugin);
extism_function_free(f);
extism_context_free(ctx);
return 0;
}

View File

@@ -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": {}
}

View File

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

View File

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

View File

@@ -1,10 +1,7 @@
#pragma once
#include <cstring>
#include <functional>
#include <map>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
@@ -23,62 +20,27 @@ extern "C" {
namespace extism {
typedef std::map<std::string, std::string> Config;
template <typename T> class ManifestKey {
bool is_set = false;
public:
T value;
ManifestKey(T x, bool is_set = false) : is_set(is_set) { value = x; }
void set(T x) {
value = x;
is_set = true;
}
bool empty() const { return is_set == false; }
};
class Wasm {
std::string _path;
std::string _url;
// TODO: add base64 encoded raw data
ManifestKey<std::string> _hash =
ManifestKey<std::string>(std::string(), false);
public:
// Create Wasm pointing to a path
static Wasm path(std::string s, std::string hash = std::string()) {
Wasm w;
w._path = s;
if (!hash.empty()) {
w._hash.set(hash);
}
return w;
}
// Create Wasm pointing to a URL
static Wasm url(std::string s, std::string hash = std::string()) {
Wasm w;
w._url = s;
if (!hash.empty()) {
w._hash.set(hash);
}
return w;
}
std::string path;
std::string url;
// TODO: add base64 encoded raw data
std::string hash;
#ifndef EXTISM_NO_JSON
Json::Value json() const {
Json::Value doc;
if (!this->_path.empty()) {
doc["path"] = this->_path;
} else if (!this->_url.empty()) {
doc["url"] = this->_url;
if (!this->path.empty()) {
doc["path"] = this->path;
}
if (!this->_hash.empty()) {
doc["hash"] = this->_hash.value;
if (!this->url.empty()) {
doc["url"] = this->url;
}
if (!this->hash.empty()) {
doc["hash"] = this->hash;
}
return doc;
@@ -90,23 +52,14 @@ class Manifest {
public:
Config config;
std::vector<Wasm> wasm;
ManifestKey<std::vector<std::string>> allowed_hosts;
ManifestKey<std::map<std::string, std::string>> allowed_paths;
ManifestKey<uint64_t> timeout_ms;
std::vector<std::string> allowed_hosts;
// Empty manifest
Manifest()
: timeout_ms(0, false), allowed_hosts(std::vector<std::string>(), false),
allowed_paths(std::map<std::string, std::string>(), false) {}
// Create manifest with a single Wasm from a path
static Manifest path(std::string s, std::string hash = std::string()) {
Manifest m;
m.add_wasm_path(s, hash);
return m;
}
// Create manifest with a single Wasm from a URL
static Manifest url(std::string s, std::string hash = std::string()) {
Manifest m;
m.add_wasm_url(s, hash);
@@ -135,71 +88,43 @@ public:
if (!this->allowed_hosts.empty()) {
Json::Value h;
for (auto s : this->allowed_hosts.value) {
for (auto s : this->allowed_hosts) {
h.append(s);
}
doc["allowed_hosts"] = h;
}
if (!this->allowed_paths.empty()) {
Json::Value h;
for (auto k : this->allowed_paths.value) {
h[k.first] = k.second;
}
doc["allowed_paths"] = h;
}
if (!this->timeout_ms.empty()) {
doc["timeout_ms"] = Json::Value(this->timeout_ms.value);
}
Json::FastWriter writer;
return writer.write(doc);
}
#endif
// Add Wasm from path
void add_wasm_path(std::string s, std::string hash = std::string()) {
Wasm w = Wasm::path(s, hash);
Wasm w;
w.path = s;
w.hash = hash;
this->wasm.push_back(w);
}
// Add Wasm from URL
void add_wasm_url(std::string u, std::string hash = std::string()) {
Wasm w = Wasm::url(u, hash);
Wasm w;
w.url = u;
w.hash = hash;
this->wasm.push_back(w);
}
// Add host to allowed hosts
void allow_host(std::string host) {
if (this->allowed_hosts.empty()) {
this->allowed_hosts.set(std::vector<std::string>{});
}
this->allowed_hosts.value.push_back(host);
}
void allow_host(std::string host) { this->allowed_hosts.push_back(host); }
// Add path to allowed paths
void allow_path(std::string src, std::string dest = std::string()) {
if (this->allowed_paths.empty()) {
this->allowed_paths.set(std::map<std::string, std::string>{});
}
if (dest.empty()) {
dest = src;
}
this->allowed_paths.value[src] = dest;
}
// Set timeout
void set_timeout_ms(uint64_t ms) { this->timeout_ms = ms; }
// Set config key/value
void set_config(std::string k, std::string v) { this->config[k] = v; }
};
class Error : public std::runtime_error {
class Error : public std::exception {
private:
std::string message;
public:
Error(std::string msg) : std::runtime_error(msg) {}
Error(std::string msg) : message(msg) {}
const char *what() { return message.c_str(); }
};
class Buffer {
@@ -218,141 +143,14 @@ public:
}
};
typedef ExtismValType ValType;
typedef ExtismValUnion ValUnion;
typedef ExtismVal Val;
class CurrentPlugin {
ExtismCurrentPlugin *pointer;
public:
CurrentPlugin(ExtismCurrentPlugin *p) : pointer(p) {}
uint8_t *memory() { return extism_current_plugin_memory(this->pointer); }
ExtismSize memory_length(uint64_t offs) {
return extism_current_plugin_memory_length(this->pointer, offs);
}
uint64_t alloc(ExtismSize size) {
return extism_current_plugin_memory_alloc(this->pointer, size);
}
void free(uint64_t offs) {
extism_current_plugin_memory_free(this->pointer, offs);
}
void returnString(Val &output, const std::string &s) {
this->returnBytes(output, (const uint8_t *)s.c_str(), s.size());
}
void returnBytes(Val &output, const uint8_t *bytes, size_t len) {
auto offs = this->alloc(len);
memcpy(this->memory() + offs, bytes, len);
output.v.i64 = offs;
}
uint8_t *inputBytes(Val &inp, size_t *length = nullptr) {
if (inp.t != ValType::I64) {
return nullptr;
}
if (length != nullptr) {
*length = this->memory_length(inp.v.i64);
}
return this->memory() + inp.v.i64;
}
std::string inputString(Val &inp) {
size_t length = 0;
char *buf = (char *)this->inputBytes(inp, &length);
return std::string(buf, length);
}
};
typedef std::function<void(CurrentPlugin, const std::vector<Val> &,
std::vector<Val> &, void *user_data)>
FunctionType;
struct UserData {
FunctionType func;
void *user_data = NULL;
std::function<void(void *)> free_user_data;
};
static void function_callback(ExtismCurrentPlugin *plugin,
const ExtismVal *inputs, ExtismSize n_inputs,
ExtismVal *outputs, ExtismSize n_outputs,
void *user_data) {
UserData *data = (UserData *)user_data;
const std::vector<Val> inp(inputs, inputs + n_inputs);
std::vector<Val> outp(outputs, outputs + n_outputs);
data->func(CurrentPlugin(plugin), inp, outp, data->user_data);
for (ExtismSize i = 0; i < n_outputs; i++) {
outputs[i] = outp[i];
}
}
static void free_user_data(void *user_data) {
UserData *data = (UserData *)user_data;
if (data->user_data != NULL && data->free_user_data != NULL) {
data->free_user_data(data->user_data);
}
}
class Function {
std::shared_ptr<ExtismFunction> func;
std::string name;
UserData user_data;
public:
Function(std::string name, const std::vector<ValType> inputs,
const std::vector<ValType> outputs, FunctionType f,
void *user_data = NULL, std::function<void(void *)> free = nullptr)
: name(name) {
this->user_data.func = f;
this->user_data.user_data = user_data;
this->user_data.free_user_data = free;
auto ptr = extism_function_new(
this->name.c_str(), inputs.data(), inputs.size(), outputs.data(),
outputs.size(), function_callback, &this->user_data, free_user_data);
this->func = std::shared_ptr<ExtismFunction>(ptr, extism_function_free);
}
void set_namespace(std::string s) {
extism_function_set_namespace(this->func.get(), s.c_str());
}
Function(const Function &f) { this->func = f.func; }
ExtismFunction *get() { return this->func.get(); }
};
class CancelHandle {
const ExtismCancelHandle *handle;
public:
CancelHandle(const ExtismCancelHandle *x) : handle(x){};
bool cancel() { return extism_plugin_cancel(this->handle); }
};
class Plugin {
std::shared_ptr<ExtismContext> context;
ExtismPlugin plugin;
std::vector<Function> functions;
public:
// Create a new plugin
Plugin(const uint8_t *wasm, ExtismSize length, bool with_wasi = false,
std::vector<Function> functions = std::vector<Function>(),
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
extism_context_new(), extism_context_free))
: functions(functions) {
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
this->plugin = extism_plugin_new(ctx.get(), wasm, length, ptrs.data(),
ptrs.size(), with_wasi);
Plugin(std::shared_ptr<ExtismContext> ctx, const uint8_t *wasm,
ExtismSize length, bool with_wasi = false) {
this->plugin = extism_plugin_new(ctx.get(), wasm, length, with_wasi);
if (this->plugin < 0) {
const char *err = extism_error(ctx.get(), -1);
throw Error(err == nullptr ? "Unable to load plugin" : err);
@@ -360,39 +158,12 @@ public:
this->context = ctx;
}
Plugin(const std::string &str, bool with_wasi = false,
std::vector<Function> functions = {},
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
extism_context_new(), extism_context_free))
: Plugin((const uint8_t *)str.c_str(), str.size(), with_wasi, functions,
ctx) {}
Plugin(const std::vector<uint8_t> &data, bool with_wasi = false,
std::vector<Function> functions = {},
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
extism_context_new(), extism_context_free))
: Plugin(data.data(), data.size(), with_wasi, functions, ctx) {}
CancelHandle cancel_handle() {
return CancelHandle(
extism_plugin_cancel_handle(this->context.get(), this->id()));
}
#ifndef EXTISM_NO_JSON
// Create a new plugin from Manifest
Plugin(const Manifest &manifest, bool with_wasi = false,
std::vector<Function> functions = {},
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
extism_context_new(), extism_context_free)) {
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
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(), ptrs.data(), ptrs.size(), with_wasi);
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);
@@ -410,15 +181,9 @@ public:
ExtismContext *get_context() const { return this->context.get(); }
void update(const uint8_t *wasm, size_t length, bool with_wasi = false,
std::vector<Function> functions = {}) {
this->functions = functions;
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
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, ptrs.data(), ptrs.size(), with_wasi);
length, with_wasi);
if (!b) {
const char *err = extism_error(this->context.get(), -1);
throw Error(err == nullptr ? "Unable to update plugin" : err);
@@ -426,17 +191,11 @@ public:
}
#ifndef EXTISM_NO_JSON
void update(const Manifest &manifest, bool with_wasi = false,
std::vector<Function> functions = {}) {
this->functions = functions;
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
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(), ptrs.data(), ptrs.size(), with_wasi);
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);
@@ -469,7 +228,6 @@ public:
this->config(json.c_str(), json.size());
}
// Call a plugin
Buffer call(const std::string &func, const uint8_t *input,
ExtismSize input_length) const {
int32_t rc = extism_plugin_call(this->context.get(), this->plugin,
@@ -490,19 +248,15 @@ public:
return Buffer(ptr, length);
}
// Call a plugin function with std::vector<uint8_t> input
Buffer call(const std::string &func,
const std::vector<uint8_t> &input) const {
return this->call(func, input.data(), input.size());
}
// Call a plugin function with string input
Buffer call(const std::string &func,
const std::string &input = std::string()) const {
Buffer call(const std::string &func, const std::string &input) const {
return this->call(func, (const uint8_t *)input.c_str(), input.size());
}
// Returns true if the specified function exists
bool function_exists(const std::string &func) const {
return extism_plugin_function_exists(this->context.get(), this->plugin,
func.c_str());
@@ -512,50 +266,38 @@ public:
class Context {
public:
std::shared_ptr<ExtismContext> pointer;
// Create a new context;
Context() {
this->pointer = std::shared_ptr<ExtismContext>(extism_context_new(),
extism_context_free);
}
// Create plugin from uint8_t*
Plugin plugin(const uint8_t *wasm, size_t length, bool with_wasi = false,
std::vector<Function> functions = {}) const {
return Plugin(wasm, length, with_wasi, functions, this->pointer);
Plugin plugin(const uint8_t *wasm, size_t length,
bool with_wasi = false) const {
return Plugin(this->pointer, wasm, length, with_wasi);
}
// Create plugin from std::string
Plugin plugin(const std::string &str, bool with_wasi = false,
std::vector<Function> functions = {}) const {
return Plugin((const uint8_t *)str.c_str(), str.size(), with_wasi,
functions, this->pointer);
Plugin plugin(const std::string &str, bool with_wasi = false) const {
return Plugin(this->pointer, (const uint8_t *)str.c_str(), str.size(),
with_wasi);
}
// Create plugin from uint8_t vector
Plugin plugin(const std::vector<uint8_t> &data, bool with_wasi = false,
std::vector<Function> functions = {}) const {
return Plugin(data.data(), data.size(), with_wasi, functions,
this->pointer);
Plugin plugin(const std::vector<uint8_t> &data,
bool with_wasi = false) const {
return Plugin(this->pointer, data.data(), data.size(), with_wasi);
}
#ifndef EXTISM_NO_JSON
// Create plugin from Manifest
Plugin plugin(const Manifest &manifest, bool with_wasi = false,
std::vector<Function> functions = {}) const {
return Plugin(manifest, with_wasi, functions, this->pointer);
Plugin plugin(const Manifest &manifest, bool with_wasi = false) const {
return Plugin(this->pointer, manifest, with_wasi);
}
#endif
// Remove all plugins
void reset() { extism_context_reset(this->pointer.get()); }
};
// Set global log file for plugins
inline bool set_log_file(const char *filename, const char *level) {
return extism_log_file(filename, level);
}
// Get libextism version
inline std::string version() { return std::string(extism_version()); }
} // namespace extism

View File

@@ -10,8 +10,6 @@ std::vector<uint8_t> read(const char *filename) {
std::istreambuf_iterator<char>());
}
const std::string code = "../../wasm/code.wasm";
namespace {
using namespace extism;
@@ -21,24 +19,26 @@ TEST(Context, Basic) {
}
TEST(Plugin, Manifest) {
Manifest manifest = Manifest::path(code);
Context context;
Manifest manifest = Manifest::path("code.wasm");
manifest.set_config("a", "1");
ASSERT_NO_THROW(Plugin plugin(manifest));
Plugin plugin(manifest);
ASSERT_NO_THROW(Plugin plugin = context.plugin(manifest));
Plugin plugin = context.plugin(manifest);
Buffer buf = plugin.call("count_vowels", "this is a test");
ASSERT_EQ((std::string)buf, "{\"count\": 4}");
}
TEST(Plugin, BadManifest) {
Context context;
Manifest manifest;
ASSERT_THROW(Plugin plugin(manifest), Error);
ASSERT_THROW(Plugin plugin = context.plugin(manifest), Error);
}
TEST(Plugin, Bytes) {
Context context;
auto wasm = read(code.c_str());
auto wasm = read("code.wasm");
ASSERT_NO_THROW(Plugin plugin = context.plugin(wasm));
Plugin plugin = context.plugin(wasm);
@@ -48,7 +48,7 @@ TEST(Plugin, Bytes) {
TEST(Plugin, UpdateConfig) {
Context context;
auto wasm = read(code.c_str());
auto wasm = read("code.wasm");
Plugin plugin = context.plugin(wasm);
Config config;
@@ -58,33 +58,13 @@ TEST(Plugin, UpdateConfig) {
TEST(Plugin, FunctionExists) {
Context context;
auto wasm = read(code.c_str());
auto wasm = read("code.wasm");
Plugin plugin = context.plugin(wasm);
ASSERT_FALSE(plugin.function_exists("bad_function"));
ASSERT_TRUE(plugin.function_exists("count_vowels"));
}
TEST(Plugin, HostFunction) {
auto wasm = read("../../wasm/code-functions.wasm");
auto t = std::vector<ValType>{ValType::I64};
Function hello_world =
Function("hello_world", t, t,
[](CurrentPlugin plugin, const std::vector<Val> &params,
std::vector<Val> &results, void *user_data) {
auto offs = plugin.alloc(4);
memcpy(plugin.memory() + offs, "test", 4);
results[0].v.i64 = (int64_t)offs;
});
auto functions = std::vector<Function>{
hello_world,
};
Plugin plugin(wasm, true, functions);
auto buf = plugin.call("count_vowels", "aaa");
ASSERT_EQ(buf.length, 4);
ASSERT_EQ((std::string)buf, "test");
}
}; // namespace
int main(int argc, char **argv) {

479
dotnet/.gitignore vendored
View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -1 +0,0 @@
win-x64.dll

View File

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

View File

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

View File

@@ -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.**

View File

@@ -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>

View File

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

View File

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

View File

@@ -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.7.0</Version>
<Authors>Extism Contributors</Authors>
<Description>Extism SDK that allows hosting Extism plugins in .NET apps.</Description>
<Tags>extism, wasm, plugin</Tags>
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@@ -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)
{
}
}

View File

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

View File

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

View File

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

View File

@@ -1,210 +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 readonly HostFunction[] _functions;
private int _disposed;
/// <summary>
/// Create a and load a plug-in
/// Using this constructor will give the plug-in it's own internal Context
/// </summary>
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
/// <param name="functions">List of host functions expected by the plugin.</param>
/// <param name="withWasi">Enable/Disable WASI.</param>
public static Plugin Create(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi) {
var context = new Context();
return context.CreatePlugin(wasm, functions, withWasi);
}
internal Plugin(Context context, HostFunction[] functions, int index)
{
_context = context;
_functions = functions;
Index = index;
}
/// <summary>
/// A pointer to the native Plugin struct.
/// </summary>
internal int Index { get; }
/// <summary>
/// Update a plugin, keeping the existing ID.
/// </summary>
/// <param name="wasm">The plugin WASM bytes.</param>
/// <param name="withWasi">Enable/Disable WASI.</param>
unsafe public bool Update(ReadOnlySpan<byte> wasm, bool withWasi)
{
CheckNotDisposed();
var functions = _functions.Select(f => f.NativeHandle).ToArray();
fixed (byte* wasmPtr = wasm)
{
return LibExtism.extism_plugin_update(_context.NativeHandle, Index, wasmPtr, wasm.Length, functions, 0, 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, Index, jsonPtr, json.Length);
}
}
/// <summary>
/// Checks if a specific function exists in the current plugin.
/// </summary>
unsafe public bool FunctionExists(string name)
{
CheckNotDisposed();
return LibExtism.extism_plugin_function_exists(_context.NativeHandle, Index, 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, Index, functionName, dataPtr, data.Length);
if (response == 0)
{
return OutputData();
}
else
{
var errorMsg = GetError();
if (errorMsg != null)
{
throw new ExtismException(errorMsg);
}
else
{
throw new ExtismException("Call to Extism failed");
}
}
}
}
/// <summary>
/// Get the length of a plugin's output data.
/// </summary>
/// <returns></returns>
unsafe internal int OutputLength()
{
CheckNotDisposed();
return (int)LibExtism.extism_plugin_output_length(_context.NativeHandle, Index);
}
/// <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, Index).ToPointer();
return new Span<byte>(ptr, length);
}
}
/// <summary>
/// Get the error associated with the current plugin.
/// </summary>
/// <returns></returns>
unsafe internal string? GetError()
{
CheckNotDisposed();
var result = LibExtism.extism_error(_context.NativeHandle, Index);
return Marshal.PtrToStringUTF8(result);
}
/// <summary>
/// Frees all resources held by this Plugin.
/// </summary>
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, DisposedMarker) == DisposedMarker)
{
// Already disposed.
return;
}
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Throw an appropriate exception if the plugin has been disposed.
/// </summary>
/// <exception cref="ObjectDisposedException"></exception>
protected void CheckNotDisposed()
{
Interlocked.MemoryBarrier();
if (_disposed == DisposedMarker)
{
ThrowDisposedException();
}
}
[DoesNotReturn]
private static void ThrowDisposedException()
{
throw new ObjectDisposedException(nameof(Plugin));
}
/// <summary>
/// Frees all resources held by this Plugin.
/// </summary>
unsafe protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Free up any managed resources here
}
// Free up unmanaged resources
LibExtism.extism_plugin_free(_context.NativeHandle, Index);
}
/// <summary>
/// Destructs the current Plugin and frees all resources used by it.
/// </summary>
~Plugin()
{
Dispose(false);
}
}

View File

@@ -1,2 +0,0 @@
## Extism.Sdk
Extism SDK that allows hosting Extism plugins in .NET apps.

View File

@@ -1,73 +0,0 @@
using Extism.Sdk.Native;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Xunit;
namespace Extism.Sdk.Tests;
public class BasicTests
{
[Fact]
public void CountHelloWorldVowelsWithoutContext()
{
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code.wasm"));
using var plugin = Plugin.Create(wasm, Array.Empty<HostFunction>(), withWasi: true);
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
}
[Fact]
public void CountHelloWorldVowels()
{
using var context = new Context();
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code.wasm"));
using var plugin = context.CreatePlugin(wasm, Array.Empty<HostFunction>(), withWasi: true);
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
}
[Fact]
public void CountVowelsHostFunctions()
{
using var context = new Context();
var userData = Marshal.StringToHGlobalAnsi("Hello again!");
using var helloWorld = new HostFunction(
"hello_world",
"env",
new[] { ExtismValType.I64 },
new[] { ExtismValType.I64 },
userData,
HelloWorld);
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code-functions.wasm"));
using var plugin = context.CreatePlugin(wasm, new[] { helloWorld }, withWasi: true);
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
void HelloWorld(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> outputs, nint data)
{
Console.WriteLine("Hello from .NET!");
var text = Marshal.PtrToStringAnsi(data);
Console.WriteLine(text);
var input = plugin.ReadString(new nint(inputs[0].v.i64));
Console.WriteLine($"Input: {input}");
var output = new string(input); // clone the string
outputs[0].v.i64 = plugin.WriteString(output);
}
}
}

View File

@@ -1,42 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<None Include="..\..\..\wasm\code.wasm" Link="code.wasm">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\..\..\wasm\code-functions.wasm" Link="code-functions.wasm">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Extism.runtime.win-x64" Version="0.4.0" />
</ItemGroup>
</Project>

View File

@@ -19,30 +19,6 @@
(name extism)
(synopsis "Extism bindings")
(description "Bindings to Extism, the universal plugin system")
(depends
(ocaml (>= 4.14.1))
dune
(ctypes (>= 0.18.0))
(ctypes-foreign (>= 0.18.0))
(bigstringaf (>= 0.9.0))
(ppx_yojson_conv (>= v0.15.0))
(extism-manifest (= :version))
(ppx_inline_test (>= v0.15.0))
(cmdliner (>= 1.1.1))
)
(tags
(topics wasm plugin)))
(package
(name extism-manifest)
(synopsis "Extism manifest bindings")
(description "Bindings to the Extism manifest format")
(depends
(ocaml (>= 4.14.1))
dune
(ppx_yojson_conv (>= v0.15.0))
(ppx_inline_test (>= v0.15.0))
(base64 (>= 3.5.0))
)
(depends ocaml dune ctypes-foreign bigstringaf ppx_yojson_conv base64 ppx_inline_test)
(tags
(topics wasm plugin)))

View File

@@ -13,7 +13,7 @@ You can find this package on [hex.pm](https://hex.pm/packages/extism).
```elixir
def deps do
[
{:extism, "~> 0.1.0"}
{:extism, "~> 0.0.1-rc.5"}
]
end
```
@@ -70,4 +70,4 @@ The key method to know here is [Extism.Plugin#call](Extism.Plugin.html#call/3) w
```elixir
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
```
```

View File

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

View File

@@ -49,7 +49,7 @@ defmodule Extism.Context do
## 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)
- manifest: The String or Map of the WASM module or manifest
- wasi: A bool you set to true if you want WASI support
"""

View File

@@ -16,8 +16,6 @@ defmodule Extism.Native do
def plugin_has_function(_ctx, _plugin_id, _function_name), do: error()
def plugin_free(_ctx, _plugin_id), do: error()
def set_log_file(_filename, _level), do: error()
def plugin_cancel_handle(_ctx, _plugin_id), do: error()
def plugin_cancel(_handle), do: error()
defp error, do: :erlang.nif_error(:nif_not_loaded)
end

View File

@@ -15,25 +15,12 @@ defmodule Extism.Plugin do
}
end
@doc """
Creates a new plugin
"""
def new(manifest, wasi \\ false, context \\ nil) do
ctx = context || Extism.Context.new()
{:ok, manifest_payload} = JSON.encode(manifest)
case Extism.Native.plugin_new_with_manifest(ctx.ptr, manifest_payload, wasi) do
{:error, err} -> {:error, err}
res -> {:ok, Extism.Plugin.wrap_resource(ctx, res)}
end
end
@doc """
Call a plugin's function by name
## Examples
iex> {:ok, plugin} = Extism.Plugin.new(manifest, false)
iex> {:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
iex> {:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
# {:ok, "{\"count\": 4}"}
@@ -57,14 +44,6 @@ defmodule Extism.Plugin do
@doc """
Updates the manifest of the given plugin
## Parameters
- ctx: The Context to manage this plugin
- manifest: The String or Map of the WASM module or [manifest](https://extism.org/docs/concepts/manifest)
- wasi: A bool you set to true if you want WASI support
"""
def update(plugin, manifest, wasi) when is_map(manifest) do
{:ok, manifest_payload} = JSON.encode(manifest)

View File

@@ -4,8 +4,8 @@ defmodule Extism.MixProject do
def project do
[
app: :extism,
version: "0.5.0",
elixir: "~> 1.12",
version: "0.0.1-rc.5",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps(),
package: package(),
@@ -23,7 +23,7 @@ defmodule Extism.MixProject do
defp deps do
[
{:rustler, "~> 0.29.1"},
{:rustler, "~> 0.26.0"},
{:json, "~> 1.4"},
{:ex_doc, "~> 0.21", only: :dev, runtime: false}
]
@@ -43,7 +43,7 @@ defmodule Extism.MixProject do
licenses: ["BSD-3-Clause"],
description: "Extism Host SDK for Elixir and Erlang",
name: "extism",
files: ~w(lib native .formatter.exs mix.exs README.md LICENSE),
files: ~w(lib native priv .formatter.exs mix.exs README.md LICENSE),
links: %{"GitHub" => "https://github.com/extism/extism"}
]
end

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "extism_nif"
version = "0.3.0"
version = "0.0.1-rc.5"
edition = "2021"
authors = ["Benjamin Eckel <bhelx@simst.im>"]
@@ -9,10 +9,7 @@ name = "extism_nif"
path = "src/lib.rs"
crate-type = ["cdylib"]
# need this to be here and be empty
[workspace]
[dependencies]
rustler = "0.28.0"
extism = "0.5.0"
rustler = "0.26.0"
extism = { version = "0.0.1-rc.5" }
log = "0.4"

View File

@@ -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,33 +15,27 @@ mod atoms {
}
struct ExtismContext {
ctx: RwLock<Context>,
ctx: RwLock<Context>
}
unsafe impl Sync for ExtismContext {}
unsafe impl Send for ExtismContext {}
struct ExtismCancelHandle {
handle: RwLock<extism::CancelHandle>,
}
unsafe impl Sync for ExtismCancelHandle {}
unsafe impl Send for ExtismCancelHandle {}
fn load(env: Env, _: Term) -> bool {
rustler::resource!(ExtismContext, env);
rustler::resource!(ExtismCancelHandle, env);
true
}
fn to_rustler_error(extism_error: extism::Error) -> rustler::Error {
rustler::Error::Term(Box::new(extism_error.to_string()))
match extism_error {
extism::Error::UnableToLoadPlugin(msg) => rustler::Error::Term(Box::new(msg)),
extism::Error::Message(msg) => rustler::Error::Term(Box::new(msg)),
extism::Error::Json(json_err) => rustler::Error::Term(Box::new(json_err.to_string()))
}
}
#[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]
@@ -52,18 +46,14 @@ fn context_reset(ctx: ResourceArc<ExtismContext>) {
#[rustler::nif]
fn context_free(ctx: ResourceArc<ExtismContext>) {
let context = ctx.ctx.read().unwrap();
let context = &ctx.ctx.read().unwrap();
std::mem::drop(context)
}
#[rustler::nif]
fn plugin_new_with_manifest(
ctx: ResourceArc<ExtismContext>,
manifest_payload: String,
wasi: bool,
) -> Result<i32, rustler::Error> {
let context = ctx.ctx.write().unwrap();
let result = match Plugin::new(&context, manifest_payload, [], wasi) {
fn plugin_new_with_manifest(ctx: ResourceArc<ExtismContext>, manifest_payload: String, wasi: bool) -> Result<i32, rustler::Error> {
let context = &ctx.ctx.write().unwrap();
let result = match Plugin::new(context, manifest_payload, wasi) {
Err(e) => Err(to_rustler_error(e)),
Ok(plugin) => {
let plugin_id = plugin.as_i32();
@@ -77,64 +67,38 @@ fn plugin_new_with_manifest(
}
#[rustler::nif]
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 result = match plugin.call(name, input) {
Err(e) => Err(to_rustler_error(e)),
Ok(result) => match str::from_utf8(result) {
Ok(output) => Ok(output.to_string()),
Err(_e) => Err(rustler::Error::Term(Box::new(
"Could not read output from plugin",
))),
},
};
// this forget should be safe because the context will clean up
// all it's plugins when it is dropped
mem::forget(plugin);
result
}
#[rustler::nif]
fn plugin_update_manifest(
ctx: ResourceArc<ExtismContext>,
plugin_id: i32,
manifest_payload: String,
wasi: bool,
) -> Result<(), rustler::Error> {
let context = &ctx.ctx.read().unwrap();
let mut plugin = unsafe { Plugin::from_id(plugin_id, context) };
let result = match plugin.update(manifest_payload, [], wasi) {
Ok(()) => Ok(()),
Err(e) => Err(to_rustler_error(e)),
};
// this forget should be safe because the context will clean up
// all it's plugins when it is dropped
mem::forget(plugin);
result
}
#[rustler::nif]
fn plugin_cancel_handle(
ctx: ResourceArc<ExtismContext>,
plugin_id: i32,
) -> Result<ResourceArc<ExtismCancelHandle>, 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 plugin = unsafe { Plugin::from_id(plugin_id, context) };
let handle = plugin.cancel_handle();
Ok(ResourceArc::new(ExtismCancelHandle {
handle: RwLock::new(handle),
}))
let result = match plugin.call(name, input) {
Err(e) => Err(to_rustler_error(e)),
Ok(result) => {
match str::from_utf8(&result) {
Ok(output) => Ok(output.to_string()),
Err(_e) => Err(rustler::Error::Term(Box::new("Could not read output from plugin")))
}
}
};
// this forget should be safe because the context will clean up
// all it's plugins when it is dropped
mem::forget(plugin);
result
}
#[rustler::nif]
fn plugin_cancel(handle: ResourceArc<ExtismCancelHandle>) -> bool {
handle.handle.read().unwrap().cancel()
fn plugin_update_manifest(ctx: ResourceArc<ExtismContext>, plugin_id: i32, manifest_payload: String, wasi: bool) -> Result<(), rustler::Error> {
let context = &ctx.ctx.read().unwrap();
let mut plugin = unsafe { Plugin::from_id(plugin_id, context) };
let result = match plugin.update(manifest_payload, wasi) {
Ok(()) => {
Ok(())
},
Err(e) => Err(to_rustler_error(e))
};
// this forget should be safe because the context will clean up
// all it's plugins when it is dropped
mem::forget(plugin);
result
}
#[rustler::nif]
@@ -149,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);
@@ -190,8 +142,6 @@ rustler::init!(
plugin_call,
plugin_update_manifest,
plugin_has_function,
plugin_cancel_handle,
plugin_cancel,
plugin_free,
set_log_file,
],

View File

@@ -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

View File

@@ -1,34 +0,0 @@
# This file is generated by dune, edit dune-project instead
opam-version: "2.0"
synopsis: "Extism manifest bindings"
description: "Bindings to the Extism manifest format"
maintainer: ["Extism Authors <oss@extism.org>"]
authors: ["Extism Authors <oss@extism.org>"]
license: "BSD-3-Clause"
tags: ["topics" "wasm" "plugin"]
homepage: "https://github.com/extism/extism"
doc: "https://github.com/extism/extism"
bug-reports: "https://github.com/extism/extism/issues"
depends: [
"ocaml" {>= "4.14.1"}
"dune" {>= "3.2"}
"ppx_yojson_conv" {>= "v0.15.0"}
"ppx_inline_test" {>= "v0.15.0"}
"base64" {>= "3.5.0"}
"odoc" {with-doc}
]
build: [
["dune" "subst"] {dev}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
]
dev-repo: "git+https://github.com/extism/extism.git"

350
extism.go
View File

@@ -5,50 +5,13 @@ import (
"errors"
"fmt"
"io"
"runtime/cgo"
"unsafe"
)
/*
#cgo CFLAGS: -I/usr/local/include
#cgo LDFLAGS: -L/usr/local/lib -lextism
#cgo pkg-config: libextism.pc
#include <extism.h>
#include <stdlib.h>
int64_t extism_val_i64(ExtismValUnion* x){
return x->i64;
}
int32_t extism_val_i32(ExtismValUnion* x){
return x->i32;
}
float extism_val_f32(ExtismValUnion* x){
return x->f32;
}
double extism_val_f64(ExtismValUnion* x){
return x->f64;
}
void extism_val_set_i64(ExtismValUnion* x, int64_t i){
x->i64 = i;
}
void extism_val_set_i32(ExtismValUnion* x, int32_t i){
x->i32 = i;
}
void extism_val_set_f32(ExtismValUnion* x, float f){
x->f32 = f;
}
void extism_val_set_f64(ExtismValUnion* x, double f){
x->f64 = f;
}
*/
import "C"
@@ -57,106 +20,6 @@ type Context struct {
pointer *C.ExtismContext
}
type ValType = C.ExtismValType
type Val = C.ExtismVal
type Size = C.ExtismSize
var (
I32 ValType = C.I32
I64 ValType = C.I64
F32 ValType = C.F32
F64 ValType = C.F64
V128 ValType = C.V128
FuncRef ValType = C.FuncRef
ExternRef ValType = C.ExternRef
)
// Function is used to define host functions
type Function struct {
pointer *C.ExtismFunction
userData cgo.Handle
}
// Free a function
func (f *Function) Free() {
C.extism_function_free(f.pointer)
f.pointer = nil
f.userData.Delete()
}
// NewFunction creates a new host function with the given name, input/outputs and optional user data, which can be an
// arbitrary `interface{}`
func NewFunction(name string, inputs []ValType, outputs []ValType, f unsafe.Pointer, userData interface{}) Function {
var function Function
function.userData = cgo.NewHandle(userData)
cname := C.CString(name)
ptr := unsafe.Pointer(function.userData)
var inputsPtr *C.ExtismValType = nil
if len(inputs) > 0 {
inputsPtr = (*C.ExtismValType)(&inputs[0])
}
var outputsPtr *C.ExtismValType = nil
if len(outputs) > 0 {
outputsPtr = (*C.ExtismValType)(&outputs[0])
}
function.pointer = C.extism_function_new(
cname,
inputsPtr,
C.uint64_t(len(inputs)),
outputsPtr,
C.uint64_t(len(outputs)),
(*[0]byte)(f),
ptr,
nil,
)
C.free(unsafe.Pointer(cname))
return function
}
func (f *Function) SetNamespace(s string) {
cstr := C.CString(s)
defer C.free(unsafe.Pointer(cstr))
C.extism_function_set_namespace(f.pointer, cstr)
}
func (f Function) WithNamespace(s string) Function {
f.SetNamespace(s)
return f
}
type CurrentPlugin struct {
pointer *C.ExtismCurrentPlugin
}
func GetCurrentPlugin(ptr unsafe.Pointer) CurrentPlugin {
return CurrentPlugin{
pointer: (*C.ExtismCurrentPlugin)(ptr),
}
}
func (p *CurrentPlugin) Memory(offs uint) []byte {
length := C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs))
data := unsafe.Pointer(C.extism_current_plugin_memory(p.pointer))
return unsafe.Slice((*byte)(unsafe.Add(data, offs)), C.int(length))
}
// Alloc a new memory block of the given length, returning its offset
func (p *CurrentPlugin) Alloc(n uint) uint {
return uint(C.extism_current_plugin_memory_alloc(p.pointer, C.uint64_t(n)))
}
// Free the memory block specified by the given offset
func (p *CurrentPlugin) Free(offs uint) {
C.extism_current_plugin_memory_free(p.pointer, C.uint64_t(offs))
}
// Length returns the number of bytes allocated at the specified offset
func (p *CurrentPlugin) Length(offs uint) uint {
return uint(C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs)))
}
// NewContext creates a new context, it should be freed using the `Free` method
func NewContext() Context {
p := C.extism_context_new()
@@ -173,9 +36,8 @@ func (ctx *Context) Free() {
// Plugin is used to call WASM functions
type Plugin struct {
ctx *Context
id int32
functions []Function
ctx *Context
id int32
}
type WasmData struct {
@@ -191,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{}
@@ -203,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 {
@@ -234,32 +94,14 @@ func ExtismVersion() string {
return C.GoString(C.extism_version())
}
func register(ctx *Context, data []byte, functions []Function, wasi bool) (Plugin, error) {
func register(ctx *Context, data []byte, wasi bool) (Plugin, error) {
ptr := makePointer(data)
functionPointers := []*C.ExtismFunction{}
for _, f := range functions {
functionPointers = append(functionPointers, f.pointer)
}
plugin := C.int32_t(-1)
if len(functions) == 0 {
plugin = C.extism_plugin_new(
ctx.pointer,
(*C.uchar)(ptr),
C.uint64_t(len(data)),
nil,
0,
C._Bool(wasi))
} else {
plugin = C.extism_plugin_new(
ctx.pointer,
(*C.uchar)(ptr),
C.uint64_t(len(data)),
&functionPointers[0],
C.uint64_t(len(functions)),
C._Bool(wasi),
)
}
plugin := C.extism_plugin_new(
ctx.pointer,
(*C.uchar)(ptr),
C.uint64_t(len(data)),
C._Bool(wasi),
)
if plugin < 0 {
err := C.extism_error(ctx.pointer, C.int32_t(-1))
@@ -273,44 +115,21 @@ func register(ctx *Context, data []byte, functions []Function, wasi bool) (Plugi
)
}
return Plugin{id: int32(plugin), ctx: ctx, functions: functions}, nil
return Plugin{id: int32(plugin), ctx: ctx}, nil
}
func update(ctx *Context, plugin int32, data []byte, functions []Function, wasi bool) error {
func update(ctx *Context, plugin int32, data []byte, wasi bool) error {
ptr := makePointer(data)
functionPointers := []*C.ExtismFunction{}
for _, f := range functions {
functionPointers = append(functionPointers, f.pointer)
}
b := bool(C.extism_plugin_update(
ctx.pointer,
C.int32_t(plugin),
(*C.uchar)(ptr),
C.uint64_t(len(data)),
C._Bool(wasi),
))
if len(functions) == 0 {
b := bool(C.extism_plugin_update(
ctx.pointer,
C.int32_t(plugin),
(*C.uchar)(ptr),
C.uint64_t(len(data)),
nil,
0,
C._Bool(wasi),
))
if b {
return nil
}
} else {
b := bool(C.extism_plugin_update(
ctx.pointer,
C.int32_t(plugin),
(*C.uchar)(ptr),
C.uint64_t(len(data)),
&functionPointers[0],
C.uint64_t(len(functions)),
C._Bool(wasi),
))
if b {
return nil
}
if b {
return nil
}
err := C.extism_error(ctx.pointer, C.int32_t(-1))
@@ -324,57 +143,44 @@ func update(ctx *Context, plugin int32, data []byte, functions []Function, wasi
)
}
// NewPlugin creates a plugin in its own context
func NewPlugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
ctx := NewContext()
return ctx.Plugin(module, functions, wasi)
}
// NewPlugin creates a plugin in its own context from a manifest
func NewPluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
ctx := NewContext()
return ctx.PluginFromManifest(manifest, functions, wasi)
}
// PluginFromManifest creates a plugin from a `Manifest`
func (ctx *Context) PluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
func (ctx *Context) PluginFromManifest(manifest Manifest, wasi bool) (Plugin, error) {
data, err := json.Marshal(manifest)
if err != nil {
return Plugin{id: -1}, err
}
return register(ctx, data, functions, wasi)
return register(ctx, data, wasi)
}
// Plugin creates a plugin from a WASM module
func (ctx *Context) Plugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
func (ctx *Context) Plugin(module io.Reader, wasi bool) (Plugin, error) {
wasm, err := io.ReadAll(module)
if err != nil {
return Plugin{id: -1}, err
}
return register(ctx, wasm, functions, wasi)
return register(ctx, wasm, wasi)
}
// Update a plugin with a new WASM module
func (p *Plugin) Update(module io.Reader, functions []Function, wasi bool) error {
func (p *Plugin) Update(module io.Reader, wasi bool) error {
wasm, err := io.ReadAll(module)
if err != nil {
return err
}
p.functions = functions
return update(p.ctx, p.id, wasm, functions, wasi)
return update(p.ctx, p.id, wasm, wasi)
}
// Update a plugin with a new Manifest
func (p *Plugin) UpdateManifest(manifest Manifest, functions []Function, wasi bool) error {
func (p *Plugin) UpdateManifest(manifest Manifest, wasi bool) error {
data, err := json.Marshal(manifest)
if err != nil {
return err
}
p.functions = functions
return update(p.ctx, p.id, data, functions, wasi)
return update(p.ctx, p.id, data, wasi)
}
// Set configuration values
@@ -388,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)
@@ -396,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)
@@ -425,7 +231,8 @@ func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
if length > 0 {
x := C.extism_plugin_output_data(plugin.ctx.pointer, C.int32_t(plugin.id))
return unsafe.Slice((*byte)(x), C.int(length)), nil
y := (*[]byte)(unsafe.Pointer(&x))
return []byte((*y)[0:length]), nil
}
return []byte{}, nil
@@ -444,80 +251,3 @@ func (plugin *Plugin) Free() {
func (ctx Context) Reset() {
C.extism_context_reset(ctx.pointer)
}
// ValGetI64 returns an I64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetI64(v unsafe.Pointer) int64 {
return int64(C.extism_val_i64(&(*Val)(v).v))
}
// ValGetUInt returns a uint from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetUInt(v unsafe.Pointer) uint {
return uint(C.extism_val_i64(&(*Val)(v).v))
}
// ValGetI32 returns an int32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetI32(v unsafe.Pointer) int32 {
return int32(C.extism_val_i32(&(*Val)(v).v))
}
// ValGetF32 returns a float32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetF32(v unsafe.Pointer) float32 {
return float32(C.extism_val_f32(&(*Val)(v).v))
}
// ValGetF32 returns a float64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetF64(v unsafe.Pointer) float64 {
return float64(C.extism_val_i64(&(*Val)(v).v))
}
// ValSetI64 stores an int64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
func ValSetI64(v unsafe.Pointer, i int64) {
C.extism_val_set_i64(&(*Val)(v).v, C.int64_t(i))
}
// ValSetI32 stores an int32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
func ValSetI32(v unsafe.Pointer, i int32) {
C.extism_val_set_i32(&(*Val)(v).v, C.int32_t(i))
}
// ValSetF32 stores a float32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
func ValSetF32(v unsafe.Pointer, i float32) {
C.extism_val_set_f32(&(*Val)(v).v, C.float(i))
}
// ValSetF64 stores a float64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
func ValSetF64(v unsafe.Pointer, f float64) {
C.extism_val_set_f64(&(*Val)(v).v, C.double(f))
}
func (p *CurrentPlugin) ReturnBytes(v unsafe.Pointer, b []byte) {
mem := p.Alloc(uint(len(b)))
ptr := p.Memory(mem)
copy(ptr, b)
ValSetI64(v, int64(mem))
}
func (p *CurrentPlugin) ReturnString(v unsafe.Pointer, s string) {
p.ReturnBytes(v, []byte(s))
}
func (p *CurrentPlugin) InputBytes(v unsafe.Pointer) []byte {
return p.Memory(ValGetUInt(v))
}
func (p *CurrentPlugin) InputString(v unsafe.Pointer) string {
return string(p.InputBytes(v))
}
type CancelHandle struct {
pointer *C.ExtismCancelHandle
}
func (p *Plugin) CancelHandle() CancelHandle {
pointer := C.extism_plugin_cancel_handle(p.ctx.pointer, C.int(p.id))
return CancelHandle{pointer}
}
func (c *CancelHandle) Cancel() bool {
return bool(C.extism_plugin_cancel(c.pointer))
}

View File

@@ -10,15 +10,13 @@ homepage: "https://github.com/extism/extism"
doc: "https://github.com/extism/extism"
bug-reports: "https://github.com/extism/extism/issues"
depends: [
"ocaml" {>= "4.14.1"}
"ocaml"
"dune" {>= "3.2"}
"ctypes" {>= "0.18.0"}
"ctypes-foreign" {>= "0.18.0"}
"bigstringaf" {>= "0.9.0"}
"ppx_yojson_conv" {>= "v0.15.0"}
"extism-manifest" {= version}
"ppx_inline_test" {>= "v0.15.0"}
"cmdliner" {>= "1.1.1"}
"ctypes-foreign"
"bigstringaf"
"ppx_yojson_conv"
"base64"
"ppx_inline_test"
"odoc" {with-doc}
]
build: [
@@ -36,5 +34,3 @@ build: [
]
]
dev-repo: "git+https://github.com/extism/extism.git"
build-env: [EXTISM_TEST_NO_LIB = ""]
post-messages: ["See https://extism.org/docs/install/ for information about installing libextism"]

View File

@@ -1,2 +0,0 @@
build-env: [EXTISM_TEST_NO_LIB = ""]
post-messages: ["See https://extism.org/docs/install/ for information about installing libextism"]

View File

@@ -4,19 +4,13 @@ import (
"encoding/json"
"fmt"
"testing"
"time"
)
func manifest(functions bool) Manifest {
path := "./wasm/code.wasm"
if functions {
path = "./wasm/code-functions.wasm"
}
func manifest() Manifest {
return Manifest{
Wasm: []Wasm{
WasmFile{
Path: path,
Path: "./wasm/code.wasm",
},
},
}
@@ -44,7 +38,7 @@ func TestCallPlugin(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
plugin, err := ctx.PluginFromManifest(manifest(), false)
if err != nil {
t.Error(err)
}
@@ -64,7 +58,7 @@ func TestFreePlugin(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
plugin, err := ctx.PluginFromManifest(manifest(), false)
if err != nil {
t.Error(err)
}
@@ -84,7 +78,7 @@ func TestContextReset(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
plugin, err := ctx.PluginFromManifest(manifest(), false)
if err != nil {
t.Error(err)
}
@@ -104,7 +98,7 @@ func TestCanUpdateAManifest(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
plugin, err := ctx.PluginFromManifest(manifest(), false)
if err != nil {
t.Error(err)
}
@@ -113,7 +107,7 @@ func TestCanUpdateAManifest(t *testing.T) {
t.Error(err)
}
plugin.UpdateManifest(manifest(false), []Function{}, false)
plugin.UpdateManifest(manifest(), false)
// can still call the plugin
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
@@ -125,7 +119,7 @@ func TestFunctionExists(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
plugin, err := ctx.PluginFromManifest(manifest(), false)
if err != nil {
t.Error(err)
}
@@ -142,7 +136,7 @@ func TestErrorsOnUnknownFunction(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
plugin, err := ctx.PluginFromManifest(manifest(), false)
if err != nil {
t.Error(err)
}
@@ -152,33 +146,3 @@ func TestErrorsOnUnknownFunction(t *testing.T) {
t.Fatal("Was expecting call to unknown function to fail")
}
}
func TestCancel(t *testing.T) {
manifest := Manifest{
Wasm: []Wasm{
WasmFile{
Path: "./wasm/loop.wasm",
},
},
}
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest, []Function{}, false)
if err != nil {
t.Error(err)
}
cancelHandle := plugin.CancelHandle()
go func(handle CancelHandle) {
time.Sleep(time.Second * 1)
handle.Cancel()
}(cancelHandle)
_, err = plugin.Call("infinite_loop", []byte(""))
if err == nil {
t.Fail()
}
}

View File

@@ -4,38 +4,17 @@ import (
"encoding/json"
"fmt"
"os"
"runtime/cgo"
"unsafe"
"github.com/extism/extism"
)
/*
#include <extism.h>
EXTISM_GO_FUNCTION(hello_world);
*/
import "C"
//export hello_world
func hello_world(plugin unsafe.Pointer, inputs *C.ExtismVal, nInputs C.ExtismSize, outputs *C.ExtismVal, nOutputs C.ExtismSize, userData uintptr) {
fmt.Println("Hello from Go!")
s := cgo.Handle(userData)
fmt.Println(s.Value().(string))
inputSlice := unsafe.Slice(inputs, nInputs)
outputSlice := unsafe.Slice(outputs, nOutputs)
// Get memory pointed to by first element of input slice
p := extism.GetCurrentPlugin(plugin)
str := p.InputString(unsafe.Pointer(&inputSlice[0]))
fmt.Println(str)
outputSlice[0] = inputSlice[0]
}
func main() {
version := extism.ExtismVersion()
fmt.Println("Extism Version: ", version)
ctx := extism.NewContext()
defer ctx.Free() // this will free the context and all associated plugins
// set some input data to provide to the plugin module
var data []byte
if len(os.Args) > 1 {
@@ -43,10 +22,9 @@ func main() {
} else {
data = []byte("testing from go -> wasm shared memory...")
}
manifest := extism.Manifest{Wasm: []extism.Wasm{extism.WasmFile{Path: "../wasm/code-functions.wasm"}}}
f := extism.NewFunction("hello_world", []extism.ValType{extism.I64}, []extism.ValType{extism.I64}, C.hello_world, "Hello again!")
defer f.Free()
plugin, err := extism.NewPluginFromManifest(manifest, []extism.Function{f}, true)
manifest := extism.Manifest{Wasm: []extism.Wasm{extism.WasmFile{Path: "../wasm/code.wasm"}}}
plugin, err := ctx.PluginFromManifest(manifest, false)
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@@ -1,5 +1,5 @@
# Revision history for extism
## 0.2.0.0 -- 2023-01-16
## 0.1.0.0 -- YYYY-mm-dd
* First version. Released on an unsuspecting world.

View File

@@ -1,24 +1,23 @@
module Main where
import System.Exit (exitFailure, exitSuccess)
import qualified Data.ByteString as B
import Extism
import Extism.CurrentPlugin
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 (ErrorMessage msg)) = do
_ <- putStrLn msg
exitFailure
hello plugin params msg = do
putStrLn "Hello from Haskell!"
putStrLn msg
offs <- allocBytes plugin (toByteString "{\"count\": 999}")
return [toI64 offs]
handlePlugin plugin = do
res <- Extism.call plugin "count_vowels" (Extism.toByteString "this is a test")
try (\bs -> do
_ <- putStrLn (Extism.fromByteString bs)
_ <- Extism.free plugin
exitSuccess) res
main = do
setLogFile "stdout" Error
let m = manifest [wasmFile "../wasm/code-functions.wasm"]
f <- hostFunction "hello_world" [I64] [I64] hello "Hello, again"
plugin <- unwrap <$> createPluginFromManifest m [f] True
res <- unwrap <$> call plugin "count_vowels" (toByteString "this is a test")
putStrLn (fromByteString res)
free plugin
context <- Extism.newContext ()
plugin <- Extism.pluginFromManifest context (manifest [wasmFile "../wasm/code.wasm"]) False
try handlePlugin plugin

View File

@@ -1,33 +0,0 @@
.PHONY: test
prepare:
cabal update
build: prepare
cabal build
test: prepare
cabal test
clean:
cabal clean
publish: clean prepare
cabal v2-haddock --haddock-for-hackage ./manifest/extism-manifest.cabal
cabal v2-haddock --haddock-for-hackage
cabal sdist ./manifest/extism-manifest.cabal
cabal sdist
# TODO: upload
format:
# TODO
lint:
cabal check
hlint src manifest
docs:
# TODO
show-docs: docs
# TODO

View File

@@ -1 +0,0 @@
packages: extism.cabal manifest/extism-manifest.cabal

View File

@@ -1,45 +1,54 @@
cabal-version: 3.0
cabal-version: 2.4
name: extism
version: 0.5.0
license: BSD-3-Clause
maintainer: oss@extism.org
version: 0.0.1.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
extra-doc-files: CHANGELOG.md
maintainer: oss@extism.org
-- A copyright notice.
-- copyright:
category: Plugins, WebAssembly
extra-source-files: CHANGELOG.md
library
exposed-modules: Extism Extism.CurrentPlugin
reexported-modules: Extism.Manifest
hs-source-dirs: src
other-modules: Extism.Bindings
default-language: Haskell2010
extra-libraries: extism
extra-lib-dirs: /usr/local/lib
build-depends:
base >= 4.16.1 && < 5,
bytestring >= 0.11.3 && <= 0.12,
json >= 0.10 && <= 0.11,
extism-manifest >= 0.0.0 && < 0.4.0
exposed-modules: Extism Extism.Manifest
test-suite extism-example
type: exitcode-stdio-1.0
main-is: Example.hs
-- Modules included in this library but not exported.
other-modules:
-- LANGUAGE extensions used by modules in this package.
-- other-extensions:
build-depends:
base ^>=4.16.1.0
, bytestring
, base64-bytestring
, json
hs-source-dirs: src
default-language: Haskell2010
build-depends:
base,
extism,
bytestring
test-suite extism-test
type: exitcode-stdio-1.0
main-is: Test.hs
extra-libraries: extism
extra-lib-dirs: /usr/local/lib
Test-Suite extism-example
type: exitcode-stdio-1.0
main-is: Example.hs
build-depends: base, extism, bytestring
default-language: Haskell2010
Test-Suite extism-test
type: exitcode-stdio-1.0
main-is: Test.hs
hs-source-dirs: test
build-depends: base, extism, bytestring, HUnit
default-language: Haskell2010
build-depends:
base,
extism,
bytestring,
HUnit

View File

@@ -1,5 +0,0 @@
# Revision history for extism-manifest
## 0.1.0.0 -- YYYY-mm-dd
* First version. Released on an unsuspecting world.

View File

@@ -1,60 +0,0 @@
module Extism.JSON (
module Extism.JSON,
module Text.JSON
) where
import Text.JSON
import qualified Data.ByteString as B
import Data.ByteString.Internal (c2w, w2c)
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Char8 as BS (unpack)
data Nullable a = Null | NotNull a
makeArray x = JSArray [showJSON a | a <- x]
isNull JSNull = True
isNull _ = False
filterNulls obj = [(a, b) | (a, b) <- obj, not (isNull b)]
object x = makeObj $ filterNulls x
objectWithNulls = makeObj
nonNull = NotNull
null' = Null
(.=) a b = (a, showJSON b)
toNullable (Just x) = NotNull x
toNullable Nothing = Null
fromNullable (NotNull x) = Just x
fromNullable Null = Nothing
fromNotNull (NotNull x) = x
fromNotNull Null = error "Value is Null"
mapNullable f Null = Null
mapNullable f (NotNull x) = NotNull (f x)
(.?) (JSObject a) k =
case valFromObj k a of
Ok x -> NotNull x
Error _ -> Null
(.?) _ _ = Null
(.??) a k = toNullable $ lookup k a
find :: JSON a => String -> JSValue -> Nullable a
find k obj = obj .? k
update :: JSON a => String -> a -> JSValue -> JSValue
update k v (JSObject obj) = object $ fromJSObject obj ++ [k .= v]
instance JSON a => JSON (Nullable a) where
showJSON (NotNull x) = showJSON x
showJSON Null = JSNull
readJSON JSNull = Ok Null
readJSON x = readJSON x
newtype Base64 = Base64 B.ByteString
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)

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