Compare commits

..

2 Commits

Author SHA1 Message Date
Steve Manuel
a3341eb238 chore: bump version for extism-convert 2023-11-03 15:47:55 -06:00
Steve Manuel
2d01efb833 feat!: add alt protobuf crate support to convert crate 2023-11-03 15:42:56 -06:00
290 changed files with 32109 additions and 2188 deletions

28
.github/workflows/browser-ci.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
on:
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

31
.github/workflows/ci-cpp.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
on:
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

28
.github/workflows/ci-d.yml vendored Normal file
View File

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

26
.github/workflows/ci-dotnet.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
on:
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

33
.github/workflows/ci-elixir.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
on:
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

27
.github/workflows/ci-go.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
on:
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!"

39
.github/workflows/ci-haskell.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
on:
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

29
.github/workflows/ci-java.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
on:
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

30
.github/workflows/ci-node.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
on:
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

40
.github/workflows/ci-ocaml.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
on:
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

32
.github/workflows/ci-php.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
on:
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

32
.github/workflows/ci-python.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
on:
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

30
.github/workflows/ci-ruby.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
on:
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

119
.github/workflows/ci-rust.yml vendored Normal file
View File

@@ -0,0 +1,119 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-rust.yml
- convert/**
- manifest/**
- runtime/**
- rust/**
- libextism/**
workflow_dispatch:
name: Rust CI
env:
RUNTIME_CRATE: extism
LIBEXTISM_CRATE: libextism
jobs:
lib:
name: Extism runtime lib
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Cache Rust environment
uses: Swatinem/rust-cache@v1
- name: Cache libextism
id: cache-libextism
uses: actions/cache@v3
with:
path: target/release/libextism.*
key: ${{ runner.os }}-libextism-${{ hashFiles('runtime/**') }}-${{ hashFiles('manifest/**') }}-${{ hashFiles('convert/**') }}
- name: Cache target
id: cache-target
uses: actions/cache@v3
with:
path: target/**
key: ${{ runner.os }}-target-${{ 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-${{ github.sha }}
- name: Format
run: cargo fmt --check -p ${{ env.RUNTIME_CRATE }}
- name: Lint
run: cargo clippy --release --all-features --no-deps -p ${{ env.RUNTIME_CRATE }}
- name: Test
run: cargo test --release -p ${{ env.RUNTIME_CRATE }}
- name: Test all features
run: cargo test --all-features --release -p ${{ env.RUNTIME_CRATE }}
- name: Test no features
run: cargo test --no-default-features --release -p ${{ env.RUNTIME_CRATE }}
bench:
name: Benchmarking
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Cache Rust environment
uses: Swatinem/rust-cache@v1
- name: Cache target
id: cache-target
uses: actions/cache@v3
with:
path: target/**
key: ${{ runner.os }}-target-${{ github.sha }}
- run: cargo install cargo-criterion
- run: cargo criterion

29
.github/workflows/ci-zig.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
on:
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

@@ -1,119 +1,25 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-rust.yml
- convert/**
- manifest/**
- runtime/**
- rust/**
- libextism/**
workflow_dispatch:
on: [pull_request, workflow_dispatch]
name: Rust CI
env:
RUNTIME_CRATE: extism
LIBEXTISM_CRATE: libextism
name: CI
jobs:
lib:
name: Extism runtime lib
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust:
- stable
sdk_api_coverage:
name: SDK API Coverage Report
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
- name: Setup Python env
uses: actions/setup-python@v4
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/**') }}-${{ hashFiles('convert/**') }}
- name: Cache target
id: cache-target
uses: actions/cache@v3
with:
path: target/**
key: ${{ runner.os }}-target-${{ 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-${{ github.sha }}
- name: Format
run: cargo fmt --check
- name: Lint
run: cargo clippy --all --release --all-features --no-deps -- -D "clippy::all"
- name: Test
run: cargo test --release
- name: Test all features
run: cargo test --all-features --release
- name: Test no features
run: cargo test --no-default-features --release
bench:
name: Benchmarking
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
rust:
- stable
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Cache Rust environment
uses: Swatinem/rust-cache@v1
- name: Cache target
id: cache-target
uses: actions/cache@v3
with:
path: target/**
key: ${{ runner.os }}-target-${{ github.sha }}
- run: cargo install cargo-criterion
- run: cargo criterion
python-version: "3.9"
check-latest: true
- name: Install dependencies
run: |
sudo apt-get install ripgrep
pip3 install pycparser
- name: Run coverage script
id: coverage
run: |
python scripts/sdk_coverage.py

26
.github/workflows/release-convert.yaml vendored Normal file
View File

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

View File

@@ -10,9 +10,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
filter: tree:0
fetch-tags: true
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v3.0.3
@@ -23,48 +21,26 @@ jobs:
workflow: release.yml
name: release-artifacts
- name: Extract Archive
run: |
extract_archive() {
# Check if both pattern and destination are provided
if [ -z "$1" ] || [ -z "$2" ]; then
echo "Usage: $0 <filename_pattern> <destination_directory>"
return 1
fi
# Set the filename pattern and destination directory
filename_pattern="$1"
destination_directory="$2"
# Find the archive file with the specified pattern
archive_file=$(ls $filename_pattern 2>/dev/null)
# Check if an archive file is found
if [ -n "$archive_file" ]; then
echo "Found archive file: $archive_file"
# Create the destination directory if it doesn't exist
mkdir -p "$destination_directory"
# Extract the archive to the specified destination
tar -xzvf "$archive_file" -C "$destination_directory"
echo "Extraction complete. Contents placed in: $destination_directory"
else
echo "No matching archive file found with the pattern: $filename_pattern"
fi
}
extract_archive "libextism-x86_64-pc-windows-msvc-*.tar.gz" "nuget/runtimes/win-x64/native/"
extract_archive "libextism-aarch64-apple-darwin-*.tar.gz" "nuget/runtimes/osx-arm64/native/"
extract_archive "libextism-x86_64-apple-darwin-*.tar.gz" "nuget/runtimes/osx-x64/native/"
extract_archive "libextism-x86_64-unknown-linux-gnu-*.tar.gz" "nuget/runtimes/linux-x64/native/"
extract_archive "libextism-aarch64-unknown-linux-gnu-*.tar.gz" "nuget/runtimes/linux-arm64/native/"
extract_archive "libextism-aarch64-unknown-linux-musl-*.tar.gz" "nuget/runtimes/linux-musl-arm64/native/"
- name: Pack NuGet packages
run: |
find ./nuget -type f -name "*.csproj" -exec dotnet pack {} -o release-artifacts \;
mkdir -p dotnet/nuget/runtimes/win-x64/native/
tar -xvzf libextism-x86_64-pc-windows-msvc-main.tar.gz -C dotnet/nuget/runtimes/win-x64/native/
mkdir -p dotnet/nuget/runtimes/osx-arm64/native/
tar -xvzf libextism-aarch64-apple-darwin-main.tar.gz -C dotnet/nuget/runtimes/osx-arm64/native/
mkdir -p dotnet/nuget/runtimes/osx-x64/native/
tar -xvzf libextism-x86_64-apple-darwin-main.tar.gz -C dotnet/nuget/runtimes/osx-x64/native/
mkdir -p dotnet/nuget/runtimes/linux-x64/native/
tar -xvzf libextism-x86_64-unknown-linux-gnu-main.tar.gz -C dotnet/nuget/runtimes/linux-x64/native/
mkdir -p dotnet/nuget/runtimes/linux-arm64/native/
tar -xvzf libextism-aarch64-unknown-linux-gnu-main.tar.gz -C dotnet/nuget/runtimes/linux-arm64/native/
mkdir -p dotnet/nuget/runtimes/linux-musl-arm64/native/
tar -xvzf libextism-aarch64-unknown-linux-musl-main.tar.gz -C dotnet/nuget/runtimes/linux-musl-arm64/native/
- name: Publish NuGet packages
run: |
dotnet nuget push --source https://api.nuget.org/v3/index.json ./release-artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }}
dotnet pack .\dotnet\nuget\Nuget.sln -o release-artifacts
dotnet nuget push --source https://api.nuget.org/v3/index.json ./release-artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }}

32
.github/workflows/release-dotnet.yaml vendored Normal file
View File

@@ -0,0 +1,32 @@
on:
workflow_dispatch:
name: Release .NET SDK
jobs:
release-sdks:
name: release-dotnet
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install extism shared library
shell: bash
run: |
mkdir -p /home/runner/.local/bin/
export PATH="/home/runner/.local/bin/:$PATH"
curl https://raw.githubusercontent.com/extism/cli/main/install.sh | sh
extism --sudo --prefix /usr/local install
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v3.0.3
with:
dotnet-version: 7.x
- name: Test .NET Sdk
run: |
cd dotnet
LD_LIBRARY_PATH=/usr/local/lib dotnet test ./Extism.sln
- name: Publish .NET Sdk
run: |
cd dotnet/src/Extism.Sdk/
dotnet pack -c Release
dotnet nuget push --source https://api.nuget.org/v3/index.json ./bin/Release/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }}

34
.github/workflows/release-elixir.yaml vendored Normal file
View File

@@ -0,0 +1,34 @@
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

16
.github/workflows/release-haskell.yaml vendored Normal file
View File

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

22
.github/workflows/release-java.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
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

26
.github/workflows/release-manifest.yaml vendored Normal file
View File

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

30
.github/workflows/release-node.yaml vendored Normal file
View File

@@ -0,0 +1,30 @@
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

25
.github/workflows/release-ruby.yaml vendored Normal file
View File

@@ -0,0 +1,25 @@
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,6 +1,4 @@
on:
release:
types: [published, edited]
workflow_dispatch:
name: Release Runtime/Rust SDK
@@ -12,21 +10,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: '${{ github.ref }}'
- name: Set version
shell: bash
run: |
version="${{ github.ref }}"
if [[ "$version" = "refs/heads/main" ]]; then
version="0.0.0-dev"
else
version="${version/refs\/tags\/v/}"
fi
sed -i -e "s/0.0.0+replaced-by-ci/${version}/g" Cargo.toml
pyproject="$(cat extism-maturin/pyproject.toml)"
<<<"$pyproject" >extism-maturin/pyproject.toml sed -e 's/^version = "0.0.0.replaced-by-ci"/version = "'"$version"'"/g'
- name: Setup Rust env
uses: actions-rs/toolchain@v1
@@ -36,38 +19,8 @@ jobs:
override: true
target: ${{ matrix.target }}
- name: Release Rust Convert Crate
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |
version=$(cargo metadata --format-version=1 | jq -r '.packages[] | select(.name == "extism") | .version')
if ! &>/dev/null curl -sLIf https://crates.io/api/v1/crates/extism-convert/${version}/download; then
cargo publish --manifest-path convert/Cargo.toml --allow-dirty
else
echo "already published ${version}"
fi
- name: Release Rust Manifest Crate
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |
version=$(cargo metadata --format-version=1 | jq -r '.packages[] | select(.name == "extism") | .version')
if ! &>/dev/null curl -sLIf https://crates.io/api/v1/crates/extism-manifest/${version}/download; then
cargo publish --manifest-path manifest/Cargo.toml --allow-dirty
else
echo "already published ${version}"
fi
- name: Release Runtime
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |
version=$(cargo metadata --format-version=1 | jq -r '.packages[] | select(.name == "extism") | .version')
if ! &>/dev/null curl -sLIf https://crates.io/api/v1/crates/extism/${version}/download; then
cargo publish --manifest-path runtime/Cargo.toml --allow-dirty
else
echo "already published ${version}"
fi
cargo publish --manifest-path runtime/Cargo.toml

View File

@@ -1,7 +1,7 @@
on:
workflow_dispatch:
push:
branches: [ main, "v*" ]
branches: [ main ]
tags:
- 'v*'
@@ -28,56 +28,42 @@ jobs:
target: 'x86_64-apple-darwin'
artifact: 'libextism.dylib'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'macos'
target: 'aarch64-apple-darwin'
artifact: 'libextism.dylib'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'aarch64-unknown-linux-gnu'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'aarch64-unknown-linux-musl'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'x86_64-unknown-linux-gnu'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'x86_64-unknown-linux-musl'
artifact: ''
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: ''
static-pc-in: 'extism-static.pc.in'
- os: 'windows'
target: 'x86_64-pc-windows-gnu'
artifact: 'extism.dll'
static-artifact: 'libextism.a'
static-dll-artifact: 'libextism.dll.a'
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'windows'
target: 'x86_64-pc-windows-msvc'
artifact: 'extism.dll'
static-artifact: 'extism.lib'
static-dll-artifact: 'extism.dll.lib'
pc-in: ''
static-pc-in: ''
@@ -85,19 +71,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Set version
shell: bash
run: |
version="${{ github.ref }}"
if [[ "$version" = "refs/heads/main" ]]; then
version="0.0.0-dev"
else
version="${version/refs\/tags\/v/}"
fi
sed -i -e "s/0.0.0+replaced-by-ci/${version}/g" Cargo.toml
pyproject="$(cat extism-maturin/pyproject.toml)"
<<<"$pyproject" >extism-maturin/pyproject.toml sed -e 's/^version = "0.0.0.replaced-by-ci"/version = "'"$version"'"/g'
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
@@ -119,6 +92,19 @@ jobs:
command: build
args: --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
- name: set extism-maturin version
shell: bash
run: |
pyproject="$(cat extism-maturin/pyproject.toml)"
version="${{ github.ref }}"
if [[ "$version" = "refs/heads/main" ]]; then
version="0.0.0-dev"
else
version="${version/refs\/tags\/v/}"
fi
<<<"$pyproject" >extism-maturin/pyproject.toml sed -e 's/^version = "0.0.0.replaced-by-ci"/version = "'"$version"'"/g'
- uses: actions/setup-python@v4
with:
python-version: '3.10'
@@ -166,8 +152,7 @@ jobs:
cp LICENSE ${SRC_DIR}
tar -C ${SRC_DIR} -czvf ${ARCHIVE} extism.h \
${{ matrix.artifact }} ${{ matrix.static-artifact }} \
${{ matrix.pc-in }} ${{ matrix.static-pc-in }} \
${{ matrix.static-dll-artifact }}
${{ matrix.pc-in }} ${{ matrix.static-pc-in }}
ls -ll ${ARCHIVE}
if &>/dev/null which shasum; then

4
.gitignore vendored
View File

@@ -46,6 +46,4 @@ java/.DS_Store
extism-maturin/src/extism.h
runtime/*.log
libextism/example
libextism/extism*.pc
*.cwasm
test-cache
libextism/extism*.pc

1
.ocamlformat Normal file
View File

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

View File

@@ -1,17 +1,9 @@
[workspace]
resolver = "2"
members = ["extism-maturin", "manifest", "runtime", "libextism", "convert"]
members = [
"extism-maturin",
"manifest",
"runtime",
"libextism",
"convert"
]
exclude = ["kernel"]
[workspace.package]
edition = "2021"
authors = ["The Extism Authors", "oss@extism.org"]
license = "BSD-3-Clause"
homepage = "https://extism.org"
repository = "https://github.com/extism/extism"
version = "0.0.0+replaced-by-ci"
[workspace.dependencies]
extism = { path = "./runtime", version = "0.0.0+replaced-by-ci" }
extism-convert = { path = "./convert", version = "0.0.0+replaced-by-ci" }
extism-manifest = { path = "./manifest", version = "0.0.0+replaced-by-ci" }

View File

@@ -1,117 +0,0 @@
# HACKING
## cutting releases
### goals
Cutting a release should be a boring, rote process with as little excitement as
possible. Following the processes in this document, we should be able to cut a
release at any time without worrying about producing bad artifacts. Our process
should let us resolve build issues without affecting library users.
### branching
1. The `main` branch represents the next major version of the library.
2. Previous major versions should be tracked using `v0.x`, `v1.x`, `v2.x`, used
for backporting changes as necessary.
3. Libraries should generate a `latest` release using, e.g.,
`marvinpinto/action-automatic-releases` on changes to the `main` branch.
### tag and release process
1. Pick a target semver value. Prepend the semver value with `v`: `v1.2.3`.
Increment the minor version for additive changes and patch for bugfixes.
- For trickier changes, consider using release candidates: `rc0`, `rc1`, etc.
2. Create an empty git commit for the tag to point at: `git commit -m 'v1.2.3-rc1' --allow-empty`.
3. Create a new tag against that commit: `v1.2.3-rc1`.
4. Push the changes to the library: `git push origin main v1.2.3-rc1`.
- You can separate these steps: `git push origin main` followed by `git push origin v1.2.3-rc1`,
if you want to make absolutely sure the commit you're pushing builds correctly before tagging it.
5. Wait for the tag `build` workflow to complete.
- The `build` workflow should create a _draft_ release (using `softprops/action-gh-release` with `draft`
set to `true`) and upload built artifacts to the release.
6. Once the workflow is complete, do whatever testing is necessary using the artifacts.
- TODO: We can add automation to this step so that we test on downstream deps automatically: e.g., if we
build a new kernel, we _should_ be able to trigger tests in the `python-sdk` _using_ that new kernel.
7. Once we're confident the release is good, go to the releases page for the library and edit the draft release.
- If the release is a release candidate (`rc0..N`), make sure to mark the release as a "prerelease".
- Publish the draft release.
- This kicks off the publication workflow: taking the artifacts built during the `build` workflow and publishing
them to any necessary registry or repository.
- In extism, this publishes `extism-maturin` to PyPI as `extism-sys` and the dotnet packages to nuget.
- In `python-sdk`, this publishes `extism` to PyPI.
- In `js-sdk`, this publishes `@extism/extism` (and `extism`) to NPM.
> **Note**
> If you're at all worried about a release, use a private fork of the target library repo to test the release first (e.g., `extism/dev-extism`.)
#### CLI flow
For official releases:
```
$ git commit -m 'v9.9.9' --allow-empty
$ git tag v9.9.9
$ git push origin main v9.9.9
$ gh run watch
$ gh release edit v9.9.9 --tag v9.9.9 --title 'v9.9.9' --draft=false
$ gh run watch
```
For prereleases:
```
$ git commit -m 'v9.9.9' --allow-empty
$ git tag v9.9.9
$ git push origin main v9.9.9
$ gh run watch
$ gh release edit v9.9.9 --tag v9.9.9 --title 'v9.9.9' --draft=false --prerelease
$ gh run watch
```
### implementation
Libraries should:
- Provide a `ci` workflow, triggered on PR and `workflow_dispatch`.
- This workflow should exercise the tests, linting, and documentation generation of the library.
- Provide a `build` workflow, triggered on `v*` tags and merges to `main`
- This workflow should produce artifacts and attach them to a draft release (if operating on a tag) or a `latest` release (if operating on `main`.)
- Artifacts include: source tarballs, checksums, shared objects, and documentation.
- Provide a `release` workflow, triggered on github releases:
- This workflow should expect artifacts from the draft release to be available.
- Artifacts from the release should be published to their final destination as part of this workflow: tarballs to NPM, documentation to Cloudflare R2/Amazon S3/$yourFavoriteBucket.
### A rough list of libraries and downstreams
```mermaid
flowchart TD;
A["runtime"] --> B["libextism"];
B --> C["extism-maturin"];
B --> X["nuget-extism"];
C --> D["python-sdk"];
B --> E["ruby-sdk"];
A --> F["go-sdk"];
G["plugins"] --> B;
G --> D;
G --> E;
G --> F;
G --> H["js-sdk"];
F --> I["cli"];
G --> J["dotnet-sdk"];
X --> J;
G --> K["cpp-sdk"];
G --> L["zig-sdk"];
B --> L;
G --> M["haskell-sdk"];
B --> M;
G --> N["php-sdk"];
B --> N;
G --> O["elixir-sdk"];
B --> O;
G --> P["d-sdk"];
B --> P;
G --> Q["ocaml-sdk"];
B --> Q;
```

View File

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

View File

@@ -1,10 +1,12 @@
# [Extism](https://extism.org)
### _Welcome!_
**Please note:** This project still under active development and APIs are changing as we hit 1.0. Currently, the main branch has many breaking changes. Our current release is 0.5.x. Until we release 1.0, we are cutting these releases from the [stable](https://github.com/extism/extism/tree/stable) branch.
If you're interested in working on or building with Extism, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know - we are happy to help get you started.
[![Discord](https://img.shields.io/discord/1011124058408112148?color=%23404eed&label=Community%20Chat&logo=Discord&logoColor=%23404eed)](https://discord.gg/cx3usBCWnc)
![GitHub Org's stars](https://img.shields.io/github/stars/extism)
![GitHub all releases](https://img.shields.io/github/downloads/extism/extism/total)
![GitHub License](https://img.shields.io/github/license/extism/extism)
![GitHub release (with filter)](https://img.shields.io/github/v/release/extism/extism)
# [Extism](https://extism.org)
The universal plug-in system. Run WebAssembly extensions inside your app. Use idiomatic Host SDKs for [Go](https://github.com/extism/go-sdk#readme),
[Ruby](https://github.com/extism/ruby-sdk#readme),
@@ -29,6 +31,7 @@ Plug-in development kits (PDK) for plug-in authors supported in [Rust](https://g
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/210286900-39b144fd-1b26-4dd0-b7a9-2b5755bc174d.png" alt="Extism embedded SDK language support"/>
</p>
Add a flexible, secure, and _bLaZiNg FaSt_ plug-in system to your project. Server, desktop, mobile, web, database -- you name it. Enable users to write and execute safe extensions to your software in **3 easy steps:**
### 1. Import
@@ -43,13 +46,7 @@ Identify the place(s) in your code where some arbitrary logic should run (the pl
Load WebAssembly modules at any time in your app's lifetime and Extism will execute them in a secure sandbox, fully isolated from your program's memory.
# API Status
**Please note:** This project still under active development and APIs are still changing. We are aiming for a stable 1.0 release in January, 2024.
The main branch may have breaking changes until that point, but if you starting today, a 1.0.0-rcx release is the best place to start.
If you experience any problems or have any questions, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know.
Our community is very responsive and happy to help get you started.
---
## Usage

130
browser/.gitignore vendored Normal file
View File

@@ -0,0 +1,130 @@
# 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.*

5
browser/.prettierrc Normal file
View File

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

28
browser/Makefile Normal file
View File

@@ -0,0 +1,28 @@
.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

4
browser/README.md Normal file
View File

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

23
browser/build.js Normal file
View File

@@ -0,0 +1,23 @@
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",
});

248
browser/index.html Normal file
View File

@@ -0,0 +1,248 @@
<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>

5
browser/jest.config.js Normal file
View File

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

14458
browser/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

39
browser/package.json Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "@extism/runtime-browser",
"version": "0.3.1",
"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"
}
}

104
browser/src/allocator.ts Normal file
View File

@@ -0,0 +1,104 @@
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);
}
}

45
browser/src/context.ts Normal file
View File

@@ -0,0 +1,45 @@
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 | WebAssembly.Module;
/**
* 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 | WebAssembly.Module | null = null;
if (manifest instanceof ArrayBuffer || manifest instanceof WebAssembly.Module) {
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);
}
}

24
browser/src/index.test.ts Normal file
View File

@@ -0,0 +1,24 @@
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);
});
});

4
browser/src/index.ts Normal file
View File

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

40
browser/src/manifest.ts Normal file
View File

@@ -0,0 +1,40 @@
/**
* 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>;
};

217
browser/src/plugin.ts Normal file
View File

@@ -0,0 +1,217 @@
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 | WebAssembly.Module;
allocator: Allocator;
config?: PluginConfig;
vars: Record<string, Uint8Array>;
input: Uint8Array;
output: Uint8Array;
module?: WebAssembly.WebAssemblyInstantiatedSource;
functions: Record<string, ExtismFunction>;
constructor(moduleData: ArrayBuffer | WebAssembly.Module, functions: Record<string, ExtismFunction> = {}, config?: PluginConfig) {
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,
};
if (this.moduleData instanceof WebAssembly.Module) {
const instance = new WebAssembly.Instance(this.moduleData, env);
this.module = {
instance,
module: this.moduleData
}
//@ts-ignore
wasi.inst = instance;
}
else {
this.module = await WebAssembly.instantiate(this.moduleData, env);
//@ts-ignore
wasi.inst = this.module.instance;
}
if (!this.module) throw Error("Unable to instantiate module");
// normally we would call wasi.start here but it doesn't respect when there is
// no _start function
if (this.module.instance.exports._start) {
//@ts-ignore
this.module.instance.exports._start();
}
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;
}
}

13
browser/tsconfig.json Normal file
View File

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

6
browser/tslint.json Normal file
View File

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

2
c/Makefile Normal file
View File

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

49
composer.json Normal file
View File

@@ -0,0 +1,49 @@
{
"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"
},
"suggest": {},
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
"Extism\\": "php/src/"
},
"files": [
"php/src/Context.php",
"php/src/Plugin.php"
]
},
"autoload-dev": {
"psr-4": {}
},
"config": {
"sort-packages": true
},
"extra": {},
"scripts": {},
"scripts-descriptions": {}
}

163
composer.lock generated Normal file
View File

@@ -0,0 +1,163 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0e0352cd3a96e03fd9c964888deedb29",
"packages": [
{
"name": "ircmaxell/ffime",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/ircmaxell/FFIMe.git",
"reference": "431a3c13d9906b974d50b13bf8295097ea000c5e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ircmaxell/FFIMe/zipball/431a3c13d9906b974d50b13bf8295097ea000c5e",
"reference": "431a3c13d9906b974d50b13bf8295097ea000c5e",
"shasum": ""
},
"require": {
"ircmaxell/php-c-parser": "dev-master",
"ircmaxell/php-object-symbolresolver": "dev-master",
"php": ">=8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.0"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"FFIMe\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Anthony Ferrara",
"email": "ircmaxell@gmail.com"
}
],
"description": "Make life easy when working with 7.4's FFI",
"support": {
"issues": "https://github.com/ircmaxell/FFIMe/issues",
"source": "https://github.com/ircmaxell/FFIMe/tree/master"
},
"time": "2023-04-03T00:43:12+00:00"
},
{
"name": "ircmaxell/php-c-parser",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/ircmaxell/php-c-parser.git",
"reference": "29e0223704e4ee00c66f43506f5f52db151b3517"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ircmaxell/php-c-parser/zipball/29e0223704e4ee00c66f43506f5f52db151b3517",
"reference": "29e0223704e4ee00c66f43506f5f52db151b3517",
"shasum": ""
},
"require": {
"php": ">=7.4"
},
"require-dev": {
"ircmaxell/php-yacc": "dev-master",
"phpunit/phpunit": "^8.0"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"PHPCParser\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Anthony Ferrara",
"email": "ircmaxell@gmail.com"
},
{
"name": "Bob Weinand",
"email": "bobwei9@hotmail.com"
}
],
"description": "Parse C when using PHP",
"support": {
"issues": "https://github.com/ircmaxell/php-c-parser/issues",
"source": "https://github.com/ircmaxell/php-c-parser/tree/master"
},
"time": "2023-03-23T10:58:24+00:00"
},
{
"name": "ircmaxell/php-object-symbolresolver",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/ircmaxell/php-object-symbolresolver.git",
"reference": "dfe1b1aa6c15b198bdef50fff8485e98e89f2a09"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ircmaxell/php-object-symbolresolver/zipball/dfe1b1aa6c15b198bdef50fff8485e98e89f2a09",
"reference": "dfe1b1aa6c15b198bdef50fff8485e98e89f2a09",
"shasum": ""
},
"require": {
"php": ">=7.4"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"PHPObjectSymbolResolver\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Anthony Ferrara",
"email": "ircmaxell@gmail.com"
},
{
"name": "Bob Weinand",
"email": "bobwei9@hotmail.com"
}
],
"description": "An object file (ELF, Mach-O) parser",
"support": {
"issues": "https://github.com/ircmaxell/php-object-symbolresolver/issues",
"source": "https://github.com/ircmaxell/php-object-symbolresolver/tree/master"
},
"time": "2022-09-15T18:21:50+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {
"ircmaxell/ffime": 20
},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": "^7.4 || ^8"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
}

View File

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

View File

@@ -15,10 +15,9 @@ use base64::Engine;
/// and `FromBytesOwned` using `serde_json::from_vec`
#[macro_export]
macro_rules! encoding {
($pub:vis $name:ident, $to_vec:expr, $from_slice:expr) => {
($name:ident, $to_vec:expr, $from_slice:expr) => {
#[doc = concat!(stringify!($name), " encoding")]
#[derive(Debug)]
$pub struct $name<T>(pub T);
pub struct $name<T>(pub T);
impl<T> $name<T> {
pub fn into_inner(self) -> T {
@@ -26,12 +25,6 @@ macro_rules! encoding {
}
}
impl<T> From<T> for $name<T> {
fn from(data: T) -> Self {
Self(data)
}
}
impl<T: serde::de::DeserializeOwned> $crate::FromBytesOwned for $name<T> {
fn from_bytes_owned(data: &[u8]) -> std::result::Result<Self, $crate::Error> {
let x = $from_slice(data)?;
@@ -50,10 +43,10 @@ macro_rules! encoding {
};
}
encoding!(pub Json, serde_json::to_vec, serde_json::from_slice);
encoding!(Json, serde_json::to_vec, serde_json::from_slice);
#[cfg(feature = "msgpack")]
encoding!(pub Msgpack, rmp_serde::to_vec, rmp_serde::from_slice);
encoding!(Msgpack, rmp_serde::to_vec, rmp_serde::from_slice);
impl<'a> ToBytes<'a> for serde_json::Value {
type Bytes = Vec<u8>;
@@ -76,15 +69,8 @@ impl FromBytesOwned for serde_json::Value {
///
/// A value wrapped in `Base64` will automatically be encoded/decoded using base64, the inner value should not
/// already be base64 encoded.
#[derive(Debug)]
pub struct Base64<T: AsRef<[u8]>>(pub T);
impl<T: AsRef<[u8]>> From<T> for Base64<T> {
fn from(data: T) -> Self {
Self(data)
}
}
impl<'a, T: AsRef<[u8]>> ToBytes<'a> for Base64<T> {
type Bytes = String;
@@ -112,18 +98,10 @@ impl FromBytesOwned for Base64<String> {
/// Protobuf encoding
///
/// Allows for `prost` Protobuf messages to be used as arguments to Extism plugin calls
#[cfg(feature = "protobuf")]
#[derive(Debug)]
#[cfg(feature = "prost")]
pub struct Protobuf<T: prost::Message>(pub T);
#[cfg(feature = "protobuf")]
impl<T: prost::Message> From<T> for Protobuf<T> {
fn from(data: T) -> Self {
Self(data)
}
}
#[cfg(feature = "protobuf")]
#[cfg(feature = "prost")]
impl<'a, T: prost::Message> ToBytes<'a> for Protobuf<T> {
type Bytes = Vec<u8>;
@@ -132,59 +110,31 @@ impl<'a, T: prost::Message> ToBytes<'a> for Protobuf<T> {
}
}
#[cfg(feature = "protobuf")]
#[cfg(feature = "prost")]
impl<T: Default + prost::Message> FromBytesOwned for Protobuf<T> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Protobuf(T::decode(data)?))
}
}
/// Raw does no conversion, it just copies the memory directly.
/// Note: This will only work for types that implement [bytemuck::Pod](https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html)
#[cfg(all(feature = "raw", target_endian = "little"))]
pub struct Raw<'a, T: bytemuck::Pod>(pub &'a T);
/// Protobuf encoding
///
/// Allows for `protobuf` Protobuf messages to be used as arguments to Extism plugin calls
#[cfg(feature = "protobuf")]
pub struct Protobuf<T: protobuf::Message>(pub T);
#[cfg(all(feature = "raw", target_endian = "little"))]
impl<'a, T: bytemuck::Pod> ToBytes<'a> for Raw<'a, T> {
type Bytes = &'a [u8];
#[cfg(feature = "protobuf")]
impl<'a, T: protobuf::Message> ToBytes<'a> for Protobuf<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(bytemuck::bytes_of(self.0))
Ok(self.0.write_to_bytes()?)
}
}
#[cfg(all(feature = "raw", target_endian = "little"))]
impl<'a, T: bytemuck::Pod> FromBytes<'a> for Raw<'a, T> {
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
let x = bytemuck::try_from_bytes(data).map_err(|x| Error::msg(x.to_string()))?;
Ok(Raw(x))
}
}
#[cfg(all(test, feature = "raw", target_endian = "little"))]
mod tests {
use crate::*;
#[test]
fn test_raw() {
#[derive(Debug, Clone, Copy, PartialEq)]
struct TestRaw {
a: i32,
b: f64,
c: bool,
}
unsafe impl bytemuck::Pod for TestRaw {}
unsafe impl bytemuck::Zeroable for TestRaw {}
let x = TestRaw {
a: 123,
b: 45678.91011,
c: true,
};
let raw = Raw(&x).to_bytes().unwrap();
let y = Raw::from_bytes(&raw).unwrap();
assert_eq!(&x, y.0);
let y: Result<Raw<[u8; std::mem::size_of::<TestRaw>()]>, Error> = Raw::from_bytes(&raw);
assert!(y.is_ok());
#[cfg(feature = "protobuf")]
impl<'a, T: Default + protobuf::Message> FromBytesOwned for Protobuf<T> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Protobuf(T::parse_from_bytes(data)?))
}
}

View File

@@ -21,9 +21,6 @@ pub use encoding::Msgpack;
#[cfg(feature = "protobuf")]
pub use encoding::Protobuf;
#[cfg(all(feature = "raw", target_endian = "little"))]
pub use encoding::Raw;
pub use from_bytes::{FromBytes, FromBytesOwned};
pub use memory_handle::MemoryHandle;
pub use to_bytes::ToBytes;

16
cpp/Makefile Normal file
View File

@@ -0,0 +1,16 @@
FLAGS=`pkg-config --cflags --libs jsoncpp gtest` -lextism -lpthread
build-example:
$(CXX) -std=c++14 -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)
.PHONY: test
test: build-test
cd test && ./test

44
cpp/example.cpp Normal file
View File

@@ -0,0 +1,44 @@
#define EXTISM_NO_JSON
#include "extism.hpp"
#include <cstring>
#include <fstream>
#include <iostream>
using namespace extism;
std::vector<uint8_t> read(const char *filename) {
std::ifstream file(filename, std::ios::binary);
return std::vector<uint8_t>((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
}
int main(int argc, char *argv[]) {
auto wasm = read("../wasm/code-functions.wasm");
std::string tmp = "Testing";
// 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);
const char *input = argc > 1 ? argv[1] : "this is a test";
ExtismSize length = strlen(input);
extism::Buffer output = plugin.call("count_vowels", (uint8_t *)input, length);
std::cout << (char *)output.data << std::endl;
return 0;
}

458
cpp/extism.hpp Normal file
View File

@@ -0,0 +1,458 @@
#pragma once
#include <cstring>
#include <functional>
#include <map>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
#ifndef EXTISM_NO_JSON
#if __has_include(<jsoncpp/json/json.h>)
#include <jsoncpp/json/json.h>
#else
#include <json/json.h>
#endif
#endif // EXTISM_NO_JSON
extern "C" {
#include <extism.h>
}
namespace extism {
typedef std::map<std::string, std::string> Config;
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;
}
#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->_hash.empty()) {
doc["hash"] = this->_hash.value;
}
return doc;
}
#endif
};
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;
// 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);
return m;
}
#ifndef EXTISM_NO_JSON
std::string json() const {
Json::Value doc;
Json::Value wasm;
for (auto w : this->wasm) {
wasm.append(w.json());
}
doc["wasm"] = wasm;
if (!this->config.empty()) {
Json::Value conf;
for (auto k : this->config) {
conf[k.first] = k.second;
}
doc["config"] = conf;
}
if (!this->allowed_hosts.empty()) {
Json::Value h;
for (auto s : this->allowed_hosts.value) {
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);
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);
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);
}
// 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 {
public:
Error(std::string msg) : std::runtime_error(msg) {}
};
class Buffer {
public:
Buffer(const uint8_t *ptr, ExtismSize len) : data(ptr), length(len) {}
const uint8_t *data;
ExtismSize length;
std::string string() { return (std::string)(*this); }
std::vector<uint8_t> vector() { return (std::vector<uint8_t>)(*this); }
operator std::string() { return std::string((const char *)data, length); }
operator std::vector<uint8_t>() {
return std::vector<uint8_t>(data, data + length);
}
};
typedef ExtismValType ValType;
typedef ExtismValUnion ValUnion;
typedef ExtismVal Val;
typedef uint64_t MemoryHandle;
class CurrentPlugin {
ExtismCurrentPlugin *pointer;
public:
CurrentPlugin(ExtismCurrentPlugin *p) : pointer(p) {}
uint8_t *memory() { return extism_current_plugin_memory(this->pointer); }
uint8_t *memory(MemoryHandle offs) { return this->memory() + offs; }
ExtismSize memoryLength(MemoryHandle offs) {
return extism_current_plugin_memory_length(this->pointer, offs);
}
MemoryHandle alloc(ExtismSize size) {
return extism_current_plugin_memory_alloc(this->pointer, size);
}
void free(MemoryHandle handle) {
extism_current_plugin_memory_free(this->pointer, handle);
}
void returnString(Val &output, const std::string &s) {
this->returnBytes(output, (const uint8_t *)s.c_str(), s.size());
}
void returnBytes(Val &output, const uint8_t *bytes, size_t len) {
auto offs = this->alloc(len);
memcpy(this->memory() + offs, bytes, len);
output.v.i64 = offs;
}
uint8_t *inputBytes(Val &inp, size_t *length = nullptr) {
if (inp.t != ValType::I64) {
return nullptr;
}
if (length != nullptr) {
*length = this->memoryLength(inp.v.i64);
}
return this->memory() + inp.v.i64;
}
std::string inputString(Val &inp) {
size_t length = 0;
char *buf = (char *)this->inputBytes(inp, &length);
return std::string(buf, length);
}
};
typedef std::function<void(CurrentPlugin, const std::vector<Val> &,
std::vector<Val> &, void *user_data)>
FunctionType;
struct UserData {
FunctionType func;
void *user_data = NULL;
std::function<void(void *)> free_user_data;
};
static void function_callback(ExtismCurrentPlugin *plugin,
const ExtismVal *inputs, ExtismSize n_inputs,
ExtismVal *outputs, ExtismSize n_outputs,
void *user_data) {
UserData *data = (UserData *)user_data;
const std::vector<Val> inp(inputs, inputs + n_inputs);
std::vector<Val> outp(outputs, outputs + n_outputs);
data->func(CurrentPlugin(plugin), inp, outp, data->user_data);
for (ExtismSize i = 0; i < n_outputs; i++) {
outputs[i] = outp[i];
}
}
static void free_user_data(void *user_data) {
UserData *data = (UserData *)user_data;
if (data->user_data != NULL && data->free_user_data != NULL) {
data->free_user_data(data->user_data);
}
}
class Function {
std::shared_ptr<ExtismFunction> func;
std::string name;
UserData user_data;
public:
Function(std::string name, const std::vector<ValType> inputs,
const std::vector<ValType> outputs, FunctionType f,
void *user_data = NULL, std::function<void(void *)> free = nullptr)
: name(name) {
this->user_data.func = f;
this->user_data.user_data = user_data;
this->user_data.free_user_data = free;
auto ptr = extism_function_new(
this->name.c_str(), inputs.data(), inputs.size(), outputs.data(),
outputs.size(), function_callback, &this->user_data, free_user_data);
this->func = std::shared_ptr<ExtismFunction>(ptr, extism_function_free);
}
void setNamespace(std::string s) {
extism_function_set_namespace(this->func.get(), s.c_str());
}
Function(const Function &f) { this->func = f.func; }
ExtismFunction *get() { return this->func.get(); }
};
class CancelHandle {
const ExtismCancelHandle *handle;
public:
CancelHandle(const ExtismCancelHandle *x) : handle(x){};
bool cancel() { return extism_plugin_cancel(this->handle); }
};
class Plugin {
std::vector<Function> functions;
public:
ExtismPlugin *plugin;
// Create a new plugin
Plugin(const uint8_t *wasm, ExtismSize length, bool with_wasi = false,
std::vector<Function> functions = std::vector<Function>())
: functions(functions) {
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
char *errmsg = nullptr;
this->plugin = extism_plugin_new(wasm, length, ptrs.data(), ptrs.size(),
with_wasi, &errmsg);
if (this->plugin == nullptr) {
std::string s(errmsg);
extism_plugin_new_error_free(errmsg);
throw Error(s);
}
}
Plugin(const std::string &str, bool with_wasi = false,
std::vector<Function> functions = {})
: Plugin((const uint8_t *)str.c_str(), str.size(), with_wasi, functions) {
}
Plugin(const std::vector<uint8_t> &data, bool with_wasi = false,
std::vector<Function> functions = {})
: Plugin(data.data(), data.size(), with_wasi, functions) {}
CancelHandle cancelHandle() {
return CancelHandle(extism_plugin_cancel_handle(this->plugin));
}
#ifndef EXTISM_NO_JSON
// Create a new plugin from Manifest
Plugin(const Manifest &manifest, bool with_wasi = false,
std::vector<Function> functions = {})
: Plugin(manifest.json().c_str(), with_wasi, functions) {}
~Plugin() {
extism_plugin_free(this->plugin);
this->plugin = nullptr;
}
void config(const Config &data) {
Json::Value conf;
for (auto k : data) {
conf[k.first] = k.second;
}
Json::FastWriter writer;
auto s = writer.write(conf);
this->config(s);
}
#endif
void config(const char *json, size_t length) {
bool b = extism_plugin_config(this->plugin, (const uint8_t *)json, length);
if (!b) {
const char *err = extism_plugin_error(this->plugin);
throw Error(err == nullptr ? "Unable to update plugin config" : err);
}
}
void config(const std::string &json) {
this->config(json.c_str(), json.size());
}
// Call a plugin
Buffer call(const std::string &func, const uint8_t *input,
ExtismSize input_length) const {
int32_t rc =
extism_plugin_call(this->plugin, func.c_str(), input, input_length);
if (rc != 0) {
const char *error = extism_plugin_error(this->plugin);
if (error == nullptr) {
throw Error("extism_call failed");
}
throw Error(error);
}
ExtismSize length = extism_plugin_output_length(this->plugin);
const uint8_t *ptr = extism_plugin_output_data(this->plugin);
return Buffer(ptr, length);
}
// Call a plugin function with std::vector<uint8_t> input
Buffer call(const std::string &func,
const std::vector<uint8_t> &input) const {
return this->call(func, input.data(), input.size());
}
// Call a plugin function with string input
Buffer call(const std::string &func,
const std::string &input = std::string()) const {
return this->call(func, (const uint8_t *)input.c_str(), input.size());
}
// Returns true if the specified function exists
bool functionExists(const std::string &func) const {
return extism_plugin_function_exists(this->plugin, func.c_str());
}
};
// Set global log file for plugins
inline bool setLogFile(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

117
cpp/test/test.cpp Normal file
View File

@@ -0,0 +1,117 @@
#include "../extism.hpp"
#include <fstream>
#include <thread>
#include <gtest/gtest.h>
std::vector<uint8_t> read(const char *filename) {
std::ifstream file(filename, std::ios::binary);
return std::vector<uint8_t>((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
}
const std::string code = "../../wasm/code.wasm";
namespace {
using namespace extism;
TEST(Plugin, Manifest) {
Manifest manifest = Manifest::path(code);
manifest.set_config("a", "1");
Plugin plugin(manifest);
Buffer buf = plugin.call("count_vowels", "this is a test");
ASSERT_EQ((std::string)buf, "{\"count\": 4}");
}
TEST(Plugin, BadManifest) {
Manifest manifest;
ASSERT_THROW(Plugin plugin(manifest), Error);
}
TEST(Plugin, Bytes) {
auto wasm = read(code.c_str());
ASSERT_NO_THROW(Plugin plugin(wasm));
Plugin plugin(wasm);
Buffer buf = plugin.call("count_vowels", "this is another test");
ASSERT_EQ(buf.string(), "{\"count\": 6}");
}
TEST(Plugin, UpdateConfig) {
auto wasm = read(code.c_str());
Plugin plugin(wasm);
Config config;
config["abc"] = "123";
ASSERT_NO_THROW(plugin.config(config));
}
TEST(Plugin, FunctionExists) {
auto wasm = read(code.c_str());
Plugin plugin(wasm);
ASSERT_FALSE(plugin.functionExists("bad_function"));
ASSERT_TRUE(plugin.functionExists("count_vowels"));
}
TEST(Plugin, HostFunction) {
auto wasm = read("../../wasm/code-functions.wasm");
auto t = std::vector<ValType>{ValType::I64};
Function hello_world =
Function("hello_world", t, t,
[](CurrentPlugin plugin, const std::vector<Val> &params,
std::vector<Val> &results, void *user_data) {
auto offs = plugin.alloc(4);
memcpy(plugin.memory() + offs, "test", 4);
results[0].v.i64 = (int64_t)offs;
});
auto functions = std::vector<Function>{
hello_world,
};
Plugin plugin(wasm, true, functions);
auto buf = plugin.call("count_vowels", "aaa");
ASSERT_EQ(buf.length, 4);
ASSERT_EQ((std::string)buf, "test");
}
void callThread(Plugin *plugin) {
auto buf = plugin->call("count_vowels", "aaa").string();
ASSERT_EQ(buf.size(), 10);
ASSERT_EQ(buf, "testing123");
}
TEST(Plugin, MultipleThreads) {
auto wasm = read("../../wasm/code-functions.wasm");
auto t = std::vector<ValType>{ValType::I64};
Function hello_world =
Function("hello_world", t, t,
[](CurrentPlugin plugin, const std::vector<Val> &params,
std::vector<Val> &results, void *user_data) {
auto offs = plugin.alloc(10);
memcpy(plugin.memory() + offs, "testing123", 10);
results[0].v.i64 = (int64_t)offs;
});
auto functions = std::vector<Function>{
hello_world,
};
Plugin plugin(wasm, true, functions);
std::vector<std::thread> threads;
for (int i = 0; i < 3; i++) {
threads.push_back(std::thread(callThread, &plugin));
}
for (auto &th : threads) {
th.join();
}
}
}; // namespace
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

7
d/.gitignore vendored Normal file
View File

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

4
d/README.md Normal file
View File

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

17
d/examples/hello/.gitignore vendored Normal file
View File

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

15
d/examples/hello/dub.json Normal file
View File

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

View File

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

258
d/extism.d Normal file
View File

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

1
d/runtime.c Normal file
View File

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

View File

@@ -1,10 +1,7 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from `dotnet new gitignore`
# dotenv files
.env
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
@@ -402,7 +399,6 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
.idea
##
## Visual studio for Mac
@@ -480,5 +476,4 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
# Vim temporary swap files
*.swp
nuget/runtimes/win-x64.dll

37
dotnet/Extism.sln Normal file
View File

@@ -0,0 +1,37 @@
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

4
dotnet/README.md Normal file
View File

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

View File

@@ -12,10 +12,6 @@
<Deterministic>true</Deterministic>
<NoBuild>true</NoBuild>
<IncludeBuildOutput>false</IncludeBuildOutput>
<MinVerIgnoreHeight>true</MinVerIgnoreHeight>
<MinVerTagPrefix>v</MinVerTagPrefix>
<MinVerVerbosity>normal</MinVerVerbosity>
</PropertyGroup>
<ItemGroup>

View File

@@ -3,6 +3,7 @@
<PropertyGroup>
<PackageId>Extism.runtime.linux-musl-arm64</PackageId>
<Description>Internal implementation package for Extism to work on Linux Musl ARM64</Description>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>

View File

@@ -11,5 +11,5 @@
Pack="true"
PackagePath="runtimes\win-x64\native\extism.dll" />
</ItemGroup>
</Project>

66
dotnet/nuget/Nuget.sln Normal file
View File

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

View File

@@ -1,2 +1,2 @@
# Extism
The cross-language framework for building with WebAssembly (wasm). For more information see https://extism.org
The cross-language framework for building with WebAssembly (wasm).

View File

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

View File

@@ -0,0 +1,29 @@
<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

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

View File

@@ -0,0 +1,5 @@
## 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

@@ -0,0 +1,24 @@
<!-- 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

@@ -0,0 +1,138 @@
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

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<LangVersion>10</LangVersion>
</PropertyGroup>
<PropertyGroup>
<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

@@ -0,0 +1,40 @@
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

@@ -0,0 +1,146 @@
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

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

@@ -0,0 +1,32 @@
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

@@ -0,0 +1,202 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace Extism.Sdk.Native;
/// <summary>
/// Represents a WASM Extism plugin.
/// </summary>
public unsafe class Plugin : IDisposable
{
private const int DisposedMarker = 1;
private readonly HostFunction[] _functions;
private int _disposed;
/// <summary>
/// Native pointer to the Extism Plugin.
/// </summary>
internal LibExtism.ExtismPlugin* NativeHandle { get; }
/// <summary>
/// Create a and load a plug-in
/// </summary>
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
/// <param name="functions">List of host functions expected by the plugin.</param>
/// <param name="withWasi">Enable/Disable WASI.</param>
public Plugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi) {
_functions = functions;
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
unsafe
{
fixed (byte* wasmPtr = wasm)
fixed (IntPtr* functionsPtr = functionHandles)
{
NativeHandle = LibExtism.extism_plugin_new(wasmPtr, wasm.Length, functionsPtr, functions.Length, withWasi, null);
if (NativeHandle == null)
{
throw new ExtismException("Unable to create plugin");
// TODO: handle error
// var s = Marshal.PtrToStringUTF8(result);
// LibExtism.extism_plugin_new_error_free(errmsg);
// throw new ExtismException(s);
}
}
}
}
/// <summary>
/// Update plugin config values, this will merge with the existing values.
/// </summary>
/// <param name="json">The configuration JSON encoded in UTF8.</param>
unsafe public bool SetConfig(ReadOnlySpan<byte> json)
{
CheckNotDisposed();
fixed (byte* jsonPtr = json)
{
return LibExtism.extism_plugin_config(NativeHandle, jsonPtr, json.Length);
}
}
/// <summary>
/// Checks if a specific function exists in the current plugin.
/// </summary>
unsafe public bool FunctionExists(string name)
{
CheckNotDisposed();
return LibExtism.extism_plugin_function_exists(NativeHandle, name);
}
/// <summary>
/// Calls a function in the current plugin and returns a status.
/// If the status represents an error, call <see cref="GetError"/> to get the error.
/// Othewise, call <see cref="OutputData"/> to get the function's output data.
/// </summary>
/// <param name="functionName">Name of the function in the plugin to invoke.</param>
/// <param name="data">A buffer to provide as input to the function.</param>
/// <returns>The exit code of the function.</returns>
/// <exception cref="ExtismException"></exception>
unsafe public ReadOnlySpan<byte> CallFunction(string functionName, ReadOnlySpan<byte> data)
{
CheckNotDisposed();
fixed (byte* dataPtr = data)
{
int response = LibExtism.extism_plugin_call(NativeHandle, functionName, dataPtr, data.Length);
if (response == 0)
{
return OutputData();
}
else
{
var errorMsg = GetError();
if (errorMsg != null)
{
throw new ExtismException(errorMsg);
}
else
{
throw new ExtismException("Call to Extism failed");
}
}
}
}
/// <summary>
/// Get the length of a plugin's output data.
/// </summary>
/// <returns></returns>
unsafe internal int OutputLength()
{
CheckNotDisposed();
return (int)LibExtism.extism_plugin_output_length(NativeHandle);
}
/// <summary>
/// Get the plugin's output data.
/// </summary>
internal ReadOnlySpan<byte> OutputData()
{
CheckNotDisposed();
var length = OutputLength();
unsafe
{
var ptr = LibExtism.extism_plugin_output_data(NativeHandle).ToPointer();
return new Span<byte>(ptr, length);
}
}
/// <summary>
/// Get the error associated with the current plugin.
/// </summary>
/// <returns></returns>
unsafe internal string? GetError()
{
CheckNotDisposed();
var result = LibExtism.extism_plugin_error(NativeHandle);
return Marshal.PtrToStringUTF8(result);
}
/// <summary>
/// Frees all resources held by this Plugin.
/// </summary>
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, DisposedMarker) == DisposedMarker)
{
// Already disposed.
return;
}
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Throw an appropriate exception if the plugin has been disposed.
/// </summary>
/// <exception cref="ObjectDisposedException"></exception>
protected void CheckNotDisposed()
{
Interlocked.MemoryBarrier();
if (_disposed == DisposedMarker)
{
ThrowDisposedException();
}
}
[DoesNotReturn]
private static void ThrowDisposedException()
{
throw new ObjectDisposedException(nameof(Plugin));
}
/// <summary>
/// Frees all resources held by this Plugin.
/// </summary>
unsafe protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Free up any managed resources here
}
// Free up unmanaged resources
LibExtism.extism_plugin_free(NativeHandle);
}
/// <summary>
/// Destructs the current Plugin and frees all resources used by it.
/// </summary>
~Plugin()
{
Dispose(false);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,42 @@
<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>

116
dscanner.ini Normal file
View File

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

23
dub.json Normal file
View File

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

1
dune Normal file
View File

@@ -0,0 +1 @@
(dirs ocaml)

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