Compare commits

..

2 Commits

Author SHA1 Message Date
Steve Manuel
5c31215ce7 update sdk coverage script to fully include elixir
Co-authored-by: zach <zach@dylib.so>
2022-10-25 20:06:34 -06:00
Benjamin Eckel
fc95a99e40 test(elixir-sdk): Add Elixir to SDK coverage script 2022-10-25 17:34:52 -05:00
183 changed files with 6952 additions and 8823 deletions

View File

@@ -1,30 +0,0 @@
on: [workflow_call]
name: libextism
runs:
using: composite
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Cache Rust environment
uses: Swatinem/rust-cache@v1
- name: Cache libextism
id: cache-libextism
uses: actions/cache@v3
with:
path: target/**
key: ${{ runner.os }}-libextism-${{ hashFiles('runtime/**') }}-${{ hashFiles('manifest/**') }}-${{ hashFiles('libextism/**') }}
- name: Build
if: steps.cache-libextism.outputs.cache-hit != 'true'
shell: bash
run: cargo build --release -p libextism
- name: Install extism shared library
shell: bash
run: |
sudo make install

View File

@@ -1,41 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "cargo" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "pip"
directory: "python"
schedule:
interval: "weekly"
- package-ecosystem: "mix"
directory: "elixir"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "node"
schedule:
interval: "weekly"
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "bundler"
directory: "ruby"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -1,25 +1,18 @@
on:
pull_request:
paths:
- .github/actions/extism/**
- .github/workflows/ci-rust.yml
- convert/**
- manifest/**
- runtime/**
- rust/**
- libextism/**
workflow_dispatch:
on: [push, pull_request]
name: Rust CI
name: CI
env:
RUNTIME_CRATE: extism
LIBEXTISM_CRATE: libextism
RUNTIME_MANIFEST: runtime/Cargo.toml
RUNTIME_CRATE: extism-runtime
RUST_SDK_CRATE: extism
jobs:
lib:
name: Extism runtime lib
build_and_test:
name: Build & Test
runs-on: ${{ matrix.os }}
env:
MIX_ENV: test
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
@@ -28,92 +21,151 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: ${{ matrix.rust }}
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
run: cargo fmt --check -p ${{ env.RUNTIME_CRATE }}
- name: Build
run: cargo build --release -p ${{ env.RUNTIME_CRATE }}
- name: Lint
run: cargo clippy --all --release --all-features --no-deps -- -D "clippy::all"
run: cargo clippy --release --no-deps -p ${{ env.RUNTIME_CRATE }}
- 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
run: cargo test --all-features --release -p ${{ env.RUNTIME_CRATE }}
- name: Install extism shared library
shell: bash
run: sudo make install
- name: Setup Elixir Host SDK
if: ${{ runner.os != 'macOS' }}
uses: erlef/setup-beam@v1
with:
experimental-otp: true
otp-version: '25.0.4'
elixir-version: '1.14.0'
- name: Test Elixir Host SDK
if: ${{ runner.os != 'macOS' }}
run: |
cd elixir
LD_LIBRARY_PATH=/usr/local/lib mix do deps.get, test
- name: Setup Go env
uses: actions/setup-go@v3
- name: Test Go Host SDK
run: |
go version
cd go && LD_LIBRARY_PATH=/usr/local/lib go run main.go
- name: Setup Python env
uses: actions/setup-python@v4
with:
python-version: "3.9"
check-latest: true
- name: Install Poetry
uses: snok/install-poetry@v1
- name: Test Python Host SDK
run: |
cd python
poetry install
poetry run python example.py
poetry run python -m unittest discover
- name: Setup Ruby env
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.0"
- name: Test Ruby Host SDK
run: |
cd ruby
bundle install
ruby example.rb
rake test
- name: Setup Node env
uses: actions/setup-node@v3
with:
node-version: 16
- name: Test Node Host SDK
run: |
cd node
npm i
npm run build
LD_LIBRARY_PATH=/usr/local/lib node example.js
- name: Test Rust Host SDK
run: LD_LIBRARY_PATH=/usr/local/lib cargo test --release -p ${{ env.RUST_SDK_CRATE }}
# - name: Setup OCaml env
# uses: ocaml/setup-ocaml@v2
# - name: Test OCaml Host SDK
# run: |
# opam install -y .
# cd ocaml
# opam exec -- dune exec extism
- name: Setup Haskell env
uses: haskell/actions/setup@v2
with:
enable-stack: true
stack-version: "latest"
- name: Test Haskell SDK
run: |
cd haskell
LD_LIBRARY_PATH=/usr/local/lib stack test
- name: Setup PHP env
uses: shivammathur/setup-php@v2
with:
php-version: "8.1"
extensions: ffi
tools: composer
env:
fail-fast: true
- name: Test PHP SDK
run: |
cd php/example
composer install
php index.php
sdk_api_coverage:
name: SDK API Coverage Report
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: 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 target
id: cache-target
uses: actions/cache@v3
with:
path: target/**
key: ${{ runner.os }}-target-${{ github.sha }}
- run: cargo install cargo-criterion
- run: cargo criterion
python-version: "3.9"
check-latest: true
- name: Install dependencies
run: |
sudo apt-get install ripgrep
pip3 install pycparser
- name: Run coverage script
id: coverage
run: |
python scripts/sdk_coverage.py

View File

@@ -1,47 +0,0 @@
on:
workflow_dispatch:
pull_request:
paths:
- kernel/**
name: Kernel
jobs:
kernel:
name: Build extism-runtime.wasm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
target: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- name: Install deps
run: |
sudo apt install wabt --yes
- name: Build kernel
shell: bash
continue-on-error: true
run: |
cd kernel
sh build.sh
git diff --exit-code
export GIT_EXIT_CODE=$?
- uses: peter-evans/create-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: ${{ env.GIT_EXIT_CODE }} != 0
with:
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 }}"
branch: "update-kernel--${{ github.event.pull_request.head.ref }}"
commit-message: "update(kernel): extism-runtime.wasm in ${{ github.event.pull_request.head.ref }}"
delete-branch: true

View File

@@ -1,70 +0,0 @@
on:
workflow_dispatch:
name: Release .NET Native NuGet Packages
jobs:
release-runtimes:
name: release-dotnet-native
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
filter: tree:0
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v3.0.3
with:
dotnet-version: 7.x
- uses: dawidd6/action-download-artifact@v2
with:
workflow: release.yml
name: release-artifacts
- name: Extract Archive
run: |
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 \;
- 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 }}

View File

@@ -1,41 +0,0 @@
name: Release Python SDK
on:
release:
types: [published, edited]
jobs:
release-sdks:
name: release-python
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install poetry
run: pipx install poetry
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: install twine
run: |
pip install twine
- name: download release
run: |
tag='${{ github.ref }}'
tag="${tag/refs\/tags\//}"
mkdir dist
cd dist
gh release download "$tag" -p 'extism_sys-*'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: upload release
run: |
twine upload dist/*
env:
TWINE_USERNAME: ${{ secrets.PYPI_API_USER }}
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

View File

@@ -1,73 +0,0 @@
on:
release:
types: [published, edited]
workflow_dispatch:
name: Release Runtime/Rust SDK
jobs:
release-runtime:
name: release-rust
runs-on: ubuntu-latest
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
with:
toolchain: stable
profile: minimal
override: true
target: ${{ matrix.target }}
- name: Release Rust Convert Crate
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |
version=$(cargo metadata --format-version=1 | jq -r '.packages[] | select(.name == "extism") | .version')
if ! &>/dev/null curl -sLIf https://crates.io/api/v1/crates/extism-convert/${version}/download; then
cargo publish --manifest-path convert/Cargo.toml --allow-dirty
else
echo "already published ${version}"
fi
- name: Release Rust Manifest Crate
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |
version=$(cargo metadata --format-version=1 | jq -r '.packages[] | select(.name == "extism") | .version')
if ! &>/dev/null curl -sLIf https://crates.io/api/v1/crates/extism-manifest/${version}/download; then
cargo publish --manifest-path manifest/Cargo.toml --allow-dirty
else
echo "already published ${version}"
fi
- name: Release Runtime
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |
version=$(cargo metadata --format-version=1 | jq -r '.packages[] | select(.name == "extism") | .version')
if ! &>/dev/null curl -sLIf https://crates.io/api/v1/crates/extism/${version}/download; then
cargo publish --manifest-path runtime/Cargo.toml --allow-dirty
else
echo "already published ${version}"
fi

View File

@@ -1,103 +1,33 @@
on:
workflow_dispatch:
push:
branches: [ main, "v*" ]
tags:
- 'v*'
release:
types: [created]
name: Release
env:
RUNTIME_MANIFEST: runtime/Cargo.toml
RUNTIME_CRATE: libextism
RUNTIME_CRATE: extism-runtime
RUSTFLAGS: -C target-feature=-crt-static
ARTIFACT_DIR: release-artifacts
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
release:
name: ${{ matrix.os }} ${{ matrix.target }}
runs-on: ${{ matrix.os }}-latest
release-linux:
name: linux
runs-on: ubuntu-latest
strategy:
matrix:
include:
- os: 'macos'
target: 'x86_64-apple-darwin'
artifact: 'libextism.dylib'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'macos'
target: 'aarch64-apple-darwin'
artifact: 'libextism.dylib'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'aarch64-unknown-linux-gnu'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'aarch64-unknown-linux-musl'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'x86_64-unknown-linux-gnu'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'x86_64-unknown-linux-musl'
artifact: ''
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: ''
static-pc-in: 'extism-static.pc.in'
- os: 'windows'
target: 'x86_64-pc-windows-gnu'
artifact: 'extism.dll'
static-artifact: 'libextism.a'
static-dll-artifact: 'libextism.dll.a'
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'windows'
target: 'x86_64-pc-windows-msvc'
artifact: 'extism.dll'
static-artifact: 'extism.lib'
static-dll-artifact: 'extism.dll.lib'
pc-in: ''
static-pc-in: ''
target:
[
aarch64-unknown-linux-gnu,
aarch64-unknown-linux-musl,
x86_64-unknown-linux-gnu,
]
# i686-unknown-linux-gnu,
if: always()
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set version
shell: bash
run: |
version="${{ github.ref }}"
if [[ "$version" = "refs/heads/main" ]]; then
version="0.0.0-dev"
else
version="${version/refs\/tags\/v/}"
fi
sed -i -e "s/0.0.0+replaced-by-ci/${version}/g" Cargo.toml
pyproject="$(cat extism-maturin/pyproject.toml)"
<<<"$pyproject" >extism-maturin/pyproject.toml sed -e 's/^version = "0.0.0.replaced-by-ci"/version = "'"$version"'"/g'
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
@@ -106,55 +36,16 @@ jobs:
override: true
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
prefix-key: "${{matrix.os}}-${{matrix.target}}"
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-on-failure: "true"
- name: Build Target (${{ matrix.os }} ${{ matrix.target }})
- name: Build Target (${{ matrix.target }})
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.os != 'windows' }}
use-cross: true
command: build
args: --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Build wheels
uses: PyO3/maturin-action@v1
# maturin's cffi integration struggles with gnu headers on windows.
# there's partial work towards fixing this in `extism-maturin/build.rs`, but it's
# not sufficient to get it to work. omit it for now!
if: ${{ matrix.target != 'x86_64-pc-windows-gnu' && matrix.target != 'aarch64-unknown-linux-gnu' }}
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter -m extism-maturin/Cargo.toml
sccache: 'true'
manylinux: auto
- name: Build GNU Linux wheels
uses: PyO3/maturin-action@v1
# One of our deps, "ring", needs a newer sysroot than what "manylinux: auto" provides.
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter -m extism-maturin/Cargo.toml
sccache: 'true'
manylinux: 2_28
- name: Add pkg-config files except on MSVC
if: ${{ matrix.target != 'x86_64-pc-windows-msvc' }}
shell: bash
run: |
SRC_DIR=target/${{ matrix.target }}/release
cp libextism/extism*.pc.in ${SRC_DIR}
- name: Prepare Artifact
shell: bash
run: |
EXT=so
SRC_DIR=target/${{ matrix.target }}/release
DEST_DIR=${{ env.ARTIFACT_DIR }}
RELEASE_NAME=libextism-${{ matrix.target }}-${{ github.ref_name }}
@@ -164,63 +55,270 @@ jobs:
# compress the shared library & create checksum
cp runtime/extism.h ${SRC_DIR}
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 }}
tar -C ${SRC_DIR} -czvf ${ARCHIVE} libextism.${EXT} extism.h
ls -ll ${ARCHIVE}
if &>/dev/null which shasum; then
shasum -a 256 ${ARCHIVE} > ${CHECKSUM}
else
# windows doesn't have shasum available, so we use certutil instead.
certutil -hashfile ${ARCHIVE} SHA256 >${CHECKSUM}
fi
shasum -a 256 ${ARCHIVE} > ${CHECKSUM}
# copy archive and checksum into release artifact directory
mkdir -p ${DEST_DIR}
cp ${ARCHIVE} ${DEST_DIR}
cp ${CHECKSUM} ${DEST_DIR}
# copy any built wheels.
if [ -e dist/*.whl ]; then
cp dist/*.whl ${DEST_DIR}
fi
ls -ll ${DEST_DIR}
ls ${DEST_DIR}
- name: Upload Artifact to Summary
uses: actions/upload-artifact@v3
with:
name: ${{ env.ARTIFACT_DIR }}
path: ${{ env.ARTIFACT_DIR }}
path: |
*.tar.gz
*.txt
- name: Upload Artifact to Draft Release
- name: Upload Artifact to Release
uses: softprops/action-gh-release@v1
with:
draft: true
files: |
${{ env.ARTIFACT_DIR }}/*
if: startsWith(github.ref, 'refs/tags/')
release-latest:
name: create latest release
runs-on: ubuntu-latest
needs: [release]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/download-artifact@v3
with:
name: ${{ env.ARTIFACT_DIR }}
- uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
automatic_release_tag: "latest"
prerelease: true
title: "Development Build"
files: |
*.tar.gz
*.txt
*.whl
if: github.ref == 'refs/heads/main'
release-macos:
name: macos
runs-on: macos-latest
strategy:
matrix:
target: [x86_64-apple-darwin, aarch64-apple-darwin]
if: always()
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
target: ${{ matrix.target }}
- name: Build Target (${{ matrix.target }})
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
- name: Prepare Artifact
run: |
EXT=dylib
SRC_DIR=target/${{ matrix.target }}/release
DEST_DIR=${{ env.ARTIFACT_DIR }}
RELEASE_NAME=libextism-${{ matrix.target }}-${{ github.ref_name }}
ARCHIVE=${RELEASE_NAME}.tar.gz
CHECKSUM=${RELEASE_NAME}.checksum.txt
# compress the shared library & create checksum
cp runtime/extism.h ${SRC_DIR}
cp LICENSE ${SRC_DIR}
tar -C ${SRC_DIR} -czvf ${ARCHIVE} libextism.${EXT} extism.h
ls -ll ${ARCHIVE}
shasum -a 256 ${ARCHIVE} > ${CHECKSUM}
# copy archive and checksum into release artifact directory
mkdir -p ${DEST_DIR}
cp ${ARCHIVE} ${DEST_DIR}
cp ${CHECKSUM} ${DEST_DIR}
ls ${DEST_DIR}
- name: Upload Artifact to Summary
uses: actions/upload-artifact@v3
with:
name: ${{ env.ARTIFACT_DIR }}
path: |
*.tar.gz
*.txt
- name: Upload Artifact to Release
uses: softprops/action-gh-release@v1
with:
files: |
*.tar.gz
*.txt
release-windows:
name: windows
runs-on: windows-latest
strategy:
matrix:
target:
[x86_64-pc-windows-gnu, x86_64-pc-windows-msvc]
# i686-pc-windows-gnu,
# i686-pc-windows-msvc,
# aarch64-pc-windows-msvc
if: always()
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
target: ${{ matrix.target }}
- name: Build Target (${{ matrix.target }})
uses: actions-rs/cargo@v1
with:
command: build
args: --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
- name: Prepare Artifact
shell: bash
run: |
EXT=dll
SRC_DIR=target/${{ matrix.target }}/release
DEST_DIR=${{ env.ARTIFACT_DIR }}
RELEASE_NAME=libextism-${{ matrix.target }}-${{ github.ref_name }}
ARCHIVE=${RELEASE_NAME}.tar.gz
CHECKSUM=${RELEASE_NAME}.checksum.txt
# compress the shared library & create checksum
cp runtime/extism.h ${SRC_DIR}
cp LICENSE ${SRC_DIR}
tar -C ${SRC_DIR} -czvf ${ARCHIVE} extism.${EXT} extism.h
ls -ll ${ARCHIVE}
certutil -hashfile ${ARCHIVE} SHA256 >${CHECKSUM}
# copy archive and checksum into release artifact directory
mkdir -p ${DEST_DIR}
cp ${ARCHIVE} ${DEST_DIR}
cp ${CHECKSUM} ${DEST_DIR}
ls ${DEST_DIR}
- name: Upload Artifact to Summary
uses: actions/upload-artifact@v3
with:
name: ${{ env.ARTIFACT_DIR }}
path: |
*.tar.gz
*.txt
- name: Upload Artifact to Release
uses: softprops/action-gh-release@v1
with:
files: |
*.tar.gz
*.txt
release-sdks:
needs: [release-linux, release-macos] # release-windows
name: publish-sdks
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node env
uses: actions/setup-node@v3
with:
node-version: 16
registry-url: "https://registry.npmjs.org"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
CI: true
- name: Release Node Host SDK
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
CI: true
run: |
cd node
npm publish
- name: Setup Rust env
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
target: ${{ matrix.target }}
- name: Release Rust Host SDK
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |
# order of crate publication matter: manifest, runtime, rust
cargo publish --manifest-path manifest/Cargo.toml
# allow for crates.io to update so dependant crates can locate extism-manifest
sleep 5
cargo publish --manifest-path runtime/Cargo.toml --no-verify
cargo publish --manifest-path rust/Cargo.toml
- name: Setup Elixir Host SDK
uses: erlef/setup-beam@v1
with:
experimental-otp: true
otp-version: '25.0.4'
elixir-version: '1.14.0'
- name: Publish Elixir Host SDK to hex.pm
env:
HEX_API_KEY: ${{ secrets.HEX_PM_API_TOKEN }}
run: |
cd elixir
cp ../LICENSE .
mix do deps.get, deps.compile
mix hex.build
mix hex.publish --yes
- name: Setup Ruby
uses: actions/setup-ruby@v1
with:
ruby-version: '3.1' # Version range or exact version of a Ruby version to use, using semvers version range syntax.
- name: Publish Ruby Gem
env:
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_TOKEN }}
run: |
cd ruby
gem build extism.gemspec
gem push extism*.gem -k $RUBYGEMS_API_KEY
- name: Publish Elixir Host SDK to hex.pm
env:
HEX_API_KEY: ${{ secrets.HEX_PM_API_TOKEN }}
run: |
cd elixir
mix do deps.get, deps.compile
mix hex.build
mix hex.publish --yes
- name: Setup Python env
uses: actions/setup-python@v4
with:
python-version: "3.9"
check-latest: true
- name: Build Python Host SDK
run: |
pushd python
python3 -m pip install --upgrade build
cp ../LICENSE .
cp ../README.md .
python3 -m poetry build
popd
- name: Release Python Host SDK
uses: pypa/gh-action-pypi-publish@release/v1
env:
INPUT_VERIFY_METADATA: false
with:
user: ${{ secrets.PYPI_API_USER }}
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: python/dist/

25
.gitignore vendored
View File

@@ -13,10 +13,6 @@ __pycache__
python/dist
python/poetry.lock
c/main
cpp/test/test
cpp/example
.dub
dub.selections.json
go/main
ruby/.bundle/
ruby/.yardoc
@@ -27,25 +23,12 @@ ruby/pkg/
ruby/spec/reports/
ruby/tmp/
ruby/Gemfile.lock
cpp/example
rust/target
rust/test.log
duniverse
_build
ocaml/duniverse
ocaml/_build
php/Extism.php
python/docs
dist-newstyle
.stack-work
vendor
zig/zig-*
zig/example-out/
zig/*.log
java/*.iml
java/*.log
java/.idea
java/.DS_Store
extism-maturin/src/extism.h
runtime/*.log
libextism/example
libextism/extism*.pc
*.cwasm
test-cache
vendor

View File

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

View File

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

View File

@@ -1,9 +1,7 @@
DEST?=/usr/local
SOEXT=so
AEXT=a
FEATURES?=default
DEFAULT_FEATURES?=yes
RUST_TARGET?=
UNAME := $(shell uname -s)
ifeq ($(UNAME),Darwin)
@@ -20,43 +18,20 @@ else
FEATURE_FLAGS=--features $(FEATURES)
endif
ifeq ($(RUST_TARGET),)
TARGET_FLAGS=
else
TARGET_FLAGS=--target $(RUST_TARGET)
endif
build:
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml $(TARGET_FLAGS)
sed -e "s%@CMAKE_INSTALL_PREFIX@%$(DEST)%" libextism/extism.pc.in > libextism/extism.pc
sed -e "s%@CMAKE_INSTALL_PREFIX@%$(DEST)%" libextism/extism-static.pc.in > libextism/extism-static.pc
bench:
@(cargo criterion $(TARGET_FLAGS) || echo 'For nicer output use cargo-criterion: `cargo install cargo-criterion` - using `cargo bench`') && cargo bench $(TARGET_FLAGS)
.PHONY: kernel
kernel:
cd kernel && bash build.sh
.PHONY: build
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
build:
cargo build --release $(FEATURE_FLAGS) --manifest-path runtime/Cargo.toml
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 libextism/extism.pc $(DEST)/lib/pkgconfig/extism.pc
install libextism/extism-static.pc $(DEST)/lib/pkgconfig/extism-static.pc
install runtime/extism.h $(DEST)/include
install target/release/libextism.$(SOEXT) $(DEST)/lib
uninstall:
rm -f $(DEST)/include/extism.h $(DEST)/lib/libextism.$(SOEXT) $(DEST)/lib/libextism.$(AEXT) \
$(DEST)/lib/pkgconfig/extism*.pc
rm -f $(DEST)/include/extism.h $(DEST)/lib/libextism.$(SOEXT)

View File

@@ -1,32 +1,21 @@
# [Extism](https://extism.org)
### _Welcome!_
**Please note:** this project still under active development. It's usable, but expect some rough edges while work is underway. If you're interested in working on or building with Extism, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know - we are happy to help get you started.
[![Discord](https://img.shields.io/discord/1011124058408112148?color=%23404eed&label=Community%20Chat&logo=Discord&logoColor=%23404eed)](https://discord.gg/cx3usBCWnc)
![GitHub Org's stars](https://img.shields.io/github/stars/extism)
![GitHub all releases](https://img.shields.io/github/downloads/extism/extism/total)
![GitHub License](https://img.shields.io/github/license/extism/extism)
![GitHub release (with filter)](https://img.shields.io/github/v/release/extism/extism)
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),
&amp; more (others coming soon).
# [Extism](https://extism.org)
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).
The universal plug-in system. Run WebAssembly extensions inside your app. Use idiomatic Host SDKs for [Go](https://extism.org/docs/integrate-into-your-codebase/go-host-sdk),
[Ruby](https://extism.org/docs/integrate-into-your-codebase/ruby-host-sdk), [Python](https://extism.org/docs/integrate-into-your-codebase/python-host-sdk),
[Node](https://extism.org/docs/integrate-into-your-codebase/node-host-sdk), [Rust](https://extism.org/docs/integrate-into-your-codebase/rust-host-sdk),
[C](https://extism.org/docs/integrate-into-your-codebase/c-host-sdk), [C++](https://extism.org/docs/integrate-into-your-codebase/cpp-host-sdk),
[OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk), [Haskell](https://extism.org/docs/integrate-into-your-codebase/haskell-host-sdk), [PHP](https://extism.org/docs/integrate-into-your-codebase/php-host-sdk) &amp; more (others coming soon).
Plug-in development kits (PDK) for plug-in authors supported in [Rust](https://github.com/extism/rust-pdk), [AssemblyScript](https://github.com/extism/assemblyscript-pdk), [Go](https://github.com/extism/go-pdk), [C/C++](https://github.com/extism/c-pdk).
<p align="center">
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/210286900-39b144fd-1b26-4dd0-b7a9-2b5755bc174d.png" alt="Extism embedded SDK language support"/>
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/191437220-4030840d-1a9e-47b9-a44a-39a004885308.png"/>
</p>
Add a flexible, secure, and _bLaZiNg FaSt_ plug-in system to your project. Server, desktop, mobile, web, database -- you name it. Enable users to write and execute safe extensions to your software in **3 easy steps:**
@@ -43,13 +32,7 @@ Identify the place(s) in your code where some arbitrary logic should run (the pl
Load WebAssembly modules at any time in your app's lifetime and Extism will execute them in a secure sandbox, fully isolated from your program's memory.
# API Status
**Please note:** This project still under active development and APIs are still changing. We are aiming for a stable 1.0 release in January, 2024.
The main branch may have breaking changes until that point, but if you starting today, a 1.0.0-rcx release is the best place to start.
If you experience any problems or have any questions, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know.
Our community is very responsive and happy to help get you started.
---
## Usage
@@ -68,8 +51,8 @@ The easiest way to start would be to join the [Discord](https://discord.gg/cx3us
Extism is an open-source product from the team at:
<p align="left">
<a href="https://dylib.so" _target="blanks"><img width="200px" src="https://user-images.githubusercontent.com/7517515/198204119-5afdebb9-a5d8-4322-bd2a-46179c8d7b24.svg"/></a>
</p>
<a href="https://dylib.so" _target="blanks"><img width="200px" src="https://user-images.githubusercontent.com/7517515/195408048-0f199b26-cba3-4635-b683-f43b1e610c82.svg"/></a>
</p>![dylibso-logo-outline]
_Reach out and tell us what you're building! We'd love to help._

2
c/Makefile Normal file
View File

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

60
c/main.c Normal file
View File

@@ -0,0 +1,60 @@
#include "../runtime/extism.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
uint8_t *read_file(const char *filename, size_t *len) {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
return NULL;
}
fseek(fp, 0, SEEK_END);
size_t length = ftell(fp);
fseek(fp, 0, SEEK_SET);
uint8_t *data = malloc(length);
if (data == NULL) {
return NULL;
}
assert(fread(data, 1, length, fp) == length);
fclose(fp);
*len = length;
return data;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fputs("Not enough arguments\n", stderr);
exit(1);
}
ExtismContext *ctx = extism_context_new();
size_t len = 0;
uint8_t *data = read_file("../wasm/code.wasm", &len);
ExtismPlugin plugin = extism_plugin_new(ctx, data, len, false);
free(data);
if (plugin < 0) {
exit(1);
}
assert(extism_plugin_call(ctx, plugin, "count_vowels", (uint8_t *)argv[1],
strlen(argv[1])) == 0);
ExtismSize out_len = extism_plugin_output_length(ctx, plugin);
const uint8_t *output = extism_plugin_output_data(ctx, plugin);
write(STDOUT_FILENO, output, out_len);
write(STDOUT_FILENO, "\n", 1);
extism_plugin_free(ctx, plugin);
extism_context_free(ctx);
return 0;
}

50
composer.json Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "extism/extism",
"description": "Make your software programmable. Run WebAssembly extensions in your app using the first off-the-shelf, universal plug-in system.",
"license": "BSD-3-Clause",
"type": "library",
"keywords": [
"WebAssembly",
"plugin-system",
"runtime",
"plug-in"
],
"authors": [
{
"name": "The Extism Authors",
"email": "oss@extism.org",
"homepage": "https://extism.org"
},
{
"name": "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/Plugin.php",
"php/src/generate.php",
"php/src/extism.h"
]
},
"autoload-dev": {
"psr-4": {}
},
"config": {
"sort-packages": true
},
"extra": {},
"scripts": {},
"scripts-descriptions": {}
}

163
composer.lock generated Normal file
View File

@@ -0,0 +1,163 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0e0352cd3a96e03fd9c964888deedb29",
"packages": [
{
"name": "ircmaxell/ffime",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/ircmaxell/FFIMe.git",
"reference": "5f648f95ecf23262a2e58f4e4c9001bd1b5f9c98"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ircmaxell/FFIMe/zipball/5f648f95ecf23262a2e58f4e4c9001bd1b5f9c98",
"reference": "5f648f95ecf23262a2e58f4e4c9001bd1b5f9c98",
"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": "2022-09-01T18:56:19+00:00"
},
{
"name": "ircmaxell/php-c-parser",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/ircmaxell/php-c-parser.git",
"reference": "55e0a4fdf88d6e955d928860e1e107a68492c1cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ircmaxell/php-c-parser/zipball/55e0a4fdf88d6e955d928860e1e107a68492c1cf",
"reference": "55e0a4fdf88d6e955d928860e1e107a68492c1cf",
"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": "2022-08-27T17:37:14+00:00"
},
{
"name": "ircmaxell/php-object-symbolresolver",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/ircmaxell/php-object-symbolresolver.git",
"reference": "3734df2b22d7c8273ee6f6f2155fddde6056d057"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ircmaxell/php-object-symbolresolver/zipball/3734df2b22d7c8273ee6f6f2155fddde6056d057",
"reference": "3734df2b22d7c8273ee6f6f2155fddde6056d057",
"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-08-14T19:30:20+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {
"ircmaxell/ffime": 20
},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": "^7.4 || ^8"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
}

View File

@@ -1,28 +0,0 @@
[package]
name = "extism-convert"
readme = "./README.md"
edition.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
version.workspace = true
description = "Traits to make Rust types usable with Extism"
[dependencies]
anyhow = "1.0.75"
base64 = "~0.21"
bytemuck = {version = "1.14.0", optional = true }
prost = { version = "0.12.0", optional = true }
rmp-serde = { version = "1.1.2", optional = true }
serde = "1.0.186"
serde_json = "1.0.105"
[dev-dependencies]
serde = { version = "1.0.186", features = ["derive"] }
[features]
default = ["msgpack", "protobuf", "raw"]
msgpack = ["rmp-serde"]
protobuf = ["prost"]
raw = ["bytemuck"]

View File

@@ -1,12 +0,0 @@
# extism-convert
The [extism-convert](https://crates.io/crates/extism-convert) crate is used by the [Rust SDK](https://crates.io/crates/extism) and [Rust PDK](https://crates.io/crates/extism-pdk) to provide a shared interface for
encoding and decoding values that can be passed to Extism function calls.
A set of types (Json, Msgpack, Protobuf) that can be used to specify a serde encoding are also provided. These are
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.
## Documentation
See [extism-convert on docs.rs](https://docs.rs/extism-convert/latest/extism_convert/) for in-depth documentation.

View File

@@ -1,190 +0,0 @@
use crate::*;
use base64::Engine;
/// The `encoding` macro can be used to create newtypes that implement a particular encoding for the
/// inner value.
///
/// For example, the following line creates a new JSON encoding using serde_json:
///
/// ```
/// 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_vec`
#[macro_export]
macro_rules! encoding {
($pub:vis $name:ident, $to_vec:expr, $from_slice:expr) => {
#[doc = concat!(stringify!($name), " encoding")]
#[derive(Debug)]
$pub struct $name<T>(pub T);
impl<T> $name<T> {
pub fn into_inner(self) -> T {
self.0
}
}
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)?;
std::result::Result::Ok($name(x))
}
}
impl<'a, T: serde::Serialize> $crate::ToBytes<'a> for $name<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> std::result::Result<Self::Bytes, $crate::Error> {
let enc = $to_vec(&self.0)?;
std::result::Result::Ok(enc)
}
}
};
}
encoding!(pub Json, serde_json::to_vec, serde_json::from_slice);
#[cfg(feature = "msgpack")]
encoding!(pub Msgpack, rmp_serde::to_vec, rmp_serde::from_slice);
impl<'a> ToBytes<'a> for serde_json::Value {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(serde_json::to_vec(self)?)
}
}
impl FromBytesOwned for serde_json::Value {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(serde_json::from_slice(data)?)
}
}
/// Base64 conversion
///
/// When using `Base64` with `ToBytes` any type that implement `AsRef<[T]>` may be used as the inner value,
/// but only `Base64<String>` and `Base64<Vec>` may be used with `FromBytes`
///
/// A value wrapped in `Base64` will automatically be encoded/decoded using base64, the inner value should not
/// already be base64 encoded.
#[derive(Debug)]
pub struct Base64<T: AsRef<[u8]>>(pub T);
impl<T: AsRef<[u8]>> From<T> for Base64<T> {
fn from(data: T) -> Self {
Self(data)
}
}
impl<'a, T: AsRef<[u8]>> ToBytes<'a> for Base64<T> {
type Bytes = String;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(base64::engine::general_purpose::STANDARD.encode(&self.0))
}
}
impl FromBytesOwned for Base64<Vec<u8>> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Base64(
base64::engine::general_purpose::STANDARD.decode(data)?,
))
}
}
impl FromBytesOwned for Base64<String> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Base64(String::from_utf8(
base64::engine::general_purpose::STANDARD.decode(data)?,
)?))
}
}
/// Protobuf encoding
///
/// Allows for `prost` Protobuf messages to be used as arguments to Extism plugin calls
#[cfg(feature = "protobuf")]
#[derive(Debug)]
pub struct Protobuf<T: prost::Message>(pub T);
#[cfg(feature = "protobuf")]
impl<T: prost::Message> From<T> for Protobuf<T> {
fn from(data: T) -> Self {
Self(data)
}
}
#[cfg(feature = "protobuf")]
impl<'a, T: prost::Message> ToBytes<'a> for Protobuf<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.0.encode_to_vec())
}
}
#[cfg(feature = "protobuf")]
impl<T: Default + prost::Message> FromBytesOwned for Protobuf<T> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Protobuf(T::decode(data)?))
}
}
/// Raw does no conversion, it just copies the memory directly.
/// Note: This will only work for types that implement [bytemuck::Pod](https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html)
#[cfg(all(feature = "raw", target_endian = "little"))]
pub struct Raw<'a, T: bytemuck::Pod>(pub &'a T);
#[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(test, feature = "raw", target_endian = "little"))]
mod tests {
use crate::*;
#[test]
fn test_raw() {
#[derive(Debug, Clone, Copy, PartialEq)]
struct TestRaw {
a: i32,
b: f64,
c: bool,
}
unsafe impl bytemuck::Pod for TestRaw {}
unsafe impl bytemuck::Zeroable for TestRaw {}
let x = TestRaw {
a: 123,
b: 45678.91011,
c: true,
};
let raw = Raw(&x).to_bytes().unwrap();
let y = Raw::from_bytes(&raw).unwrap();
assert_eq!(&x, y.0);
let y: Result<Raw<[u8; std::mem::size_of::<TestRaw>()]>, Error> = Raw::from_bytes(&raw);
assert!(y.is_ok());
}
}

View File

@@ -1,100 +0,0 @@
use crate::*;
/// `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.
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`
pub trait FromBytesOwned: Sized {
/// Decode a value from a slice of bytes, the resulting value should not borrow the input
/// data.
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error>;
}
impl<'a> FromBytes<'a> for &'a [u8] {
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
Ok(data)
}
}
impl<'a> FromBytes<'a> for &'a str {
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
Ok(std::str::from_utf8(data)?)
}
}
impl<'a, T: FromBytesOwned> FromBytes<'a> for T {
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
T::from_bytes_owned(data)
}
}
impl FromBytesOwned for Box<[u8]> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(data.to_vec().into_boxed_slice())
}
}
impl FromBytesOwned for Vec<u8> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(data.to_vec())
}
}
impl FromBytesOwned for String {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(std::str::from_utf8(data)?.to_string())
}
}
impl FromBytesOwned for f64 {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Self::from_le_bytes(data.try_into()?))
}
}
impl FromBytesOwned for f32 {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Self::from_le_bytes(data.try_into()?))
}
}
impl FromBytesOwned for i64 {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Self::from_le_bytes(data.try_into()?))
}
}
impl FromBytesOwned for i32 {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Self::from_le_bytes(data.try_into()?))
}
}
impl FromBytesOwned for u64 {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Self::from_le_bytes(data.try_into()?))
}
}
impl FromBytesOwned for u32 {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Self::from_le_bytes(data.try_into()?))
}
}
impl FromBytesOwned for () {
fn from_bytes_owned(_: &[u8]) -> Result<Self, Error> {
Ok(())
}
}
impl<'a, T: FromBytes<'a>> FromBytes<'a> for std::io::Cursor<T> {
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
Ok(std::io::Cursor::new(T::from_bytes(data)?))
}
}

View File

@@ -1,32 +0,0 @@
//! The [extism-convert](https://crates.io/crates/extism-convert) crate is used by the [Rust SDK](https://crates.io/crates/extism) and [Rust PDK](https://crates.io/crates/extism-pdk) to provide a shared interface for
//! encoding and decoding values that can be passed to Extism function calls.
//!
//! A set of types (Json, Msgpack) that can be used to specify a serde encoding are also provided. These are
//! 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.
pub use anyhow::Error;
mod encoding;
mod from_bytes;
mod memory_handle;
mod to_bytes;
pub use encoding::{Base64, Json};
#[cfg(feature = "msgpack")]
pub use encoding::Msgpack;
#[cfg(feature = "protobuf")]
pub use encoding::Protobuf;
#[cfg(all(feature = "raw", target_endian = "little"))]
pub use encoding::Raw;
pub use from_bytes::{FromBytes, FromBytesOwned};
pub use memory_handle::MemoryHandle;
pub use to_bytes::ToBytes;
#[cfg(test)]
mod tests;

View File

@@ -1,42 +0,0 @@
/// `MemoryHandle` describes where in memory a block of data is stored
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub struct MemoryHandle {
/// The offset of the region in Extism linear memory
pub offset: u64,
/// The length of the memory region
pub length: u64,
}
impl MemoryHandle {
/// Create a new `MemoryHandle` from an offset in memory and length
///
/// # Safety
/// This function is unsafe because the specified memory region may not be valid.
pub unsafe fn new(offset: u64, length: u64) -> MemoryHandle {
MemoryHandle { offset, length }
}
/// `NULL` equivalent
pub fn null() -> MemoryHandle {
MemoryHandle {
offset: 0,
length: 0,
}
}
/// Get the offset of a memory handle
pub fn offset(&self) -> u64 {
self.offset
}
/// Get the length of the memory region
pub fn len(&self) -> usize {
self.length as usize
}
/// Returns `true` when the length is 0
pub fn is_empty(&self) -> bool {
self.length == 0
}
}

View File

@@ -1,39 +0,0 @@
use crate::*;
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
struct Testing {
a: String,
b: i64,
c: f32,
}
#[test]
fn roundtrip_json() {
let x = Testing {
a: "foobar".to_string(),
b: 123,
c: 456.7,
};
let bytes = Json(&x).to_bytes().unwrap();
let Json(y): Json<Testing> = FromBytes::from_bytes(&bytes).unwrap();
assert_eq!(x, y);
}
#[test]
fn roundtrip_msgpack() {
let x = Testing {
a: "foobar".to_string(),
b: 123,
c: 456.7,
};
let bytes = Msgpack(&x).to_bytes().unwrap();
let Msgpack(y): Msgpack<Testing> = FromBytes::from_bytes(&bytes).unwrap();
assert_eq!(x, y);
}
#[test]
fn roundtrip_base64() {
let bytes = Base64("this is a test").to_bytes().unwrap();
let Base64(s): Base64<String> = FromBytes::from_bytes(bytes.as_bytes()).unwrap();
assert_eq!(s, "this is a test");
}

View File

@@ -1,102 +0,0 @@
use crate::*;
/// `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.
pub trait ToBytes<'a> {
/// A configurable byte slice representation, allows any type that implements `AsRef<[u8]>`
type Bytes: AsRef<[u8]>;
/// `to_bytes` converts a value into `Self::Bytes`
fn to_bytes(&self) -> Result<Self::Bytes, Error>;
}
impl<'a> ToBytes<'a> for () {
type Bytes = [u8; 0];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok([])
}
}
impl<'a> ToBytes<'a> for Vec<u8> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.clone())
}
}
impl<'a> ToBytes<'a> for String {
type Bytes = String;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.clone())
}
}
impl<'a> ToBytes<'a> for &'a [u8] {
type Bytes = &'a [u8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self)
}
}
impl<'a> ToBytes<'a> for &'a str {
type Bytes = &'a str;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self)
}
}
impl<'a> ToBytes<'a> for f64 {
type Bytes = [u8; 8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.to_le_bytes())
}
}
impl<'a> ToBytes<'a> for f32 {
type Bytes = [u8; 4];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.to_le_bytes())
}
}
impl<'a> ToBytes<'a> for i64 {
type Bytes = [u8; 8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.to_le_bytes())
}
}
impl<'a> ToBytes<'a> for i32 {
type Bytes = [u8; 4];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.to_le_bytes())
}
}
impl<'a> ToBytes<'a> for u64 {
type Bytes = [u8; 8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.to_le_bytes())
}
}
impl<'a> ToBytes<'a> for u32 {
type Bytes = [u8; 4];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.to_le_bytes())
}
}
impl<'a, T: ToBytes<'a>> ToBytes<'a> for &'a T {
type Bytes = T::Bytes;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
<T as ToBytes>::to_bytes(self)
}
}

3
cpp/Makefile Normal file
View File

@@ -0,0 +1,3 @@
build:
clang++ -std=c++11 -o example example.cpp -lextism -L .

27
cpp/example.cpp Normal file
View File

@@ -0,0 +1,27 @@
#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.wasm");
Context context = Context();
Plugin plugin = context.plugin(wasm);
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;
}

114
cpp/extism.hpp Normal file
View File

@@ -0,0 +1,114 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
extern "C" {
#include <extism.h>
}
namespace extism {
class Error : public std::exception {
private:
std::string message;
public:
Error(std::string msg) : message(msg) {}
const char *what() { return message.c_str(); }
};
class Buffer {
public:
Buffer(const uint8_t *ptr, ExtismSize len) : data(ptr), length(len) {}
const uint8_t *data;
ExtismSize length;
operator std::string() { return std::string((const char *)data, length); }
operator std::vector<uint8_t>() {
return std::vector<uint8_t>(data, data + length);
}
};
class Plugin {
std::shared_ptr<ExtismContext> context;
ExtismPlugin plugin;
public:
Plugin(std::shared_ptr<ExtismContext> ctx, const uint8_t *wasm,
ExtismSize length, bool with_wasi = false) {
this->plugin = extism_plugin_new(ctx.get(), wasm, length, with_wasi);
if (this->plugin < 0) {
const char *err = extism_error(ctx.get(), -1);
throw Error(err == nullptr ? "Unable to load plugin" : err);
}
this->context = ctx;
}
~Plugin() {
extism_plugin_free(this->context.get(), this->plugin);
this->plugin = -1;
}
void update(const uint8_t *wasm, size_t length, bool with_wasi = false) {
bool b = extism_plugin_update(this->context.get(), this->plugin, wasm,
length, with_wasi);
if (!b) {
const char *err = extism_error(this->context.get(), -1);
throw Error(err == nullptr ? "Unable to update plugin" : err);
}
}
Buffer call(const std::string &func, const uint8_t *input,
ExtismSize input_length) {
int32_t rc = extism_plugin_call(this->context.get(), this->plugin,
func.c_str(), input, input_length);
if (rc != 0) {
const char *error = extism_error(this->context.get(), this->plugin);
if (error == nullptr) {
throw Error("extism_call failed");
}
throw Error(error);
}
ExtismSize length =
extism_plugin_output_length(this->context.get(), this->plugin);
const uint8_t *ptr =
extism_plugin_output_data(this->context.get(), this->plugin);
return Buffer(ptr, length);
}
Buffer call(const std::string &func, const std::vector<uint8_t> &input) {
return this->call(func, input.data(), input.size());
}
Buffer call(const std::string &func, const std::string &input) {
return this->call(func, (const uint8_t *)input.c_str(), input.size());
}
};
class Context {
public:
std::shared_ptr<ExtismContext> pointer;
Context() {
this->pointer = std::shared_ptr<ExtismContext>(extism_context_new(),
extism_context_free);
}
Plugin plugin(const uint8_t *wasm, size_t length, bool with_wasi = false) {
return Plugin(this->pointer, wasm, length, with_wasi);
}
Plugin plugin(const std::string &str, bool with_wasi = false) {
return Plugin(this->pointer, (const uint8_t *)str.c_str(), str.size(),
with_wasi);
}
Plugin plugin(const std::vector<uint8_t> &data, bool with_wasi = false) {
return Plugin(this->pointer, data.data(), data.size(), with_wasi);
}
};
} // namespace extism

1
dune Normal file
View File

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

24
dune-project Normal file
View File

@@ -0,0 +1,24 @@
(lang dune 3.2)
(name extism)
(generate_opam_files true)
(source
(github extism/extism))
(authors "Extism Authors <oss@extism.org>")
(maintainers "Extism Authors <oss@extism.org>")
(license BSD-3-Clause)
(documentation https://github.com/extism/extism)
(package
(name extism)
(synopsis "Extism bindings")
(description "Bindings to Extism, the universal plugin system")
(depends ocaml dune ctypes-foreign bigstringaf ppx_yojson_conv base64)
(tags
(topics wasm plugin)))

4
elixir/.formatter.exs Normal file
View File

@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

28
elixir/.gitignore vendored Normal file
View File

@@ -0,0 +1,28 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
extism-*.tar
# Temporary files, for example, from tests.
/tmp/
/priv/

38
elixir/README.md Normal file
View File

@@ -0,0 +1,38 @@
# Extism
Extism Host SDK for Elixir and Erlang
## Installation
You can find this package on [hex.pm](https://hex.pm/packages/extism).
```elixir
def deps do
[
{:extism, "~> 0.0.1-rc.5"}
]
end
```
## Usage
```elixir
# Create a context for which plugins can be allocated and cleaned
ctx = Extism.Context.new()
# point to some wasm code, this is the count_vowels example that ships with extism
manifest = %{ wasm: [ %{ path: "/Users/ben/code/extism/wasm/code.wasm" } ]}
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
# {:ok,
# %Extism.Plugin{
# resource: 0,
# reference: #Reference<0.520418104.1263009793.80956>
# }}
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
# {:ok, "{\"count\": 4}"}
{:ok, result} = JSON.decode(output)
# {:ok, %{"count" => 4}}
# free up the context and any plugins we allocated
Extism.Context.free(ctx)
```

5
elixir/lib/extism.ex Normal file
View File

@@ -0,0 +1,5 @@
defmodule Extism do
def set_log_file(filepath, level) do
Extism.Native.set_log_file(filepath, level)
end
end

View File

@@ -0,0 +1,33 @@
defmodule Extism.Context do
defstruct [
# The actual NIF Resource. A pointer in this case
ptr: nil
]
def wrap_resource(ptr) do
%__MODULE__{
ptr: ptr
}
end
def new() do
ptr = Extism.Native.context_new()
Extism.Context.wrap_resource(ptr)
end
def reset(ctx) do
Extism.Native.context_reset(ctx.ptr)
end
def free(ctx) do
Extism.Native.context_free(ctx.ptr)
end
def new_plugin(ctx, manifest, wasi) do
{:ok, manifest_payload} = JSON.encode(manifest)
case Extism.Native.plugin_new_with_manifest(ctx.ptr, manifest_payload, wasi) do
{:error, err} -> {:error, err}
res -> {:ok, Extism.Plugin.wrap_resource(ctx, res)}
end
end
end

View File

@@ -0,0 +1,17 @@
defmodule Extism.Native do
use Rustler,
otp_app: :extism,
crate: :extism_nif
def context_new(), do: error()
def context_reset(_ctx), do: error()
def context_free(_ctx), do: error()
def plugin_new_with_manifest(_ctx, _manifest, _wasi), do: error()
def plugin_call(_ctx, _plugin_id, _name, _input), do: error()
def plugin_update_manifest(_ctx, _plugin_id, _manifest, _wasi), do: error()
def plugin_has_function(_ctx, _plugin_id, _function_name), do: error()
def plugin_free(_ctx, _plugin_id), do: error()
def set_log_file(_filename, _level), do: error()
defp error, do: :erlang.nif_error(:nif_not_loaded)
end

View File

@@ -0,0 +1,45 @@
defmodule Extism.Plugin do
defstruct [
# The actual NIF Resource. PluginIndex and the context
plugin_id: nil,
ctx: nil
]
def wrap_resource(ctx, plugin_id) do
%__MODULE__{
ctx: ctx,
plugin_id: plugin_id
}
end
def call(plugin, name, input) do
case Extism.Native.plugin_call(plugin.ctx.ptr, plugin.plugin_id, name, input) do
{:error, err} -> {:error, err}
res -> {:ok, res}
end
end
def update(plugin, manifest, wasi) when is_map(manifest) do
{:ok, manifest_payload} = JSON.encode(manifest)
case Extism.Native.plugin_update_manifest(plugin.ctx.ptr, plugin.plugin_id, manifest_payload, wasi) do
{:error, err} -> {:error, err}
res -> :ok
end
end
def free(plugin) do
Extism.Native.plugin_free(plugin.ctx.ptr, plugin.plugin_id)
end
def has_function(plugin, function_name) do
Extism.Native.plugin_has_function(plugin.ctx.ptr, plugin.plugin_id, function_name)
end
end
defimpl Inspect, for: Extim.Plugin do
import Inspect.Algebra
def inspect(dict, opts) do
concat(["#Extism.Plugin<", to_doc(dict.plugin_id, opts), ">"])
end
end

49
elixir/mix.exs Normal file
View File

@@ -0,0 +1,49 @@
defmodule Extism.MixProject do
use Mix.Project
def project do
[
app: :extism,
version: "0.0.1-rc.5",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps(),
package: package(),
aliases: aliases()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
defp deps do
[
{:rustler, "~> 0.26.0"},
{:json, "~> 1.4"},
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
]
end
defp aliases do
[
fmt: [
"format",
"cmd cargo fmt --manifest-path native/io/Cargo.toml"
]
]
end
defp package do
[
licenses: ["BSD-3-Clause"],
description: "Extism Host SDK for Elixir and Erlang",
name: "extism",
files: ~w(lib native priv .formatter.exs mix.exs README.md LICENSE),
links: %{ "GitHub" => "https://github.com/extism/extism" },
]
end
end

12
elixir/mix.lock Normal file
View File

@@ -0,0 +1,12 @@
%{
"earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
"ex_doc": {:hex, :ex_doc, "0.29.0", "4a1cb903ce746aceef9c1f9ae8a6c12b742a5461e6959b9d3b24d813ffbea146", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "f096adb8bbca677d35d278223361c7792d496b3fc0d0224c9d4bc2f651af5db1"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"json": {:hex, :json, "1.4.1", "8648f04a9439765ad449bc56a3ff7d8b11dd44ff08ffcdefc4329f7c93843dfa", [:mix], [], "hexpm", "9abf218dbe4ea4fcb875e087d5f904ef263d012ee5ed21d46e9dbca63f053d16"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"rustler": {:hex, :rustler, "0.26.0", "06a2773d453ee3e9109efda643cf2ae633dedea709e2455ac42b83637c9249bf", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "42961e9d2083d004d5a53e111ad1f0c347efd9a05cb2eb2ffa1d037cdc74db91"},
"toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"},
}

View File

@@ -0,0 +1,15 @@
[target.'cfg(target_os = "macos")']
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
# See https://github.com/rust-lang/rust/issues/59302
[target.x86_64-unknown-linux-musl]
rustflags = [
"-C", "target-feature=-crt-static"
]
# Provides a small build size, but takes more time to build.
[profile.release]
lto = true

View File

@@ -0,0 +1,15 @@
[package]
name = "extism_nif"
version = "0.0.1-rc.5"
edition = "2021"
authors = ["Benjamin Eckel <bhelx@simst.im>"]
[lib]
name = "extism_nif"
path = "src/lib.rs"
crate-type = ["cdylib"]
[dependencies]
rustler = "0.26.0"
extism = { version = "0.0.1-rc.5" }
log = "0.4"

View File

@@ -0,0 +1,149 @@
use rustler::{Atom, Env, Term, ResourceArc};
use extism::{Plugin, Context};
use std::str;
use std::path::Path;
use std::str::FromStr;
use std::sync::RwLock;
use std::mem;
mod atoms {
rustler::atoms! {
ok,
error,
unknown // Other error
}
}
struct ExtismContext {
ctx: RwLock<Context>
}
fn load(env: Env, _: Term) -> bool {
rustler::resource!(ExtismContext, env);
true
}
fn to_rustler_error(extism_error: extism::Error) -> rustler::Error {
match extism_error {
extism::Error::UnableToLoadPlugin(msg) => rustler::Error::Term(Box::new(msg)),
extism::Error::Message(msg) => rustler::Error::Term(Box::new(msg)),
extism::Error::Json(json_err) => rustler::Error::Term(Box::new(json_err.to_string()))
}
}
#[rustler::nif]
fn context_new() -> ResourceArc<ExtismContext> {
ResourceArc::new(
ExtismContext { ctx: RwLock::new(Context::new()) }
)
}
#[rustler::nif]
fn context_reset(ctx: ResourceArc<ExtismContext>) {
let context = &mut ctx.ctx.write().unwrap();
context.reset()
}
#[rustler::nif]
fn context_free(ctx: ResourceArc<ExtismContext>) {
let context = &ctx.ctx.read().unwrap();
std::mem::drop(context)
}
#[rustler::nif]
fn plugin_new_with_manifest(ctx: ResourceArc<ExtismContext>, manifest_payload: String, wasi: bool) -> Result<i32, rustler::Error> {
let context = &ctx.ctx.write().unwrap();
let result = match Plugin::new(context, manifest_payload, wasi) {
Err(e) => Err(to_rustler_error(e)),
Ok(plugin) => {
let plugin_id = plugin.as_i32();
// this forget should be safe because the context will clean up
// all it's plugins when it is dropped
mem::forget(plugin);
Ok(plugin_id)
}
};
result
}
#[rustler::nif]
fn plugin_call(ctx: ResourceArc<ExtismContext>, plugin_id: i32, name: String, input: String) -> Result<String, rustler::Error> {
let context = &ctx.ctx.read().unwrap();
let plugin = unsafe { Plugin::from_id(plugin_id, context) };
let result = match plugin.call(name, input) {
Err(e) => Err(to_rustler_error(e)),
Ok(result) => {
match str::from_utf8(&result) {
Ok(output) => Ok(output.to_string()),
Err(_e) => Err(rustler::Error::Term(Box::new("Could not read output from plugin")))
}
}
};
// this forget should be safe because the context will clean up
// all it's plugins when it is dropped
mem::forget(plugin);
result
}
#[rustler::nif]
fn plugin_update_manifest(ctx: ResourceArc<ExtismContext>, plugin_id: i32, manifest_payload: String, wasi: bool) -> Result<(), rustler::Error> {
let context = &ctx.ctx.read().unwrap();
let mut plugin = unsafe { Plugin::from_id(plugin_id, context) };
let result = match plugin.update(manifest_payload, wasi) {
Ok(()) => {
Ok(())
},
Err(e) => Err(to_rustler_error(e))
};
// this forget should be safe because the context will clean up
// all it's plugins when it is dropped
mem::forget(plugin);
result
}
#[rustler::nif]
fn plugin_free(ctx: ResourceArc<ExtismContext>, plugin_id: i32) -> Result<(), rustler::Error> {
let context = &ctx.ctx.read().unwrap();
let plugin = unsafe { Plugin::from_id(plugin_id, context) };
std::mem::drop(plugin);
Ok(())
}
#[rustler::nif]
fn set_log_file(filename: String, log_level: String) -> Result<Atom, rustler::Error> {
let path = Path::new(&filename);
match log::Level::from_str(&log_level) {
Err(_e) => Err(rustler::Error::Term(Box::new(format!("{} not a valid log level", log_level)))),
Ok(level) => {
extism::set_log_file(path, Some(level));
Ok(atoms::ok())
}
}
}
#[rustler::nif]
fn plugin_has_function(ctx: ResourceArc<ExtismContext>, plugin_id: i32, function_name: String) -> Result<bool, rustler::Error> {
let context = &ctx.ctx.read().unwrap();
let plugin = unsafe { Plugin::from_id(plugin_id, context) };
let has_function = plugin.has_function(function_name);
// this forget should be safe because the context will clean up
// all it's plugins when it is dropped
mem::forget(plugin);
Ok(has_function)
}
rustler::init!(
"Elixir.Extism.Native",
[
context_new,
context_reset,
context_free,
plugin_new_with_manifest,
plugin_call,
plugin_update_manifest,
plugin_has_function,
plugin_free,
set_log_file,
],
load = load
);

View File

@@ -0,0 +1,81 @@
defmodule ExtismTest do
use ExUnit.Case
doctest Extism
test "context create & reset" do
ctx = Extism.Context.new()
path = Path.join([__DIR__, "../../wasm/code.wasm"])
manifest = %{wasm: [%{path: path}]}
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
Extism.Context.reset(ctx)
# we should expect an error after resetting context
{:error, _err} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
end
defp new_plugin() do
ctx = Extism.Context.new()
path = Path.join([__DIR__, "../../wasm/code.wasm"])
manifest = %{wasm: [%{path: path}]}
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
{ctx, plugin}
end
test "counts vowels" do
{ctx, plugin} = new_plugin()
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
assert JSON.decode(output) == {:ok, %{"count" => 4}}
Extism.Context.free(ctx)
end
test "can make multiple calls on a plugin" do
{ctx, plugin} = new_plugin()
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
assert JSON.decode(output) == {:ok, %{"count" => 4}}
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test again")
assert JSON.decode(output) == {:ok, %{"count" => 7}}
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test thrice")
assert JSON.decode(output) == {:ok, %{"count" => 6}}
Extism.Context.free(ctx)
end
test "can free a plugin" do
{ctx, plugin} = new_plugin()
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
assert JSON.decode(output) == {:ok, %{"count" => 4}}
Extism.Plugin.free(plugin)
# Expect an error when calling a plugin that was freed
{:error, _err} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
Extism.Context.free(ctx)
end
test "can update manifest" do
{ctx, plugin} = new_plugin()
path = Path.join([__DIR__, "../../wasm/code.wasm"])
manifest = %{wasm: [%{path: path}]}
assert Extism.Plugin.update(plugin, manifest, true) == :ok
Extism.Context.free(ctx)
end
test "errors on bad manifest" do
ctx = Extism.Context.new()
{:error, _msg} = Extism.Context.new_plugin(ctx, %{"wasm" => 123}, false)
Extism.Context.free(ctx)
end
test "errors on unknown function" do
{ctx, plugin} = new_plugin()
{:error, _msg} = Extism.Plugin.call(plugin, "unknown", "this is a test")
Extism.Context.free(ctx)
end
test "set_log_file" do
Extism.set_log_file("/tmp/logfile.log", "debug")
end
test "has_function" do
{ctx, plugin} = new_plugin()
assert Extism.Plugin.has_function(plugin, "count_vowels")
assert !Extism.Plugin.has_function(plugin, "unknown")
Extism.Context.free(ctx)
end
end

View File

@@ -0,0 +1 @@
ExUnit.start()

View File

@@ -1,20 +0,0 @@
[package]
name = "extism-sys"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
version.workspace = true
# Explicitly omit authors from this package since our Cargo "authors" are
# incompatible with PyPI's requirements.
# authors.workspace = true
[lib]
name = "extism_sys"
crate-type = ["cdylib"]
[dependencies]
extism = { workspace = true }
[build-dependencies]
cc = "1"

View File

@@ -1,57 +0,0 @@
use std::borrow::Cow;
fn rewrite(line: &'_ str) -> Option<Cow<'_, str>> {
let line = line.trim();
if line.is_empty() {
return None;
}
if line.starts_with('#') {
return None;
}
if cfg!(target_os = "macos") {
if line.starts_with("typedef __builtin_va_list ") {
return None;
}
} else if cfg!(target_os = "windows") {
if line.contains("__gnuc_va_list")
|| line.starts_with("__pragma")
|| line.contains("__attribute__")
|| line.contains("uintptr_t")
|| line.contains("intptr_t")
|| line.contains("size_t")
|| line.contains("ptrdiff_t")
{
return None;
}
return Some(Cow::Owned(line.replace("__attribute__((__cdecl__))", "")));
};
Some(Cow::Borrowed(line))
}
fn main() {
println!("cargo:rerun-if-changed=src/extism.c");
println!("cargo:rerun-if-changed=../runtime/extism.h");
std::fs::copy("../runtime/extism.h", "src/extism.h").unwrap();
let data = String::from_utf8(
cc::Build::new()
.file("src/extism.c")
.warnings(false)
.extra_warnings(false)
.expand(),
)
.unwrap();
let data: Vec<&str> = data.split('\n').collect();
let data: String = data
.into_iter()
.filter_map(rewrite)
.collect::<Vec<Cow<'_, str>>>()
.join("\n\n");
std::fs::write("../target/header.h", data).unwrap();
}

View File

@@ -1,17 +0,0 @@
[build-system]
requires = ["maturin>=1.1,<2.0"]
build-backend = "maturin"
[project]
name = "extism-sys"
version = "0.0.0+replaced-by-ci"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = ["cffi"]
[tool.maturin]
bindings = "cffi"

View File

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

View File

@@ -1 +0,0 @@
pub use extism::sdk::*;

253
extism.go Normal file
View File

@@ -0,0 +1,253 @@
package extism
import (
"encoding/json"
"errors"
"fmt"
"io"
"unsafe"
)
/*
#cgo pkg-config: libextism.pc
#include <extism.h>
#include <stdlib.h>
*/
import "C"
// Context is used to manage Plugins
type Context struct {
pointer *C.ExtismContext
}
// NewContext creates a new context, it should be freed using the `Free` method
func NewContext() Context {
p := C.extism_context_new()
return Context{
pointer: p,
}
}
// Free a context
func (ctx *Context) Free() {
C.extism_context_free(ctx.pointer)
ctx.pointer = nil
}
// Plugin is used to call WASM functions
type Plugin struct {
ctx *Context
id int32
}
type WasmData struct {
Data []byte `json:"data"`
Hash string `json:"hash,omitempty"`
Name string `json:"name,omitempty"`
}
type WasmFile struct {
Path string `json:"path"`
Hash string `json:"hash,omitempty"`
Name string `json:"name,omitempty"`
}
type WasmUrl struct {
Url string `json:"url"`
Hash string `json:"hash,omitempty"`
Header map[string]string `json:"header,omitempty"`
Name string `json:"name,omitempty"`
Method string `json:"method,omitempty"`
}
type Wasm interface{}
type Manifest struct {
Wasm []Wasm `json:"wasm"`
Memory struct {
Max uint32 `json:"max,omitempty"`
} `json:"memory,omitempty"`
Config map[string]string `json:"config,omitempty"`
AllowedHosts []string `json:"allowed_hosts,omitempty"`
}
func makePointer(data []byte) unsafe.Pointer {
var ptr unsafe.Pointer = nil
if len(data) > 0 {
ptr = unsafe.Pointer(&data[0])
}
return ptr
}
// SetLogFile sets the log file and level, this is a global setting
func SetLogFile(filename string, level string) bool {
name := C.CString(filename)
l := C.CString(level)
r := C.extism_log_file(name, l)
C.free(unsafe.Pointer(name))
C.free(unsafe.Pointer(l))
return bool(r)
}
// ExtismVersion gets the Extism version string
func ExtismVersion() string {
return C.GoString(C.extism_version())
}
func register(ctx *Context, data []byte, wasi bool) (Plugin, error) {
ptr := makePointer(data)
plugin := C.extism_plugin_new(
ctx.pointer,
(*C.uchar)(ptr),
C.uint64_t(len(data)),
C._Bool(wasi),
)
if plugin < 0 {
err := C.extism_error(ctx.pointer, C.int32_t(-1))
msg := "Unknown"
if err != nil {
msg = C.GoString(err)
}
return Plugin{id: -1}, errors.New(
fmt.Sprintf("Unable to load plugin: %s", msg),
)
}
return Plugin{id: int32(plugin), ctx: ctx}, nil
}
func update(ctx *Context, plugin int32, data []byte, wasi bool) error {
ptr := makePointer(data)
b := bool(C.extism_plugin_update(
ctx.pointer,
C.int32_t(plugin),
(*C.uchar)(ptr),
C.uint64_t(len(data)),
C._Bool(wasi),
))
if b {
return nil
}
err := C.extism_error(ctx.pointer, C.int32_t(-1))
msg := "Unknown"
if err != nil {
msg = C.GoString(err)
}
return errors.New(
fmt.Sprintf("Unable to load plugin: %s", msg),
)
}
// PluginFromManifest creates a plugin from a `Manifest`
func (ctx *Context) PluginFromManifest(manifest Manifest, wasi bool) (Plugin, error) {
data, err := json.Marshal(manifest)
if err != nil {
return Plugin{id: -1}, err
}
return register(ctx, data, wasi)
}
// Plugin creates a plugin from a WASM module
func (ctx *Context) Plugin(module io.Reader, wasi bool) (Plugin, error) {
wasm, err := io.ReadAll(module)
if err != nil {
return Plugin{id: -1}, err
}
return register(ctx, wasm, wasi)
}
// Update a plugin with a new WASM module
func (p *Plugin) Update(module io.Reader, wasi bool) error {
wasm, err := io.ReadAll(module)
if err != nil {
return err
}
return update(p.ctx, p.id, wasm, wasi)
}
// Update a plugin with a new Manifest
func (p *Plugin) UpdateManifest(manifest Manifest, wasi bool) error {
data, err := json.Marshal(manifest)
if err != nil {
return err
}
return update(p.ctx, p.id, data, wasi)
}
// Set configuration values
func (plugin Plugin) SetConfig(data map[string][]byte) error {
s, err := json.Marshal(data)
if err != nil {
return err
}
ptr := makePointer(s)
C.extism_plugin_config(plugin.ctx.pointer, C.int(plugin.id), (*C.uchar)(ptr), C.uint64_t(len(s)))
return nil
}
/// FunctionExists returns true when the name function is present in the plugin
func (plugin Plugin) FunctionExists(functionName string) bool {
name := C.CString(functionName)
b := C.extism_plugin_function_exists(plugin.ctx.pointer, C.int(plugin.id), name)
C.free(unsafe.Pointer(name))
return bool(b)
}
/// Call a function by name with the given input, returning the output
func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
ptr := makePointer(input)
name := C.CString(functionName)
rc := C.extism_plugin_call(
plugin.ctx.pointer,
C.int32_t(plugin.id),
name,
(*C.uchar)(ptr),
C.uint64_t(len(input)),
)
C.free(unsafe.Pointer(name))
if rc != 0 {
err := C.extism_error(plugin.ctx.pointer, C.int32_t(plugin.id))
msg := "<unset by plugin>"
if err != nil {
msg = C.GoString(err)
}
return nil, errors.New(
fmt.Sprintf("Plugin error: %s, code: %d", msg, rc),
)
}
length := C.extism_plugin_output_length(plugin.ctx.pointer, C.int32_t(plugin.id))
if length > 0 {
x := C.extism_plugin_output_data(plugin.ctx.pointer, C.int32_t(plugin.id))
y := (*[]byte)(unsafe.Pointer(&x))
return []byte((*y)[0:length]), nil
}
return []byte{}, nil
}
// Free a plugin
func (plugin *Plugin) Free() {
if plugin.ctx.pointer == nil {
return
}
C.extism_plugin_free(plugin.ctx.pointer, C.int32_t(plugin.id))
plugin.id = -1
}
// Reset removes all registered plugins in a Context
func (ctx Context) Reset() {
C.extism_context_reset(ctx.pointer)
}

35
extism.opam Normal file
View File

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

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/extism/extism
go 1.18

7
go/go.mod Normal file
View File

@@ -0,0 +1,7 @@
module github.com/extism/extism-go-example
go 1.18
replace github.com/extism/extism => ../
require github.com/extism/extism v0.0.0-00010101000000-000000000000

47
go/main.go Normal file
View File

@@ -0,0 +1,47 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/extism/extism"
)
func main() {
version := extism.ExtismVersion()
fmt.Println("Extism Version: ", version)
ctx := extism.NewContext()
defer ctx.Free() // this will free the context and all associated plugins
// set some input data to provide to the plugin module
var data []byte
if len(os.Args) > 1 {
data = []byte(os.Args[1])
} else {
data = []byte("testing from go -> wasm shared memory...")
}
manifest := extism.Manifest{Wasm: []extism.Wasm{extism.WasmFile{Path: "../wasm/code.wasm"}}}
plugin, err := ctx.PluginFromManifest(manifest, false)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// use the extism Go library to provide the input data to the plugin, execute it, and then
// collect the plugin state and error if present
out, err := plugin.Call("count_vowels", data)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// "out" is []byte type, and the plugin sends back json, so deserialize it into a map.
// expect this object: `{"count": n}`
var dest map[string]int
json.Unmarshal(out, &dest)
fmt.Println("Count:", dest["count"])
}

5
haskell/CHANGELOG.md Normal file
View File

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

23
haskell/Example.hs Normal file
View File

@@ -0,0 +1,23 @@
module Main where
import System.Exit (exitFailure, exitSuccess)
import qualified Data.ByteString as B
import Extism
import Extism.Manifest
try f (Right x) = f x
try f (Left (Error msg)) = do
_ <- putStrLn msg
exitFailure
handlePlugin plugin = do
res <- Extism.call plugin "count_vowels" (Extism.toByteString "this is a test")
try (\bs -> do
_ <- putStrLn (Extism.fromByteString bs)
_ <- Extism.free plugin
exitSuccess) res
main = do
context <- Extism.newContext ()
plugin <- Extism.pluginFromManifest context (manifest [wasmFile "../wasm/code.wasm"]) False
try handlePlugin plugin

47
haskell/extism.cabal Normal file
View File

@@ -0,0 +1,47 @@
cabal-version: 2.4
name: extism
version: 0.0.1.0
-- A short (one-line) description of the package.
synopsis: Extism bindings
-- A longer description of the package.
description: Bindings to Extism, the universal plugin system
-- A URL where users can report bugs.
bug-reports: https://github.com/extism/extism
-- The license under which the package is released.
license: BSD-3-Clause
author: Extism authors
maintainer: oss@extism.org
-- A copyright notice.
-- copyright:
category: Plugins
extra-source-files: CHANGELOG.md
library
exposed-modules: Extism Extism.Manifest
-- Modules included in this library but not exported.
other-modules:
-- LANGUAGE extensions used by modules in this package.
-- other-extensions:
build-depends:
base ^>=4.16.1.0
, bytestring
, base64-bytestring
, json
hs-source-dirs: src
default-language: Haskell2010
extra-libraries: extism
extra-lib-dirs: /usr/local/lib
Test-Suite extism-example
type: exitcode-stdio-1.0
main-is: Example.hs
build-depends: base, extism, bytestring
default-language: Haskell2010

172
haskell/src/Extism.hs Normal file
View File

@@ -0,0 +1,172 @@
{-# LANGUAGE ForeignFunctionInterface #-}
module Extism (module Extism, module Extism.Manifest) where
import GHC.Int
import GHC.Word
import Foreign.C.Types
import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.C.String
import Control.Monad (void)
import Data.ByteString as B
import Data.ByteString.Internal (c2w, w2c)
import Data.ByteString.Unsafe (unsafeUseAsCString)
import Text.JSON (JSON, toJSObject, encode)
import Extism.Manifest (Manifest, toString)
newtype ExtismContext = ExtismContext () deriving Show
foreign import ccall unsafe "extism.h extism_context_new" extism_context_new :: IO (Ptr ExtismContext)
foreign import ccall unsafe "extism.h &extism_context_free" extism_context_free :: FunPtr (Ptr ExtismContext -> IO ())
foreign import ccall unsafe "extism.h extism_plugin_new" extism_plugin_new :: Ptr ExtismContext -> Ptr Word8 -> Word64 -> CBool -> IO Int32
foreign import ccall unsafe "extism.h extism_plugin_update" extism_plugin_update :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Word64 -> CBool -> IO CBool
foreign import ccall unsafe "extism.h extism_plugin_call" extism_plugin_call :: Ptr ExtismContext -> Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
foreign import ccall unsafe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismContext -> Int32 -> CString -> IO CBool
foreign import ccall unsafe "extism.h extism_error" extism_error :: Ptr ExtismContext -> Int32 -> IO CString
foreign import ccall unsafe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismContext -> Int32 -> IO Word64
foreign import ccall unsafe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismContext -> Int32 -> IO (Ptr Word8)
foreign import ccall unsafe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
foreign import ccall unsafe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Int64 -> IO CBool
foreign import ccall unsafe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismContext -> Int32 -> IO ()
foreign import ccall unsafe "extism.h extism_context_reset" extism_context_reset :: Ptr ExtismContext -> IO ()
foreign import ccall unsafe "extism.h extism_version" extism_version :: IO CString
-- Context manages plugins
newtype Context = Context (ForeignPtr ExtismContext)
-- Plugins can be used to call WASM function
data Plugin = Plugin Context Int32
-- Extism error
newtype Error = Error String deriving Show
-- Helper function to convert a string to a bytestring
toByteString :: String -> ByteString
toByteString x = B.pack (Prelude.map c2w x)
-- Helper function to convert a bytestring to a string
fromByteString :: ByteString -> String
fromByteString bs = Prelude.map w2c $ B.unpack bs
-- Get the Extism version string
extismVersion :: () -> IO String
extismVersion () = do
v <- extism_version
peekCString v
-- Remove all registered plugins in a Context
reset :: Context -> IO ()
reset (Context ctx) =
withForeignPtr ctx (\ctx ->
extism_context_reset ctx)
-- Create a new context
newContext :: () -> IO Context
newContext () = do
ptr <- extism_context_new
fptr <- newForeignPtr extism_context_free ptr
return (Context fptr)
-- Create a plugin from a WASM module, `useWasi` determines if WASI should
-- be linked
plugin :: Context -> B.ByteString -> Bool -> IO (Either Error Plugin)
plugin c wasm useWasi =
let length = fromIntegral (B.length wasm) in
let wasi = fromInteger (if useWasi then 1 else 0) in
let Context ctx = c in
do
withForeignPtr ctx (\ctx -> do
p <- unsafeUseAsCString wasm (\s ->
extism_plugin_new ctx (castPtr s) length wasi)
if p < 0 then do
err <- extism_error ctx (-1)
e <- peekCString err
return $ Left (Error e)
else
return $ Right (Plugin c p))
-- Create a plugin from a Manifest
pluginFromManifest :: Context -> Manifest -> Bool -> IO (Either Error Plugin)
pluginFromManifest ctx manifest useWasi =
let wasm = toByteString $ toString manifest in
plugin ctx wasm useWasi
-- Update a plugin with a new WASM module
update :: Plugin -> B.ByteString -> Bool -> IO (Either Error ())
update (Plugin (Context ctx) id) wasm useWasi =
let length = fromIntegral (B.length wasm) in
let wasi = fromInteger (if useWasi then 1 else 0) in
do
withForeignPtr ctx (\ctx -> do
b <- unsafeUseAsCString wasm (\s ->
extism_plugin_update ctx id (castPtr s) length wasi)
if b <= 0 then do
err <- extism_error ctx (-1)
e <- peekCString err
return $ Left (Error e)
else
return (Right ()))
-- Update a plugin with a new Manifest
updateManifest :: Plugin -> Manifest -> Bool -> IO (Either Error ())
updateManifest plugin manifest useWasi =
let wasm = toByteString $ toString manifest in
update plugin wasm useWasi
-- Check if a plugin is value
isValid :: Plugin -> Bool
isValid (Plugin _ p) = p >= 0
-- Set configuration values for a plugin
setConfig :: Plugin -> [(String, Maybe String)] -> IO ()
setConfig (Plugin (Context ctx) plugin) x =
if plugin < 0
then return ()
else
let obj = toJSObject x in
let bs = toByteString (encode obj) in
let length = fromIntegral (B.length bs) in
unsafeUseAsCString bs (\s -> do
withForeignPtr ctx (\ctx ->
void $ extism_plugin_config ctx plugin (castPtr s) length))
-- Set the log file and level, this is a global configuration
setLogFile :: String -> String -> IO ()
setLogFile filename level =
withCString filename (\f ->
withCString level (\l -> do
void $ extism_log_file f l))
-- Check if a function exists in the given plugin
functionExists :: Plugin -> String -> IO Bool
functionExists (Plugin (Context ctx) plugin) name = do
withForeignPtr ctx (\ctx -> do
b <- withCString name (extism_plugin_function_exists ctx plugin)
if b == 1 then return True else return False)
--- Call a function provided by the given plugin
call :: Plugin -> String -> B.ByteString -> IO (Either Error B.ByteString)
call (Plugin (Context ctx) plugin) name input =
let length = fromIntegral (B.length input) in
do
withForeignPtr ctx (\ctx -> do
rc <- withCString name (\name ->
unsafeUseAsCString input (\input ->
extism_plugin_call ctx plugin name (castPtr input) length))
err <- extism_error ctx plugin
if err /= nullPtr
then do e <- peekCString err
return $ Left (Error e)
else if rc == 0
then do
length <- extism_plugin_output_length ctx plugin
ptr <- extism_plugin_output_data ctx plugin
buf <- packCStringLen (castPtr ptr, fromIntegral length)
return $ Right buf
else return $ Left (Error "Call failed"))
-- Free a plugin
free :: Plugin -> IO ()
free (Plugin (Context ctx) plugin) =
withForeignPtr ctx (\ctx ->
extism_plugin_free ctx plugin)

View File

@@ -0,0 +1,183 @@
module Extism.Manifest where
import Text.JSON
(
JSValue(JSNull, JSString, JSArray),
toJSString, showJSON, makeObj, encode
)
import qualified Data.ByteString as B
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Char8 as BS (unpack)
valueOrNull f Nothing = JSNull
valueOrNull f (Just x) = f x
makeString s = JSString (toJSString s)
stringOrNull = valueOrNull makeString
makeArray f [] = JSNull
makeArray f x = JSArray [f a | a <- x]
filterNulls obj = [(a, b) | (a, b) <- obj, not (isNull b)]
mapObj f x = makeObj (filterNulls [(a, f b) | (a, b) <- x])
isNull JSNull = True
isNull _ = False
newtype Memory = Memory
{
memoryMax :: Maybe Int
}
class JSONValue a where
toJSONValue :: a -> JSValue
instance JSONValue Memory where
toJSONValue x =
case memoryMax x of
Nothing -> makeObj []
Just max -> makeObj [("max", showJSON max)]
data HttpRequest = HttpRequest
{
url :: String
, header :: [(String, String)]
, method :: Maybe String
}
requestObj x =
let meth = stringOrNull $ method x in
let h = mapObj makeString $ header x in
filterNulls [
("url", makeString $ url x),
("header", h),
("method", meth)
]
instance JSONValue HttpRequest where
toJSONValue x =
makeObj $ requestObj x
data WasmFile = WasmFile
{
filePath :: String
, fileName :: Maybe String
, fileHash :: Maybe String
}
instance JSONValue WasmFile where
toJSONValue x =
let path = makeString $ filePath x in
let name = stringOrNull $ fileName x in
let hash = stringOrNull $ fileHash x in
makeObj $ filterNulls [
("path", path),
("name", name),
("hash", hash)
]
data WasmCode = WasmCode
{
codeBytes :: B.ByteString
, codeName :: Maybe String
, codeHash :: Maybe String
}
instance JSONValue WasmCode where
toJSONValue x =
let bytes = makeString $ BS.unpack $ B64.encode $ codeBytes x in
let name = stringOrNull $ codeName x in
let hash = stringOrNull $ codeHash x in
makeObj $ filterNulls [
("data", bytes),
("name", name),
("hash", hash)
]
data WasmURL = WasmURL
{
req :: HttpRequest
, urlName :: Maybe String
, urlHash :: Maybe String
}
instance JSONValue WasmURL where
toJSONValue x =
let request = requestObj $ req x in
let name = stringOrNull $ urlName x in
let hash = stringOrNull $ urlHash x in
makeObj $ filterNulls $ ("name", name) : ("hash", hash) : request
data Wasm = File WasmFile | Code WasmCode | URL WasmURL
instance JSONValue Wasm where
toJSONValue x =
case x of
File f -> toJSONValue f
Code d -> toJSONValue d
URL u -> toJSONValue u
wasmFile :: String -> Wasm
wasmFile path =
File WasmFile { filePath = path, fileName = Nothing, fileHash = Nothing}
wasmURL :: String -> String -> Wasm
wasmURL method url =
let r = HttpRequest { url = url, header = [], method = Just method } in
URL WasmURL { req = r, urlName = Nothing, urlHash = Nothing }
wasmCode :: B.ByteString -> Wasm
wasmCode code =
Code WasmCode { codeBytes = code, codeName = Nothing, codeHash = Nothing }
withName :: Wasm -> String -> Wasm
withName (Code code) name = Code code { codeName = Just name }
withName (URL url) name = URL url { urlName = Just name }
withName (File f) name = File f { fileName = Just name }
withHash :: Wasm -> String -> Wasm
withHash (Code code) hash = Code code { codeHash = Just hash }
withHash (URL url) hash = URL url { urlHash = Just hash }
withHash (File f) hash = File f { fileHash = Just hash }
data Manifest = Manifest
{
wasm :: [Wasm]
, memory :: Maybe Memory
, config :: [(String, String)]
, allowed_hosts :: [String]
}
manifest :: [Wasm] -> Manifest
manifest wasm =
Manifest {
wasm = wasm,
memory = Nothing,
config = [],
allowed_hosts = []
}
withConfig :: Manifest -> [(String, String)] -> Manifest
withConfig m config =
m { config = config }
withHosts :: Manifest -> [String] -> Manifest
withHosts m hosts =
m { allowed_hosts = hosts }
instance JSONValue Manifest where
toJSONValue x =
let w = makeArray toJSONValue $ wasm x in
let mem = valueOrNull toJSONValue $ memory x in
let c = mapObj makeString $ config x in
let hosts = makeArray makeString $ allowed_hosts x in
makeObj $ filterNulls [
("wasm", w),
("memory", mem),
("config", c),
("allowed_hosts", hosts)
]
toString :: Manifest -> String
toString manifest =
encode (toJSONValue manifest)

67
haskell/stack.yaml Normal file
View File

@@ -0,0 +1,67 @@
# This file was automatically generated by 'stack init'
#
# Some commonly used options have been documented as comments in this file.
# For advanced use and comprehensive documentation of the format, please see:
# https://docs.haskellstack.org/en/stable/yaml_configuration/
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
# A snapshot resolver dictates the compiler version and the set of packages
# to be used for project dependencies. For example:
#
# resolver: lts-3.5
# resolver: nightly-2015-09-21
# resolver: ghc-7.10.2
#
# The location of a snapshot can be provided as a file or url. Stack assumes
# a snapshot provided as a file might change, whereas a url resource does not.
#
# resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml
resolver:
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml
# User packages to be built.
# Various formats can be used as shown in the example below.
#
# packages:
# - some-directory
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
# subdirs:
# - auto-update
# - wai
packages:
- .
# Dependency packages to be pulled from upstream that are not in the resolver.
# These entries can reference officially published versions as well as
# forks / in-progress versions pinned to a git hash. For example:
#
# extra-deps:
# - acme-missiles-0.3
# - git: https://github.com/commercialhaskell/stack.git
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
#
# extra-deps: []
# Override default flag values for local packages and extra-deps
# flags: {}
# Extra package databases containing global packages
# extra-package-dbs: []
# Control whether we use the GHC we find on the path
# system-ghc: true
#
# Require a specific version of stack, using version ranges
# require-stack-version: -any # Default
# require-stack-version: ">=2.7"
#
# Override the architecture used by stack, especially useful on Windows
# arch: i386
# arch: x86_64
#
# Extra directories used by stack for building
# extra-include-dirs: [/path/to/dir]
# extra-lib-dirs: [/path/to/dir]
#
# Allow a newer minor version of GHC than the snapshot specifies
# compiler-check: newer-minor

13
haskell/stack.yaml.lock Normal file
View File

@@ -0,0 +1,13 @@
# This file was autogenerated by Stack.
# You should not edit this file by hand.
# For more information, please see the documentation at:
# https://docs.haskellstack.org/en/stable/lock_files
packages: []
snapshots:
- completed:
size: 632828
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml
sha256: 5b02c2ce430ac62843fb884126765628da2ca2280bb9de0c6635c723e32a9f6b
original:
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml

View File

@@ -1,2 +0,0 @@
[build]
target = "wasm32-unknown-unknown"

View File

@@ -1,15 +0,0 @@
[package]
name = "extism-runtime-kernel"
version = "0.1.0"
edition = "2021"
[dependencies]
[features]
default = ["bounds-checking"]
bounds-checking = []
[workspace]
members = [
"."
]

View File

@@ -1,20 +0,0 @@
# Extism kernel
The Extism kernel implements core parts of the Extism runtime in Rust compiled to WebAssembly. This code is a conceptual
re-write of [memory.rs][] with the goal of making core parts of the Extism implementation more portable across WebAssembly
runtimes.
See [lib.rs][] for more details about the implementation itself.
## Building
Because this crate is built using the `wasm32-unknown-unknown` target, it is a separate build process from the `extism-runtime` crate.
To build `extism-runtime.wasm`, strip it and copy it to the proper location in the `extism-runtime` tree you can run:
```shell
$ sh build.sh
```
[memory.rs]: https://github.com/extism/extism/blob/f4aa139eced4a74eb4a103f78222ba503e146109/runtime/src/memory.rs
[lib.rs]: ./src/lib.rs

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env bash
export CARGO_FLAGS=""
while getopts d flag
do
case "${flag}" in
d)
echo "Disabled bounds-checking";
export CARGO_FLAGS="--no-default-features";;
*)
echo "usage $0 [-d]"
echo "\t-d: build with bounds checking disabled"
exit 1
esac
done
cargo build --package extism-runtime-kernel --bin extism-runtime --release --target wasm32-unknown-unknown $CARGO_FLAGS
cp target/wasm32-unknown-unknown/release/extism-runtime.wasm .
wasm-strip extism-runtime.wasm
mv extism-runtime.wasm ../runtime/src/extism-runtime.wasm

View File

@@ -1,4 +0,0 @@
[toolchain]
channel = "stable"
components = [ "rustfmt", "rust-std" ]
targets = [ "wasm32-unknown-unknown" ]

View File

@@ -1,10 +0,0 @@
#![no_main]
#![no_std]
pub use extism_runtime_kernel::*;
#[cfg(target_arch = "wasm32")]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
core::arch::wasm32::unreachable()
}

View File

@@ -1,545 +0,0 @@
//! # Extism kernel
//!
//! - Isolated memory from both host and plugin
//! - An allocator for managing that memory
//! - Input/output handling
//! - Error message handling
//!
//! ## Allocator
//!
//! The Extism allocator is a bump allocator that tracks the `length` of the total number of bytes
//! available to the allocator and `position` to track how much of the data has been used. Things like memory
//! have not really been optimized at all. When a new allocation that is larger than the remaning size is made,
//! the allocator attempts to call `memory.grow` if that fails a `0` offset is returned, which should be interpreted
//! as a failed allocation.
//!
//! ## Input/Output
//!
//! Input and output are just allocated blocks of memory that are marked as either input or output using
//! the `input_set` or `output_set` functions. The MemoryRoot field `input_offset` contains
//! the offset in memory to the input data and `input_length` contains the size of the input data. `output_offset`
//! and `output_length` are used for the output data.
//!
//! ## Error handling
//!
//! The `error` field is used to track the current error message. If it is set to `0` then there is no error.
//! The length of the error message can be retreived using `length`.
//!
//! ## Memory offsets
//! An offset of `0` is similar to a `NULL` pointer in C - it implies an allocation failure or memory error
//! of some kind
//!
//! ## Extism functions
//!
//! These functions are backward compatible with the pre-kernel runtime, but a few new functions are added to
//! give runtimes more access to the internals necesarry to load data in and out of a plugin.
#![no_std]
#![allow(clippy::missing_safety_doc)]
use core::sync::atomic::*;
pub type Pointer = u64;
pub type Length = u64;
/// WebAssembly page size
const PAGE_SIZE: usize = 65536;
/// Provides information about the usage status of a `MemoryBlock`
#[repr(u8)]
#[derive(PartialEq)]
pub enum MemoryStatus {
/// Unused memory that is available b
Unused = 0,
/// In-use memory
Active = 1,
/// Free memory that is available for re-use
Free = 2,
}
/// A single `MemoryRoot` exists at the start of the memory to track information about the total
/// size of the allocated memory and the position of the bump allocator.
///
/// The overall layout of the Extism-manged memory is organized like this:
/// |------|-------+---------|-------+--------------|
/// | Root | Block + Data | Block + Data | ...
/// |------|-------+---------|-------+--------------|
///
/// Where `Root` and `Block` are fixed to the size of the `MemoryRoot` and `MemoryBlock` structs. But
/// the size of `Data` is dependent on the allocation size.
///
/// This means that the offset of a `Block` is the size of `Root` plus the size of all existing `Blocks`
/// including their data.
#[repr(C)]
pub struct MemoryRoot {
/// Set to true after initialization
pub initialized: AtomicBool,
/// Position of the bump allocator, relative to `blocks` field
pub position: AtomicU64,
/// The total size of all data allocated using this allocator
pub length: AtomicU64,
/// Offset of error block
pub error: AtomicU64,
/// Input position in memory
pub input_offset: Pointer,
/// Input length
pub input_length: Length,
/// Output position in memory
pub output_offset: Pointer,
/// Output length
pub output_length: Length,
/// A pointer to the start of the first block
pub blocks: [MemoryBlock; 0],
}
/// A `MemoryBlock` contains some metadata about a single allocation
#[repr(C)]
pub struct MemoryBlock {
/// The usage status of the block, `Unused` or `Free` blocks can be re-used.
pub status: AtomicU8,
/// The total size of the allocation
pub size: usize,
/// The number of bytes currently being used. If this block is a fresh allocation then `size` and `used` will
/// always be the same. If a block is re-used then these numbers may differ.
pub used: usize,
/// A pointer to the block data
pub data: [u8; 0],
}
/// Returns the number of pages needed for the given number of bytes
pub fn num_pages(nbytes: u64) -> usize {
let npages = nbytes / PAGE_SIZE as u64;
let remainder = nbytes % PAGE_SIZE as u64;
if remainder != 0 {
(npages + 1) as usize
} else {
npages as usize
}
}
// Get the `MemoryRoot`, this is always stored at offset 1 in memory
#[inline]
unsafe fn memory_root() -> &'static mut MemoryRoot {
&mut *(1 as *mut MemoryRoot)
}
impl MemoryRoot {
/// Initialize or load the `MemoryRoot` from the correct position in memory
pub unsafe fn new() -> &'static mut MemoryRoot {
let root = memory_root();
// If this fails then `INITIALIZED` is already `true` and we can just return the
// already initialized `MemoryRoot`
if root
.initialized
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
return root;
}
// Ensure that at least one page is allocated to store the `MemoryRoot` data
if core::arch::wasm32::memory_size(0) == 0
&& core::arch::wasm32::memory_grow(0, 1) == usize::MAX
{
core::arch::wasm32::unreachable()
}
root.input_offset = 0;
root.input_length = 0;
root.output_offset = 0;
root.output_length = 0;
root.error.store(0, Ordering::Release);
// Initialize the `MemoryRoot` length, position and data
root.length.store(
PAGE_SIZE as u64 - core::mem::size_of::<MemoryRoot>() as u64,
Ordering::Release,
);
root.position.store(0, Ordering::Release);
// Ensure the first block is marked as `Unused`
#[allow(clippy::size_of_in_element_count)]
core::ptr::write_bytes(
root.blocks.as_mut_ptr() as *mut _,
MemoryStatus::Unused as u8,
core::mem::size_of::<MemoryBlock>(),
);
root
}
/// Resets the position of the allocator and zeroes out all allocations
pub unsafe fn reset(&mut self) {
// Clear allocated data
let self_position = self.position.fetch_and(0, Ordering::SeqCst);
core::ptr::write_bytes(
self.blocks.as_mut_ptr() as *mut u8,
MemoryStatus::Unused as u8,
self_position as usize,
);
// Clear extism runtime metadata
self.error.store(0, Ordering::Release);
self.input_offset = 0;
self.input_length = 0;
self.output_offset = 0;
self.output_length = 0;
}
#[inline(always)]
#[allow(unused)]
fn pointer_in_bounds(&self, p: Pointer) -> bool {
let start_ptr = self.blocks.as_ptr() as Pointer;
p >= start_ptr && p < start_ptr + self.length.load(Ordering::Acquire) as Pointer
}
#[inline(always)]
#[allow(unused)]
fn pointer_in_bounds_fast(p: Pointer) -> bool {
// Similar to `pointer_in_bounds` but less accurate on the upper bound. This uses the total memory size,
// instead of checking `MemoryRoot::length`
let end = core::arch::wasm32::memory_size(0) << 16;
p >= core::mem::size_of::<Self>() as Pointer && p <= end as Pointer
}
// Find a block that is free to use, this can be a new block or an existing freed block. The `self_position` argument
// is used to avoid loading the allocators position more than once when performing an allocation.
unsafe fn find_free_block(
&mut self,
length: Length,
self_position: u64,
) -> Option<&'static mut MemoryBlock> {
// Get the first block
let mut block = self.blocks.as_mut_ptr();
// Only loop while the block pointer is less then the current position
while (block as u64) < self.blocks.as_ptr() as u64 + self_position {
let b = &mut *block;
// Get the block status, this lets us know if we are able to re-use it
let status = b.status.load(Ordering::Acquire);
// An unused block is safe to use
if status == MemoryStatus::Unused as u8 {
return Some(b);
}
// Re-use freed blocks when they're large enough
if status == MemoryStatus::Free as u8 && b.size >= length as usize {
// Split block if there is too much excess
if b.size - length as usize >= 128 {
b.size -= length as usize;
b.used = 0;
let block1 = b.data.as_mut_ptr().add(b.size) as *mut MemoryBlock;
let b1 = &mut *block1;
b1.size = length as usize;
b1.used = 0;
b1.status.store(MemoryStatus::Free as u8, Ordering::Release);
return Some(b1);
}
// Otherwise return the whole block
return Some(b);
}
// Get the next block
block = b.next_ptr();
}
None
}
/// Create a new `MemoryBlock`, when `Some(block)` is returned, `block` will contain at least enough room for `length` bytes
/// but may be as large as `length` + `BLOCK_SPLIT_SIZE` bytes. When `None` is returned the allocation has failed.
pub unsafe fn alloc(&mut self, length: Length) -> Option<&'static mut MemoryBlock> {
let self_position = self.position.load(Ordering::Acquire);
let self_length = self.length.load(Ordering::Acquire);
let b = self.find_free_block(length, self_position);
// If there's a free block then re-use it
if let Some(b) = b {
b.used = length as usize;
b.status
.store(MemoryStatus::Active as u8, Ordering::Release);
return Some(b);
}
// Get the current index for a new block
let curr = self.blocks.as_ptr() as u64 + self_position;
// Get the number of bytes available
let mem_left = self_length - self_position - core::mem::size_of::<MemoryRoot>() as u64;
// When the allocation is larger than the number of bytes available
// we will need to try to grow the memory
if length >= mem_left {
// Calculate the number of pages needed to cover the remaining bytes
let npages = num_pages(length - mem_left);
let x = core::arch::wasm32::memory_grow(0, npages);
if x == usize::MAX {
return None;
}
self.length
.fetch_add(npages as u64 * PAGE_SIZE as u64, Ordering::SeqCst);
}
// Bump the position by the size of the actual data + the size of the MemoryBlock structure
self.position.fetch_add(
length + core::mem::size_of::<MemoryBlock>() as u64,
Ordering::SeqCst,
);
// Initialize a new block at the current position
let ptr = curr as *mut MemoryBlock;
let block = &mut *ptr;
block
.status
.store(MemoryStatus::Active as u8, Ordering::Release);
block.size = length as usize;
block.used = length as usize;
Some(block)
}
/// Finds the block at an offset in memory
pub unsafe fn find_block(&mut self, offs: Pointer) -> Option<&mut MemoryBlock> {
if !Self::pointer_in_bounds_fast(offs) {
return None;
}
// Get the first block
let mut block = self.blocks.as_mut_ptr();
// Only loop while the block pointer is less then the current position
while (block as u64) < self.blocks.as_ptr() as u64 + offs {
let b = &mut *block;
// Get the block status, this lets us know if we are able to re-use it
let status = b.status.load(Ordering::Acquire);
if status == MemoryStatus::Active as u8 && b.data.as_ptr() as Pointer == offs {
return Some(b);
}
// Get the next block
block = b.next_ptr();
}
None
}
}
impl MemoryBlock {
/// Get a pointer to the next block
///
/// NOTE: This does no checking to ensure the resulting pointer is valid, the offset
/// is calculated based on metadata provided by the current block
#[inline]
pub unsafe fn next_ptr(&mut self) -> *mut MemoryBlock {
self.data.as_mut_ptr().add(self.size) as *mut MemoryBlock
}
/// Mark a block as free
pub fn free(&mut self) {
self.status
.store(MemoryStatus::Free as u8, Ordering::Release);
}
}
// Extism functions
/// Allocate a block of memory and return the offset
#[no_mangle]
pub unsafe fn alloc(n: Length) -> Pointer {
if n == 0 {
return 0;
}
let region = MemoryRoot::new();
let block = region.alloc(n);
match block {
Some(block) => block.data.as_mut_ptr() as Pointer,
None => 0,
}
}
/// Free allocated memory
#[no_mangle]
pub unsafe fn free(p: Pointer) {
if p == 0 {
return;
}
let root = MemoryRoot::new();
let block = root.find_block(p);
if let Some(block) = block {
block.free();
// If the input pointer is freed for some reason, make sure the input length to 0
// since the original data is gone
if p == root.input_offset {
root.input_length = 0;
}
}
}
/// Get the length of an allocated memory block
#[no_mangle]
pub unsafe fn length(p: Pointer) -> Length {
if p == 0 {
return 0;
}
if let Some(block) = MemoryRoot::new().find_block(p) {
block.used as Length
} else {
0
}
}
/// Load a byte from Extism-managed memory
#[no_mangle]
pub unsafe fn load_u8(p: Pointer) -> u8 {
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::pointer_in_bounds_fast(p) {
return 0;
}
*(p as *mut u8)
}
/// Load a u64 from Extism-managed memory
#[no_mangle]
pub unsafe fn load_u64(p: Pointer) -> u64 {
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::pointer_in_bounds_fast(p + core::mem::size_of::<u64>() as u64 - 1) {
return 0;
}
*(p as *mut u64)
}
/// Load a byte from the input data
#[no_mangle]
pub unsafe fn input_load_u8(p: Pointer) -> u8 {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
if p >= root.input_length {
return 0;
}
*((root.input_offset + p) as *mut u8)
}
/// Load a u64 from the input data
#[no_mangle]
pub unsafe fn input_load_u64(p: Pointer) -> u64 {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
if p + core::mem::size_of::<u64>() as Pointer > root.input_length {
return 0;
}
*((root.input_offset + p) as *mut u64)
}
/// Write a byte in Extism-managed memory
#[no_mangle]
pub unsafe fn store_u8(p: Pointer, x: u8) {
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::pointer_in_bounds_fast(p) {
return;
}
*(p as *mut u8) = x;
}
/// Write a u64 in Extism-managed memory
#[no_mangle]
pub unsafe fn store_u64(p: Pointer, x: u64) {
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::pointer_in_bounds_fast(p + core::mem::size_of::<u64>() as u64 - 1) {
return;
}
*(p as *mut u64) = x;
}
/// Set the range of the input data in memory
#[no_mangle]
pub unsafe fn input_set(p: Pointer, len: Length) {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
{
if !root.pointer_in_bounds(p) || !root.pointer_in_bounds(p + len - 1) {
return;
}
}
root.input_offset = p;
root.input_length = len;
}
/// Set the range of the output data in memory
#[no_mangle]
pub unsafe fn output_set(p: Pointer, len: Length) {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
{
if !root.pointer_in_bounds(p) || !root.pointer_in_bounds(p + len - 1) {
return;
}
}
root.output_offset = p;
root.output_length = len;
}
/// Get the input length
#[no_mangle]
pub fn input_length() -> Length {
unsafe { MemoryRoot::new().input_length }
}
/// Get the input offset in Exitsm-managed memory
#[no_mangle]
pub fn input_offset() -> Length {
unsafe { MemoryRoot::new().input_offset }
}
/// Get the output length
#[no_mangle]
pub fn output_length() -> Length {
unsafe { MemoryRoot::new().output_length }
}
/// Get the output offset in Extism-managed memory
#[no_mangle]
pub unsafe fn output_offset() -> Length {
MemoryRoot::new().output_offset
}
/// Reset the allocator
#[no_mangle]
pub unsafe fn reset() {
MemoryRoot::new().reset()
}
/// Set the error message offset
#[no_mangle]
pub unsafe fn error_set(ptr: Pointer) {
let root = MemoryRoot::new();
// Allow ERROR to be set to 0
if ptr == 0 {
root.error.store(ptr, Ordering::SeqCst);
return;
}
#[cfg(feature = "bounds-checking")]
if !root.pointer_in_bounds(ptr) {
return;
}
root.error.store(ptr, Ordering::SeqCst);
}
/// Get the error message offset, if it's `0` then no error has been set
#[no_mangle]
pub unsafe fn error_get() -> Pointer {
MemoryRoot::new().error.load(Ordering::SeqCst)
}
/// Get the position of the allocator, this can be used as an indication of how many bytes are currently in-use
#[no_mangle]
pub unsafe fn memory_bytes() -> Length {
MemoryRoot::new().length.load(Ordering::Acquire)
}

10
libextism.pc Normal file
View File

@@ -0,0 +1,10 @@
prefix=/usr/local
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${exec_prefix}/lib
Name: extism
Description: The Extism universal plug-in system.
Version: 0.0.1
Cflags: -I${includedir}
Libs: -L${libdir} -lextism

View File

@@ -1,261 +0,0 @@
# libextism API
We [generate C headers](https://github.com/extism/extism/blob/main/runtime/extism.h) so that any language with a C-compatible FFI can bind functions to the runtime itself and embed Extism. This is how most of the [official SDKs](/docs/concepts/host-sdk) are created.
If you would like to embed Extism into a language that we currently do not support, you should take a look at the header file linked above.
The general set of functions that is necessary to satisfy the runtime requirements is:
### `extism_plugin_new`
Create a new plugin.
- `wasm`: is a WASM module (wat or wasm) or a JSON encoded manifest
- `wasm_size`: the length of the `wasm` parameter
- `functions`: is an array of `ExtismFunction*`
- `n_functions`: is the number of functions
- `with_wasi`: enables/disables WASI
- `errmsg`: error message during plugin creation
```c
ExtismPlugin extism_plugin_new(const uint8_t *wasm,
ExtismSize wasm_size,
const ExtismFunction **functions,
ExtismSize n_functions,
bool with_wasi,
char **errmsg);
```
### `extism_plugin_free`
Remove a plugin from the registry and free associated memory.
```c
void extism_plugin_free(ExtismPlugin *plugin);
```
### `extism_plugin_config`
Update plugin config values, this will merge with the existing values.
```c
bool extism_plugin_config(ExtismPlugin *plugin,
const uint8_t *json,
ExtismSize json_size);
```
### `extism_plugin_function_exists`
Returns true if `func_name` exists.
```c
bool extism_plugin_function_exists(ExtismPlugin *plugin,
const char *func_name);
```
### `extism_plugin_call`
Call a function.
- `func_name`: is the function to call
- `data`: is the input data
- `data_len`: is the length of `data`
```c
int32_t extism_plugin_call(ExtismPlugin *plugin,
const char *func_name,
const uint8_t *data,
ExtismSize data_len);
```
### `extism_plugin_error`
Get the error associated with a `Plugin`
```c
const char *extism_plugin_error(ExtismPlugin *plugin);
```
### `extism_plugin_output_length`
Get the length of a plugin's output data.
```c
ExtismSize extism_plugin_output_length(ExtismPlugin *plugin);
```
### `extism_plugin_output_data`
Get the plugin's output data.
```c
const uint8_t *extism_plugin_output_data(ExtismPlugin *plugin);
```
### `extism_plugin_reset`
Reset the Extism runtime, this will invalidate all allocated memory.
```c
bool extism_plugin_reset(ExtismPlugin *plugin);
```
### `extism_log_file`
Set log file and level.
```c
bool extism_log_file(const char *filename, const char *log_level);
```
### `extism_log_custom`
Enable a custom log handler, this will buffer logs until `extism_log_drain`
is called Log level should be one of: info, error, trace, debug, warn
```c
bool extism_log_custom(const char *log_level);
```
### `extism_log_drain`
Calls the provided callback function for each buffered log line.
This is only needed when `extism_log_custom` is used.
```c
void extism_log_drain(void (*handler)(const char *, uintptr_t));
```
### `extism_version`
Get the Extism version string.
```c
const char *extism_version(void);
```
### `extism_current_plugin_memory`
Returns a pointer to the memory of the currently running plugin
```c
uint8_t *extism_current_plugin_memory(ExtismCurrentPlugin *plugin);
```
### `extism_current_plugin_memory_alloc`
Allocate a memory block in the currently running plugin
```c
uint64_t extism_current_plugin_memory_alloc(ExtismCurrentPlugin *plugin, ExtismSize n);
```
### `extism_current_plugin_memory_length`
Get the length of an allocated block
```c
ExtismSize extism_current_plugin_memory_length(ExtismCurrentPlugin *plugin, ExtismSize n);
```
### `extism_current_plugin_memory_free`
Free an allocated memory block
```c
void extism_current_plugin_memory_free(ExtismCurrentPlugin *plugin, uint64_t ptr);
```
### `extism_function_new`
Create a new host function
- `name`: function name, this should be valid UTF-8
- `inputs`: argument types
- `n_inputs`: number of argument types
- `outputs`: return types
- `n_outputs`: number of return types
- `func`: the function to call
- `user_data`: a pointer that will be passed to the function when it's called
this value should live as long as the function exists
- `free_user_data`: a callback to release the `user_data` value when the resulting
`ExtismFunction` is freed.
Returns a new `ExtismFunction` or `null` if the `name` argument is invalid.
```c
ExtismFunction *extism_function_new(const char *name,
const ExtismValType *inputs,
ExtismSize n_inputs,
const ExtismValType *outputs,
ExtismSize n_outputs,
ExtismFunctionType func,
void *user_data,
void (*free_user_data)(void *_));
```
### `extism_function_set_namespace`
Set the namespace of an `ExtismFunction`
```c
void extism_function_set_namespace(ExtismFunction *ptr, const char *namespace_);
```
### `extism_function_free`
Free an `ExtismFunction`
```c
void extism_function_free(ExtismFunction *ptr);
```
### `extism_plugin_cancel_handle`
Get handle for plugin cancellation
```c
const ExtismCancelHandle *extism_plugin_cancel_handle(const ExtismPlugin *plugin);
```
### `extism_plugin_cancel`
Cancel a running plugin from another thread
```c
bool extism_plugin_cancel(const ExtismCancelHandle *handle);
```
## Type definitions:
### `ExtismPlugin`
```c
typedef int32_t ExtismPlugin;
```
### `ExtismSize`
```c
typedef uint64_t ExtismSize;
```
### `ExtismFunction`
`ExtismFunction` is used to register host functions with plugins
```c
typedef struct ExtismFunction ExtismFunction;
```
### `ExtismCurrentPlugin`
`ExtismCurrentPlugin` provides access to the currently executing plugin from within a host function
```c
typedef struct ExtismCurrentPlugin ExtismCurrentPlugin;
```
### `ExtismCancelHandle`
`ExtismCancelHandle` can be used to cancel a running plugin from another thread
```c
typedef struct ExtismCancelHandle ExtismCancelHandle;
```

View File

@@ -1,34 +0,0 @@
project(extism)
cmake_minimum_required(VERSION 3.22)
include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
GIT_TAG v0.4.4
)
FetchContent_MakeAvailable(Corrosion)
corrosion_import_crate(MANIFEST_PATH ./Cargo.toml PROFILE release CRATES libextism FEATURES default)
target_include_directories(extism INTERFACE ../runtime)
target_include_directories(extism-static INTERFACE ../runtime)
target_include_directories(extism-shared INTERFACE ../runtime)
configure_file(extism.pc.in extism.pc @ONLY)
configure_file(extism-static.pc.in extism-static.pc @ONLY)
# corrosion doesn't supporting installing libraries yet
# https://github.com/corrosion-rs/corrosion/issues/415
# so we'll do it ourselves
include(GNUInstallDirs)
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/libextism.a
DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/libextism.so
DESTINATION ${CMAKE_INSTALL_LIBDIR}
OPTIONAL
)
install( FILES ../runtime/extism.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/extism.pc ${CMAKE_CURRENT_BINARY_DIR}/extism-static.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)

View File

@@ -1,23 +0,0 @@
[package]
name = "libextism"
edition.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
version.workspace = true
description = "libextism"
[lib]
name = "extism"
crate-type = ["cdylib", "staticlib"]
doc = false
[dependencies]
extism = {workspace = true, path = "../runtime"}
[features]
default = ["http", "register-http", "register-filesystem"]
register-http = ["extism/register-http"] # enables wasm to be downloaded using http
register-filesystem = ["extism/register-filesystem"] # enables wasm to be loaded from disk
http = ["extism/http"] # enables extism_http_request

View File

@@ -1,34 +0,0 @@
.PHONY: build
build:
$(CC) -g -o example example.c -lextism
.PHONY: static
static:
$(CC) -g -o example example.c -l:libextism.a -lm
# if needed, set PKG_CONFIG_PATH= to the directory with extism*.pc installed
LDFLAGS=`pkg-config --libs extism`
.PHONY: pkg-config
pkg-config:
$(CC) -g -o example example.c $(LDFLAGS)
LDFLAGS_STATIC=`pkg-config --static --libs extism-static`
.PHONY: pkg-config-static
pkg-config-static:
$(CC) -g -o example example.c $(LDFLAGS_STATIC)
# This produces an entirely static binary
#
# MUSL libc is highly recommended over glibc for this purpose as some glibc
# functionality such as getaddrinfo, iconv depends on dynamically loading glibc.
#
# To build and install libextism with musl for x86_64 in the parent directory:
# make RUST_TARGET=x86_64-unknown-linux-musl && sudo make RUST_TARGET=x86_64-unknown-linux-musl install
# Then, from this directory you can build with CC=musl-gcc make fully-static
.PHONY: fully-static
fully-static:
$(CC) -static -g -o example example.c $(LDFLAGS_STATIC)
.PHONY: clean
clean:
rm -f example

View File

@@ -1,292 +0,0 @@
# Extism C SDK
This crate contains no actual code, but is used to generated `libextism` from the [extism](../runtime) crate.
The C SDK is a little different from the other languages because it is generated from the Rust source using cbindgen. It operates at a lower level than the other SDKs because they build higher level abstractions on top of it.
## Building from source
`libextism` can be built using the `Makefile` in the root of the repository:
```shell
make
```
`libextism` will be built in `target/release/libextism.*` and the header file can be found in `runtime/extism.h`
## Installation
The [Extism CLI](https://github.com/extism/cli) can be used to install releases from Github:
```shell
sudo PATH="$PATH" env extism lib install
```
Or from source:
```shell
sudo make install DEST=/usr/local
```
This will install the shared object into `/usr/local/lib` and `extism.h` into `/usr/local/include`.
## Getting Started
To use libextism you should include the header file:
```c
#include <extism.h>
```
and link the library:
```
-lextism
```
### Creating A Plug-in
The primary concept in Extism is the [plug-in](https://extism.org/docs/concepts/plug-in). You can think of a plug-in as a code module stored in a `.wasm` file.
Since you may not have an Extism plug-in on hand to test, let's load a demo plug-in from the web:
```c
#include <extism.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void print_plugin_output(ExtismPlugin *plugin, int32_t rc){
if (rc != EXTISM_SUCCESS) {
fprintf(stderr, "ERROR: %s\n", extism_plugin_error(plugin));
return;
}
size_t outlen = extism_plugin_output_length(plugin);
const uint8_t *out = extism_plugin_output_data(plugin);
write(STDOUT_FILENO, out, outlen);
}
int main(void) {
const char *manifest = "{\"wasm\": [{\"url\": "
"\"https://github.com/extism/plugins/releases/latest/"
"download/count_vowels.wasm\"}]}";
char *errmsg = NULL;
ExtismPlugin *plugin = extism_plugin_new(
(const uint8_t *)manifest, strlen(manifest), NULL, 0, true, &errmsg);
if (plugin == NULL) {
fprintf(stderr, "ERROR: %s\n", errmsg);
extism_plugin_new_error_free(errmsg);
exit(1);
}
const char *input = "Hello, world!";
print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels",
(const uint8_t *)input, strlen(input)));
extism_plugin_free(plugin);
return 0;
}
```
> **Note**: In this case the manifest is a string constant, however it has a rich schema and a lot of options, see the [extism-manifest docs](https://docs.rs/extism-manifest/latest/extism_manifest/) for more details.
### Calling A Plug-in's Exports
This plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one "export" function: `count_vowels`. We can call exports using `extism_plugin_call`, then will use `extism_plugin_output_length`
and `extism_plugin_output_data` to get the result:
```c
int32_t rc = extism_plugin_call(plugin, "count_vowels",
(const uint8_t *)input, strlen(input));
if (rc != EXTISM_SUCCESS) {
fprintf(stderr, "ERROR: %s\n", extism_plugin_error(plugin));
exit(2);
}
size_t outlen = extism_plugin_output_length(plugin);
const uint8_t *out = extism_plugin_output_data(plugin);
write(STDOUT_FILENO, out, outlen);
```
Will print
```
{"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
```
All exports have a simple interface of bytes-in and bytes-out. This plug-in happens to take a string and return a JSON encoded string with a report of results.
### Plug-in State
Plug-ins may be stateful or stateless. Plug-ins can maintain state b/w calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. You can see this by making subsequent calls to the export:
```c
print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels",
(const uint8_t *)input, strlen(input)));
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels",
(const uint8_t *)input, strlen(input)));
# => {"count": 3, "total": 9, "vowels": "aeiouAEIOU"}
```
These variables will persist until this plug-in is freed or you initialize a new one.
### Configuration
Plug-ins may optionally take a configuration object. This is a static way to configure the plug-in. Our count-vowels plugin takes an optional configuration to change out which characters are considered vowels. Example:
```c
const char *input = "Yellow, world!";
print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels",
(const uint8_t *)input, strlen(input)));
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
const char * config = "{\"vowels\": \"aeiouyAEIOUY\"}";
extism_plugin_config(plugin, config, strlen(config));
print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels",
(const uint8_t *)input, strlen(input)));
# => {"count": 4, "total": 4, "vowels": "aeiouyAEIOUY"}
```
### Host Functions
Let's extend our count-vowels example a little bit: Instead of storing the `total` in an ephemeral plug-in var, let's store it in a persistent key-value store!
Wasm can't use our KV store on it's own. This is where [Host Functions](https://extism.org/docs/concepts/host-functions) come in.
[Host functions](https://extism.org/docs/concepts/host-functions) allow us to grant new capabilities to our plug-ins from our application. They are simply some C functions you write which can be passed down and invoked from any language inside the plug-in.
Let's load the manifest like usual but load up this `count_vowels_kvstore` plug-in from `https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm`
> *Note*: The source code for this is [here](https://github.com/extism/plugins/blob/main/count_vowels_kvstore/src/lib.rs) and is written in rust, but it could be written in any of our PDK languages.
Unlike our previous plug-in, this plug-in expects you to provide host functions that satisfy our its import interface for a KV store.
We want to expose two functions to our plugin, `kv_write(key: String, value: Bytes)` which writes a bytes value to a key and `kv_read(key: String) -> Bytes` which reads the bytes at the given `key`.
```c
#include <extism.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// A stubbed out KV store
typedef struct KVStore KVStore;
extern KVStore *fake_kv_store_new();
extern void fake_kv_store_free(KVStore *kv);
extern void fake_kv_store_set(KVStore *kv, const char *key, size_t keylen,
uint32_t);
extern const uint32_t fake_kv_store_get(KVStore *kv, const char *key,
size_t keylen);
// Our host functions to access the fake KV store
void kv_get(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
size_t ninputs, ExtismVal *outputs, size_t noutputs,
void *userdata) {
// Cast the userdata pointer
KVStore *kv = (KVStore *)userdata;
// Get the offset to the key in the plugin memory
uint64_t offs = inputs[0].v.i64;
size_t keylen = extism_current_plugin_memory_length(plugin, offs);
// Allocate a new block to return
uint64_t outoffs =
extism_current_plugin_memory_alloc(plugin, sizeof(uint32_t));
// Load the value from our k/v store
uint64_t value = fake_kv_store_get(
kv, (const char *)extism_current_plugin_memory(plugin) + offs, keylen);
// Update the plugin memory
*(uint64_t *)(extism_current_plugin_memory(plugin) + outoffs) = value;
// Return the offset to our allocated block
outputs[0].t = PTR;
outputs[0].v.i64 = outoffs;
}
void kv_set(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
size_t ninputs, ExtismVal *outputs, size_t noutputs,
void *userdata) {
// Cast the userdata pointer
KVStore *kv = (KVStore *)userdata;
// Get the offset to the key in the plugin memory
uint64_t keyoffs = inputs[0].v.i64;
size_t keylen = extism_current_plugin_memory_length(plugin, keyoffs);
// Get the offset to the value in the plugin memory
uint64_t valueoffs = inputs[1].v.i64;
size_t valuelen = extism_current_plugin_memory_length(plugin, valueoffs);
// Set key => value
fake_kv_store_set(
kv, (const char *)extism_current_plugin_memory(plugin) + keyoffs, keylen,
*(uint32_t *)(extism_current_plugin_memory(plugin) + keyoffs));
}
int main(void) {
KVStore *kv = fake_kv_store_new();
const char *manifest = "{\"wasm\": [{\"url\": "
"\"https://github.com/extism/plugins/releases/latest/"
"download/count_vowels_kvstore.wasm\"}]}";
const ExtismValType kv_get_inputs[] = {PTR};
const ExtismValType kv_get_outputs[] = {PTR};
ExtismFunction *kv_get_fn = extism_function_new(
"kv_get", kv_get_inputs, 1, kv_get_outputs, 1, kv_get, kv, NULL);
const ExtismValType kv_set_inputs[] = {PTR};
const ExtismValType kv_set_outputs[] = {PTR};
ExtismFunction *kv_set_fn = extism_function_new(
"kv_set", kv_set_inputs, 1, kv_set_outputs, 1, kv_set, kv, NULL);
const ExtismFunction *functions[] = {kv_get_fn};
char *errmsg = NULL;
ExtismPlugin *plugin = extism_plugin_new(
(const uint8_t *)manifest, strlen(manifest), functions, 1, true, &errmsg);
if (plugin == NULL) {
fprintf(stderr, "ERROR: %s\n", errmsg);
extism_plugin_new_error_free(errmsg);
exit(1);
}
const char *input = "Hello, world!";
print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels",
(const uint8_t *)input,
strlen(input)));
print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels",
(const uint8_t *)input,
strlen(input)));
extism_plugin_free(plugin);
extism_function_free(kv_get_fn);
extism_function_free(kv_set_fn);
fake_kv_store_free(kv);
return 0;
}
```
> *Note*: In order to write host functions you should get familiar with the `extism_current_plugin_*` functions.
Now when we invoke the event:
```c
print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels",
(const uint8_t *)input,
strlen(input)));
# => Read from key=count-vowels"
# => Writing value=3 from key=count-vowels"
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels",
(const uint8_t *)input,
strlen(input)));
# => Read from key=count-vowels"
# => Writing value=6 from key=count-vowels"
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
```

View File

@@ -1,92 +0,0 @@
#include "../runtime/extism.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
void log_handler(const char *line, uintptr_t length) {
fwrite(line, length, 1, stderr);
}
void hello_world(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
uint64_t n_inputs, ExtismVal *outputs, uint64_t n_outputs,
void *data) {
puts("Hello from C!");
puts(data);
ExtismSize ptr_offs = inputs[0].v.i64;
uint8_t *buf = extism_current_plugin_memory(plugin) + ptr_offs;
uint64_t length = extism_current_plugin_memory_length(plugin, ptr_offs);
fwrite(buf, length, 1, stdout);
fputc('\n', stdout);
outputs[0].v.i64 = inputs[0].v.i64;
}
void free_data(void *x) { puts("Freeing userdata"); }
uint8_t *read_file(const char *filename, size_t *len) {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
return NULL;
}
fseek(fp, 0, SEEK_END);
size_t length = ftell(fp);
fseek(fp, 0, SEEK_SET);
uint8_t *data = malloc(length);
if (data == NULL) {
fclose(fp);
return NULL;
}
assert(fread(data, 1, length, fp) == length);
fclose(fp);
*len = length;
return data;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fputs("Not enough arguments\n", stderr);
exit(1);
}
extism_log_custom("extism=trace,cranelift=trace");
size_t len = 0;
uint8_t *data = read_file("../wasm/code-functions.wasm", &len);
ExtismValType inputs[] = {PTR};
ExtismValType outputs[] = {PTR};
ExtismFunction *f =
extism_function_new("hello_world", inputs, 1, outputs, 1, hello_world,
"Hello, again!", free_data);
char *errmsg = NULL;
ExtismPlugin *plugin = extism_plugin_new(
data, len, (const ExtismFunction **)&f, 1, true, &errmsg);
free(data);
if (plugin == NULL) {
puts(errmsg);
extism_plugin_new_error_free(errmsg);
exit(1);
}
assert(extism_plugin_call(plugin, "count_vowels", (uint8_t *)argv[1],
strlen(argv[1])) == 0);
ExtismSize out_len = extism_plugin_output_length(plugin);
const uint8_t *output = extism_plugin_output_data(plugin);
write(STDOUT_FILENO, output, out_len);
write(STDOUT_FILENO, "\n", 1);
extism_plugin_free(plugin);
extism_function_free(f);
extism_log_drain(log_handler);
return 0;
}

View File

@@ -1,10 +0,0 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix}
libdir=${prefix}/lib
includedir=${prefix}/include
Version: 1.0.0
Name: Extism
Description: The framework for building with WebAssembly (wasm).
Libs: -L${libdir} -l:libextism.a
Libs.private: -lm
Cflags: -I${includedir}

View File

@@ -1,10 +0,0 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix}
libdir=${prefix}/lib
includedir=${prefix}/include
Version: 1.0.0
Name: Extism
Description: The framework for building with WebAssembly (wasm).
Libs: -L${libdir} -lextism
Libs.private: -lm
Cflags: -I${includedir}

View File

@@ -1,10 +0,0 @@
//! This crate is used to generate `libextism` using `extism-runtime`
pub use extism::sdk::*;
#[cfg(test)]
#[test]
fn test_version() {
let s = unsafe { std::ffi::CStr::from_ptr(extism_version()) };
assert!(s.to_bytes() != b"0.0.0");
}

View File

@@ -1,22 +1,25 @@
[package]
name = "extism-manifest"
version = "0.0.1-rc.5"
edition = "2021"
authors = ["The Extism Authors", "oss@extism.org"]
license = "BSD-3-Clause"
homepage = "https://extism.org"
repository = "https://github.com/extism/extism"
description = "Extism plug-in manifest crate"
edition.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
version.workspace = true
[dependencies]
serde = { version = "1", features = ["derive"] }
base64 = "~0.21"
schemars = { version = "0.8", optional = true }
serde_json = "1"
serde = {version = "1", features=["derive"]}
base64 = "0.20.0-alpha"
schemars = {version = "0.8", optional=true}
[features]
json_schema = ["schemars"]
[dev-dependencies]
serde_json = "1"
[[example]]
name = "json_schema"
required-features = ["json_schema"]

View File

@@ -13,16 +13,6 @@
"type": "string"
}
},
"allowed_paths": {
"default": null,
"type": [
"object",
"null"
],
"additionalProperties": {
"type": "string"
}
},
"config": {
"default": {},
"type": "object",
@@ -32,35 +22,27 @@
},
"memory": {
"default": {
"max_pages": null
"max": null
},
"allOf": [
{
"$ref": "#/definitions/MemoryOptions"
"$ref": "#/definitions/ManifestMemory"
}
]
},
"timeout_ms": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
},
"wasm": {
"default": [],
"type": "array",
"items": {
"$ref": "#/definitions/Wasm"
"$ref": "#/definitions/ManifestWasm"
}
}
},
"definitions": {
"MemoryOptions": {
"ManifestMemory": {
"type": "object",
"properties": {
"max_pages": {
"max": {
"type": [
"integer",
"null"
@@ -70,7 +52,7 @@
}
}
},
"Wasm": {
"ManifestWasm": {
"anyOf": [
{
"type": "object",
@@ -131,7 +113,7 @@
"null"
]
},
"headers": {
"header": {
"default": {},
"type": "object",
"additionalProperties": {

View File

@@ -1,200 +1,64 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
#[deprecated]
pub type ManifestMemory = MemoryOptions;
/// Configure memory settings
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Default, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct MemoryOptions {
/// The max number of WebAssembly pages that should be allocated
#[serde(alias = "max")]
pub max_pages: Option<u32>,
pub struct ManifestMemory {
pub max: Option<u32>,
}
/// Generic HTTP request structure
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct HttpRequest {
/// The request URL
pub url: String,
/// Request headers
#[serde(default)]
#[serde(alias = "header")]
pub headers: std::collections::BTreeMap<String, String>,
/// Request method
pub header: std::collections::BTreeMap<String, String>,
pub method: Option<String>,
}
impl HttpRequest {
/// Create a new `HttpRequest` to the given URL
pub fn new(url: impl Into<String>) -> HttpRequest {
HttpRequest {
url: url.into(),
headers: Default::default(),
header: Default::default(),
method: None,
}
}
/// Update the method
pub fn with_method(mut self, method: impl Into<String>) -> HttpRequest {
self.method = Some(method.into());
self
}
/// Add a header
pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> HttpRequest {
self.headers.insert(key.into(), value.into());
self.header.insert(key.into(), value.into());
self
}
}
/// Provides additional metadata about a Webassembly module
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct WasmMetadata {
/// Module name, this is used by Extism to determine which is the `main` module
pub name: Option<String>,
/// Module hash, if the data loaded from disk or via HTTP doesn't match an error will be raised
pub hash: Option<String>,
}
impl From<HttpRequest> for Wasm {
fn from(req: HttpRequest) -> Self {
Wasm::Url {
req,
meta: WasmMetadata::default(),
}
}
}
impl From<std::path::PathBuf> for Wasm {
fn from(path: std::path::PathBuf) -> Self {
Wasm::File {
path,
meta: WasmMetadata::default(),
}
}
}
impl From<Vec<u8>> for Wasm {
fn from(data: Vec<u8>) -> Self {
Wasm::Data {
data,
meta: WasmMetadata::default(),
}
}
}
#[deprecated]
pub type ManifestWasm = Wasm;
/// The `Wasm` type specifies how to access a WebAssembly module
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
#[serde(deny_unknown_fields)]
pub enum Wasm {
/// From disk
pub enum ManifestWasm {
File {
path: PathBuf,
#[serde(flatten)]
meta: WasmMetadata,
path: std::path::PathBuf,
name: Option<String>,
hash: Option<String>,
},
/// From memory
Data {
#[serde(with = "base64")]
#[cfg_attr(feature = "json_schema", schemars(schema_with = "base64_schema"))]
data: Vec<u8>,
#[serde(flatten)]
meta: WasmMetadata,
name: Option<String>,
hash: Option<String>,
},
/// Via HTTP
Url {
#[serde(flatten)]
req: HttpRequest,
#[serde(flatten)]
meta: WasmMetadata,
name: Option<String>,
hash: Option<String>,
},
}
impl Wasm {
/// Load Wasm from a path
pub fn file(path: impl AsRef<std::path::Path>) -> Self {
Wasm::File {
path: path.as_ref().to_path_buf(),
meta: Default::default(),
}
}
/// Load Wasm directly from a buffer
pub fn data(data: impl Into<Vec<u8>>) -> Self {
Wasm::Data {
data: data.into(),
meta: Default::default(),
}
}
/// Load Wasm from a URL
pub fn url(url: impl Into<String>) -> Self {
Wasm::Url {
req: HttpRequest {
url: url.into(),
headers: Default::default(),
method: None,
},
meta: Default::default(),
}
}
/// Load Wasm from an HTTP request
pub fn http(req: impl Into<HttpRequest>) -> Self {
Wasm::Url {
req: req.into(),
meta: Default::default(),
}
}
/// Get the metadata
pub fn meta(&self) -> &WasmMetadata {
match self {
Wasm::File { path: _, meta } => meta,
Wasm::Data { data: _, meta } => meta,
Wasm::Url { req: _, meta } => meta,
}
}
/// Get mutable access to the metadata
pub fn meta_mut(&mut self) -> &mut WasmMetadata {
match self {
Wasm::File { path: _, meta } => meta,
Wasm::Data { data: _, meta } => meta,
Wasm::Url { req: _, meta } => meta,
}
}
/// Update Wasm module name
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.meta_mut().name = Some(name.into());
self
}
/// Update Wasm module hash
pub fn with_hash(mut self, hash: impl Into<String>) -> Self {
self.meta_mut().hash = Some(hash.into());
self
}
}
#[cfg(feature = "json_schema")]
fn base64_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
use schemars::{schema::SchemaObject, JsonSchema};
@@ -203,166 +67,30 @@ fn base64_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::
schema.into()
}
/// The `Manifest` type is used to configure the runtime and specify how to load modules.
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Default, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct Manifest {
/// WebAssembly modules, the `main` module should be named `main` or listed last
#[serde(default)]
pub wasm: Vec<Wasm>,
/// Memory options
pub wasm: Vec<ManifestWasm>,
#[serde(default)]
pub memory: MemoryOptions,
/// Config values are made accessible using the PDK `extism_config_get` function
pub memory: ManifestMemory,
#[serde(default)]
pub config: BTreeMap<String, String>,
#[serde(default)]
/// Specifies which hosts may be accessed via HTTP, if this is empty then
/// no hosts may be accessed. Wildcards may be used.
pub allowed_hosts: Option<Vec<String>>,
/// Specifies which paths should be made available on disk when using WASI. This is a mapping from
/// the path on disk to the path it should be available inside the plugin.
/// For example, `".": "/tmp"` would mount the current directory as `/tmp` inside the module
#[serde(default)]
pub allowed_paths: Option<BTreeMap<PathBuf, PathBuf>>,
/// The plugin timeout, by default this is set to 30s
#[serde(default)]
pub timeout_ms: Option<u64>,
}
impl Manifest {
/// Create a new manifest
pub fn new(wasm: impl IntoIterator<Item = impl Into<Wasm>>) -> Manifest {
Manifest {
wasm: wasm.into_iter().map(|x| x.into()).collect(),
..Default::default()
}
}
pub fn with_wasm(mut self, wasm: impl Into<Wasm>) -> Self {
self.wasm.push(wasm.into());
self
}
/// Disallow HTTP requests to all hosts
pub fn disallow_all_hosts(mut self) -> Self {
self.allowed_hosts = Some(vec![]);
self
}
/// Set memory options
pub fn with_memory_options(mut self, memory: MemoryOptions) -> Self {
self.memory = memory;
self
}
/// Set MemoryOptions::memory_max
pub fn with_memory_max(mut self, max: u32) -> Self {
self.memory.max_pages = Some(max);
self
}
/// Add a hostname to `allowed_hosts`
pub fn with_allowed_host(mut self, host: impl Into<String>) -> Self {
match &mut self.allowed_hosts {
Some(h) => {
h.push(host.into());
}
None => self.allowed_hosts = Some(vec![host.into()]),
}
self
}
/// Set `allowed_hosts`
pub fn with_allowed_hosts(mut self, hosts: impl Iterator<Item = String>) -> Self {
self.allowed_hosts = Some(hosts.collect());
self
}
/// Add a path to `allowed_paths`
pub fn with_allowed_path(mut self, src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Self {
let src = src.as_ref().to_path_buf();
let dest = dest.as_ref().to_path_buf();
match &mut self.allowed_paths {
Some(p) => {
p.insert(src, dest);
}
None => {
let mut p = BTreeMap::new();
p.insert(src, dest);
self.allowed_paths = Some(p);
}
}
self
}
/// Set `allowed_paths`
pub fn with_allowed_paths(mut self, paths: impl Iterator<Item = (PathBuf, PathBuf)>) -> Self {
self.allowed_paths = Some(paths.collect());
self
}
/// Set `config`
pub fn with_config(
mut self,
c: impl Iterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self {
for (k, v) in c {
self.config.insert(k.into(), v.into());
}
self
}
/// Set a single `config` key
pub fn with_config_key(mut self, k: impl Into<String>, v: impl Into<String>) -> Self {
self.config.insert(k.into(), v.into());
self
}
/// Set `timeout_ms`, which will interrupt a plugin function's execution if it meets or
/// exceeds this value. When an interrupt is made, the plugin will not be able to recover and
/// continue execution.
pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
self.timeout_ms = Some(timeout.as_millis() as u64);
self
}
}
mod base64 {
use base64::{engine::general_purpose, Engine as _};
use serde::{Deserialize, Serialize};
use serde::{Deserializer, Serializer};
pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
let base64 = general_purpose::STANDARD.encode(v.as_slice());
let base64 = base64::encode(v);
String::serialize(&base64, s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
let base64 = String::deserialize(d)?;
general_purpose::STANDARD
.decode(base64.as_bytes())
.map_err(serde::de::Error::custom)
}
}
impl<'a> From<Manifest> for std::borrow::Cow<'a, [u8]> {
fn from(m: Manifest) -> Self {
let s = serde_json::to_vec(&m).unwrap();
std::borrow::Cow::Owned(s)
}
}
impl<'a> From<&Manifest> for std::borrow::Cow<'a, [u8]> {
fn from(m: &Manifest) -> Self {
let s = serde_json::to_vec(&m).unwrap();
std::borrow::Cow::Owned(s)
base64::decode(base64.as_bytes()).map_err(serde::de::Error::custom)
}
}

1
node/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist/

22
node/example.js Normal file
View File

@@ -0,0 +1,22 @@
import { withContext, Context } from './dist/index.js';
import { readFileSync } from 'fs';
withContext(async function (context) {
let wasm = readFileSync('../wasm/code.wasm');
let p = context.plugin(wasm);
if (!p.functionExists('count_vowels')) {
console.log("no function 'count_vowels' in wasm");
process.exit(1);
}
let buf = await p.call('count_vowels', process.argv[2] || 'this is a test');
console.log(JSON.parse(buf.toString())['count']);
p.free();
});
// or, use a context like this:
let ctx = new Context();
let wasm = readFileSync('../wasm/code.wasm');
let p = ctx.plugin(wasm);
// ... where the context can be passed around to various functions etc.

287
node/package-lock.json generated Normal file
View File

@@ -0,0 +1,287 @@
{
"name": "@extism/extism",
"version": "0.0.1-rc.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@extism/extism",
"version": "0.0.1-rc.5",
"license": "BSD-3-Clause",
"dependencies": {
"ffi-napi": "^4.0.3"
},
"devDependencies": {
"@types/ffi-napi": "^4.0.6",
"@types/node": "^18.11.4",
"typescript": "^4.8.4"
}
},
"node_modules/@types/ffi-napi": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/ffi-napi/-/ffi-napi-4.0.6.tgz",
"integrity": "sha512-yrBtqeVD1aeVo271jXVEo3iAtbzSGVGRssJv9W9JlUfg5Z5FgHJx2MV88GRwVATu/XWg6zyenW/cb1MNAuOtaQ==",
"dev": true,
"dependencies": {
"@types/node": "*",
"@types/ref-napi": "*",
"@types/ref-struct-di": "*"
}
},
"node_modules/@types/node": {
"version": "18.11.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.4.tgz",
"integrity": "sha512-BxcJpBu8D3kv/GZkx/gSMz6VnTJREBj/4lbzYOQueUOELkt8WrO6zAcSPmp9uRPEW/d+lUO8QK0W2xnS1hEU0A==",
"dev": true
},
"node_modules/@types/ref-napi": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/ref-napi/-/ref-napi-3.0.5.tgz",
"integrity": "sha512-u+L/RdwTuJes3pDypOVR/MtcqzoULu8Z8yulP6Tw5z7eXV1ba1llizNVFtI/m2iPfDy/dPPt+3ar1QCgonTzsw==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/ref-struct-di": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@types/ref-struct-di/-/ref-struct-di-1.1.7.tgz",
"integrity": "sha512-nnHR26qrCnQqxwHTv+rqzu/hGgDZl45TUs4bO6ZjpuC8/M2JoXFxk63xrWmAmqsLe55oxOgAWssyr3YHAMY89g==",
"dev": true,
"dependencies": {
"@types/ref-napi": "*"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/ffi-napi": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/ffi-napi/-/ffi-napi-4.0.3.tgz",
"integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==",
"hasInstallScript": true,
"dependencies": {
"debug": "^4.1.1",
"get-uv-event-loop-napi-h": "^1.0.5",
"node-addon-api": "^3.0.0",
"node-gyp-build": "^4.2.1",
"ref-napi": "^2.0.1 || ^3.0.2",
"ref-struct-di": "^1.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/get-symbol-from-current-process-h": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.2.tgz",
"integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw=="
},
"node_modules/get-uv-event-loop-napi-h": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/get-uv-event-loop-napi-h/-/get-uv-event-loop-napi-h-1.0.6.tgz",
"integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==",
"dependencies": {
"get-symbol-from-current-process-h": "^1.0.1"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/node-addon-api": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz",
"integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A=="
},
"node_modules/node-gyp-build": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz",
"integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/ref-napi": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-3.0.3.tgz",
"integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==",
"hasInstallScript": true,
"dependencies": {
"debug": "^4.1.1",
"get-symbol-from-current-process-h": "^1.0.2",
"node-addon-api": "^3.0.0",
"node-gyp-build": "^4.2.1"
},
"engines": {
"node": ">= 10.0"
}
},
"node_modules/ref-struct-di": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ref-struct-di/-/ref-struct-di-1.1.1.tgz",
"integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==",
"dependencies": {
"debug": "^3.1.0"
}
},
"node_modules/ref-struct-di/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/typescript": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
}
},
"dependencies": {
"@types/ffi-napi": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/ffi-napi/-/ffi-napi-4.0.6.tgz",
"integrity": "sha512-yrBtqeVD1aeVo271jXVEo3iAtbzSGVGRssJv9W9JlUfg5Z5FgHJx2MV88GRwVATu/XWg6zyenW/cb1MNAuOtaQ==",
"dev": true,
"requires": {
"@types/node": "*",
"@types/ref-napi": "*",
"@types/ref-struct-di": "*"
}
},
"@types/node": {
"version": "18.11.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.4.tgz",
"integrity": "sha512-BxcJpBu8D3kv/GZkx/gSMz6VnTJREBj/4lbzYOQueUOELkt8WrO6zAcSPmp9uRPEW/d+lUO8QK0W2xnS1hEU0A==",
"dev": true
},
"@types/ref-napi": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/ref-napi/-/ref-napi-3.0.5.tgz",
"integrity": "sha512-u+L/RdwTuJes3pDypOVR/MtcqzoULu8Z8yulP6Tw5z7eXV1ba1llizNVFtI/m2iPfDy/dPPt+3ar1QCgonTzsw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/ref-struct-di": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@types/ref-struct-di/-/ref-struct-di-1.1.7.tgz",
"integrity": "sha512-nnHR26qrCnQqxwHTv+rqzu/hGgDZl45TUs4bO6ZjpuC8/M2JoXFxk63xrWmAmqsLe55oxOgAWssyr3YHAMY89g==",
"dev": true,
"requires": {
"@types/ref-napi": "*"
}
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
},
"ffi-napi": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/ffi-napi/-/ffi-napi-4.0.3.tgz",
"integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==",
"requires": {
"debug": "^4.1.1",
"get-uv-event-loop-napi-h": "^1.0.5",
"node-addon-api": "^3.0.0",
"node-gyp-build": "^4.2.1",
"ref-napi": "^2.0.1 || ^3.0.2",
"ref-struct-di": "^1.1.0"
}
},
"get-symbol-from-current-process-h": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.2.tgz",
"integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw=="
},
"get-uv-event-loop-napi-h": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/get-uv-event-loop-napi-h/-/get-uv-event-loop-napi-h-1.0.6.tgz",
"integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==",
"requires": {
"get-symbol-from-current-process-h": "^1.0.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node-addon-api": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz",
"integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A=="
},
"node-gyp-build": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz",
"integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg=="
},
"ref-napi": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-3.0.3.tgz",
"integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==",
"requires": {
"debug": "^4.1.1",
"get-symbol-from-current-process-h": "^1.0.2",
"node-addon-api": "^3.0.0",
"node-gyp-build": "^4.2.1"
}
},
"ref-struct-di": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ref-struct-di/-/ref-struct-di-1.1.1.tgz",
"integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==",
"requires": {
"debug": "^3.1.0"
},
"dependencies": {
"debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"requires": {
"ms": "^2.1.1"
}
}
}
},
"typescript": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"dev": true
}
}
}

39
node/package.json Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "@extism/extism",
"version": "0.0.1-rc.5",
"description": "Extism Host SDK for Node",
"keywords": [
"extism",
"webassembly",
"wasm",
"plugins",
"extension"
],
"author": "The Extism Authors <oss@extism.org>",
"license": "BSD-3-Clause",
"private": false,
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"homepage": "https://extism.org",
"repository": {
"type": "git",
"url": "https://github.com/extism/extism.git"
},
"scripts": {
"prepare" : "npm run build",
"example": "node example.js",
"build": "tsc"
},
"dependencies": {
"ffi-napi": "^4.0.3"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/ffi-napi": "^4.0.6",
"@types/node": "^18.11.4",
"typescript": "^4.8.4"
}
}

253
node/src/index.ts Normal file
View File

@@ -0,0 +1,253 @@
import ffi from 'ffi-napi';
import path from 'path';
import url from 'url';
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const context = 'void*';
const _functions = {
extism_context_new: [context, []],
extism_context_free: ['void', [context]],
extism_plugin_new: ['int32', [context, 'string', 'uint64', 'bool']],
extism_plugin_update: ['bool', [context, 'int32', 'string', 'uint64', 'bool']],
extism_error: ['char*', [context, 'int32']],
extism_plugin_call: ['int32', [context, 'int32', 'string', 'string', 'uint64']],
extism_plugin_output_length: ['uint64', [context, 'int32']],
extism_plugin_output_data: ['uint8*', [context, 'int32']],
extism_log_file: ['bool', ['string', 'char*']],
extism_plugin_function_exists: ['bool', [context, 'int32', 'string']],
extism_plugin_config: ['void', [context, 'int32', 'char*', 'uint64']],
extism_plugin_free: ['void', [context, 'int32']],
extism_context_reset: ['void', [context]],
extism_version: ['char*', []],
};
interface LibExtism {
extism_context_new: () => Buffer;
extism_context_free: (ctx: Buffer) => void;
extism_plugin_new: (ctx: Buffer, data: string | Buffer, data_len: number, wasi: boolean) => number;
extism_plugin_update: (ctx: Buffer, plugin_id: number, data: string | Buffer, data_len: number, wasi: boolean) => boolean;
extism_error: (ctx: Buffer, plugin_id: number) => string;
extism_plugin_call: (ctx: Buffer, plugin_id: number, func: string, input: string, input_len: number) => number;
extism_plugin_output_length: (ctx: Buffer, plugin_id: number) => number;
extism_plugin_output_data: (ctx: Buffer, plugin_id: Number) => Uint8Array;
extism_log_file: (file: string, level: string) => boolean;
extism_plugin_function_exists: (ctx: Buffer, plugin_id: number, func: string) => boolean;
extism_plugin_config: (ctx: Buffer, plugin_id: number, data: string | Buffer, data_len: number) => void;
extism_plugin_free: (ctx: Buffer, plugin_id: number) => void;
extism_context_reset: (ctx: Buffer) => void;
extism_version: () => string;
}
function locate(paths: string[]): LibExtism {
for (var i = 0; i < paths.length; i++) {
try {
// @ts-ignore
return ffi.Library(path.join(paths[i], 'libextism'), _functions);
} catch (exn) {
continue;
}
}
throw 'Unable to locate libextism';
}
const searchPath = [
__dirname,
'/usr/local/lib',
'/usr/lib',
path.join(process.env.HOME as string, '.local', 'lib'),
];
if (process.env.EXTISM_PATH) {
searchPath.unshift(path.join(process.env.EXTISM_PATH, 'lib'));
}
const lib = locate(searchPath);
// Set the log file and level
export function setLogFile(filename: string, level?: string) {
lib.extism_log_file(filename, level || "info");
}
// Get the version of Extism
export function extismVersion(): string {
return lib.extism_version().toString();
}
// @ts-ignore
const pluginRegistry = new FinalizationRegistry(({ id, pointer }) => {
lib.extism_plugin_free(pointer, id);
});
// @ts-ignore
const contextRegistry = new FinalizationRegistry((pointer) => {
lib.extism_context_free(pointer);
});
export type ManifestWasmFile = {
path: string;
name?: string;
hash?: string;
}
export type ManifestWasmData = {
data: Uint8Array;
name?: string;
hash?: string;
}
export type ManifestMemory = {
max?: number;
}
export type PluginConfig = Map<string, string>;
export type ManifestWasm = ManifestWasmFile | ManifestWasmData;
export type Manifest = {
wasm: Array<ManifestWasm>;
memory: ManifestMemory;
config?: PluginConfig;
allowed_hosts?: Array<string>;
}
type ManifestData = Manifest | Buffer | string;
// Context manages plugins
export class Context {
pointer: Buffer | null;
constructor() {
this.pointer = lib.extism_context_new();
contextRegistry.register(this, this.pointer, this);
}
// Create a new plugin, optionally enabling WASI
plugin(data: ManifestData, wasi: boolean = false, config?: PluginConfig) {
return new Plugin(this, data, wasi, config);
}
// Free a context, this should be called when it is
// no longer needed
free() {
if (this.pointer) {
contextRegistry.unregister(this);
lib.extism_context_free(this.pointer);
}
this.pointer = null;
}
// Remove all registered plugins
reset() {
if (this.pointer)
lib.extism_context_reset(this.pointer);
}
}
export async function withContext(f: (ctx: Context) => Promise<any>) {
let ctx = new Context();
try {
let x = await f(ctx);
ctx.free();
return x;
} catch (err) {
ctx.free();
throw err;
}
}
// Plugin provides an interface for calling WASM functions
export class Plugin {
id: number;
ctx: Context;
constructor(ctx: Context, data: ManifestData, wasi: boolean = false, config?: PluginConfig) {
let dataRaw: string | Buffer;
if (Buffer.isBuffer(data) || typeof data === 'string') {
dataRaw = data
} else if (typeof data === 'object' && data.wasm) {
dataRaw = JSON.stringify(data)
} else {
throw Error(`Unknown manifest type ${typeof data}`);
}
if (!ctx.pointer) throw Error("No Context set");
let plugin = lib.extism_plugin_new(ctx.pointer, dataRaw, dataRaw.length, wasi);
if (plugin < 0) {
var err = lib.extism_error(ctx.pointer, -1);
if (err.length === 0) {
throw "extism_context_plugin failed";
}
throw `Unable to load plugin: ${err.toString()}`;
}
this.id = plugin;
this.ctx = ctx;
pluginRegistry.register(this, { id: this.id, pointer: this.ctx.pointer }, this);
if (config != null) {
let s = JSON.stringify(config);
lib.extism_plugin_config(ctx.pointer, this.id, s, s.length);
}
}
// Update an existing plugin with new WASM or manifest
update(data: ManifestData, wasi: boolean = false, config?: PluginConfig) {
let dataRaw: string | Buffer;
if (Buffer.isBuffer(data) || typeof data === 'string') {
dataRaw = data
} else if (typeof data === 'object' && data.wasm) {
dataRaw = JSON.stringify(data)
} else {
throw Error("Unknown manifest type type");
}
if (!this.ctx.pointer) throw Error("No Context set");
const ok = lib.extism_plugin_update(this.ctx.pointer, this.id, dataRaw, dataRaw.length, wasi);
if (!ok) {
var err = lib.extism_error(this.ctx.pointer, -1);
if (err.length === 0) {
throw "extism_plugin_update failed";
}
throw `Unable to update plugin: ${err.toString()}`;
}
if (config != null) {
let s = JSON.stringify(config);
lib.extism_plugin_config(this.ctx.pointer, this.id, s, s.length);
}
}
// Check if a function exists
functionExists(name: string) {
if (!this.ctx.pointer) throw Error("No Context set");
return lib.extism_plugin_function_exists(this.ctx.pointer, this.id, name);
}
// Call a function by name with the given input
async call(name: string, input: string) {
return new Promise((resolve, reject) => {
if (!this.ctx.pointer) throw Error("No Context set");
var rc = lib.extism_plugin_call(this.ctx.pointer, this.id, name, input, input.length);
if (rc !== 0) {
var err = lib.extism_error(this.ctx.pointer, this.id);
if (err.length === 0) {
reject(`extism_plugin_call: "${name}" failed`);
}
reject(`Plugin error: ${err.toString()}, code: ${rc}`);
}
var out_len = lib.extism_plugin_output_length(this.ctx.pointer, this.id);
var buf = Buffer.from(lib.extism_plugin_output_data(this.ctx.pointer, this.id).buffer, 0, out_len);
resolve(buf);
});
}
// Free a plugin, this should be called when the plugin is no longer needed
free() {
if (this.ctx.pointer && this.id !== -1) {
pluginRegistry.unregister(this);
lib.extism_plugin_free(this.ctx.pointer, this.id);
this.id = -1;
}
}
}

107
node/tsconfig.json Normal file
View File

@@ -0,0 +1,107 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"lib": ["es6"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "NodeNext", /* Specify what module code is generated. */
"rootDir": "./src", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"exclude": [
"node_modules",
"dist"
]
}

BIN
node/user_code.wasm Executable file

Binary file not shown.

484
nuget/.gitignore vendored
View File

@@ -1,484 +0,0 @@
## 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
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET
project.lock.json
project.fragment.lock.json
artifacts/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
.idea
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# Vim temporary swap files
*.swp

View File

@@ -1,32 +0,0 @@
<Project>
<PropertyGroup>
<Authors>Extism Contributors</Authors>
<PackageTags>extism, wasm, plugin</PackageTags>
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://extism.org</PackageProjectUrl>
<RepositoryUrl>https://github.com/your/repository.git</RepositoryUrl>
<TargetFramework>netstandard2.1</TargetFramework>
<Deterministic>true</Deterministic>
<NoBuild>true</NoBuild>
<IncludeBuildOutput>false</IncludeBuildOutput>
<MinVerIgnoreHeight>true</MinVerIgnoreHeight>
<MinVerTagPrefix>v</MinVerTagPrefix>
<MinVerVerbosity>normal</MinVerVerbosity>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MinVer" Version="4.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<NuspecFile>Extism.runtime.all.nuspec</NuspecFile>
</PropertyGroup>
<Target Name="Replace" BeforeTargets="GenerateNuspec">
<WriteLinesToFile
File="$(NuspecFile)"
Lines="$([System.IO.File]::ReadAllText($(NuspecFile)).Replace('$Version','$(MinVerVersion)'))"
Overwrite="true"/>
</Target>
</Project>

View File

@@ -1,27 +0,0 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>Extism.runtime.all</id>
<version>$Version</version>
<authors>Extism Contributors</authors>
<owners>Extism Contributors</owners>
<description>Internal implementation package for Extism</description>
<tags>extism wasm plugin</tags>
<license type="expression">BSD-3-Clause</license>
<projectUrl>https://github.com/extism/extism</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<copyright>Copyright © 2023 Extism Contributors</copyright>
<readme>README.md</readme>
<dependencies>
<dependency id="Extism.runtime.linux-arm64" version="$Version" />
<dependency id="Extism.runtime.linux-musl-arm64" version="$Version" />
<dependency id="Extism.runtime.linux-x64" version="$Version" />
<dependency id="Extism.runtime.osx-arm64" version="$Version" />
<dependency id="Extism.runtime.osx-x64" version="$Version" />
<dependency id="Extism.runtime.win-x64" version="$Version" />
</dependencies>
</metadata>
<files>
<file src="README.md" target="" />
</files>
</package>

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Extism.runtime.linux-arm64</PackageId>
<Description>Internal implementation package for Extism to work on Linux ARM64</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\linux-arm64\native\libextism.so"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\linux-arm64\native\libextism.so" />
</ItemGroup>
</Project>

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Extism.runtime.linux-musl-arm64</PackageId>
<Description>Internal implementation package for Extism to work on Linux Musl ARM64</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\linux-musl-arm64\native\libextism.so"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\linux-musl-arm64\native\libextism.so" />
</ItemGroup>
</Project>

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Extism.runtime.linux-x64</PackageId>
<Description>Internal implementation package for Extism to work on Linux x64</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\linux-x64\native\libextism.so"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\linux-x64\native\libextism.so" />
</ItemGroup>
</Project>

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Extism.runtime.osx-arm64</PackageId>
<Description>Internal implementation package for Extism to work on macOS ARM64</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\osx-arm64\native\libextism.dylib"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\osx-arm64\native\libextism.dylib" />
</ItemGroup>
</Project>

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Extism.runtime.osx-x64</PackageId>
<Description>Internal implementation package for Extism to work on macOS x64</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\osx-x64\native\libextism.dylib"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\osx-x64\native\libextism.dylib" />
</ItemGroup>
</Project>

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Extism.runtime.win-x64</PackageId>
<Description>Internal implementation package for Extism to work on Windows x64</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\win-x64\native\extism.dll"
CopyToOutputDirectory="Always"
Pack="true"
PackagePath="runtimes\win-x64\native\extism.dll" />
</ItemGroup>
</Project>

View File

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

1
ocaml/.ocamlformat Normal file
View File

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

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