mirror of
https://github.com/extism/extism.git
synced 2026-01-11 23:08:06 -05:00
Compare commits
2 Commits
kernel-tes
...
add-protob
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3341eb238 | ||
|
|
2d01efb833 |
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -1 +0,0 @@
|
||||
* @zshipko
|
||||
BIN
.github/assets/logo-horizontal-darkmode.png
vendored
BIN
.github/assets/logo-horizontal-darkmode.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 465 KiB |
BIN
.github/assets/logo-horizontal.png
vendored
BIN
.github/assets/logo-horizontal.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 465 KiB |
33
.github/dependabot.yml
vendored
33
.github/dependabot.yml
vendored
@@ -9,8 +9,33 @@ updates:
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
ignore:
|
||||
- dependency-name: "wasmtime"
|
||||
- dependency-name: "wasi-common"
|
||||
- dependency-name: "wiggle"
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directory: "python"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "mix"
|
||||
directory: "elixir"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "node"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "bundler"
|
||||
directory: "ruby"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
28
.github/workflows/browser-ci.yml
vendored
Normal file
28
.github/workflows/browser-ci.yml
vendored
Normal 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
31
.github/workflows/ci-cpp.yml
vendored
Normal 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
28
.github/workflows/ci-d.yml
vendored
Normal 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
26
.github/workflows/ci-dotnet.yml
vendored
Normal 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
33
.github/workflows/ci-elixir.yml
vendored
Normal 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
27
.github/workflows/ci-go.yml
vendored
Normal 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
39
.github/workflows/ci-haskell.yml
vendored
Normal 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
29
.github/workflows/ci-java.yml
vendored
Normal 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
30
.github/workflows/ci-node.yml
vendored
Normal 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
40
.github/workflows/ci-ocaml.yml
vendored
Normal 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
32
.github/workflows/ci-php.yml
vendored
Normal 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
32
.github/workflows/ci-python.yml
vendored
Normal 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
30
.github/workflows/ci-ruby.yml
vendored
Normal 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
119
.github/workflows/ci-rust.yml
vendored
Normal 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
29
.github/workflows/ci-zig.yml
vendored
Normal 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
|
||||
135
.github/workflows/ci.yml
vendored
135
.github/workflows/ci.yml
vendored
@@ -1,124 +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@v4
|
||||
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
|
||||
- run: |
|
||||
git fetch
|
||||
git checkout main
|
||||
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
|
||||
|
||||
|
||||
4
.github/workflows/kernel.yml
vendored
4
.github/workflows/kernel.yml
vendored
@@ -21,9 +21,6 @@ jobs:
|
||||
target: wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: install wasm-tools
|
||||
uses: bytecodealliance/actions/wasm-tools/setup@v1
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt install wabt --yes
|
||||
@@ -42,7 +39,6 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
if: ${{ env.GIT_EXIT_CODE }} != 0
|
||||
with:
|
||||
author: "zshipko <zshipko@users.noreply.github.com>"
|
||||
title: "update(kernel): extism-runtime.wasm in ${{ github.event.pull_request.head.ref }}"
|
||||
body: "Automated PR to update `runtime/src/extism-runtime.wasm` in PR #${{ github.event.number }}"
|
||||
base: "${{ github.event.pull_request.head.ref }}"
|
||||
|
||||
26
.github/workflows/release-convert.yaml
vendored
Normal file
26
.github/workflows/release-convert.yaml
vendored
Normal 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
|
||||
77
.github/workflows/release-dotnet-native.yaml
vendored
77
.github/workflows/release-dotnet-native.yaml
vendored
@@ -1,6 +1,4 @@
|
||||
on:
|
||||
release:
|
||||
types: [published, edited]
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release .NET Native NuGet Packages
|
||||
@@ -12,64 +10,37 @@ 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
|
||||
with:
|
||||
dotnet-version: 7.x
|
||||
- name: download release
|
||||
run: |
|
||||
tag='${{ github.ref }}'
|
||||
tag="${tag/refs\/tags\//}"
|
||||
gh release download "$tag" -p 'libextism-*.tar.gz'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
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
32
.github/workflows/release-dotnet.yaml
vendored
Normal 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
34
.github/workflows/release-elixir.yaml
vendored
Normal 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
16
.github/workflows/release-haskell.yaml
vendored
Normal 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
22
.github/workflows/release-java.yml
vendored
Normal 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
26
.github/workflows/release-manifest.yaml
vendored
Normal 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
30
.github/workflows/release-node.yaml
vendored
Normal 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
25
.github/workflows/release-ruby.yaml
vendored
Normal 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
|
||||
|
||||
61
.github/workflows/release-rust.yaml
vendored
61
.github/workflows/release-rust.yaml
vendored
@@ -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,50 +19,8 @@ jobs:
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Release Rust convert-macros 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-macros/${version}/download; then
|
||||
cargo publish --manifest-path convert-macros/Cargo.toml --allow-dirty
|
||||
else
|
||||
echo "already published ${version}"
|
||||
fi
|
||||
|
||||
- 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
|
||||
|
||||
80
.github/workflows/release.yml
vendored
80
.github/workflows/release.yml
vendored
@@ -1,12 +1,17 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ main, "v*" ]
|
||||
branches: [ main ]
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
name: Release
|
||||
|
||||
env:
|
||||
RUNTIME_MANIFEST: runtime/Cargo.toml
|
||||
RUNTIME_CRATE: libextism
|
||||
RUSTFLAGS: -C target-feature=-crt-static
|
||||
ARTIFACT_DIR: release-artifacts
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -16,11 +21,6 @@ jobs:
|
||||
release:
|
||||
name: ${{ matrix.os }} ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
env:
|
||||
RUNTIME_MANIFEST: runtime/Cargo.toml
|
||||
RUNTIME_CRATE: libextism
|
||||
RUSTFLAGS: -C target-feature=-crt-static
|
||||
ARTIFACT_DIR: release-artifacts-${{ matrix.os }}-${{ matrix.target }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -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: 'libextism.so'
|
||||
static-artifact: 'libextism.a'
|
||||
static-dll-artifact: ''
|
||||
pc-in: 'extism.pc.in'
|
||||
static-pc-in: 'extism-static.pc.in'
|
||||
- os: 'windows'
|
||||
target: 'x86_64-pc-windows-gnu'
|
||||
artifact: 'extism.dll'
|
||||
static-artifact: 'libextism.a'
|
||||
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,24 +71,13 @@ 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'
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
@@ -111,15 +86,24 @@ jobs:
|
||||
cache-on-failure: "true"
|
||||
|
||||
- name: Build Target (${{ matrix.os }} ${{ matrix.target }})
|
||||
if: ${{ matrix.os != 'windows' }}
|
||||
run: |
|
||||
cargo install cross
|
||||
cross build --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.os != 'windows' }}
|
||||
command: build
|
||||
args: --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Build Target (${{ matrix.os }} ${{ matrix.target }})
|
||||
if: ${{ matrix.os == 'windows' }}
|
||||
- name: set extism-maturin version
|
||||
shell: bash
|
||||
run: |
|
||||
cargo build --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
|
||||
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:
|
||||
@@ -168,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
|
||||
@@ -192,7 +175,7 @@ jobs:
|
||||
ls -ll ${DEST_DIR}
|
||||
|
||||
- name: Upload Artifact to Summary
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_DIR }}
|
||||
path: ${{ env.ARTIFACT_DIR }}
|
||||
@@ -211,10 +194,9 @@ jobs:
|
||||
needs: [release]
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
pattern: release-artifacts-*
|
||||
merge-multiple: true
|
||||
name: ${{ env.ARTIFACT_DIR }}
|
||||
|
||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -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
1
.ocamlformat
Normal file
@@ -0,0 +1 @@
|
||||
version = 0.26.0
|
||||
23
Cargo.toml
23
Cargo.toml
@@ -1,18 +1,9 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["extism-maturin", "manifest", "runtime", "libextism", "convert", "convert-macros"]
|
||||
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-convert-macros = { path = "./convert-macros", version = "0.0.0+replaced-by-ci" }
|
||||
extism-manifest = { path = "./manifest", version = "0.0.0+replaced-by-ci" }
|
||||
|
||||
117
DEVELOPING.md
117
DEVELOPING.md
@@ -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;
|
||||
```
|
||||
|
||||
29
Makefile
29
Makefile
@@ -3,13 +3,10 @@ SOEXT=so
|
||||
AEXT=a
|
||||
FEATURES?=default
|
||||
DEFAULT_FEATURES?=yes
|
||||
RUST_TARGET?=
|
||||
EXTRA_LIBS=
|
||||
|
||||
UNAME := $(shell uname -s)
|
||||
ifeq ($(UNAME),Darwin)
|
||||
SOEXT=dylib
|
||||
EXTRA_LIBS=-framework Security
|
||||
endif
|
||||
|
||||
ifeq ($(DEFAULT_FEATURES),no)
|
||||
@@ -22,39 +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)%" \
|
||||
-e "s%Libs: %Libs: $(EXTRA_LIBS) %" 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
|
||||
|
||||
|
||||
204
README.md
204
README.md
@@ -1,188 +1,62 @@
|
||||
<div align="center">
|
||||
<a href="https://extism.org">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset=".github/assets/logo-horizontal-darkmode.png">
|
||||
<img alt="Extism - the WebAssembly framework" width="75%" style="max-width: 600px" src=".github/assets/logo-horizontal.png">
|
||||
</picture>
|
||||
</a>
|
||||
### _Welcome!_
|
||||
|
||||
[](https://extism.org/discord)
|
||||

|
||||

|
||||

|
||||

|
||||
**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.
|
||||
|
||||
</div>
|
||||
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.
|
||||
|
||||
# Overview
|
||||
[](https://discord.gg/cx3usBCWnc)
|
||||
|
||||
Extism is a lightweight framework for building with WebAssembly (Wasm). It
|
||||
supports running Wasm code on servers, the edge, CLIs, IoT, browsers and
|
||||
everything in between. Extism is designed to be "universal" in that it supports
|
||||
a common interface, no matter where it runs.
|
||||
# [Extism](https://extism.org)
|
||||
|
||||
> **Note:** One of the primary use cases for Extism is **building extensible
|
||||
> software & plugins**. You want to be able to execute arbitrary, untrusted code
|
||||
> from your users? Extism makes this safe and practical to do.
|
||||
The universal plug-in system. Run WebAssembly extensions inside your app. Use idiomatic Host SDKs for [Go](https://github.com/extism/go-sdk#readme),
|
||||
[Ruby](https://github.com/extism/ruby-sdk#readme),
|
||||
[Python](https://github.com/extism/python-sdk#readme),
|
||||
[JavaScript](https://github.com/extism/js-sdk#readme),
|
||||
[Rust](/runtime/#readme),
|
||||
[C](libextism/#readme),
|
||||
[C++](https://github.com/extism/cpp-sdk/#readme),
|
||||
[OCaml](https://github.com/extism/ocaml-sdk#readme),
|
||||
[Haskell](https://github.com/extism/haskell-sdk#readme),
|
||||
[PHP](https://github.com/extism/php-sdk#readme),
|
||||
[Elixir](https://github.com/extism/elixir-sdk#readme),
|
||||
[.NET](https://github.com/extism/dotnet-sdk#readme),
|
||||
[Java](https://github.com/extism/java-sdk#readme),
|
||||
[Zig](https://github.com/extism/zig-sdk#readme),
|
||||
[D](https://github.com/extism/d-sdk#readme),
|
||||
& more (others coming soon).
|
||||
|
||||
Additionally, Extism adds some extra utilities on top of standard Wasm runtimes.
|
||||
For example, we support persistent memory/module-scope variables, secure &
|
||||
host-controlled HTTP without WASI, runtime limiters & timers, simpler host
|
||||
function linking, and more. Extism users build:
|
||||
Plug-in development kits (PDK) for plug-in authors supported in [Rust](https://github.com/extism/rust-pdk#readme), [AssemblyScript](https://github.com/extism/assemblyscript-pdk#readme), [Go](https://github.com/extism/go-pdk#readme), [C/C++](https://github.com/extism/c-pdk#readme), [Haskell](https://github.com/extism/haskell-pdk#readme), [JavaScript](https://github.com/extism/js-pdk#readme), [C#](https://github.com/extism/dotnet-pdk#readme), [F#](https://github.com/extism/dotnet-pdk#readme) and [Zig](https://github.com/extism/zig-pdk#readme).
|
||||
|
||||
- plug-in systems
|
||||
- FaaS platforms
|
||||
- code generators
|
||||
- web applications
|
||||
- & much more...
|
||||
<p align="center">
|
||||
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/210286900-39b144fd-1b26-4dd0-b7a9-2b5755bc174d.png" alt="Extism embedded SDK language support"/>
|
||||
</p>
|
||||
|
||||
# Supported Targets
|
||||
|
||||
We currently provide releases for the following targets:
|
||||
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:**
|
||||
|
||||
- aarch64-apple-darwin
|
||||
- aarch64-unknown-linux-gnu
|
||||
- aarch64-unknown-linux-musl
|
||||
- x86_64-apple-darwin
|
||||
- x86_64-pc-windows-gnu
|
||||
- x86_64-pc-windows-msvc
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
### 1. Import
|
||||
|
||||
For Android we suggest taking a look at the [Chicory SDK](https://github.com/extism/chicory-sdk) for a pure Java
|
||||
Extism runtime.
|
||||
Import an Extism Host SDK into your code as a library dependency.
|
||||
|
||||
# Run WebAssembly In Your App
|
||||
### 2. Integrate
|
||||
|
||||
Pick a SDK to import into your program, and refer to the documentation to get
|
||||
started:
|
||||
Identify the place(s) in your code where some arbitrary logic should run (the plug-in!), returning your code some results.
|
||||
|
||||
| Type | Language | Source Code | Package |
|
||||
| ----------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| Rust SDK | <img alt="Rust SDK" src="https://extism.org/img/sdk-languages/rust.svg" width="50px"/> | https://github.com/extism/extism/tree/main/runtime | [Crates.io](https://crates.io/crates/extism) |
|
||||
| JS SDK | <img alt="JS SDK" src="https://extism.org/img/sdk-languages/js.svg" width="50px"/> | https://github.com/extism/js-sdk <br/>(supports Web, Node, Deno & Bun!) | [NPM](https://www.npmjs.com/package/@extism/extism) |
|
||||
| Elixir SDK | <img alt="Elixir SDK" src="https://extism.org/img/sdk-languages/elixir.svg" width="50px"/> | https://github.com/extism/elixir-sdk | [Hex](https://hex.pm/packages/extism) |
|
||||
| Go SDK | <img alt="Go SDK" src="https://extism.org/img/sdk-languages/go.svg" width="50px"/> | https://github.com/extism/go-sdk | [Go mod](https://pkg.go.dev/github.com/extism/go-sdk) |
|
||||
| Haskell SDK | <img alt="Haskell SDK" src="https://extism.org/img/sdk-languages/haskell.svg" width="50px"/> | https://github.com/extism/haskell-sdk | [Hackage](https://hackage.haskell.org/package/extism) |
|
||||
| Java SDK | <img alt="Java SDK" src="https://extism.org/img/sdk-languages/java-android.svg" width="50px"/> | https://github.com/extism/java-sdk | [Sonatype](https://central.sonatype.com/artifact/org.extism.sdk/extism) |
|
||||
| .NET SDK | <img alt=".NET SDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-sdk <br/>(supports C# & F#!) | [Nuget](https://www.nuget.org/packages/Extism.Sdk) |
|
||||
| OCaml SDK | <img alt="OCaml SDK" src="https://extism.org/img/sdk-languages/ocaml.svg" width="50px"/> | https://github.com/extism/ocaml-sdk | [opam](https://opam.ocaml.org/packages/extism/) |
|
||||
| Perl SDK | <img alt="Perl SDK" src="https://extism.org/img/sdk-languages/perl.svg" width="50px"/> | https://github.com/extism/perl-sdk | [CPAN](https://metacpan.org/pod/Extism) |
|
||||
| PHP SDK | <img alt="PHP SDK" src="https://extism.org/img/sdk-languages/php.svg" width="50px"/> | https://github.com/extism/php-sdk | [Packagist](https://packagist.org/packages/extism/extism) |
|
||||
| Python SDK | <img alt="Python SDK" src="https://extism.org/img/sdk-languages/python.svg" width="50px"/> | https://github.com/extism/python-sdk | [PyPi](https://pypi.org/project/extism/) |
|
||||
| Ruby SDK | <img alt="Ruby SDK" src="https://extism.org/img/sdk-languages/ruby.svg" width="50px"/> | https://github.com/extism/ruby-sdk | [RubyGems](https://rubygems.org/gems/extism) |
|
||||
| Zig SDK | <img alt="Zig SDK" src="https://extism.org/img/sdk-languages/zig.svg" width="50px"/> | https://github.com/extism/zig-sdk | N/A |
|
||||
| C SDK | <img alt="C SDK" src="https://extism.org/img/sdk-languages/c.svg" width="50px"/> | https://github.com/extism/extism/tree/main/libextism | N/A |
|
||||
| C++ SDK | <img alt="C++ SDK" src="https://extism.org/img/sdk-languages/cpp.svg" width="50px"/> | https://github.com/extism/cpp-sdk | N/A |
|
||||
### 3. Execute
|
||||
|
||||
# Compile WebAssembly to run in Extism Hosts
|
||||
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.
|
||||
|
||||
Extism Hosts (running the SDK) must execute WebAssembly code that has a
|
||||
[PDK, or Plug-in Development Kit](https://extism.org/docs/concepts/pdk), library
|
||||
compiled in to the `.wasm` binary. PDKs make it easy for plug-in / extension
|
||||
code authors to read input from the host and return data back, read provided
|
||||
configuration, set/get variables, make outbound HTTP calls if allowed, and more.
|
||||
|
||||
Pick a PDK to import into your Wasm program, and refer to the documentation to
|
||||
get started:
|
||||
|
||||
| Type | Language | Source Code | Package |
|
||||
| ------------------ | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------- |
|
||||
| Rust PDK | <img alt="Rust PDK" src="https://extism.org/img/sdk-languages/rust.svg" width="50px"/> | https://github.com/extism/rust-pdk | [Crates.io](https://crates.io/crates/extism-pdk) |
|
||||
| JS PDK | <img alt="JS PDK" src="https://extism.org/img/sdk-languages/js.svg" width="50px"/> | https://github.com/extism/js-pdk | N/A |
|
||||
| Python PDK | <img alt="Python PDK" src="https://extism.org/img/sdk-languages/python.svg" width="50px"/> | https://github.com/extism/python-pdk | N/A |
|
||||
| Go PDK | <img alt="Go PDK" src="https://extism.org/img/sdk-languages/go.svg" width="50px"/> | https://github.com/extism/go-pdk | [Go mod](https://pkg.go.dev/github.com/extism/go-pdk) |
|
||||
| Haskell PDK | <img alt="Haskell PDK" src="https://extism.org/img/sdk-languages/haskell.svg" width="50px"/> | https://github.com/extism/haskell-pdk | [Hackage](https://hackage.haskell.org/package/extism-pdk) |
|
||||
| AssemblyScript PDK | <img alt="AssemblyScript PDK" src="https://extism.org/img/sdk-languages/assemblyscript.svg" width="50px"/> | https://github.com/extism/assemblyscript-pdk | [NPM](https://www.npmjs.com/package/@extism/as-pdk) |
|
||||
| .NET PDK | <img alt=".NET PDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-pdk <br/>(supports C# & F#!) | [Nuget](https://www.nuget.org/packages/Extism.Pdk) |
|
||||
| C PDK | <img alt="C PDK" src="https://extism.org/img/sdk-languages/c.svg" width="50px"/> | https://github.com/extism/c-pdk | N/A |
|
||||
| C++ PDK | <img alt="C++ PDK" src="https://extism.org/img/sdk-languages/cpp.svg" width="50px"/> | https://github.com/extism/cpp-pdk | N/A |
|
||||
| Zig PDK | <img alt="Zig PDK" src="https://extism.org/img/sdk-languages/zig.svg" width="50px"/> | https://github.com/extism/zig-pdk | N/A |
|
||||
|
||||
# Generating Bindings
|
||||
|
||||
It's often very useful to define a schema to describe the function signatures
|
||||
and types you want to use between Extism SDK and PDK languages.
|
||||
|
||||
[XTP Bindgen](https://github.com/dylibso/xtp-bindgen) is an open source
|
||||
framework to generate PDK bindings for Extism plug-ins. It's used by the
|
||||
[XTP Platform](https://www.getxtp.com/), but can be used outside of the platform
|
||||
to define any Extism compatible plug-in system.
|
||||
|
||||
## 1. Install the `xtp` CLI.
|
||||
|
||||
See installation instructions
|
||||
[here](https://docs.xtp.dylibso.com/docs/cli#installation).
|
||||
|
||||
## 2. Create a schema using our OpenAPI-inspired IDL:
|
||||
|
||||
```yaml
|
||||
version: v1-draft
|
||||
exports:
|
||||
CountVowels:
|
||||
input:
|
||||
type: string
|
||||
contentType: text/plain; charset=utf-8
|
||||
output:
|
||||
$ref: "#/components/schemas/VowelReport"
|
||||
contentType: application/json
|
||||
# components.schemas defined in example-schema.yaml...
|
||||
```
|
||||
|
||||
> See an example in [example-schema.yaml](./example-schema.yaml), or a full
|
||||
> "kitchen sink" example on
|
||||
> [the docs page](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema/).
|
||||
|
||||
## 3. Generate bindings to use from your plugins:
|
||||
|
||||
```
|
||||
xtp plugin init --schema-file ./example-schema.yaml
|
||||
> 1. TypeScript
|
||||
2. Go
|
||||
3. Rust
|
||||
4. Python
|
||||
5. C#
|
||||
6. Zig
|
||||
7. C++
|
||||
8. GitHub Template
|
||||
9. Local Template
|
||||
```
|
||||
|
||||
This will create an entire boilerplate plugin project for you to get started
|
||||
with. Implement the empty function(s), and run `xtp plugin build` to compile
|
||||
your plugin.
|
||||
|
||||
> For more information about XTP Bindgen, see the
|
||||
> [dylibso/xtp-bindgen](https://github.com/dylibso/xtp-bindgen) repository and
|
||||
> the official
|
||||
> [XTP Schema documentation](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema).
|
||||
|
||||
# Support
|
||||
|
||||
## Discord
|
||||
|
||||
If you experience any problems or have any questions, please join our
|
||||
[Discord](https://extism.org/discord) and let us know. Our community is very
|
||||
responsive and happy to help get you started.
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Head to the [project website](https://extism.org) for more information and docs.
|
||||
Also, consider reading an [overview](https://extism.org/docs/overview) of Extism
|
||||
and its goals & approach.
|
||||
Head to the [project website](https://extism.org) for more information and docs. Also, consider reading an [overview](https://extism.org/docs/overview) of Extism and its goals & approach.
|
||||
|
||||
## Contribution
|
||||
|
||||
Thank you for considering a contribution to Extism, we are happy to help you
|
||||
make a PR or find something to work on!
|
||||
Thank you for considering a contribution to Extism, we are happy to help you make a PR or find something to work on!
|
||||
|
||||
The easiest way to start would be to join the
|
||||
[Discord](https://extism.org/discord) or open an issue on the
|
||||
[`extism/proposals`](https://github.com/extism/proposals) issue tracker, which
|
||||
can eventually become an Extism Improvement Proposal (EIP).
|
||||
|
||||
For more information, please read the
|
||||
[Contributing](https://extism.org/docs/concepts/contributing) guide.
|
||||
The easiest way to start would be to join the [Discord](https://discord.gg/cx3usBCWnc) or open an issue on the [`extism/proposals`](https://github.com/extism/proposals) issue tracker, which can eventually become an Extism Improvement Proposal (EIP).
|
||||
|
||||
---
|
||||
|
||||
@@ -191,8 +65,8 @@ For more information, please read the
|
||||
Extism is an open-source product from the team at:
|
||||
|
||||
<p align="left">
|
||||
<a href="https://dylibso.com" _target="blanks"><img width="200px" src="https://user-images.githubusercontent.com/7517515/198204119-5afdebb9-a5d8-4322-bd2a-46179c8d7b24.svg"/></a>
|
||||
<a href="https://dylib.so" _target="blanks"><img width="200px" src="https://user-images.githubusercontent.com/7517515/198204119-5afdebb9-a5d8-4322-bd2a-46179c8d7b24.svg"/></a>
|
||||
</p>
|
||||
|
||||
_Reach out and tell us what you're building! We'd love to help:_
|
||||
<a href="mailto:hello@dylibso.com">hello@dylibso.com</a>
|
||||
|
||||
_Reach out and tell us what you're building! We'd love to help._
|
||||
|
||||
130
browser/.gitignore
vendored
Normal file
130
browser/.gitignore
vendored
Normal 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
5
browser/.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true
|
||||
}
|
||||
28
browser/Makefile
Normal file
28
browser/Makefile
Normal 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
4
browser/README.md
Normal 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
23
browser/build.js
Normal 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",
|
||||
});
|
||||
Binary file not shown.
248
browser/index.html
Normal file
248
browser/index.html
Normal 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
5
browser/jest.config.js
Normal 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
14458
browser/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
browser/package.json
Normal file
39
browser/package.json
Normal 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
104
browser/src/allocator.ts
Normal 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
45
browser/src/context.ts
Normal 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
24
browser/src/index.test.ts
Normal 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
4
browser/src/index.ts
Normal 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
40
browser/src/manifest.ts
Normal 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
217
browser/src/plugin.ts
Normal 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
13
browser/tsconfig.json
Normal 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
6
browser/tslint.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": [
|
||||
"tslint:recommended",
|
||||
"tslint-config-prettier"
|
||||
]
|
||||
}
|
||||
2
c/Makefile
Normal file
2
c/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
build:
|
||||
clang -g -o main main.c -lextism -L .
|
||||
49
composer.json
Normal file
49
composer.json
Normal 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
163
composer.lock
generated
Normal 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"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
[package]
|
||||
name = "extism-convert-macros"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
description = "Macros to remove boilerplate with Extism"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[features]
|
||||
extism-path = []
|
||||
extism-pdk-path = []
|
||||
|
||||
[dependencies]
|
||||
manyhow.version = "0.11.0"
|
||||
proc-macro-crate = "3.1.0"
|
||||
proc-macro2 = "1.0.78"
|
||||
quote = "1.0.35"
|
||||
syn = { version = "2.0.48", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0.89"
|
||||
@@ -1,108 +0,0 @@
|
||||
use std::iter;
|
||||
|
||||
use manyhow::{ensure, error_message, manyhow, Result};
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::{parse_quote, Attribute, DeriveInput, Path};
|
||||
|
||||
/// Tries to resolve the path to `extism_convert` dynamically, falling back to feature flags when unsuccessful.
|
||||
fn convert_path() -> Path {
|
||||
match (
|
||||
crate_name("extism"),
|
||||
crate_name("extism-convert"),
|
||||
crate_name("extism-pdk"),
|
||||
) {
|
||||
(Ok(FoundCrate::Name(name)), ..) => {
|
||||
let ident = format_ident!("{name}");
|
||||
parse_quote!(::#ident::convert)
|
||||
}
|
||||
(_, Ok(FoundCrate::Name(name)), ..) | (.., Ok(FoundCrate::Name(name))) => {
|
||||
let ident = format_ident!("{name}");
|
||||
parse_quote!(::#ident)
|
||||
}
|
||||
(Ok(FoundCrate::Itself), ..) => parse_quote!(::extism::convert),
|
||||
(_, Ok(FoundCrate::Itself), ..) => parse_quote!(::extism_convert),
|
||||
(.., Ok(FoundCrate::Itself)) => parse_quote!(::extism_pdk),
|
||||
_ if cfg!(feature = "extism-path") => parse_quote!(::extism::convert),
|
||||
_ if cfg!(feature = "extism-pdk-path") => parse_quote!(::extism_pdk),
|
||||
_ => parse_quote!(::extism_convert),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_encoding(attrs: &[Attribute]) -> Result<Path> {
|
||||
let encodings: Vec<_> = attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path().is_ident("encoding"))
|
||||
.collect();
|
||||
ensure!(!encodings.is_empty(), "encoding needs to be specified"; try = "`#[encoding(Json)]`");
|
||||
ensure!(encodings.len() < 2, encodings[1], "only one encoding can be specified"; try = "remove `{}`", encodings[1].to_token_stream());
|
||||
|
||||
Ok(encodings[0].parse_args().map_err(
|
||||
|e| error_message!(e.span(), "{e}"; note= "expects a path"; try = "`#[encoding(Json)]`"),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[manyhow]
|
||||
#[proc_macro_derive(ToBytes, attributes(encoding))]
|
||||
pub fn to_bytes(
|
||||
DeriveInput {
|
||||
attrs,
|
||||
ident,
|
||||
generics,
|
||||
..
|
||||
}: DeriveInput,
|
||||
) -> Result {
|
||||
let encoding = extract_encoding(&attrs)?;
|
||||
let convert = convert_path();
|
||||
|
||||
let (_, type_generics, _) = generics.split_for_impl();
|
||||
|
||||
let mut generics = generics.clone();
|
||||
generics.make_where_clause().predicates.push(
|
||||
parse_quote!(for<'__to_bytes_b> #encoding<&'__to_bytes_b Self>: #convert::ToBytes<'__to_bytes_b>)
|
||||
);
|
||||
generics.params = iter::once(parse_quote!('__to_bytes_a))
|
||||
.chain(generics.params)
|
||||
.collect();
|
||||
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
||||
|
||||
Ok(quote! {
|
||||
impl #impl_generics #convert::ToBytes<'__to_bytes_a> for #ident #type_generics #where_clause
|
||||
{
|
||||
type Bytes = ::std::vec::Vec<u8>;
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, #convert::Error> {
|
||||
#convert::ToBytes::to_bytes(&#encoding(self)).map(|__bytes| __bytes.as_ref().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
#[manyhow]
|
||||
#[proc_macro_derive(FromBytes, attributes(encoding))]
|
||||
pub fn from_bytes(
|
||||
DeriveInput {
|
||||
attrs,
|
||||
ident,
|
||||
mut generics,
|
||||
..
|
||||
}: DeriveInput,
|
||||
) -> Result {
|
||||
let encoding = extract_encoding(&attrs)?;
|
||||
let convert = convert_path();
|
||||
generics
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
.push(parse_quote!(#encoding<Self>: #convert::FromBytesOwned));
|
||||
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
|
||||
Ok(quote! {
|
||||
impl #impl_generics #convert::FromBytesOwned for #ident #type_generics #where_clause
|
||||
{
|
||||
fn from_bytes_owned(__data: &[u8]) -> Result<Self, #convert::Error> {
|
||||
<#encoding<Self> as #convert::FromBytesOwned>::from_bytes_owned(__data).map(|__encoding| __encoding.0)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#[test]
|
||||
fn ui() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/ui/*.rs");
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use extism_convert_macros::ToBytes;
|
||||
|
||||
#[derive(ToBytes)]
|
||||
struct MissingEncoding;
|
||||
|
||||
#[derive(ToBytes)]
|
||||
#[encoding]
|
||||
struct EmptyAttr;
|
||||
|
||||
#[derive(ToBytes)]
|
||||
#[encoding = "string"]
|
||||
struct EqNoParen;
|
||||
|
||||
#[derive(ToBytes)]
|
||||
#[encoding(something, else)]
|
||||
struct NotAPath;
|
||||
|
||||
#[derive(ToBytes)]
|
||||
#[encoding(Multiple)]
|
||||
#[encoding(Encodings)]
|
||||
struct MultipleEncodings;
|
||||
|
||||
fn main() {}
|
||||
@@ -1,44 +0,0 @@
|
||||
error: encoding needs to be specified
|
||||
|
||||
= try: `#[encoding(Json)]`
|
||||
--> tests/ui/invalid-encoding.rs:3:10
|
||||
|
|
||||
3 | #[derive(ToBytes)]
|
||||
| ^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `ToBytes` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: expected attribute arguments in parentheses: #[encoding(...)]
|
||||
|
||||
= note: expects a path
|
||||
= try: `#[encoding(Json)]`
|
||||
--> tests/ui/invalid-encoding.rs:7:3
|
||||
|
|
||||
7 | #[encoding]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: expected parentheses: #[encoding(...)]
|
||||
|
||||
= note: expects a path
|
||||
= try: `#[encoding(Json)]`
|
||||
--> tests/ui/invalid-encoding.rs:11:12
|
||||
|
|
||||
11 | #[encoding = "string"]
|
||||
| ^
|
||||
|
||||
error: unexpected token
|
||||
|
||||
= note: expects a path
|
||||
= try: `#[encoding(Json)]`
|
||||
--> tests/ui/invalid-encoding.rs:15:21
|
||||
|
|
||||
15 | #[encoding(something, else)]
|
||||
| ^
|
||||
|
||||
error: only one encoding can be specified
|
||||
|
||||
= try: remove `#[encoding(Encodings)]`
|
||||
--> tests/ui/invalid-encoding.rs:20:1
|
||||
|
|
||||
20 | #[encoding(Encodings)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -1,31 +1,28 @@
|
||||
[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.22"
|
||||
bytemuck = {version = "1.14.0", optional = true }
|
||||
prost = { version = "0.13.1", optional = true }
|
||||
protobuf = { version = "3.2.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"
|
||||
extism-convert-macros.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
serde = { version = "1.0.186", features = ["derive"] }
|
||||
|
||||
[features]
|
||||
default = ["msgpack", "prost", "raw"]
|
||||
default = ["msgpack", "prost"]
|
||||
msgpack = ["rmp-serde"]
|
||||
raw = ["bytemuck"]
|
||||
extism-path = ["extism-convert-macros/extism-path"]
|
||||
extism-pdk-path = ["extism-convert-macros/extism-pdk-path"]
|
||||
prost = ["prost"]
|
||||
protobuf = ["protobuf"]
|
||||
|
||||
@@ -11,14 +11,13 @@ use base64::Engine;
|
||||
/// extism_convert::encoding!(MyJson, serde_json::to_vec, serde_json::from_slice);
|
||||
/// ```
|
||||
///
|
||||
/// This will create a struct `struct MyJson<T>(pub T)` and implement [`ToBytes`] using [`serde_json::to_vec`]
|
||||
/// and [`FromBytesOwned`] using [`serde_json::from_slice`]
|
||||
/// This will create a struct `struct MyJson<T>(pub T)` and implement `ToBytes` using `serde_json::to_vec`
|
||||
/// and `FromBytesOwned` using `serde_json::from_vec`
|
||||
#[macro_export]
|
||||
macro_rules! encoding {
|
||||
($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,12 +43,12 @@ 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 ToBytes<'_> for serde_json::Value {
|
||||
impl<'a> ToBytes<'a> for serde_json::Value {
|
||||
type Bytes = Vec<u8>;
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -76,16 +69,9 @@ 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<T: AsRef<[u8]>> ToBytes<'_> for Base64<T> {
|
||||
impl<'a, T: AsRef<[u8]>> ToBytes<'a> for Base64<T> {
|
||||
type Bytes = String;
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -113,18 +99,10 @@ impl FromBytesOwned for Base64<String> {
|
||||
///
|
||||
/// Allows for `prost` Protobuf messages to be used as arguments to Extism plugin calls
|
||||
#[cfg(feature = "prost")]
|
||||
#[derive(Debug)]
|
||||
pub struct Prost<T: prost::Message>(pub T);
|
||||
pub struct Protobuf<T: prost::Message>(pub T);
|
||||
|
||||
#[cfg(feature = "prost")]
|
||||
impl<T: prost::Message> From<T> for Prost<T> {
|
||||
fn from(data: T) -> Self {
|
||||
Self(data)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "prost")]
|
||||
impl<T: prost::Message> ToBytes<'_> for Prost<T> {
|
||||
impl<'a, T: prost::Message> ToBytes<'a> for Protobuf<T> {
|
||||
type Bytes = Vec<u8>;
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -133,20 +111,20 @@ impl<T: prost::Message> ToBytes<'_> for Prost<T> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "prost")]
|
||||
impl<T: Default + prost::Message> FromBytesOwned for Prost<T> {
|
||||
impl<T: Default + prost::Message> FromBytesOwned for Protobuf<T> {
|
||||
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
|
||||
Ok(Prost(T::decode(data)?))
|
||||
Ok(Protobuf(T::decode(data)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// Protobuf encoding
|
||||
///
|
||||
/// Allows for `rust-protobuf` Protobuf messages to be used as arguments to Extism plugin calls
|
||||
/// Allows for `protobuf` Protobuf messages to be used as arguments to Extism plugin calls
|
||||
#[cfg(feature = "protobuf")]
|
||||
pub struct Protobuf<T: protobuf::Message>(pub T);
|
||||
|
||||
#[cfg(feature = "protobuf")]
|
||||
impl<T: protobuf::Message> ToBytes<'_> for Protobuf<T> {
|
||||
impl<'a, T: protobuf::Message> ToBytes<'a> for Protobuf<T> {
|
||||
type Bytes = Vec<u8>;
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -155,33 +133,8 @@ impl<T: protobuf::Message> ToBytes<'_> for Protobuf<T> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "protobuf")]
|
||||
impl<T: Default + protobuf::Message> FromBytesOwned for Protobuf<T> {
|
||||
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)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
||||
#[cfg(all(feature = "raw", target_endian = "little"))]
|
||||
impl<'a, T: bytemuck::Pod> ToBytes<'a> for Raw<'a, T> {
|
||||
type Bytes = &'a [u8];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
Ok(bytemuck::bytes_of(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[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(feature = "raw", target_endian = "big"))]
|
||||
compile_error!("The raw feature is only supported on little endian targets");
|
||||
|
||||
@@ -1,68 +1,14 @@
|
||||
use crate::*;
|
||||
|
||||
pub use extism_convert_macros::FromBytes;
|
||||
|
||||
/// `FromBytes` is used to define how a type should be decoded when working with
|
||||
/// Extism memory. It is used for plugin output and host function input.
|
||||
///
|
||||
/// `FromBytes` can be derived by delegating encoding to generic type implementing
|
||||
/// `FromBytes`, e.g., [`Json`], [`Msgpack`].
|
||||
///
|
||||
/// ```
|
||||
/// use extism_convert::{Json, FromBytes};
|
||||
/// use serde::Deserialize;
|
||||
///
|
||||
/// #[derive(FromBytes, Deserialize, PartialEq, Debug)]
|
||||
/// #[encoding(Json)]
|
||||
/// struct Struct {
|
||||
/// hello: String,
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Struct::from_bytes(br#"{"hello":"hi"}"#)?, Struct { hello: "hi".into() });
|
||||
/// # Ok::<(), extism_convert::Error>(())
|
||||
/// ```
|
||||
///
|
||||
/// Custom encodings can also be used, through new-types with a single generic
|
||||
/// argument, i.e., `Type<T>(T)`, that implement `FromBytesOwned` for the struct.
|
||||
///
|
||||
/// ```
|
||||
/// use std::str::{self, FromStr};
|
||||
/// use std::convert::Infallible;
|
||||
/// use extism_convert::{Error, FromBytes, FromBytesOwned};
|
||||
///
|
||||
/// // Custom serialization using `FromStr`
|
||||
/// struct StringEnc<T>(T);
|
||||
/// impl<T: FromStr> FromBytesOwned for StringEnc<T> where Error: From<<T as FromStr>::Err> {
|
||||
/// fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
|
||||
/// Ok(Self(str::from_utf8(data)?.parse()?))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[derive(FromBytes, PartialEq, Debug)]
|
||||
/// #[encoding(StringEnc)]
|
||||
/// struct Struct {
|
||||
/// hello: String,
|
||||
/// }
|
||||
///
|
||||
/// impl FromStr for Struct {
|
||||
/// type Err = Infallible;
|
||||
/// fn from_str(s: &str) -> Result<Self, Infallible> {
|
||||
/// Ok(Self { hello: s.to_owned() })
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Struct::from_bytes(b"hi")?, Struct { hello: "hi".into() });
|
||||
/// # Ok::<(), extism_convert::Error>(())
|
||||
/// ```
|
||||
pub trait FromBytes<'a>: Sized {
|
||||
/// Decode a value from a slice of bytes
|
||||
fn from_bytes(data: &'a [u8]) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
/// `FromBytesOwned` is similar to [`FromBytes`] but it doesn't borrow from the input slice.
|
||||
/// [`FromBytes`] is automatically implemented for all types that implement `FromBytesOwned`.
|
||||
///
|
||||
/// `FromBytesOwned` can be derived through [`#[derive(FromBytes)]`](FromBytes).
|
||||
/// `FromBytesOwned` is similar to `FromBytes` but it doesn't borrow from the input slice.
|
||||
/// `FromBytes` is automatically implemented for all types that implement `FromBytesOwned`
|
||||
pub trait FromBytesOwned: Sized {
|
||||
/// Decode a value from a slice of bytes, the resulting value should not borrow the input
|
||||
/// data.
|
||||
@@ -141,16 +87,6 @@ impl FromBytesOwned for u32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromBytesOwned for bool {
|
||||
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
|
||||
if let Some(x) = data.first() {
|
||||
Ok(*x != 0)
|
||||
} else {
|
||||
Err(Error::msg("Expected one byte to read boolean value"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromBytesOwned for () {
|
||||
fn from_bytes_owned(_: &[u8]) -> Result<Self, Error> {
|
||||
Ok(())
|
||||
@@ -162,13 +98,3 @@ impl<'a, T: FromBytes<'a>> FromBytes<'a> for std::io::Cursor<T> {
|
||||
Ok(std::io::Cursor::new(T::from_bytes(data)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: FromBytes<'a>> FromBytes<'a> for Option<T> {
|
||||
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
|
||||
if data.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
T::from_bytes(data).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
//! similar to [axum extractors](https://docs.rs/axum/latest/axum/extract/index.html#intro) - they are
|
||||
//! implemented as a tuple struct with a single field that is meant to be extracted using pattern matching.
|
||||
|
||||
// Makes proc-macros able to resolve `::extism_convert` correctly
|
||||
extern crate self as extism_convert;
|
||||
|
||||
pub use anyhow::Error;
|
||||
|
||||
mod encoding;
|
||||
@@ -21,15 +18,9 @@ pub use encoding::{Base64, Json};
|
||||
#[cfg(feature = "msgpack")]
|
||||
pub use encoding::Msgpack;
|
||||
|
||||
#[cfg(feature = "prost")]
|
||||
pub use encoding::Prost;
|
||||
|
||||
#[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;
|
||||
|
||||
@@ -20,7 +20,6 @@ fn roundtrip_json() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "msgpack")]
|
||||
fn roundtrip_msgpack() {
|
||||
let x = Testing {
|
||||
a: "foobar".to_string(),
|
||||
@@ -38,64 +37,3 @@ fn roundtrip_base64() {
|
||||
let Base64(s): Base64<String> = FromBytes::from_bytes(bytes.as_bytes()).unwrap();
|
||||
assert_eq!(s, "this is a test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rountrip_option() {
|
||||
// `None` case
|
||||
let e0: Option<Json<Testing>> = FromBytes::from_bytes(&[]).unwrap();
|
||||
let b = e0.to_bytes().unwrap();
|
||||
let e1: Option<Json<Testing>> = FromBytes::from_bytes(&b).unwrap();
|
||||
assert!(e0.is_none());
|
||||
assert_eq!(e0.is_none(), e1.is_none());
|
||||
|
||||
// `Some` case
|
||||
let x = Testing {
|
||||
a: "foobar".to_string(),
|
||||
b: 123,
|
||||
c: 456.7,
|
||||
};
|
||||
let bytes = Json(&x).to_bytes().unwrap();
|
||||
let y: Option<Json<Testing>> = FromBytes::from_bytes(&bytes).unwrap();
|
||||
let b = ToBytes::to_bytes(&y).unwrap();
|
||||
let z: Option<Json<Testing>> = FromBytes::from_bytes(&b).unwrap();
|
||||
assert_eq!(y.unwrap().0, z.unwrap().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_bool() {
|
||||
// `None` case
|
||||
let a = true.to_bytes().unwrap();
|
||||
let b = false.to_bytes().unwrap();
|
||||
assert_ne!(a, b);
|
||||
|
||||
assert_eq!(a, [1]);
|
||||
assert_eq!(b, [0]);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "raw", target_endian = "little"))]
|
||||
mod raw_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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,7 @@
|
||||
use crate::*;
|
||||
|
||||
pub use extism_convert_macros::ToBytes;
|
||||
|
||||
/// `ToBytes` is used to define how a type should be encoded when working with
|
||||
/// Extism memory. It is used for plugin input and host function output.
|
||||
///
|
||||
/// `ToBytes` can be derived by delegating encoding to generic type implementing
|
||||
/// `ToBytes`, e.g., [`Json`], [`Msgpack`].
|
||||
///
|
||||
/// ```
|
||||
/// use extism_convert::{Json, ToBytes};
|
||||
/// use serde::Serialize;
|
||||
///
|
||||
/// #[derive(ToBytes, Serialize)]
|
||||
/// #[encoding(Json)]
|
||||
/// struct Struct {
|
||||
/// hello: String,
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Struct { hello: "hi".into() }.to_bytes()?, br#"{"hello":"hi"}"#);
|
||||
/// # Ok::<(), extism_convert::Error>(())
|
||||
/// ```
|
||||
///
|
||||
/// But custom types can also be used, as long as they are new-types with a single
|
||||
/// generic argument, i.e., `Type<T>(T)`, that implement `ToBytes` for the struct.
|
||||
///
|
||||
/// ```
|
||||
/// use extism_convert::{Error, ToBytes};
|
||||
///
|
||||
/// // Custom serialization using `ToString`
|
||||
/// struct StringEnc<T>(T);
|
||||
/// impl<T: ToString> ToBytes<'_> for StringEnc<&T> {
|
||||
/// type Bytes = String;
|
||||
///
|
||||
/// fn to_bytes(&self) -> Result<String, Error> {
|
||||
/// Ok(self.0.to_string())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[derive(ToBytes)]
|
||||
/// #[encoding(StringEnc)]
|
||||
/// struct Struct {
|
||||
/// hello: String,
|
||||
/// }
|
||||
///
|
||||
/// impl ToString for Struct {
|
||||
/// fn to_string(&self) -> String {
|
||||
/// self.hello.clone()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Struct { hello: "hi".into() }.to_bytes()?, b"hi");
|
||||
/// # Ok::<(), Error>(())
|
||||
/// ```
|
||||
pub trait ToBytes<'a> {
|
||||
/// A configurable byte slice representation, allows any type that implements `AsRef<[u8]>`
|
||||
type Bytes: AsRef<[u8]>;
|
||||
@@ -61,21 +10,21 @@ pub trait ToBytes<'a> {
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error>;
|
||||
}
|
||||
|
||||
impl ToBytes<'_> for () {
|
||||
impl<'a> ToBytes<'a> for () {
|
||||
type Bytes = [u8; 0];
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
Ok([])
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes<'_> for Vec<u8> {
|
||||
impl<'a> ToBytes<'a> for Vec<u8> {
|
||||
type Bytes = Vec<u8>;
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
Ok(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes<'_> for String {
|
||||
impl<'a> ToBytes<'a> for String {
|
||||
type Bytes = String;
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
Ok(self.clone())
|
||||
@@ -96,7 +45,7 @@ impl<'a> ToBytes<'a> for &'a str {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes<'_> for f64 {
|
||||
impl<'a> ToBytes<'a> for f64 {
|
||||
type Bytes = [u8; 8];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -104,7 +53,7 @@ impl ToBytes<'_> for f64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes<'_> for f32 {
|
||||
impl<'a> ToBytes<'a> for f32 {
|
||||
type Bytes = [u8; 4];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -112,7 +61,7 @@ impl ToBytes<'_> for f32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes<'_> for i64 {
|
||||
impl<'a> ToBytes<'a> for i64 {
|
||||
type Bytes = [u8; 8];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -120,7 +69,7 @@ impl ToBytes<'_> for i64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes<'_> for i32 {
|
||||
impl<'a> ToBytes<'a> for i32 {
|
||||
type Bytes = [u8; 4];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -128,7 +77,7 @@ impl ToBytes<'_> for i32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes<'_> for u64 {
|
||||
impl<'a> ToBytes<'a> for u64 {
|
||||
type Bytes = [u8; 8];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -136,7 +85,7 @@ impl ToBytes<'_> for u64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes<'_> for u32 {
|
||||
impl<'a> ToBytes<'a> for u32 {
|
||||
type Bytes = [u8; 4];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -144,14 +93,6 @@ impl ToBytes<'_> for u32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes<'_> for bool {
|
||||
type Bytes = [u8; 1];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
Ok([*self as u8])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ToBytes<'a>> ToBytes<'a> for &'a T {
|
||||
type Bytes = T::Bytes;
|
||||
|
||||
@@ -159,26 +100,3 @@ impl<'a, T: ToBytes<'a>> ToBytes<'a> for &'a T {
|
||||
<T as ToBytes>::to_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ToBytes<'a>> ToBytes<'a> for Option<T> {
|
||||
type Bytes = Vec<u8>;
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
match self {
|
||||
Some(x) => x.to_bytes().map(|x| x.as_ref().to_vec()),
|
||||
None => Ok(vec![]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use extism_convert::{Json, ToBytes};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(ToBytes, Serialize)]
|
||||
#[encoding(Json)]
|
||||
struct Struct {
|
||||
hello: String,
|
||||
}
|
||||
}
|
||||
|
||||
16
cpp/Makefile
Normal file
16
cpp/Makefile
Normal 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
44
cpp/example.cpp
Normal 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
458
cpp/extism.hpp
Normal 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
117
cpp/test/test.cpp
Normal 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> ¶ms,
|
||||
std::vector<Val> &results, void *user_data) {
|
||||
auto offs = plugin.alloc(4);
|
||||
memcpy(plugin.memory() + offs, "test", 4);
|
||||
results[0].v.i64 = (int64_t)offs;
|
||||
});
|
||||
auto functions = std::vector<Function>{
|
||||
hello_world,
|
||||
};
|
||||
Plugin plugin(wasm, true, functions);
|
||||
auto buf = plugin.call("count_vowels", "aaa");
|
||||
ASSERT_EQ(buf.length, 4);
|
||||
ASSERT_EQ((std::string)buf, "test");
|
||||
}
|
||||
|
||||
void callThread(Plugin *plugin) {
|
||||
auto buf = plugin->call("count_vowels", "aaa").string();
|
||||
ASSERT_EQ(buf.size(), 10);
|
||||
ASSERT_EQ(buf, "testing123");
|
||||
}
|
||||
|
||||
TEST(Plugin, MultipleThreads) {
|
||||
auto wasm = read("../../wasm/code-functions.wasm");
|
||||
auto t = std::vector<ValType>{ValType::I64};
|
||||
Function hello_world =
|
||||
Function("hello_world", t, t,
|
||||
[](CurrentPlugin plugin, const std::vector<Val> ¶ms,
|
||||
std::vector<Val> &results, void *user_data) {
|
||||
auto offs = plugin.alloc(10);
|
||||
memcpy(plugin.memory() + offs, "testing123", 10);
|
||||
results[0].v.i64 = (int64_t)offs;
|
||||
});
|
||||
auto functions = std::vector<Function>{
|
||||
hello_world,
|
||||
};
|
||||
Plugin plugin(wasm, true, functions);
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
threads.push_back(std::thread(callThread, &plugin));
|
||||
}
|
||||
|
||||
for (auto &th : threads) {
|
||||
th.join();
|
||||
}
|
||||
}
|
||||
|
||||
}; // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
7
d/.gitignore
vendored
Normal file
7
d/.gitignore
vendored
Normal 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
4
d/README.md
Normal 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
17
d/examples/hello/.gitignore
vendored
Normal 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
15
d/examples/hello/dub.json
Normal 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": "../../../"
|
||||
}
|
||||
}
|
||||
}
|
||||
33
d/examples/hello/source/app.d
Normal file
33
d/examples/hello/source/app.d
Normal 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
258
d/extism.d
Normal 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
1
d/runtime.c
Normal file
@@ -0,0 +1 @@
|
||||
#include "runtime/extism.h"
|
||||
9
nuget/.gitignore → dotnet/.gitignore
vendored
9
nuget/.gitignore → dotnet/.gitignore
vendored
@@ -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
37
dotnet/Extism.sln
Normal 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
4
dotnet/README.md
Normal 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).
|
||||
|
||||
@@ -12,10 +12,6 @@
|
||||
<Deterministic>true</Deterministic>
|
||||
<NoBuild>true</NoBuild>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
|
||||
<MinVerIgnoreHeight>true</MinVerIgnoreHeight>
|
||||
<MinVerTagPrefix>v</MinVerTagPrefix>
|
||||
<MinVerVerbosity>normal</MinVerVerbosity>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -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>
|
||||
@@ -11,5 +11,5 @@
|
||||
Pack="true"
|
||||
PackagePath="runtimes\win-x64\native\extism.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
66
dotnet/nuget/Nuget.sln
Normal file
66
dotnet/nuget/Nuget.sln
Normal 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
|
||||
@@ -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).
|
||||
1
dotnet/nuget/runtimes/expected.txt
Normal file
1
dotnet/nuget/runtimes/expected.txt
Normal file
@@ -0,0 +1 @@
|
||||
win-x64.dll
|
||||
29
dotnet/samples/Extism.Sdk.Sample/Extism.Sdk.Sample.csproj
Normal file
29
dotnet/samples/Extism.Sdk.Sample/Extism.Sdk.Sample.csproj
Normal 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>
|
||||
38
dotnet/samples/Extism.Sdk.Sample/Program.cs
Normal file
38
dotnet/samples/Extism.Sdk.Sample/Program.cs
Normal 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}");
|
||||
|
||||
5
dotnet/samples/Extism.Sdk.Sample/README.md
Normal file
5
dotnet/samples/Extism.Sdk.Sample/README.md
Normal 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.**
|
||||
24
dotnet/src/Directory.build.props
Normal file
24
dotnet/src/Directory.build.props
Normal 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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user