mirror of
https://github.com/extism/extism.git
synced 2026-01-12 23:38:04 -05:00
Compare commits
400 Commits
v0.0.1-bet
...
stable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4c61e8c44 | ||
|
|
452bb2f498 | ||
|
|
c043decd95 | ||
|
|
ec70b0e7b8 | ||
|
|
1452b9fb42 | ||
|
|
9f69a68df4 | ||
|
|
dbf9f34dc6 | ||
|
|
c8a9478da1 | ||
|
|
e89ddd5a2a | ||
|
|
93392e0884 | ||
|
|
4ebd0eb372 | ||
|
|
8feee0c693 | ||
|
|
773ab32a45 | ||
|
|
6a041d0c39 | ||
|
|
745a03ece4 | ||
|
|
67eb8c1571 | ||
|
|
6a15884963 | ||
|
|
72f62c4035 | ||
|
|
e86398a612 | ||
|
|
461ac97529 | ||
|
|
f312b0bcce | ||
|
|
6102a99d98 | ||
|
|
b6f2e845d9 | ||
|
|
d1137fa739 | ||
|
|
40a42882ba | ||
|
|
889ec39e4d | ||
|
|
fa909fd53d | ||
|
|
8dd4992633 | ||
|
|
cfc9fb06a4 | ||
|
|
0e1a8677c5 | ||
|
|
d80584600b | ||
|
|
07623ef2da | ||
|
|
c7c7f9d024 | ||
|
|
3420960d7f | ||
|
|
baa060d062 | ||
|
|
3219d79a8f | ||
|
|
a3053a9ecc | ||
|
|
41450a03a9 | ||
|
|
26b34ed906 | ||
|
|
ab4995dac2 | ||
|
|
3da526286e | ||
|
|
618c132194 | ||
|
|
f4aa139ece | ||
|
|
4548480c0b | ||
|
|
7db38fd10e | ||
|
|
58f7d5fa95 | ||
|
|
f10539a431 | ||
|
|
e26e6d2da1 | ||
|
|
70a9a3da66 | ||
|
|
b86f6267f6 | ||
|
|
6a49a6ee7c | ||
|
|
3b5fea71d7 | ||
|
|
c0ccf7558c | ||
|
|
35a6887e9f | ||
|
|
81e51fb059 | ||
|
|
f606ab619f | ||
|
|
c78104a846 | ||
|
|
59101d80a6 | ||
|
|
b3b5e67abb | ||
|
|
128b3173a8 | ||
|
|
ef0b6b46ac | ||
|
|
2e5f5ef716 | ||
|
|
360df45e1a | ||
|
|
3bdf4ef0d0 | ||
|
|
0517aca413 | ||
|
|
a6807a44c9 | ||
|
|
dc6f99d924 | ||
|
|
62267e874a | ||
|
|
ab0a7c1650 | ||
|
|
12820aecff | ||
|
|
1db4528490 | ||
|
|
d0f77dd886 | ||
|
|
4016b86135 | ||
|
|
6a73b23076 | ||
|
|
0c70be285d | ||
|
|
c1c84379d7 | ||
|
|
0f8954c203 | ||
|
|
deb717b0e8 | ||
|
|
bb3902e318 | ||
|
|
86326117cc | ||
|
|
a0ec6a3083 | ||
|
|
3e5785e50c | ||
|
|
67aa3627fc | ||
|
|
15a74e07fb | ||
|
|
16950cbdda | ||
|
|
ba7098b52a | ||
|
|
3718f21f4a | ||
|
|
4e5108bc69 | ||
|
|
9d758e7fd3 | ||
|
|
48699a0126 | ||
|
|
32e5ab161c | ||
|
|
9e57369bbb | ||
|
|
26424a1581 | ||
|
|
039196b8ac | ||
|
|
6bec3f8d45 | ||
|
|
b2e0884442 | ||
|
|
c22e97a82b | ||
|
|
0c51e26820 | ||
|
|
83365e72b9 | ||
|
|
4c06ef14c0 | ||
|
|
670f364184 | ||
|
|
a4093e229a | ||
|
|
74ba0cdf0d | ||
|
|
82fae7cf29 | ||
|
|
1f9c469e31 | ||
|
|
415f423147 | ||
|
|
6bd1b665eb | ||
|
|
d4e364f883 | ||
|
|
0b7589a3eb | ||
|
|
eda80134f0 | ||
|
|
300d801d1a | ||
|
|
524f069a08 | ||
|
|
8dd5c8a138 | ||
|
|
c7f533f9c6 | ||
|
|
8d76cf0440 | ||
|
|
d950e9149b | ||
|
|
3e65e067e2 | ||
|
|
88a612ab8c | ||
|
|
3fc51ac373 | ||
|
|
12373ca34a | ||
|
|
94d5bd98c8 | ||
|
|
2922f4aad3 | ||
|
|
a39381f552 | ||
|
|
71cbbb02bb | ||
|
|
bb1a92d3f9 | ||
|
|
5d91870db6 | ||
|
|
9d6e36b8cb | ||
|
|
4b2f7d2bff | ||
|
|
3e69ceeede | ||
|
|
581e9cea99 | ||
|
|
48979b9f6d | ||
|
|
6ead4d9cd2 | ||
|
|
08e708bac5 | ||
|
|
602ca04f11 | ||
|
|
8135952842 | ||
|
|
041e0c7b4f | ||
|
|
a1ebfb7597 | ||
|
|
1eaa7854d7 | ||
|
|
c706e4efec | ||
|
|
f0f53c7827 | ||
|
|
bc4baaf67d | ||
|
|
e05169c3f1 | ||
|
|
9cad98683a | ||
|
|
a7386b1939 | ||
|
|
a44124bdb0 | ||
|
|
226155b959 | ||
|
|
1fad76148b | ||
|
|
bacb44bcc5 | ||
|
|
93c65bb4b4 | ||
|
|
beb83c697c | ||
|
|
69d450e8a2 | ||
|
|
8b81198486 | ||
|
|
c2830b03b5 | ||
|
|
43fcf4266a | ||
|
|
0221d8e4a2 | ||
|
|
222401b3db | ||
|
|
f6e55413d8 | ||
|
|
490dec4f14 | ||
|
|
8fdc0beb31 | ||
|
|
93ffec53d1 | ||
|
|
be3f324641 | ||
|
|
f5537e4bcb | ||
|
|
e1c04b42f9 | ||
|
|
c94c221854 | ||
|
|
aa04fd3e5c | ||
|
|
d73468a3ac | ||
|
|
ac7e1aeba3 | ||
|
|
668ef5c3c0 | ||
|
|
0170e79f90 | ||
|
|
a550c1b4fe | ||
|
|
c502e62510 | ||
|
|
6774b30de0 | ||
|
|
834d551990 | ||
|
|
a1f36c58d2 | ||
|
|
081c825cd8 | ||
|
|
dc3d54e260 | ||
|
|
cfb1317261 | ||
|
|
9f0e2413e3 | ||
|
|
5b9e1c1ff6 | ||
|
|
ef8004b38d | ||
|
|
9a6b4997dc | ||
|
|
87a403b7f3 | ||
|
|
908edf2918 | ||
|
|
7d34595dd7 | ||
|
|
e3a427b0cc | ||
|
|
0782d79278 | ||
|
|
472ad42048 | ||
|
|
ed6110e6ec | ||
|
|
527bcdfbcf | ||
|
|
765a1c55d5 | ||
|
|
6f534336f3 | ||
|
|
5c9aa4c90a | ||
|
|
a98b39a10f | ||
|
|
8c2255eafa | ||
|
|
094f51ba2b | ||
|
|
c2b1b78cc1 | ||
|
|
322812ede2 | ||
|
|
9ccef562a9 | ||
|
|
bc2b044770 | ||
|
|
374191f420 | ||
|
|
ba69d9fcc8 | ||
|
|
6cbde1acfc | ||
|
|
f34fa8bed2 | ||
|
|
dd0c5757da | ||
|
|
34feb4b942 | ||
|
|
34b5942dd5 | ||
|
|
d681dcd9f1 | ||
|
|
4622f32a69 | ||
|
|
0c4b985ed7 | ||
|
|
5dfe7162b4 | ||
|
|
e0f8e54504 | ||
|
|
a236e59d3c | ||
|
|
77c1d55791 | ||
|
|
ab36decbe7 | ||
|
|
0343bbbefe | ||
|
|
95ccafb02b | ||
|
|
c8308c3cdf | ||
|
|
aedb5b9c24 | ||
|
|
f34cc8f6ca | ||
|
|
dc2d015573 | ||
|
|
6a7b595c08 | ||
|
|
a68823e71c | ||
|
|
9a1d99a6e7 | ||
|
|
f2a9851867 | ||
|
|
124787127c | ||
|
|
c3ffb25891 | ||
|
|
e473d2cb7e | ||
|
|
5a5b538855 | ||
|
|
acb06b9898 | ||
|
|
1973024816 | ||
|
|
d4febd7228 | ||
|
|
bb2697193a | ||
|
|
fee7348cee | ||
|
|
9cf54d5f1f | ||
|
|
a3c6b9ee6a | ||
|
|
732b886fd5 | ||
|
|
bd8ca71275 | ||
|
|
37ee7d67b6 | ||
|
|
6b5f7999b7 | ||
|
|
de24fffc7c | ||
|
|
37b8e5bd12 | ||
|
|
c6b8429c67 | ||
|
|
ad114f44d2 | ||
|
|
45180ad473 | ||
|
|
9546dac689 | ||
|
|
197e934258 | ||
|
|
339556597b | ||
|
|
64927d9bcd | ||
|
|
86f1117ad5 | ||
|
|
34be80a7ad | ||
|
|
3e6a0071e9 | ||
|
|
d17b693c4b | ||
|
|
18fceec8f8 | ||
|
|
f5cf4f184e | ||
|
|
821661d391 | ||
|
|
f28e01125e | ||
|
|
c0faa53df5 | ||
|
|
be7961bbf6 | ||
|
|
4af4f7c41a | ||
|
|
0003246ff7 | ||
|
|
886e01b959 | ||
|
|
b30bcc3601 | ||
|
|
b57d54e63e | ||
|
|
ba2516650d | ||
|
|
97b4582aa4 | ||
|
|
762feb1056 | ||
|
|
6267682266 | ||
|
|
b1ca2f398d | ||
|
|
58ad4ce6e2 | ||
|
|
d0296de9a7 | ||
|
|
4db1303273 | ||
|
|
28d16f2fa8 | ||
|
|
e3dd34e59f | ||
|
|
bb6026976c | ||
|
|
33c0f8a4c8 | ||
|
|
b57acde149 | ||
|
|
7e8031fcdc | ||
|
|
e6499cab72 | ||
|
|
e44800f7f6 | ||
|
|
d6b403e112 | ||
|
|
b45c04c13e | ||
|
|
33dbcafdb9 | ||
|
|
c441ac703e | ||
|
|
3e3c97499c | ||
|
|
d131a344a3 | ||
|
|
71110117e4 | ||
|
|
5cfa2a0a5e | ||
|
|
cb87a30fb3 | ||
|
|
b85ee0f24c | ||
|
|
ffc1a1af41 | ||
|
|
a42a7eac5e | ||
|
|
08f5b84cd3 | ||
|
|
1345921abd | ||
|
|
1bfc9cfc71 | ||
|
|
7fb41815e9 | ||
|
|
fe38395236 | ||
|
|
fa338dc670 | ||
|
|
7e1f700ecd | ||
|
|
23fe3951a3 | ||
|
|
56e728a640 | ||
|
|
05239cf2de | ||
|
|
c76ff7c7b4 | ||
|
|
cb6d51f64f | ||
|
|
83d3670f17 | ||
|
|
77f13a6c10 | ||
|
|
2fda372c50 | ||
|
|
b8a22b3d37 | ||
|
|
577debc82a | ||
|
|
6c8927cfea | ||
|
|
8724de8667 | ||
|
|
24bf0b7d2a | ||
|
|
a78106035e | ||
|
|
5db98004a8 | ||
|
|
b0e041a54e | ||
|
|
928cf8a11c | ||
|
|
2f06c1ec9e | ||
|
|
2f0c1ea5b7 | ||
|
|
384a50e360 | ||
|
|
55faa55265 | ||
|
|
7815d9aee6 | ||
|
|
8ae09b036e | ||
|
|
ec07d35ed2 | ||
|
|
819aa81bfd | ||
|
|
8d4b720eb6 | ||
|
|
c92a920584 | ||
|
|
97a7c6607e | ||
|
|
a285f1b0c1 | ||
|
|
6115ed41db | ||
|
|
92ff5f0040 | ||
|
|
460e2a0d73 | ||
|
|
1024bb6d12 | ||
|
|
6e97f62793 | ||
|
|
0816e15a42 | ||
|
|
14e3c10b0a | ||
|
|
6c692cc5f4 | ||
|
|
bffef2e7a7 | ||
|
|
217c61373d | ||
|
|
d27f430184 | ||
|
|
b89745c519 | ||
|
|
d53e4aedf7 | ||
|
|
b17659b218 | ||
|
|
85b3aece6f | ||
|
|
e7c564a90a | ||
|
|
880617c066 | ||
|
|
f10ace7c03 | ||
|
|
09cf4451d3 | ||
|
|
51e6eb38c4 | ||
|
|
b8d2f2d6b3 | ||
|
|
fe5f9eeb8b | ||
|
|
64d9358ea9 | ||
|
|
355d4cdc4f | ||
|
|
1897276ee8 | ||
|
|
a23e4a8b88 | ||
|
|
631eb14cca | ||
|
|
460b477b87 | ||
|
|
15cd047569 | ||
|
|
52200c90ad | ||
|
|
6bad1c43eb | ||
|
|
958e56ba6d | ||
|
|
a4921e2d73 | ||
|
|
cdc614b04b | ||
|
|
d3248119f7 | ||
|
|
02f15985e2 | ||
|
|
77964c7724 | ||
|
|
15b7d0fa1f | ||
|
|
87be73570d | ||
|
|
e12efbeadb | ||
|
|
5f096161da | ||
|
|
f0490058f1 | ||
|
|
b7d52a1320 | ||
|
|
c298208e04 | ||
|
|
4c8a6b0ecb | ||
|
|
f473be9044 | ||
|
|
6883a5b6ba | ||
|
|
f9e9ff28d9 | ||
|
|
c9ded15dd2 | ||
|
|
e39901be68 | ||
|
|
4a49408045 | ||
|
|
ddde19b6f4 | ||
|
|
8191d85bc1 | ||
|
|
01dc54b6e6 | ||
|
|
85cadeaf0e | ||
|
|
66937dc2cf | ||
|
|
e7b3688c11 | ||
|
|
8bc608d1e9 | ||
|
|
7626a1b1d4 | ||
|
|
bd790fa5b3 | ||
|
|
4d3d966498 | ||
|
|
a174dedc6e | ||
|
|
981728057f | ||
|
|
88058ca31e | ||
|
|
7b27d4f883 | ||
|
|
4fa8030a64 | ||
|
|
05e9084277 | ||
|
|
ffa01403a3 | ||
|
|
cede8e179e | ||
|
|
98b463914c | ||
|
|
ba0bdbf928 | ||
|
|
bf2509c1e7 | ||
|
|
63ff9da8dc |
30
.github/actions/extism/action.yml
vendored
Normal file
30
.github/actions/extism/action.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
on: [workflow_call]
|
||||
|
||||
name: libextism
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Cache Rust environment
|
||||
uses: Swatinem/rust-cache@v1
|
||||
- name: Cache libextism
|
||||
id: cache-libextism
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/**
|
||||
key: ${{ runner.os }}-libextism-${{ hashFiles('runtime/**') }}-${{ hashFiles('manifest/**') }}-${{ hashFiles('libextism/**') }}
|
||||
- name: Build
|
||||
if: steps.cache-libextism.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: cargo build --release -p libextism
|
||||
- name: Install extism shared library
|
||||
shell: bash
|
||||
run: |
|
||||
sudo make install
|
||||
41
.github/dependabot.yml
vendored
Normal file
41
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directory: "python"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "mix"
|
||||
directory: "elixir"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "node"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "bundler"
|
||||
directory: "ruby"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
36
.github/workflows/browser-ci.yml
vendored
Normal file
36
.github/workflows/browser-ci.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-node.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- browser/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Browser CI
|
||||
|
||||
jobs:
|
||||
node:
|
||||
name: Browser
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Test Browser Runtime
|
||||
run: |
|
||||
cd browser
|
||||
npm i
|
||||
npm run test
|
||||
39
.github/workflows/ci-cpp.yml
vendored
Normal file
39
.github/workflows/ci-cpp.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-cpp.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- cpp/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: C++ CI
|
||||
|
||||
jobs:
|
||||
cpp:
|
||||
name: C++
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Install C++ SDK deps
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
run: |
|
||||
brew install jsoncpp googletest pkg-config
|
||||
- name: Install C++ SDK deps
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: |
|
||||
sudo apt-get install g++ libjsoncpp-dev libgtest-dev pkg-config
|
||||
- name: Run C++ tests
|
||||
run: |
|
||||
cd cpp
|
||||
LD_LIBRARY_PATH=/usr/local/lib make example
|
||||
LD_LIBRARY_PATH=/usr/local/lib make test
|
||||
34
.github/workflows/ci-dotnet.yml
vendored
Normal file
34
.github/workflows/ci-dotnet.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-dotnet.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- dotnet/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: .NET CI
|
||||
|
||||
jobs:
|
||||
dotnet:
|
||||
name: .NET
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup .NET Core SDK
|
||||
uses: actions/setup-dotnet@v3.0.3
|
||||
with:
|
||||
dotnet-version: 7.x
|
||||
- name: Test .NET Sdk
|
||||
run: |
|
||||
cd dotnet
|
||||
LD_LIBRARY_PATH=/usr/local/lib dotnet test ./Extism.sln
|
||||
41
.github/workflows/ci-elixir.yml
vendored
Normal file
41
.github/workflows/ci-elixir.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-elixir.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- rust/**
|
||||
- elixir/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Elixir CI
|
||||
|
||||
jobs:
|
||||
elixir:
|
||||
name: Elixir
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
MIX_ENV: test
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Elixir Host SDK
|
||||
if: ${{ runner.os != 'macOS' }}
|
||||
uses: erlef/setup-beam@v1
|
||||
with:
|
||||
experimental-otp: true
|
||||
otp-version: '25.0.4'
|
||||
elixir-version: '1.14.0'
|
||||
|
||||
- name: Test Elixir Host SDK
|
||||
if: ${{ runner.os != 'macOS' }}
|
||||
run: |
|
||||
cd elixir
|
||||
LD_LIBRARY_PATH=/usr/local/lib mix do deps.get, test
|
||||
39
.github/workflows/ci-go.yml
vendored
Normal file
39
.github/workflows/ci-go.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-go.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- extism.go
|
||||
- extism_test.go
|
||||
- go.mod
|
||||
- libextism.pc
|
||||
- go/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Go CI
|
||||
|
||||
jobs:
|
||||
go:
|
||||
name: Go
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Go env
|
||||
uses: actions/setup-go@v3
|
||||
|
||||
- name: Test Go Host SDK
|
||||
run: |
|
||||
go version
|
||||
LD_LIBRARY_PATH=/usr/local/lib go test
|
||||
cd go
|
||||
LD_LIBRARY_PATH=/usr/local/lib go run main.go | grep "Hello from Go!"
|
||||
47
.github/workflows/ci-haskell.yml
vendored
Normal file
47
.github/workflows/ci-haskell.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-haskell.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- haskell/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Haskell CI
|
||||
|
||||
jobs:
|
||||
haskell:
|
||||
name: Haskell
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Haskell env
|
||||
uses: haskell/actions/setup@v2
|
||||
with:
|
||||
enable-stack: false
|
||||
- name: Cache Haskell
|
||||
id: cache-haskell
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./haskell/dist-newstyle
|
||||
key: ${{ runner.os }}-haskell-${{ hashFiles('haskell/**') }}
|
||||
- name: Build Haskell Host SDK
|
||||
if: steps.cache-haskell.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd haskell
|
||||
cabal update
|
||||
LD_LIBRARY_PATH=/usr/local/lib cabal build
|
||||
- name: Test Haskell SDK
|
||||
run: |
|
||||
cd haskell
|
||||
cabal update
|
||||
LD_LIBRARY_PATH=/usr/local/lib cabal test
|
||||
37
.github/workflows/ci-java.yml
vendored
Normal file
37
.github/workflows/ci-java.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-java.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- java/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Java CI
|
||||
|
||||
jobs:
|
||||
java:
|
||||
name: Java
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
version: [11, 17]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '${{ matrix.version }}'
|
||||
- name: Test Java
|
||||
run: |
|
||||
cd java
|
||||
mvn --batch-mode -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn verify
|
||||
|
||||
38
.github/workflows/ci-node.yml
vendored
Normal file
38
.github/workflows/ci-node.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-node.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- node/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Node CI
|
||||
|
||||
jobs:
|
||||
node:
|
||||
name: Node
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Test Node Host SDK
|
||||
run: |
|
||||
cd node
|
||||
npm i
|
||||
LD_LIBRARY_PATH=/usr/local/lib npm run build
|
||||
LD_LIBRARY_PATH=/usr/local/lib npm run example
|
||||
LD_LIBRARY_PATH=/usr/local/lib npm run test
|
||||
50
.github/workflows/ci-ocaml.yml
vendored
Normal file
50
.github/workflows/ci-ocaml.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-ocaml.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- ocaml/**
|
||||
- dune-project
|
||||
- extism.opam
|
||||
workflow_dispatch:
|
||||
|
||||
name: OCaml CI
|
||||
|
||||
jobs:
|
||||
ocaml:
|
||||
name: OCaml
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup OCaml env
|
||||
uses: ocaml/setup-ocaml@v2
|
||||
with:
|
||||
ocaml-compiler: ocaml-base-compiler.5.0.0
|
||||
- name: Cache OCaml
|
||||
id: cache-ocaml
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: _build
|
||||
key: ${{ runner.os }}-ocaml-${{ hashFiles('ocaml/**.ml') }}-${{ hashFiles('dune-project') }}
|
||||
- name: Build OCaml Host SDK
|
||||
if: steps.cache-ocaml.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
opam install -y --deps-only .
|
||||
cd ocaml
|
||||
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune build
|
||||
- name: Test OCaml Host SDK
|
||||
run: |
|
||||
opam install -y --deps-only .
|
||||
cd ocaml
|
||||
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune exec ./bin/main.exe ../wasm/code.wasm count_vowels -- --input "qwertyuiop"
|
||||
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune runtest
|
||||
42
.github/workflows/ci-php.yml
vendored
Normal file
42
.github/workflows/ci-php.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-php.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- php/**
|
||||
- composer.json
|
||||
- composer.lock
|
||||
workflow_dispatch:
|
||||
|
||||
name: PHP CI
|
||||
|
||||
jobs:
|
||||
php:
|
||||
name: PHP
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup PHP env
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.1"
|
||||
extensions: ffi
|
||||
tools: composer
|
||||
env:
|
||||
fail-fast: true
|
||||
|
||||
- name: Test PHP SDK
|
||||
run: |
|
||||
cd php/example
|
||||
composer install
|
||||
php index.php
|
||||
40
.github/workflows/ci-python.yml
vendored
Normal file
40
.github/workflows/ci-python.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-python.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- python/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Python CI
|
||||
|
||||
jobs:
|
||||
python:
|
||||
name: Python
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Python env
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
check-latest: true
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
- name: Test Python Host SDK
|
||||
run: |
|
||||
cd python
|
||||
cp ../README.md .
|
||||
poetry install --no-dev
|
||||
poetry run python example.py
|
||||
poetry run python -m unittest discover
|
||||
38
.github/workflows/ci-ruby.yml
vendored
Normal file
38
.github/workflows/ci-ruby.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-ruby.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- ruby/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Ruby CI
|
||||
|
||||
jobs:
|
||||
ruby:
|
||||
name: Ruby
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Ruby env
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: "3.0"
|
||||
|
||||
- name: Test Ruby Host SDK
|
||||
run: |
|
||||
cd ruby
|
||||
bundle install
|
||||
ruby example.rb
|
||||
rake test
|
||||
|
||||
105
.github/workflows/ci-rust.yml
vendored
Normal file
105
.github/workflows/ci-rust.yml
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-rust.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- rust/**
|
||||
- libextism/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Rust CI
|
||||
|
||||
env:
|
||||
RUNTIME_CRATE: extism-runtime
|
||||
LIBEXTISM_CRATE: libextism
|
||||
RUST_SDK_CRATE: extism
|
||||
|
||||
jobs:
|
||||
lib:
|
||||
name: Extism runtime lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Cache Rust environment
|
||||
uses: Swatinem/rust-cache@v1
|
||||
- name: Cache libextism
|
||||
id: cache-libextism
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/release/libextism.*
|
||||
key: ${{ runner.os }}-libextism-${{ hashFiles('runtime/**') }}-${{ hashFiles('manifest/**') }}
|
||||
- name: Cache target
|
||||
id: cache-target
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/**
|
||||
key: ${{ runner.os }}-target-${{ env.GITHUB_SHA }}
|
||||
- name: Build
|
||||
if: steps.cache-libextism.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: cargo build --release -p ${{ env.LIBEXTISM_CRATE }}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: libextism-${{ matrix.os }}
|
||||
path: |
|
||||
target/release/libextism.*
|
||||
lint_and_test:
|
||||
name: Extism runtime lint and test
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Cache Rust environment
|
||||
uses: Swatinem/rust-cache@v1
|
||||
- name: Cache target
|
||||
id: cache-target
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/**
|
||||
key: ${{ runner.os }}-target-${{ env.GITHUB_SHA }}
|
||||
- name: Format
|
||||
run: cargo fmt --check -p ${{ env.RUNTIME_CRATE }}
|
||||
- name: Lint
|
||||
run: cargo clippy --release --all-features --no-deps -p ${{ env.RUNTIME_CRATE }}
|
||||
- name: Test
|
||||
run: cargo test --all-features --release -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
rust:
|
||||
name: Rust
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Test Rust Host SDK
|
||||
run: LD_LIBRARY_PATH=/usr/local/lib cargo test --release -p ${{ env.RUST_SDK_CRATE }}
|
||||
37
.github/workflows/ci-zig.yml
vendored
Normal file
37
.github/workflows/ci-zig.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
- .github/workflows/ci-zig.yml
|
||||
- manifest/**
|
||||
- runtime/**
|
||||
- libextism/**
|
||||
- zig/**
|
||||
workflow_dispatch:
|
||||
|
||||
name: Zig CI
|
||||
|
||||
jobs:
|
||||
zig:
|
||||
name: Zig
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
zig_version: ["master"] # eventually use multiple versions once stable
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Zig env
|
||||
uses: goto-bus-stop/setup-zig@v2
|
||||
with:
|
||||
version: ${{ matrix.zig_version }}
|
||||
|
||||
- name: Test Zig Host SDK
|
||||
run: |
|
||||
zig version
|
||||
cd zig
|
||||
LD_LIBRARY_PATH=/usr/local/lib zig build test
|
||||
141
.github/workflows/ci.yml
vendored
141
.github/workflows/ci.yml
vendored
@@ -1,128 +1,25 @@
|
||||
on: [ push, pull_request ]
|
||||
on: [pull_request, workflow_dispatch]
|
||||
|
||||
name: CI
|
||||
|
||||
env:
|
||||
RUNTIME_MANIFEST: runtime/Cargo.toml
|
||||
RUNTIME_CRATE: extism-runtime
|
||||
RUST_SDK_CRATE: extism
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build & Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
sdk_api_coverage:
|
||||
name: SDK API Coverage Report
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Python env
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
check-latest: true
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get install ripgrep
|
||||
pip3 install pycparser
|
||||
- name: Run coverage script
|
||||
id: coverage
|
||||
run: |
|
||||
python scripts/sdk_coverage.py
|
||||
|
||||
- name: Cache Rust environment
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Format
|
||||
run: cargo fmt --check -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Lint
|
||||
run: cargo clippy --release --no-deps -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Test
|
||||
run: cargo test --release -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Install extism shared library
|
||||
shell: bash
|
||||
run: sudo make install
|
||||
|
||||
- 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
|
||||
|
||||
|
||||
- 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
|
||||
|
||||
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Test Node Host SDK
|
||||
run: |
|
||||
cd node
|
||||
npm i
|
||||
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
|
||||
|
||||
- name: Test Haskell SDK
|
||||
run: |
|
||||
cd haskell
|
||||
LD_LIBRARY_PATH=/usr/local/lib cabal 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 Example.php
|
||||
29
.github/workflows/release-dotnet-native.yaml
vendored
Normal file
29
.github/workflows/release-dotnet-native.yaml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release .NET Native NuGet Packages
|
||||
|
||||
jobs:
|
||||
release-runtimes:
|
||||
name: release-dotnet-native
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup .NET Core SDK
|
||||
uses: actions/setup-dotnet@v3.0.3
|
||||
with:
|
||||
dotnet-version: 7.x
|
||||
- uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: release.yml
|
||||
name: release-artifacts
|
||||
- name: Extract Archive
|
||||
run: |
|
||||
tar -xvzf libextism-x86_64-pc-windows-msvc-v*.tar.gz --directory dotnet/nuget/runtimes
|
||||
mv dotnet/nuget/runtimes/extism.dll dotnet/nuget/runtimes/win-x64.dll
|
||||
- name: Publish win-x64
|
||||
run: |
|
||||
cd dotnet/nuget
|
||||
dotnet pack -o dist
|
||||
dotnet nuget push --source https://api.nuget.org/v3/index.json ./dist/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }}
|
||||
32
.github/workflows/release-dotnet.yaml
vendored
Normal file
32
.github/workflows/release-dotnet.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release .NET SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-dotnet
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Install extism shared library
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p /home/runner/.local/bin/
|
||||
export PATH="/home/runner/.local/bin/:$PATH"
|
||||
curl https://raw.githubusercontent.com/extism/cli/main/install.sh | sh
|
||||
extism --sudo --prefix /usr/local install
|
||||
- name: Setup .NET Core SDK
|
||||
uses: actions/setup-dotnet@v3.0.3
|
||||
with:
|
||||
dotnet-version: 7.x
|
||||
- name: Test .NET Sdk
|
||||
run: |
|
||||
cd dotnet
|
||||
LD_LIBRARY_PATH=/usr/local/lib dotnet test ./Extism.sln
|
||||
- name: Publish .NET Sdk
|
||||
run: |
|
||||
cd dotnet/src/Extism.Sdk/
|
||||
dotnet pack -c Release
|
||||
dotnet nuget push --source https://api.nuget.org/v3/index.json ./bin/Release/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }}
|
||||
27
.github/workflows/release-elixir.yaml
vendored
Normal file
27
.github/workflows/release-elixir.yaml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Elixir SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-elixir
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Elixir Host SDK
|
||||
uses: erlef/setup-beam@v1
|
||||
with:
|
||||
experimental-otp: true
|
||||
otp-version: '25.0.4'
|
||||
elixir-version: '1.14.0'
|
||||
|
||||
- name: Publish Elixir Host SDK to hex.pm
|
||||
env:
|
||||
HEX_API_KEY: ${{ secrets.HEX_PM_API_TOKEN }}
|
||||
run: |
|
||||
cd elixir
|
||||
cp ../LICENSE .
|
||||
make publish
|
||||
|
||||
16
.github/workflows/release-haskell.yaml
vendored
Normal file
16
.github/workflows/release-haskell.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Haskell SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-rust
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- uses: cachix/haskell-release-action@v1
|
||||
with:
|
||||
- hackage-token: "${{ secrets.HACKAGE_TOKEN }}"
|
||||
- work-dir: ./haskell
|
||||
22
.github/workflows/release-java.yml
vendored
Normal file
22
.github/workflows/release-java.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Publish package to the Maven Central Repository
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
- name: Publish package
|
||||
env:
|
||||
JRELEASER_NEXUS2_USERNAME: ${{ secrets.JRELEASER_NEXUS2_USERNAME }}
|
||||
JRELEASER_NEXUS2_PASSWORD: ${{ secrets.JRELEASER_NEXUS2_PASSWORD }}
|
||||
JRELEASER_GPG_PASSPHRASE: ${{ secrets.JRELEASER_GPG_PASSPHRASE }}
|
||||
JRELEASER_GPG_SECRET_KEY: ${{ secrets.JRELEASER_GPG_SECRET_KEY }}
|
||||
JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.JRELEASER_GPG_PUBLIC_KEY }}
|
||||
JRELEASER_GITHUB_TOKEN: "dummy"
|
||||
run: mvn -Prelease clean jreleaser:prepare deploy jreleaser:deploy -DaltDeploymentRepository=local::default::file:./target/staging-deploy
|
||||
30
.github/workflows/release-node.yaml
vendored
Normal file
30
.github/workflows/release-node.yaml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Node SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-node
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
|
||||
CI: true
|
||||
|
||||
- name: Release Node Host SDK
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
|
||||
CI: true
|
||||
run: |
|
||||
cd node
|
||||
make publish
|
||||
|
||||
36
.github/workflows/release-python.yaml
vendored
Normal file
36
.github/workflows/release-python.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Python SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-python
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python env
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
check-latest: true
|
||||
- name: Run image
|
||||
uses: abatilo/actions-poetry@v2
|
||||
|
||||
- name: Build Python Host SDK
|
||||
run: |
|
||||
cd python
|
||||
cp ../LICENSE .
|
||||
make clean
|
||||
poetry install --no-dev
|
||||
poetry build
|
||||
|
||||
- name: Release Python Host SDK
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: ${{ secrets.PYPI_API_USER }}
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: python/dist/
|
||||
|
||||
25
.github/workflows/release-ruby.yaml
vendored
Normal file
25
.github/workflows/release-ruby.yaml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Ruby SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-ruby
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.1' # Version range or exact version of a Ruby version to use, using semvers version range syntax.
|
||||
|
||||
- name: Publish Ruby Gem
|
||||
env:
|
||||
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_TOKEN }}
|
||||
run: |
|
||||
cd ruby
|
||||
make publish RUBYGEMS_API_KEY=$RUBYGEMS_API_KEY
|
||||
|
||||
41
.github/workflows/release-rust.yaml
vendored
Normal file
41
.github/workflows/release-rust.yaml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Rust SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-rust
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust env
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Release Rust Manifest Crate
|
||||
if: always()
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
|
||||
run: |
|
||||
# order of crate publication matter: manifest, runtime, rust
|
||||
cargo publish --manifest-path manifest/Cargo.toml
|
||||
# allow for crates.io to update so dependant crates can locate extism-manifest
|
||||
sleep 5
|
||||
|
||||
- name: Release Rust Host SDK
|
||||
if: always()
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
|
||||
run: |
|
||||
|
||||
cargo publish --manifest-path runtime/Cargo.toml --no-verify
|
||||
cargo publish --manifest-path rust/Cargo.toml
|
||||
|
||||
|
||||
78
.github/workflows/release.yml
vendored
78
.github/workflows/release.yml
vendored
@@ -1,12 +1,13 @@
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release
|
||||
|
||||
env:
|
||||
RUNTIME_MANIFEST: runtime/Cargo.toml
|
||||
RUNTIME_CRATE: extism-runtime
|
||||
RUNTIME_CRATE: libextism
|
||||
RUSTFLAGS: -C target-feature=-crt-static
|
||||
ARTIFACT_DIR: release-artifacts
|
||||
|
||||
@@ -14,18 +15,20 @@ jobs:
|
||||
release-linux:
|
||||
name: linux
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
strategy:
|
||||
matrix:
|
||||
target: [
|
||||
aarch64-unknown-linux-gnu,
|
||||
aarch64-unknown-linux-musl,
|
||||
i686-unknown-linux-gnu,
|
||||
x86_64-unknown-linux-gnu ]
|
||||
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@v1
|
||||
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -40,7 +43,7 @@ jobs:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
|
||||
- name: Prepare Artifact
|
||||
run: |
|
||||
EXT=so
|
||||
@@ -49,21 +52,21 @@ jobs:
|
||||
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:
|
||||
@@ -78,20 +81,18 @@ jobs:
|
||||
files: |
|
||||
*.tar.gz
|
||||
*.txt
|
||||
|
||||
|
||||
release-macos:
|
||||
name: macos
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
strategy:
|
||||
matrix:
|
||||
target: [
|
||||
x86_64-apple-darwin,
|
||||
aarch64-apple-darwin ]
|
||||
target: [x86_64-apple-darwin, aarch64-apple-darwin]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -115,21 +116,21 @@ jobs:
|
||||
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:
|
||||
@@ -148,20 +149,18 @@ jobs:
|
||||
release-windows:
|
||||
name: windows
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
strategy:
|
||||
matrix:
|
||||
target: [
|
||||
target:
|
||||
[x86_64-pc-windows-gnu, x86_64-pc-windows-msvc]
|
||||
# i686-pc-windows-gnu,
|
||||
# i686-pc-windows-msvc,
|
||||
x86_64-pc-windows-gnu,
|
||||
x86_64-pc-windows-msvc,
|
||||
# aarch64-pc-windows-msvc
|
||||
]
|
||||
# aarch64-pc-windows-msvc
|
||||
if: always()
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -173,7 +172,6 @@ jobs:
|
||||
- name: Build Target (${{ matrix.target }})
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: false
|
||||
command: build
|
||||
args: --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
@@ -186,22 +184,22 @@ jobs:
|
||||
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
|
||||
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:
|
||||
|
||||
20
.gitignore
vendored
20
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
target
|
||||
runtime/ast.json
|
||||
runtime/target
|
||||
Cargo.lock
|
||||
.DS_Store
|
||||
@@ -12,6 +13,8 @@ __pycache__
|
||||
python/dist
|
||||
python/poetry.lock
|
||||
c/main
|
||||
cpp/test/test
|
||||
cpp/example
|
||||
go/main
|
||||
ruby/.bundle/
|
||||
ruby/.yardoc
|
||||
@@ -22,11 +25,20 @@ ruby/pkg/
|
||||
ruby/spec/reports/
|
||||
ruby/tmp/
|
||||
ruby/Gemfile.lock
|
||||
cpp/example
|
||||
rust/target
|
||||
rust/test.log
|
||||
ocaml/duniverse
|
||||
ocaml/_build
|
||||
duniverse
|
||||
_build
|
||||
php/Extism.php
|
||||
python/docs
|
||||
dist-newstyle
|
||||
.stack-work
|
||||
.stack-work
|
||||
vendor
|
||||
zig/zig-*
|
||||
zig/example-out/
|
||||
zig/*.log
|
||||
java/*.iml
|
||||
java/*.log
|
||||
java/.idea
|
||||
java/.DS_Store
|
||||
|
||||
|
||||
1
.ocamlformat
Normal file
1
.ocamlformat
Normal file
@@ -0,0 +1 @@
|
||||
version = 0.24.1
|
||||
@@ -3,4 +3,6 @@ members = [
|
||||
"manifest",
|
||||
"runtime",
|
||||
"rust",
|
||||
"libextism",
|
||||
]
|
||||
exclude = ["kernel"]
|
||||
|
||||
15
Makefile
15
Makefile
@@ -18,18 +18,23 @@ else
|
||||
FEATURE_FLAGS=--features $(FEATURES)
|
||||
endif
|
||||
|
||||
build:
|
||||
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
|
||||
|
||||
.PHONY: build
|
||||
.PHONY: kernel
|
||||
kernel:
|
||||
cd kernel && bash build.sh
|
||||
|
||||
lint:
|
||||
cargo clippy --release --no-deps --manifest-path runtime/Cargo.toml
|
||||
|
||||
build:
|
||||
cargo build --release $(FEATURE_FLAGS) --manifest-path runtime/Cargo.toml
|
||||
debug:
|
||||
RUSTFLAGS=-g $(MAKE) build
|
||||
|
||||
install:
|
||||
install runtime/extism.h $(DEST)/include
|
||||
install target/release/libextism.$(SOEXT) $(DEST)/lib
|
||||
mkdir -p $(DEST)/lib $(DEST)/include
|
||||
install runtime/extism.h $(DEST)/include/extism.h
|
||||
install target/release/libextism.$(SOEXT) $(DEST)/lib/libextism.$(SOEXT)
|
||||
|
||||
uninstall:
|
||||
rm -f $(DEST)/include/extism.h $(DEST)/lib/libextism.$(SOEXT)
|
||||
|
||||
26
README.md
26
README.md
@@ -1,17 +1,32 @@
|
||||
### _Welcome!_
|
||||
|
||||
**Please note:** This project still under active development and APIs may change until we hit v1.0.
|
||||
|
||||
If you're interested in working on or building with Extism, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know - we are happy to help get you started.
|
||||
|
||||
[](https://discord.gg/cx3usBCWnc)
|
||||
|
||||
# [Extism](https://extism.org)
|
||||
|
||||
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) & more (others coming soon).
|
||||
[OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk),
|
||||
[Haskell](https://extism.org/docs/integrate-into-your-codebase/haskell-host-sdk),
|
||||
[PHP](https://extism.org/docs/integrate-into-your-codebase/php-host-sdk),
|
||||
[Elixir/Erlang](https://extism.org/docs/integrate-into-your-codebase/elixir-or-erlang-host-sdk),
|
||||
[.NET](https://extism.org/docs/integrate-into-your-codebase/dotnet-host-sdk),
|
||||
[Java](https://extism.org/docs/integrate-into-your-codebase/java-host-sdk),
|
||||
[Zig](https://extism.org/docs/integrate-into-your-codebase/zig-host-sdk) & more (others coming soon).
|
||||
|
||||
Plug-in development kits (PDK) for plug-in authors supported in Rust, AssemblyScript, Go, C/C++.
|
||||
Plug-in development kits (PDK) for plug-in authors supported in [Rust](https://github.com/extism/rust-pdk), [AssemblyScript](https://github.com/extism/assemblyscript-pdk), [Go](https://github.com/extism/go-pdk), [C/C++](https://github.com/extism/c-pdk), [Haskell](https://github.com/extism/haskell-pdk), and [Zig](https://github.com/extism/zig-pdk).
|
||||
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/7517515/184472910-36d42d73-bd1e-49e2-9b4d-9b020959603d.png"/>
|
||||
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/210286900-39b144fd-1b26-4dd0-b7a9-2b5755bc174d.png" alt="Extism embedded SDK language support"/>
|
||||
</p>
|
||||
|
||||
|
||||
Add a flexible, secure, and _bLaZiNg FaSt_ plug-in system to your project. Server, desktop, mobile, web, database -- you name it. Enable users to write and execute safe extensions to your software in **3 easy steps:**
|
||||
|
||||
### 1. Import
|
||||
@@ -30,7 +45,7 @@ Load WebAssembly modules at any time in your app's lifetime and Extism will exec
|
||||
|
||||
## Usage
|
||||
|
||||
Head to the [project website](https://extism.org) for more information and docs. Also, consider reading an [overview](/docs/overview) of Extism and its goals & approach.
|
||||
Head to the [project website](https://extism.org) for more information and docs. Also, consider reading an [overview](https://extism.org/docs/overview) of Extism and its goals & approach.
|
||||
|
||||
## Contribution
|
||||
|
||||
@@ -45,7 +60,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://dylib.so/assets/dylibso-logo.svg"/></a>
|
||||
<a href="https://dylib.so" _target="blanks"><img width="200px" src="https://user-images.githubusercontent.com/7517515/198204119-5afdebb9-a5d8-4322-bd2a-46179c8d7b24.svg"/></a>
|
||||
</p>
|
||||
|
||||
|
||||
_Reach out and tell us what you're building! We'd love to help._
|
||||
|
||||
130
browser/.gitignore
vendored
Normal file
130
browser/.gitignore
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
5
browser/.prettierrc
Normal file
5
browser/.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true
|
||||
}
|
||||
28
browser/Makefile
Normal file
28
browser/Makefile
Normal file
@@ -0,0 +1,28 @@
|
||||
.PHONY: test
|
||||
|
||||
prepare:
|
||||
npm install
|
||||
|
||||
build:
|
||||
npm run build
|
||||
|
||||
test: prepare
|
||||
npm run test
|
||||
|
||||
clean:
|
||||
echo "No clean implemented"
|
||||
|
||||
publish: clean prepare build
|
||||
npm publish
|
||||
|
||||
format:
|
||||
npx prettier --write src
|
||||
|
||||
lint:
|
||||
npx prettier --check src
|
||||
|
||||
docs:
|
||||
npx typedoc --out doc src
|
||||
|
||||
show-docs: docs
|
||||
open doc/index.html
|
||||
23
browser/build.js
Normal file
23
browser/build.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const { build } = require("esbuild");
|
||||
const { peerDependencies } = require('./package.json')
|
||||
|
||||
const sharedConfig = {
|
||||
entryPoints: ["src/index.ts"],
|
||||
bundle: true,
|
||||
minify: false,
|
||||
drop: [], // preseve debugger statements
|
||||
external: Object.keys(peerDependencies || {}),
|
||||
};
|
||||
|
||||
build({
|
||||
...sharedConfig,
|
||||
platform: 'node', // for CJS
|
||||
outfile: "dist/index.js",
|
||||
});
|
||||
|
||||
build({
|
||||
...sharedConfig,
|
||||
outfile: "dist/index.esm.js",
|
||||
platform: 'neutral', // for ESM
|
||||
format: "esm",
|
||||
});
|
||||
BIN
browser/data/code.wasm
Executable file
BIN
browser/data/code.wasm
Executable file
Binary file not shown.
248
browser/index.html
Normal file
248
browser/index.html
Normal file
@@ -0,0 +1,248 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
|
||||
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
|
||||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
|
||||
<style>
|
||||
#main {
|
||||
width: 100%;
|
||||
}
|
||||
.manifest {
|
||||
display: flex; /* or inline-flex */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
.urlInput {
|
||||
width: 600px;
|
||||
}
|
||||
.funcName {
|
||||
width: 150px;
|
||||
}
|
||||
.textAreas {
|
||||
display: flex; /* or inline-flex */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
.inputBox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.inputBox > textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.outputBox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.outputBox > textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.space {
|
||||
height: 80px;
|
||||
}
|
||||
.dragAreas {
|
||||
display: flex; /* or inline-flex */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
.dragInput {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-style: dotted;
|
||||
border-color: #000;
|
||||
}
|
||||
.dragOutput {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.dropZone {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.outputImage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script type="text/babel">
|
||||
function getBase64(file, cb) {
|
||||
var reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = function () {
|
||||
cb(reader.result)
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
console.log("error")
|
||||
};
|
||||
}
|
||||
|
||||
function arrayTob64(buffer) {
|
||||
var binary = '';
|
||||
var bytes = [].slice.call(buffer);
|
||||
bytes.forEach((b) => binary += String.fromCharCode(b));
|
||||
return window.btoa(binary);
|
||||
}
|
||||
|
||||
|
||||
class App extends React.Component {
|
||||
state = {
|
||||
url: "https://raw.githubusercontent.com/extism/extism/main/wasm/code.wasm",
|
||||
input: new Uint8Array(),
|
||||
output: new Uint8Array(),
|
||||
func_name: "count_vowels",
|
||||
functions: []
|
||||
}
|
||||
|
||||
async loadFunctions(url) {
|
||||
let helloWorld = function(index){
|
||||
console.log("Hello, " + this.allocator.getString(index));
|
||||
return index;
|
||||
};
|
||||
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": url } ] }, {"hello_world": helloWorld});
|
||||
let functions = Object.keys(await plugin.getExports())
|
||||
console.log("funcs ", functions)
|
||||
this.setState({functions})
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadFunctions(this.state.url)
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.extismContext = props.extismContext
|
||||
}
|
||||
|
||||
handleInputChange(e) {
|
||||
e.preventDefault();
|
||||
this.setState({ [e.target.name]: e.target.value })
|
||||
if (e.target.name === "url") {
|
||||
this.loadFunctions(e.target.value)
|
||||
}
|
||||
}
|
||||
|
||||
onInputKeyPress(e) {
|
||||
if (e.keyCode == 13 && e.shiftKey == true) {
|
||||
e.preventDefault()
|
||||
this.handleOnRun()
|
||||
}
|
||||
}
|
||||
|
||||
async handleOnRun(e) {
|
||||
e && e.preventDefault && e.preventDefault();
|
||||
let helloWorld = function(index){
|
||||
console.log("Hello, " + this.allocator.getString(index));
|
||||
return index;
|
||||
};
|
||||
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": this.state.url } ] }, {
|
||||
"hello_world": helloWorld
|
||||
});
|
||||
let result = await plugin.call(this.state.func_name, this.state.input)
|
||||
let output = result
|
||||
this.setState({output})
|
||||
}
|
||||
|
||||
nop = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
handleDrop = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let files = [...e.dataTransfer.files];
|
||||
if (files && files.length == 1) {
|
||||
let file = files[0]
|
||||
console.log(file)
|
||||
file.arrayBuffer().then(b => {
|
||||
this.setState({input: new Uint8Array(b)})
|
||||
this.handleOnRun()
|
||||
})
|
||||
} else {
|
||||
throw Error("Only one file please")
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const funcOptions = this.state.functions.map(f => <option value={f}>{f}</option>)
|
||||
let image = null
|
||||
if (this.state.output) {
|
||||
image = <img src={`data:image/png;base64,${arrayTob64(this.state.output)}`}/>
|
||||
}
|
||||
|
||||
return <div className="app">
|
||||
<div className="manifest">
|
||||
<div>
|
||||
<label>WASM Url: </label>
|
||||
<input type="text" name="url" className="urlInput" value={this.state.url} onChange={this.handleInputChange.bind(this)} />
|
||||
</div>
|
||||
<div>
|
||||
<label>Function: </label>
|
||||
<select type="text" name="func_name" className="funcName" value={this.state.func_name} onChange={this.handleInputChange.bind(this)}>
|
||||
{funcOptions}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={this.handleOnRun.bind(this)}>Run</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="textAreas">
|
||||
<div className="inputBox">
|
||||
<h3>Text Input</h3>
|
||||
<textarea name="input" value={this.state.input} onChange={this.handleInputChange.bind(this)} onKeyDown={this.onInputKeyPress.bind(this)}></textarea>
|
||||
</div>
|
||||
<div className="outputBox">
|
||||
<h3>Text Output</h3>
|
||||
<textarea name="output" value={new TextDecoder().decode(this.state.output)} ></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space" />
|
||||
<div className="dragAreas">
|
||||
<div className="dragInput">
|
||||
<h3>Image Input</h3>
|
||||
<div className="dropZone"
|
||||
onDrop={this.handleDrop.bind(this)}
|
||||
onDragOver={this.nop.bind(this)}
|
||||
onDragEnter={this.nop.bind(this)}
|
||||
onDragLeave={this.nop.bind(this)}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dragOutput">
|
||||
<h3>Image Output</h3>
|
||||
<div className="outputImage">
|
||||
{image}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
window.App = App
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import {ExtismContext} from './dist/index.esm.js'
|
||||
const e = React.createElement;
|
||||
|
||||
window.onload = () => {
|
||||
const domContainer = document.getElementById('main');
|
||||
console.log(domContainer)
|
||||
const root = ReactDOM.createRoot(domContainer);
|
||||
const extismContext = new ExtismContext()
|
||||
root.render(e(App, {extismContext}));
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main"></div>
|
||||
</body>
|
||||
</html>
|
||||
5
browser/jest.config.js
Normal file
5
browser/jest.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'jsdom',
|
||||
};
|
||||
14454
browser/package-lock.json
generated
Normal file
14454
browser/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
browser/package.json
Normal file
39
browser/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@extism/runtime-browser",
|
||||
"version": "0.3.0",
|
||||
"description": "Extism runtime in the browser",
|
||||
"scripts": {
|
||||
"build": "node build.js && tsc --emitDeclarationOnly --outDir dist",
|
||||
"format": "prettier --write \"src/**/*.ts\"",
|
||||
"lint": "tslint -p tsconfig.json",
|
||||
"test": "jest --config jest.config.js"
|
||||
},
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist/*"
|
||||
],
|
||||
"module": "dist/index.esm.js",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/src/index.d.ts",
|
||||
"author": "The Extism Authors <oss@extism.org>",
|
||||
"license": "BSD-3-Clause",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.2",
|
||||
"esbuild": "^0.15.13",
|
||||
"esbuild-jest": "^0.5.0",
|
||||
"jest": "^29.2.2",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typedoc": "^0.23.20",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bjorn3/browser_wasi_shim": "^0.2.7"
|
||||
}
|
||||
}
|
||||
104
browser/src/allocator.ts
Normal file
104
browser/src/allocator.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
type MemoryBlock = { offset: bigint; length: bigint };
|
||||
|
||||
export default class Allocator {
|
||||
currentIndex: bigint;
|
||||
active: Record<number, MemoryBlock>;
|
||||
freed: MemoryBlock[];
|
||||
memory: Uint8Array;
|
||||
|
||||
constructor(n: number) {
|
||||
this.currentIndex = BigInt(1);
|
||||
this.active = {};
|
||||
this.freed = [];
|
||||
this.memory = new Uint8Array(n);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.currentIndex = BigInt(1);
|
||||
this.active = {};
|
||||
this.freed = [];
|
||||
}
|
||||
|
||||
alloc(length: bigint): bigint {
|
||||
for (var i = 0; i < this.freed.length; i++) {
|
||||
let block = this.freed[i];
|
||||
if (block.length === length) {
|
||||
this.active[Number(block.offset)] = block;
|
||||
this.freed.splice(i, 1);
|
||||
return block.offset;
|
||||
} else if (block.length > length + BigInt(64)) {
|
||||
const newBlock = { offset: block.offset, length };
|
||||
block.offset += length;
|
||||
block.length -= length;
|
||||
return newBlock.offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Resize memory if needed
|
||||
// TODO: put a limit on the memory size
|
||||
if (BigInt(this.memory.length) < this.currentIndex + length) {
|
||||
const tmp = new Uint8Array(Number(this.currentIndex + length + BigInt(64)));
|
||||
tmp.set(this.memory);
|
||||
this.memory = tmp;
|
||||
}
|
||||
|
||||
const offset = this.currentIndex;
|
||||
this.currentIndex += length;
|
||||
this.active[Number(offset)] = { offset, length };
|
||||
return offset;
|
||||
}
|
||||
|
||||
getBytes(offset: bigint): Uint8Array | null {
|
||||
const block = this.active[Number(offset)];
|
||||
if (!block) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Uint8Array(this.memory.buffer, Number(offset), Number(block.length));
|
||||
}
|
||||
|
||||
getString(offset: bigint): string | null {
|
||||
const bytes = this.getBytes(offset);
|
||||
if (bytes === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TextDecoder().decode(bytes);
|
||||
}
|
||||
|
||||
allocBytes(data: Uint8Array): bigint {
|
||||
const offs = this.alloc(BigInt(data.length));
|
||||
const bytes = this.getBytes(offs);
|
||||
if (bytes === null) {
|
||||
this.free(offs);
|
||||
return BigInt(0);
|
||||
}
|
||||
|
||||
bytes.set(data);
|
||||
return offs;
|
||||
}
|
||||
|
||||
allocString(data: string): bigint {
|
||||
const bytes = new TextEncoder().encode(data);
|
||||
return this.allocBytes(bytes);
|
||||
}
|
||||
|
||||
getLength(offset: bigint): bigint {
|
||||
const block = this.active[Number(offset)];
|
||||
if (!block) {
|
||||
return BigInt(0);
|
||||
}
|
||||
|
||||
return block.length;
|
||||
}
|
||||
|
||||
free(offset: bigint) {
|
||||
const block = this.active[Number(offset)];
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete this.active[Number(offset)];
|
||||
this.freed.push(block);
|
||||
}
|
||||
}
|
||||
45
browser/src/context.ts
Normal file
45
browser/src/context.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Manifest, PluginConfig, ManifestWasmFile, ManifestWasmData } from './manifest';
|
||||
import { ExtismPlugin } from './plugin';
|
||||
|
||||
/**
|
||||
* Can be a {@link Manifest} or just the raw bytes of the WASM module as an ArrayBuffer.
|
||||
* We recommend using {@link Manifest}
|
||||
*/
|
||||
type ManifestData = Manifest | ArrayBuffer;
|
||||
|
||||
/**
|
||||
* A Context is needed to create plugins. The Context
|
||||
* is where your plugins live. Freeing the context
|
||||
* frees all of the plugins in its scope.
|
||||
*/
|
||||
export default class ExtismContext {
|
||||
/**
|
||||
* Create a plugin managed by this context
|
||||
*
|
||||
* @param manifest - The {@link ManifestData} describing the plugin code and config
|
||||
* @param config - Config details for the plugin
|
||||
* @returns A new Plugin scoped to this Context
|
||||
*/
|
||||
async newPlugin(manifest: ManifestData, functions: Record<string, any> = {}, config?: PluginConfig) {
|
||||
let moduleData: ArrayBuffer | null = null;
|
||||
if (manifest instanceof ArrayBuffer) {
|
||||
moduleData = manifest;
|
||||
} else if ((manifest as Manifest).wasm) {
|
||||
const wasmData = (manifest as Manifest).wasm;
|
||||
if (wasmData.length > 1) throw Error('This runtime only supports one module in Manifest.wasm');
|
||||
const wasm = wasmData[0];
|
||||
if ((wasm as ManifestWasmData).data) {
|
||||
moduleData = (wasm as ManifestWasmData).data;
|
||||
} else if ((wasm as ManifestWasmFile).path) {
|
||||
const response = await fetch((wasm as ManifestWasmFile).path);
|
||||
moduleData = await response.arrayBuffer();
|
||||
console.dir(moduleData);
|
||||
}
|
||||
}
|
||||
if (!moduleData) {
|
||||
throw Error(`Unsure how to interpret manifest ${manifest}`);
|
||||
}
|
||||
|
||||
return new ExtismPlugin(moduleData, functions, config);
|
||||
}
|
||||
}
|
||||
24
browser/src/index.test.ts
Normal file
24
browser/src/index.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ExtismContext } from './';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
function parse(bytes: Uint8Array): any {
|
||||
return JSON.parse(new TextDecoder().decode(bytes));
|
||||
}
|
||||
|
||||
describe('', () => {
|
||||
it('can load and call a plugin', async () => {
|
||||
// const data = fs.readFileSync(path.join(__dirname, '..', 'data', 'code.wasm'));
|
||||
// const ctx = new ExtismContext();
|
||||
// const plugin = await ctx.newPlugin({ wasm: [{ data: data }] });
|
||||
// const functions = await plugin.getExports();
|
||||
// expect(Object.keys(functions).filter((x) => !x.startsWith('__') && x !== 'memory')).toEqual(['count_vowels']);
|
||||
// let output = await plugin.call('count_vowels', 'this is a test');
|
||||
// expect(parse(output)).toEqual({ count: 4 });
|
||||
// output = await plugin.call('count_vowels', 'this is a test again');
|
||||
// expect(parse(output)).toEqual({ count: 7 });
|
||||
// output = await plugin.call('count_vowels', 'this is a test thrice');
|
||||
// expect(parse(output)).toEqual({ count: 6 });
|
||||
expect(true).toEqual(true);
|
||||
});
|
||||
});
|
||||
4
browser/src/index.ts
Normal file
4
browser/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import ExtismContext from './context';
|
||||
import { ExtismFunction, ExtismPlugin } from './plugin';
|
||||
|
||||
export { ExtismContext, ExtismFunction, ExtismPlugin };
|
||||
40
browser/src/manifest.ts
Normal file
40
browser/src/manifest.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Represents a path or url to a WASM module
|
||||
*/
|
||||
export type ManifestWasmFile = {
|
||||
path: string;
|
||||
name?: string;
|
||||
hash?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the raw bytes of a WASM file loaded into memory
|
||||
*/
|
||||
export type ManifestWasmData = {
|
||||
data: Uint8Array;
|
||||
name?: string;
|
||||
hash?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* {@link ExtismPlugin} Config
|
||||
*/
|
||||
export type PluginConfig = Map<string, string>;
|
||||
|
||||
/**
|
||||
* The WASM to load as bytes or a path
|
||||
*/
|
||||
export type ManifestWasm = ManifestWasmFile | ManifestWasmData;
|
||||
|
||||
/**
|
||||
* The manifest which describes the {@link ExtismPlugin} code and
|
||||
* runtime constraints.
|
||||
*
|
||||
* @see [Extism > Concepts > Manifest](https://extism.org/docs/concepts/manifest)
|
||||
*/
|
||||
export type Manifest = {
|
||||
wasm: Array<ManifestWasm>;
|
||||
//memory?: ManifestMemory;
|
||||
config?: PluginConfig;
|
||||
allowed_hosts?: Array<string>;
|
||||
};
|
||||
203
browser/src/plugin.ts
Normal file
203
browser/src/plugin.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import Allocator from './allocator';
|
||||
import { PluginConfig } from './manifest';
|
||||
import { WASI, Fd } from '@bjorn3/browser_wasi_shim';
|
||||
|
||||
export type ExtismFunction = any;
|
||||
|
||||
export class ExtismPlugin {
|
||||
moduleData: ArrayBuffer;
|
||||
allocator: Allocator;
|
||||
config?: PluginConfig;
|
||||
vars: Record<string, Uint8Array>;
|
||||
input: Uint8Array;
|
||||
output: Uint8Array;
|
||||
module?: WebAssembly.WebAssemblyInstantiatedSource;
|
||||
functions: Record<string, ExtismFunction>;
|
||||
|
||||
constructor(moduleData: ArrayBuffer, functions: Record<string, ExtismFunction> = {}, config?: PluginConfig) {
|
||||
this.moduleData = moduleData;
|
||||
this.allocator = new Allocator(1024 * 1024);
|
||||
this.config = config;
|
||||
this.vars = {};
|
||||
this.input = new Uint8Array();
|
||||
this.output = new Uint8Array();
|
||||
this.functions = functions;
|
||||
}
|
||||
|
||||
async getExports(): Promise<WebAssembly.Exports> {
|
||||
const module = await this._instantiateModule();
|
||||
return module.instance.exports;
|
||||
}
|
||||
|
||||
async getImports(): Promise<WebAssembly.ModuleImportDescriptor[]> {
|
||||
const module = await this._instantiateModule();
|
||||
return WebAssembly.Module.imports(module.module);
|
||||
}
|
||||
|
||||
async getInstance(): Promise<WebAssembly.Instance> {
|
||||
const module = await this._instantiateModule();
|
||||
return module.instance;
|
||||
}
|
||||
|
||||
async call(func_name: string, input: Uint8Array | string): Promise<Uint8Array> {
|
||||
const module = await this._instantiateModule();
|
||||
|
||||
if (typeof input === 'string') {
|
||||
this.input = new TextEncoder().encode(input);
|
||||
} else if (input instanceof Uint8Array) {
|
||||
this.input = input;
|
||||
} else {
|
||||
throw new Error('input should be string or Uint8Array');
|
||||
}
|
||||
|
||||
this.allocator.reset();
|
||||
|
||||
let func = module.instance.exports[func_name];
|
||||
if (!func) {
|
||||
throw Error(`function does not exist ${func_name}`);
|
||||
}
|
||||
//@ts-ignore
|
||||
func();
|
||||
return this.output;
|
||||
}
|
||||
|
||||
async _instantiateModule(): Promise<WebAssembly.WebAssemblyInstantiatedSource> {
|
||||
if (this.module) {
|
||||
return this.module;
|
||||
}
|
||||
const environment = this.makeEnv();
|
||||
const args: Array<string> = [];
|
||||
const envVars: Array<string> = [];
|
||||
let fds: Fd[] = [
|
||||
// new XtermStdio(term), // stdin
|
||||
// new XtermStdio(term), // stdout
|
||||
// new XtermStdio(term), // stderr
|
||||
];
|
||||
let wasi = new WASI(args, envVars, fds);
|
||||
let env = {
|
||||
wasi_snapshot_preview1: wasi.wasiImport,
|
||||
env: environment,
|
||||
};
|
||||
this.module = await WebAssembly.instantiate(this.moduleData, env);
|
||||
// normally we would call wasi.start here but it doesn't respect when there is
|
||||
// no _start function
|
||||
//@ts-ignore
|
||||
wasi.inst = this.module.instance;
|
||||
if (this.module.instance.exports._start) {
|
||||
//@ts-ignore
|
||||
this.module.instance.exports._start();
|
||||
}
|
||||
return this.module;
|
||||
}
|
||||
|
||||
makeEnv(): any {
|
||||
const plugin = this;
|
||||
var env: any = {
|
||||
extism_alloc(n: bigint): bigint {
|
||||
return plugin.allocator.alloc(n);
|
||||
},
|
||||
extism_free(n: bigint) {
|
||||
plugin.allocator.free(n);
|
||||
},
|
||||
extism_load_u8(n: bigint): number {
|
||||
return plugin.allocator.memory[Number(n)];
|
||||
},
|
||||
extism_load_u64(n: bigint): bigint {
|
||||
let cast = new DataView(plugin.allocator.memory.buffer, Number(n));
|
||||
return cast.getBigUint64(0, true);
|
||||
},
|
||||
extism_store_u8(offset: bigint, n: number) {
|
||||
plugin.allocator.memory[Number(offset)] = Number(n);
|
||||
},
|
||||
extism_store_u64(offset: bigint, n: bigint) {
|
||||
const tmp = new DataView(plugin.allocator.memory.buffer, Number(offset));
|
||||
tmp.setBigUint64(0, n, true);
|
||||
},
|
||||
extism_input_length(): bigint {
|
||||
return BigInt(plugin.input.length);
|
||||
},
|
||||
extism_input_load_u8(i: bigint): number {
|
||||
return plugin.input[Number(i)];
|
||||
},
|
||||
extism_input_load_u64(idx: bigint): bigint {
|
||||
let cast = new DataView(plugin.input.buffer, Number(idx));
|
||||
return cast.getBigUint64(0, true);
|
||||
},
|
||||
extism_output_set(offset: bigint, length: bigint) {
|
||||
const offs = Number(offset);
|
||||
const len = Number(length);
|
||||
plugin.output = plugin.allocator.memory.slice(offs, offs + len);
|
||||
},
|
||||
extism_error_set(i: bigint) {
|
||||
throw plugin.allocator.getString(i);
|
||||
},
|
||||
extism_config_get(i: bigint): bigint {
|
||||
if (typeof plugin.config === 'undefined') {
|
||||
return BigInt(0);
|
||||
}
|
||||
const key = plugin.allocator.getString(i);
|
||||
if (key === null) {
|
||||
return BigInt(0);
|
||||
}
|
||||
const value = plugin.config.get(key);
|
||||
if (typeof value === 'undefined') {
|
||||
return BigInt(0);
|
||||
}
|
||||
return plugin.allocator.allocString(value);
|
||||
},
|
||||
extism_var_get(i: bigint): bigint {
|
||||
const key = plugin.allocator.getString(i);
|
||||
if (key === null) {
|
||||
return BigInt(0);
|
||||
}
|
||||
const value = plugin.vars[key];
|
||||
if (typeof value === 'undefined') {
|
||||
return BigInt(0);
|
||||
}
|
||||
return plugin.allocator.allocBytes(value);
|
||||
},
|
||||
extism_var_set(n: bigint, i: bigint) {
|
||||
const key = plugin.allocator.getString(n);
|
||||
if (key === null) {
|
||||
return;
|
||||
}
|
||||
const value = plugin.allocator.getBytes(i);
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
plugin.vars[key] = value;
|
||||
},
|
||||
extism_http_request(n: bigint, i: bigint): number {
|
||||
debugger;
|
||||
return 0;
|
||||
},
|
||||
extism_length(i: bigint): bigint {
|
||||
return plugin.allocator.getLength(i);
|
||||
},
|
||||
extism_log_warn(i: bigint) {
|
||||
const s = plugin.allocator.getString(i);
|
||||
console.warn(s);
|
||||
},
|
||||
extism_log_info(i: bigint) {
|
||||
const s = plugin.allocator.getString(i);
|
||||
console.log(s);
|
||||
},
|
||||
extism_log_debug(i: bigint) {
|
||||
const s = plugin.allocator.getString(i);
|
||||
console.debug(s);
|
||||
},
|
||||
extism_log_error(i: bigint) {
|
||||
const s = plugin.allocator.getString(i);
|
||||
console.error(s);
|
||||
},
|
||||
};
|
||||
|
||||
for (const [name, func] of Object.entries(this.functions)) {
|
||||
env[name] = function () {
|
||||
return func.apply(plugin, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
}
|
||||
13
browser/tsconfig.json
Normal file
13
browser/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
6
browser/tslint.json
Normal file
6
browser/tslint.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": [
|
||||
"tslint:recommended",
|
||||
"tslint-config-prettier"
|
||||
]
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
build:
|
||||
clang -o main main.c -lextism -L .
|
||||
clang -g -o main main.c -lextism -L .
|
||||
42
c/main.c
42
c/main.c
@@ -9,6 +9,21 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void hello_world(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
|
||||
uint64_t n_inputs, ExtismVal *outputs, uint64_t n_outputs,
|
||||
void *data) {
|
||||
puts("Hello from C!");
|
||||
puts(data);
|
||||
|
||||
ExtismSize ptr_offs = inputs[0].v.i64;
|
||||
|
||||
uint8_t *buf = extism_current_plugin_memory(plugin) + ptr_offs;
|
||||
uint64_t length = extism_current_plugin_memory_length(plugin, ptr_offs);
|
||||
fwrite(buf, length, 1, stdout);
|
||||
fputc('\n', stdout);
|
||||
outputs[0].v.i64 = inputs[0].v.i64;
|
||||
}
|
||||
|
||||
uint8_t *read_file(const char *filename, size_t *len) {
|
||||
|
||||
FILE *fp = fopen(filename, "rb");
|
||||
@@ -21,6 +36,7 @@ uint8_t *read_file(const char *filename, size_t *len) {
|
||||
|
||||
uint8_t *data = malloc(length);
|
||||
if (data == NULL) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -36,21 +52,31 @@ int main(int argc, char *argv[]) {
|
||||
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_register(data, len, false);
|
||||
uint8_t *data = read_file("../wasm/code-functions.wasm", &len);
|
||||
ExtismValType inputs[] = {I64};
|
||||
ExtismValType outputs[] = {I64};
|
||||
ExtismFunction *f = extism_function_new("hello_world", inputs, 1, outputs, 1,
|
||||
hello_world, "Hello, again!", NULL);
|
||||
ExtismPlugin plugin =
|
||||
extism_plugin_new(ctx, data, len, (const ExtismFunction **)&f, 1, true);
|
||||
free(data);
|
||||
if (plugin < 0) {
|
||||
puts(extism_error(ctx, -1));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
assert(extism_call(plugin, "count_vowels", (uint8_t *)argv[1],
|
||||
strlen(argv[1])) == 0);
|
||||
ExtismSize out_len = extism_output_length(plugin);
|
||||
char output[out_len];
|
||||
extism_output_get(plugin, (uint8_t *)output, out_len);
|
||||
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_function_free(f);
|
||||
extism_context_free(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,45 +1,49 @@
|
||||
{
|
||||
"name": "extism/extism",
|
||||
"description": "Make your software programmable. Run WebAssembly extensions in your app using the first off-the-shelf, universal plug-in system.",
|
||||
"license": "BSD-3-Clause",
|
||||
"type": "project",
|
||||
"keywords": [
|
||||
"WebAssembly",
|
||||
"plugin-system",
|
||||
"runtime",
|
||||
"plug-in"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The Extism Authors",
|
||||
"email": "oss@extism.org",
|
||||
"homepage": "https://extism.org"
|
||||
},
|
||||
{
|
||||
"name": "Dylibso, Inc.",
|
||||
"email": "oss@dylib.so",
|
||||
"homepage": "https://dylib.so"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.4 || ^8",
|
||||
"ircmaxell/ffime": "dev-master"
|
||||
"name": "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"
|
||||
},
|
||||
"suggest": {},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Extism\\Plugin\\": "php/src/"
|
||||
}
|
||||
{
|
||||
"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/"
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {},
|
||||
"scripts": {},
|
||||
"scripts-descriptions": {}
|
||||
"files": [
|
||||
"php/src/Context.php",
|
||||
"php/src/Plugin.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {},
|
||||
"scripts": {},
|
||||
"scripts-descriptions": {}
|
||||
}
|
||||
17
cpp/Makefile
17
cpp/Makefile
@@ -1,3 +1,16 @@
|
||||
build:
|
||||
clang++ -std=c++11 -o example example.cpp -lextism -L .
|
||||
FLAGS=`pkg-config --cflags --libs jsoncpp gtest` -lextism -lpthread
|
||||
|
||||
build-example:
|
||||
$(CXX) -std=c++14 -o example -I. example.cpp $(FLAGS)
|
||||
|
||||
.PHONY: example
|
||||
example: build-example
|
||||
./example
|
||||
|
||||
build-test:
|
||||
$(CXX) -std=c++14 -o test/test -I. test/test.cpp $(FLAGS)
|
||||
|
||||
.PHONY: test
|
||||
test: build-test
|
||||
cd test && ./test
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#define EXTISM_NO_JSON
|
||||
#include "extism.hpp"
|
||||
|
||||
#include <cstring>
|
||||
@@ -13,18 +14,31 @@ std::vector<uint8_t> read(const char *filename) {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
auto wasm = read("../wasm/code.wasm");
|
||||
Plugin plugin(wasm);
|
||||
auto wasm = read("../wasm/code-functions.wasm");
|
||||
std::string tmp = "Testing";
|
||||
|
||||
if (argc < 2) {
|
||||
std::cout << "Not enough arguments" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
// A lambda can be used as a host function
|
||||
auto hello_world = [&tmp](CurrentPlugin plugin,
|
||||
const std::vector<Val> &inputs,
|
||||
std::vector<Val> &outputs, void *user_data) {
|
||||
std::cout << "Hello from C++" << std::endl;
|
||||
std::cout << (const char *)user_data << std::endl;
|
||||
std::cout << tmp << std::endl;
|
||||
outputs[0].v = inputs[0].v;
|
||||
};
|
||||
|
||||
auto input = std::vector<uint8_t>((uint8_t *)argv[1],
|
||||
(uint8_t *)argv[1] + strlen(argv[1]));
|
||||
auto output = plugin.call("count_vowels", input);
|
||||
std::string str(output.begin(), output.end());
|
||||
std::cout << str << std::endl;
|
||||
std::vector<Function> functions = {
|
||||
Function("hello_world", {ValType::I64}, {ValType::I64}, hello_world,
|
||||
(void *)"Hello again!",
|
||||
[](void *x) { std::cout << "Free user data" << std::endl; }),
|
||||
};
|
||||
|
||||
Plugin plugin(wasm, true, functions);
|
||||
|
||||
const char *input = argc > 1 ? argv[1] : "this is a test";
|
||||
ExtismSize length = strlen(input);
|
||||
|
||||
extism::Buffer output = plugin.call("count_vowels", (uint8_t *)input, length);
|
||||
std::cout << (char *)output.data << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../core/extism.h
|
||||
549
cpp/extism.hpp
549
cpp/extism.hpp
@@ -1,45 +1,481 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
#if __has_include(<jsoncpp/json/json.h>)
|
||||
#include <jsoncpp/json/json.h>
|
||||
#else
|
||||
#include <json/json.h>
|
||||
#endif
|
||||
#endif // EXTISM_NO_JSON
|
||||
|
||||
extern "C" {
|
||||
#include "extism.h"
|
||||
#include <extism.h>
|
||||
}
|
||||
|
||||
namespace extism {
|
||||
class Error : public std::exception {
|
||||
private:
|
||||
std::string message;
|
||||
|
||||
typedef std::map<std::string, std::string> Config;
|
||||
|
||||
template <typename T> class ManifestKey {
|
||||
bool is_set = false;
|
||||
|
||||
public:
|
||||
Error(std::string msg) : message(msg) {}
|
||||
const char *what() { return message.c_str(); }
|
||||
T value;
|
||||
ManifestKey(T x, bool is_set = false) : is_set(is_set) { value = x; }
|
||||
|
||||
void set(T x) {
|
||||
value = x;
|
||||
is_set = true;
|
||||
}
|
||||
|
||||
bool empty() const { return is_set == false; }
|
||||
};
|
||||
|
||||
class Wasm {
|
||||
std::string _path;
|
||||
std::string _url;
|
||||
// TODO: add base64 encoded raw data
|
||||
ManifestKey<std::string> _hash =
|
||||
ManifestKey<std::string>(std::string(), false);
|
||||
|
||||
public:
|
||||
// Create Wasm pointing to a path
|
||||
static Wasm path(std::string s, std::string hash = std::string()) {
|
||||
Wasm w;
|
||||
w._path = s;
|
||||
if (!hash.empty()) {
|
||||
w._hash.set(hash);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
// Create Wasm pointing to a URL
|
||||
static Wasm url(std::string s, std::string hash = std::string()) {
|
||||
Wasm w;
|
||||
w._url = s;
|
||||
if (!hash.empty()) {
|
||||
w._hash.set(hash);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
Json::Value json() const {
|
||||
Json::Value doc;
|
||||
|
||||
if (!this->_path.empty()) {
|
||||
doc["path"] = this->_path;
|
||||
} else if (!this->_url.empty()) {
|
||||
doc["url"] = this->_url;
|
||||
}
|
||||
|
||||
if (!this->_hash.empty()) {
|
||||
doc["hash"] = this->_hash.value;
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
class Manifest {
|
||||
public:
|
||||
Config config;
|
||||
std::vector<Wasm> wasm;
|
||||
ManifestKey<std::vector<std::string>> allowed_hosts;
|
||||
ManifestKey<std::map<std::string, std::string>> allowed_paths;
|
||||
ManifestKey<uint64_t> timeout_ms;
|
||||
|
||||
// Empty manifest
|
||||
Manifest()
|
||||
: timeout_ms(0, false), allowed_hosts(std::vector<std::string>(), false),
|
||||
allowed_paths(std::map<std::string, std::string>(), false) {}
|
||||
|
||||
// Create manifest with a single Wasm from a path
|
||||
static Manifest path(std::string s, std::string hash = std::string()) {
|
||||
Manifest m;
|
||||
m.add_wasm_path(s, hash);
|
||||
return m;
|
||||
}
|
||||
|
||||
// Create manifest with a single Wasm from a URL
|
||||
static Manifest url(std::string s, std::string hash = std::string()) {
|
||||
Manifest m;
|
||||
m.add_wasm_url(s, hash);
|
||||
return m;
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
std::string json() const {
|
||||
Json::Value doc;
|
||||
Json::Value wasm;
|
||||
for (auto w : this->wasm) {
|
||||
wasm.append(w.json());
|
||||
}
|
||||
|
||||
doc["wasm"] = wasm;
|
||||
|
||||
if (!this->config.empty()) {
|
||||
Json::Value conf;
|
||||
|
||||
for (auto k : this->config) {
|
||||
conf[k.first] = k.second;
|
||||
}
|
||||
doc["config"] = conf;
|
||||
}
|
||||
|
||||
if (!this->allowed_hosts.empty()) {
|
||||
Json::Value h;
|
||||
|
||||
for (auto s : this->allowed_hosts.value) {
|
||||
h.append(s);
|
||||
}
|
||||
doc["allowed_hosts"] = h;
|
||||
}
|
||||
|
||||
if (!this->allowed_paths.empty()) {
|
||||
Json::Value h;
|
||||
for (auto k : this->allowed_paths.value) {
|
||||
h[k.first] = k.second;
|
||||
}
|
||||
doc["allowed_paths"] = h;
|
||||
}
|
||||
|
||||
if (!this->timeout_ms.empty()) {
|
||||
doc["timeout_ms"] = Json::Value(this->timeout_ms.value);
|
||||
}
|
||||
|
||||
Json::FastWriter writer;
|
||||
return writer.write(doc);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Add Wasm from path
|
||||
void add_wasm_path(std::string s, std::string hash = std::string()) {
|
||||
Wasm w = Wasm::path(s, hash);
|
||||
this->wasm.push_back(w);
|
||||
}
|
||||
|
||||
// Add Wasm from URL
|
||||
void add_wasm_url(std::string u, std::string hash = std::string()) {
|
||||
Wasm w = Wasm::url(u, hash);
|
||||
this->wasm.push_back(w);
|
||||
}
|
||||
|
||||
// Add host to allowed hosts
|
||||
void allow_host(std::string host) {
|
||||
if (this->allowed_hosts.empty()) {
|
||||
this->allowed_hosts.set(std::vector<std::string>{});
|
||||
}
|
||||
this->allowed_hosts.value.push_back(host);
|
||||
}
|
||||
|
||||
// Add path to allowed paths
|
||||
void allow_path(std::string src, std::string dest = std::string()) {
|
||||
if (this->allowed_paths.empty()) {
|
||||
this->allowed_paths.set(std::map<std::string, std::string>{});
|
||||
}
|
||||
|
||||
if (dest.empty()) {
|
||||
dest = src;
|
||||
}
|
||||
this->allowed_paths.value[src] = dest;
|
||||
}
|
||||
|
||||
// Set timeout
|
||||
void set_timeout_ms(uint64_t ms) { this->timeout_ms = ms; }
|
||||
|
||||
// Set config key/value
|
||||
void set_config(std::string k, std::string v) { this->config[k] = v; }
|
||||
};
|
||||
|
||||
class Error : public std::runtime_error {
|
||||
public:
|
||||
Error(std::string msg) : std::runtime_error(msg) {}
|
||||
};
|
||||
|
||||
class Buffer {
|
||||
public:
|
||||
Buffer(const uint8_t *ptr, ExtismSize len) : data(ptr), length(len) {}
|
||||
const uint8_t *data;
|
||||
ExtismSize length;
|
||||
|
||||
std::string string() { return (std::string)(*this); }
|
||||
|
||||
std::vector<uint8_t> vector() { return (std::vector<uint8_t>)(*this); }
|
||||
|
||||
operator std::string() { return std::string((const char *)data, length); }
|
||||
operator std::vector<uint8_t>() {
|
||||
return std::vector<uint8_t>(data, data + length);
|
||||
}
|
||||
};
|
||||
|
||||
typedef ExtismValType ValType;
|
||||
typedef ExtismValUnion ValUnion;
|
||||
typedef ExtismVal Val;
|
||||
|
||||
class CurrentPlugin {
|
||||
ExtismCurrentPlugin *pointer;
|
||||
|
||||
public:
|
||||
CurrentPlugin(ExtismCurrentPlugin *p) : pointer(p) {}
|
||||
|
||||
uint8_t *memory() { return extism_current_plugin_memory(this->pointer); }
|
||||
ExtismSize memory_length(uint64_t offs) {
|
||||
return extism_current_plugin_memory_length(this->pointer, offs);
|
||||
}
|
||||
|
||||
uint64_t alloc(ExtismSize size) {
|
||||
return extism_current_plugin_memory_alloc(this->pointer, size);
|
||||
}
|
||||
|
||||
void free(uint64_t offs) {
|
||||
extism_current_plugin_memory_free(this->pointer, offs);
|
||||
}
|
||||
|
||||
void returnString(Val &output, const std::string &s) {
|
||||
this->returnBytes(output, (const uint8_t *)s.c_str(), s.size());
|
||||
}
|
||||
|
||||
void returnBytes(Val &output, const uint8_t *bytes, size_t len) {
|
||||
auto offs = this->alloc(len);
|
||||
memcpy(this->memory() + offs, bytes, len);
|
||||
output.v.i64 = offs;
|
||||
}
|
||||
|
||||
uint8_t *inputBytes(Val &inp, size_t *length = nullptr) {
|
||||
if (inp.t != ValType::I64) {
|
||||
return nullptr;
|
||||
}
|
||||
if (length != nullptr) {
|
||||
*length = this->memory_length(inp.v.i64);
|
||||
}
|
||||
return this->memory() + inp.v.i64;
|
||||
}
|
||||
|
||||
std::string inputString(Val &inp) {
|
||||
size_t length = 0;
|
||||
char *buf = (char *)this->inputBytes(inp, &length);
|
||||
return std::string(buf, length);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::function<void(CurrentPlugin, const std::vector<Val> &,
|
||||
std::vector<Val> &, void *user_data)>
|
||||
FunctionType;
|
||||
|
||||
struct UserData {
|
||||
FunctionType func;
|
||||
void *user_data = NULL;
|
||||
std::function<void(void *)> free_user_data;
|
||||
};
|
||||
|
||||
static void function_callback(ExtismCurrentPlugin *plugin,
|
||||
const ExtismVal *inputs, ExtismSize n_inputs,
|
||||
ExtismVal *outputs, ExtismSize n_outputs,
|
||||
void *user_data) {
|
||||
UserData *data = (UserData *)user_data;
|
||||
const std::vector<Val> inp(inputs, inputs + n_inputs);
|
||||
std::vector<Val> outp(outputs, outputs + n_outputs);
|
||||
data->func(CurrentPlugin(plugin), inp, outp, data->user_data);
|
||||
|
||||
for (ExtismSize i = 0; i < n_outputs; i++) {
|
||||
outputs[i] = outp[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void free_user_data(void *user_data) {
|
||||
UserData *data = (UserData *)user_data;
|
||||
if (data->user_data != NULL && data->free_user_data != NULL) {
|
||||
data->free_user_data(data->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
class Function {
|
||||
std::shared_ptr<ExtismFunction> func;
|
||||
std::string name;
|
||||
UserData user_data;
|
||||
|
||||
public:
|
||||
Function(std::string name, const std::vector<ValType> inputs,
|
||||
const std::vector<ValType> outputs, FunctionType f,
|
||||
void *user_data = NULL, std::function<void(void *)> free = nullptr)
|
||||
: name(name) {
|
||||
this->user_data.func = f;
|
||||
this->user_data.user_data = user_data;
|
||||
this->user_data.free_user_data = free;
|
||||
auto ptr = extism_function_new(
|
||||
this->name.c_str(), inputs.data(), inputs.size(), outputs.data(),
|
||||
outputs.size(), function_callback, &this->user_data, free_user_data);
|
||||
this->func = std::shared_ptr<ExtismFunction>(ptr, extism_function_free);
|
||||
}
|
||||
|
||||
void set_namespace(std::string s) {
|
||||
extism_function_set_namespace(this->func.get(), s.c_str());
|
||||
}
|
||||
|
||||
Function(const Function &f) { this->func = f.func; }
|
||||
|
||||
ExtismFunction *get() { return this->func.get(); }
|
||||
};
|
||||
|
||||
class CancelHandle {
|
||||
const ExtismCancelHandle *handle;
|
||||
|
||||
public:
|
||||
CancelHandle(const ExtismCancelHandle *x) : handle(x){};
|
||||
bool cancel() { return extism_plugin_cancel(this->handle); }
|
||||
};
|
||||
|
||||
class Plugin {
|
||||
std::shared_ptr<ExtismContext> context;
|
||||
ExtismPlugin plugin;
|
||||
std::vector<Function> functions;
|
||||
|
||||
public:
|
||||
Plugin(const uint8_t *wasm, size_t length, bool with_wasi = false) {
|
||||
this->plugin = extism_plugin_register(wasm, length, with_wasi);
|
||||
// Create a new plugin
|
||||
Plugin(const uint8_t *wasm, ExtismSize length, bool with_wasi = false,
|
||||
std::vector<Function> functions = std::vector<Function>(),
|
||||
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
|
||||
extism_context_new(), extism_context_free))
|
||||
: functions(functions) {
|
||||
std::vector<const ExtismFunction *> ptrs;
|
||||
for (auto i : this->functions) {
|
||||
ptrs.push_back(i.get());
|
||||
}
|
||||
this->plugin = extism_plugin_new(ctx.get(), wasm, length, ptrs.data(),
|
||||
ptrs.size(), with_wasi);
|
||||
if (this->plugin < 0) {
|
||||
throw Error("Unable to load plugin");
|
||||
const char *err = extism_error(ctx.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to load plugin" : err);
|
||||
}
|
||||
this->context = ctx;
|
||||
}
|
||||
|
||||
Plugin(const std::string &str, bool with_wasi = false,
|
||||
std::vector<Function> functions = {},
|
||||
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
|
||||
extism_context_new(), extism_context_free))
|
||||
: Plugin((const uint8_t *)str.c_str(), str.size(), with_wasi, functions,
|
||||
ctx) {}
|
||||
|
||||
Plugin(const std::vector<uint8_t> &data, bool with_wasi = false,
|
||||
std::vector<Function> functions = {},
|
||||
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
|
||||
extism_context_new(), extism_context_free))
|
||||
: Plugin(data.data(), data.size(), with_wasi, functions, ctx) {}
|
||||
|
||||
CancelHandle cancel_handle() {
|
||||
return CancelHandle(
|
||||
extism_plugin_cancel_handle(this->context.get(), this->id()));
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
// Create a new plugin from Manifest
|
||||
Plugin(const Manifest &manifest, bool with_wasi = false,
|
||||
std::vector<Function> functions = {},
|
||||
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
|
||||
extism_context_new(), extism_context_free)) {
|
||||
std::vector<const ExtismFunction *> ptrs;
|
||||
for (auto i : this->functions) {
|
||||
ptrs.push_back(i.get());
|
||||
}
|
||||
|
||||
auto buffer = manifest.json();
|
||||
this->plugin =
|
||||
extism_plugin_new(ctx.get(), (const uint8_t *)buffer.c_str(),
|
||||
buffer.size(), ptrs.data(), ptrs.size(), with_wasi);
|
||||
if (this->plugin < 0) {
|
||||
const char *err = extism_error(ctx.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to load plugin from manifest" : err);
|
||||
}
|
||||
this->context = ctx;
|
||||
}
|
||||
#endif
|
||||
|
||||
~Plugin() {
|
||||
extism_plugin_free(this->context.get(), this->plugin);
|
||||
this->plugin = -1;
|
||||
}
|
||||
|
||||
ExtismPlugin id() const { return this->plugin; }
|
||||
|
||||
ExtismContext *get_context() const { return this->context.get(); }
|
||||
|
||||
void update(const uint8_t *wasm, size_t length, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) {
|
||||
this->functions = functions;
|
||||
std::vector<const ExtismFunction *> ptrs;
|
||||
for (auto i : this->functions) {
|
||||
ptrs.push_back(i.get());
|
||||
}
|
||||
bool b = extism_plugin_update(this->context.get(), this->plugin, wasm,
|
||||
length, ptrs.data(), ptrs.size(), with_wasi);
|
||||
if (!b) {
|
||||
const char *err = extism_error(this->context.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to update plugin" : err);
|
||||
}
|
||||
}
|
||||
|
||||
Plugin(const std::string &s, bool with_wasi = false)
|
||||
: Plugin((const uint8_t *)s.c_str(), s.size(), with_wasi) {}
|
||||
Plugin(const std::vector<uint8_t> &s, bool with_wasi = false)
|
||||
: Plugin(s.data(), s.size(), with_wasi) {}
|
||||
#ifndef EXTISM_NO_JSON
|
||||
void update(const Manifest &manifest, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) {
|
||||
this->functions = functions;
|
||||
std::vector<const ExtismFunction *> ptrs;
|
||||
for (auto i : this->functions) {
|
||||
ptrs.push_back(i.get());
|
||||
}
|
||||
auto buffer = manifest.json();
|
||||
bool b = extism_plugin_update(
|
||||
this->context.get(), this->plugin, (const uint8_t *)buffer.c_str(),
|
||||
buffer.size(), ptrs.data(), ptrs.size(), with_wasi);
|
||||
if (!b) {
|
||||
const char *err = extism_error(this->context.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to update plugin" : err);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> call(const std::string &func,
|
||||
std::vector<uint8_t> input) {
|
||||
void config(const Config &data) {
|
||||
Json::Value conf;
|
||||
|
||||
int32_t rc =
|
||||
extism_call(this->plugin, func.c_str(), input.data(), input.size());
|
||||
for (auto k : data) {
|
||||
conf[k.first] = k.second;
|
||||
}
|
||||
|
||||
Json::FastWriter writer;
|
||||
auto s = writer.write(conf);
|
||||
this->config(s);
|
||||
}
|
||||
#endif
|
||||
|
||||
void config(const char *json, size_t length) {
|
||||
bool b = extism_plugin_config(this->context.get(), this->plugin,
|
||||
(const uint8_t *)json, length);
|
||||
if (!b) {
|
||||
const char *err = extism_error(this->context.get(), this->plugin);
|
||||
throw Error(err == nullptr ? "Unable to update plugin config" : err);
|
||||
}
|
||||
}
|
||||
|
||||
void config(const std::string &json) {
|
||||
this->config(json.c_str(), json.size());
|
||||
}
|
||||
|
||||
// Call a plugin
|
||||
Buffer call(const std::string &func, const uint8_t *input,
|
||||
ExtismSize input_length) const {
|
||||
int32_t rc = extism_plugin_call(this->context.get(), this->plugin,
|
||||
func.c_str(), input, input_length);
|
||||
if (rc != 0) {
|
||||
const char *error = extism_error(this->plugin);
|
||||
const char *error = extism_error(this->context.get(), this->plugin);
|
||||
if (error == nullptr) {
|
||||
throw Error("extism_call failed");
|
||||
}
|
||||
@@ -47,10 +483,79 @@ public:
|
||||
throw Error(error);
|
||||
}
|
||||
|
||||
ExtismSize length = extism_output_length(this->plugin);
|
||||
std::vector<uint8_t> out = std::vector<uint8_t>(length);
|
||||
extism_output_get(this->plugin, out.data(), length);
|
||||
return out;
|
||||
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);
|
||||
}
|
||||
|
||||
// Call a plugin function with std::vector<uint8_t> input
|
||||
Buffer call(const std::string &func,
|
||||
const std::vector<uint8_t> &input) const {
|
||||
return this->call(func, input.data(), input.size());
|
||||
}
|
||||
|
||||
// Call a plugin function with string input
|
||||
Buffer call(const std::string &func,
|
||||
const std::string &input = std::string()) const {
|
||||
return this->call(func, (const uint8_t *)input.c_str(), input.size());
|
||||
}
|
||||
|
||||
// Returns true if the specified function exists
|
||||
bool function_exists(const std::string &func) const {
|
||||
return extism_plugin_function_exists(this->context.get(), this->plugin,
|
||||
func.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
class Context {
|
||||
public:
|
||||
std::shared_ptr<ExtismContext> pointer;
|
||||
|
||||
// Create a new context;
|
||||
Context() {
|
||||
this->pointer = std::shared_ptr<ExtismContext>(extism_context_new(),
|
||||
extism_context_free);
|
||||
}
|
||||
|
||||
// Create plugin from uint8_t*
|
||||
Plugin plugin(const uint8_t *wasm, size_t length, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) const {
|
||||
return Plugin(wasm, length, with_wasi, functions, this->pointer);
|
||||
}
|
||||
|
||||
// Create plugin from std::string
|
||||
Plugin plugin(const std::string &str, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) const {
|
||||
return Plugin((const uint8_t *)str.c_str(), str.size(), with_wasi,
|
||||
functions, this->pointer);
|
||||
}
|
||||
|
||||
// Create plugin from uint8_t vector
|
||||
Plugin plugin(const std::vector<uint8_t> &data, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) const {
|
||||
return Plugin(data.data(), data.size(), with_wasi, functions,
|
||||
this->pointer);
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
// Create plugin from Manifest
|
||||
Plugin plugin(const Manifest &manifest, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) const {
|
||||
return Plugin(manifest, with_wasi, functions, this->pointer);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Remove all plugins
|
||||
void reset() { extism_context_reset(this->pointer.get()); }
|
||||
};
|
||||
|
||||
// Set global log file for plugins
|
||||
inline bool set_log_file(const char *filename, const char *level) {
|
||||
return extism_log_file(filename, level);
|
||||
}
|
||||
|
||||
// Get libextism version
|
||||
inline std::string version() { return std::string(extism_version()); }
|
||||
} // namespace extism
|
||||
|
||||
93
cpp/test/test.cpp
Normal file
93
cpp/test/test.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include "../extism.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
std::vector<uint8_t> read(const char *filename) {
|
||||
std::ifstream file(filename, std::ios::binary);
|
||||
return std::vector<uint8_t>((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
const std::string code = "../../wasm/code.wasm";
|
||||
|
||||
namespace {
|
||||
using namespace extism;
|
||||
|
||||
TEST(Context, Basic) {
|
||||
Context context;
|
||||
ASSERT_NE(context.pointer, nullptr);
|
||||
}
|
||||
|
||||
TEST(Plugin, Manifest) {
|
||||
Manifest manifest = Manifest::path(code);
|
||||
manifest.set_config("a", "1");
|
||||
|
||||
ASSERT_NO_THROW(Plugin plugin(manifest));
|
||||
Plugin plugin(manifest);
|
||||
|
||||
Buffer buf = plugin.call("count_vowels", "this is a test");
|
||||
ASSERT_EQ((std::string)buf, "{\"count\": 4}");
|
||||
}
|
||||
|
||||
TEST(Plugin, BadManifest) {
|
||||
Manifest manifest;
|
||||
ASSERT_THROW(Plugin plugin(manifest), Error);
|
||||
}
|
||||
|
||||
TEST(Plugin, Bytes) {
|
||||
Context context;
|
||||
auto wasm = read(code.c_str());
|
||||
ASSERT_NO_THROW(Plugin plugin = context.plugin(wasm));
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
Buffer buf = plugin.call("count_vowels", "this is another test");
|
||||
ASSERT_EQ(buf.string(), "{\"count\": 6}");
|
||||
}
|
||||
|
||||
TEST(Plugin, UpdateConfig) {
|
||||
Context context;
|
||||
auto wasm = read(code.c_str());
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
Config config;
|
||||
config["abc"] = "123";
|
||||
ASSERT_NO_THROW(plugin.config(config));
|
||||
}
|
||||
|
||||
TEST(Plugin, FunctionExists) {
|
||||
Context context;
|
||||
auto wasm = read(code.c_str());
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
ASSERT_FALSE(plugin.function_exists("bad_function"));
|
||||
ASSERT_TRUE(plugin.function_exists("count_vowels"));
|
||||
}
|
||||
|
||||
TEST(Plugin, HostFunction) {
|
||||
auto wasm = read("../../wasm/code-functions.wasm");
|
||||
auto t = std::vector<ValType>{ValType::I64};
|
||||
Function hello_world =
|
||||
Function("hello_world", t, t,
|
||||
[](CurrentPlugin plugin, const std::vector<Val> ¶ms,
|
||||
std::vector<Val> &results, void *user_data) {
|
||||
auto offs = plugin.alloc(4);
|
||||
memcpy(plugin.memory() + offs, "test", 4);
|
||||
results[0].v.i64 = (int64_t)offs;
|
||||
});
|
||||
auto functions = std::vector<Function>{
|
||||
hello_world,
|
||||
};
|
||||
Plugin plugin(wasm, true, functions);
|
||||
auto buf = plugin.call("count_vowels", "aaa");
|
||||
ASSERT_EQ(buf.length, 4);
|
||||
ASSERT_EQ((std::string)buf, "test");
|
||||
}
|
||||
|
||||
}; // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
479
dotnet/.gitignore
vendored
Normal file
479
dotnet/.gitignore
vendored
Normal file
@@ -0,0 +1,479 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Tye
|
||||
.tye/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
##
|
||||
## Visual studio for Mac
|
||||
##
|
||||
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
|
||||
# Mac bundle stuff
|
||||
*.dmg
|
||||
*.app
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
nuget/runtimes/win-x64.dll
|
||||
37
dotnet/Extism.sln
Normal file
37
dotnet/Extism.sln
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.4.33110.190
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.Sdk", "src\Extism.Sdk\Extism.Sdk.csproj", "{1FAA7B6E-249C-4E4C-AE7A-A493A9D24475}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.Sdk.Tests", "test\Extism.Sdk\Extism.Sdk.Tests.csproj", "{DB440D61-C781-4C59-9223-9A79CC9FB4E7}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extism.Sdk.Sample", "samples\Extism.Sdk.Sample\Extism.Sdk.Sample.csproj", "{2232E572-E8BA-46A1-AF31-E4168960DB75}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1FAA7B6E-249C-4E4C-AE7A-A493A9D24475}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1FAA7B6E-249C-4E4C-AE7A-A493A9D24475}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1FAA7B6E-249C-4E4C-AE7A-A493A9D24475}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1FAA7B6E-249C-4E4C-AE7A-A493A9D24475}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DB440D61-C781-4C59-9223-9A79CC9FB4E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DB440D61-C781-4C59-9223-9A79CC9FB4E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DB440D61-C781-4C59-9223-9A79CC9FB4E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DB440D61-C781-4C59-9223-9A79CC9FB4E7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2232E572-E8BA-46A1-AF31-E4168960DB75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2232E572-E8BA-46A1-AF31-E4168960DB75}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2232E572-E8BA-46A1-AF31-E4168960DB75}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2232E572-E8BA-46A1-AF31-E4168960DB75}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {2B6BF267-F2A5-4CB5-8DFD-F11CC8787E6B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
24
dotnet/nuget/Extism.runtime.win.csproj
Normal file
24
dotnet/nuget/Extism.runtime.win.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<NoBuild>true</NoBuild>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>Extism.runtime.win-x64</PackageId>
|
||||
<Version>0.7.0</Version>
|
||||
<Authors>Extism Contributors</Authors>
|
||||
<Description>Internal implementation package for Extism to work on Windows x64</Description>
|
||||
<Tags>extism, wasm, plugin</Tags>
|
||||
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="runtimes/win-x64.dll"
|
||||
CopyToOutputDirectory="Always"
|
||||
Pack="true"
|
||||
PackagePath="runtimes\win-x64\native\extism.dll" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
1
dotnet/nuget/runtimes/expected.txt
Normal file
1
dotnet/nuget/runtimes/expected.txt
Normal file
@@ -0,0 +1 @@
|
||||
win-x64.dll
|
||||
29
dotnet/samples/Extism.Sdk.Sample/Extism.Sdk.Sample.csproj
Normal file
29
dotnet/samples/Extism.Sdk.Sample/Extism.Sdk.Sample.csproj
Normal file
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\..\wasm\code.wasm" Link="code.wasm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\..\..\wasm\code-functions.wasm" Link="code-functions.wasm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Extism.runtime.win-x64" Version="0.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
40
dotnet/samples/Extism.Sdk.Sample/Program.cs
Normal file
40
dotnet/samples/Extism.Sdk.Sample/Program.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Extism.Sdk;
|
||||
using Extism.Sdk.Native;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
var context = new Context();
|
||||
|
||||
var userData = Marshal.StringToHGlobalAnsi("Hello again!");
|
||||
|
||||
using var helloWorld = new HostFunction(
|
||||
"hello_world",
|
||||
"env",
|
||||
new[] { ExtismValType.I64 },
|
||||
new[] { ExtismValType.I64 },
|
||||
userData,
|
||||
HelloWorld);
|
||||
|
||||
void HelloWorld(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> outputs, nint data)
|
||||
{
|
||||
Console.WriteLine("Hello from .NET!");
|
||||
|
||||
var text = Marshal.PtrToStringAnsi(data);
|
||||
Console.WriteLine(text);
|
||||
|
||||
var input = plugin.ReadString(new nint(inputs[0].v.i64));
|
||||
Console.WriteLine($"Input: {input}");
|
||||
|
||||
outputs[0].v.i64 = plugin.WriteString(input);
|
||||
}
|
||||
|
||||
var wasm = File.ReadAllBytes("./code-functions.wasm");
|
||||
using var plugin = context.CreatePlugin(wasm, new[] { helloWorld }, withWasi: true);
|
||||
|
||||
var output = Encoding.UTF8.GetString(
|
||||
plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World!"))
|
||||
);
|
||||
|
||||
Console.WriteLine($"Output: {output}");
|
||||
|
||||
5
dotnet/samples/Extism.Sdk.Sample/README.md
Normal file
5
dotnet/samples/Extism.Sdk.Sample/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## Example 1
|
||||
|
||||
This example shows how you can use the library in the most basic way.
|
||||
It loads up the sample wasm plugin and lets you to pass inputs to it and show the ouput.
|
||||
**Please note that on Windows you have to manually copy the `extism.dll` file to the ouput directory.**
|
||||
24
dotnet/src/Directory.build.props
Normal file
24
dotnet/src/Directory.build.props
Normal file
@@ -0,0 +1,24 @@
|
||||
<!-- Recommended practices for publishing nuget packages from: https://devblogs.microsoft.com/dotnet/producing-packages-with-source-link/ -->
|
||||
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!-- Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
||||
<!-- Embed source files that are not tracked by the source control manager in the PDB -->
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
|
||||
<!-- Recommended: Embed symbols containing Source Link in the main file (exe/dll) -->
|
||||
<DebugType>embedded</DebugType>
|
||||
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
191
dotnet/src/Extism.Sdk/Context.cs
Normal file
191
dotnet/src/Extism.Sdk/Context.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Extism context through which you can load <see cref="Plugin"/>s.
|
||||
/// </summary>
|
||||
public unsafe class Context : IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, Plugin> _plugins = new ConcurrentDictionary<int, Plugin>();
|
||||
|
||||
private const int DisposedMarker = 1;
|
||||
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new Extism Context.
|
||||
/// </summary>
|
||||
public Context()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
NativeHandle = LibExtism.extism_context_new();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Native pointer to the Extism Context.
|
||||
/// </summary>
|
||||
internal LibExtism.ExtismContext* NativeHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads an Extism <see cref="Plugin"/>.
|
||||
/// </summary>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="functions">List of host functions expected by the plugin.</param>
|
||||
/// <param name="withWasi">Enable/Disable WASI.</param>
|
||||
public Plugin CreatePlugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* wasmPtr = wasm)
|
||||
fixed (IntPtr* functionsPtr = functionHandles)
|
||||
{
|
||||
var index = LibExtism.extism_plugin_new(NativeHandle, wasmPtr, wasm.Length, functionsPtr, functions.Length, withWasi);
|
||||
if (index == -1)
|
||||
{
|
||||
var errorMsg = GetError();
|
||||
if (errorMsg != null)
|
||||
{
|
||||
throw new ExtismException(errorMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExtismException("Failed to create plugin.");
|
||||
}
|
||||
}
|
||||
|
||||
return _plugins[index] = new Plugin(this, functions, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a plugin by index.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of plugin.</param>
|
||||
/// <returns></returns>
|
||||
public Plugin GetPlugin(int index)
|
||||
{
|
||||
return _plugins[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all plugins from this <see cref="Context"/>'s registry.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
LibExtism.extism_context_reset(NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get this this <see cref="Context"/>'s last error.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal string? GetError()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
var result = LibExtism.extism_error(NativeHandle, -1);
|
||||
return Marshal.PtrToStringUTF8(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Context.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _disposed, DisposedMarker) == DisposedMarker)
|
||||
{
|
||||
// Already disposed.
|
||||
return;
|
||||
}
|
||||
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throw an appropriate exception if the plugin has been disposed.
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
protected void CheckNotDisposed()
|
||||
{
|
||||
Interlocked.MemoryBarrier();
|
||||
if (_disposed == DisposedMarker)
|
||||
{
|
||||
ThrowDisposedException();
|
||||
}
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
private static void ThrowDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Context));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Context.
|
||||
/// </summary>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Free up any managed resources here
|
||||
}
|
||||
|
||||
foreach (var plugin in _plugins.Values)
|
||||
{
|
||||
plugin.Dispose();
|
||||
}
|
||||
|
||||
// Free up unmanaged resources
|
||||
LibExtism.extism_context_free(NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructs the current Context and frees all resources used by it.
|
||||
/// </summary>
|
||||
~Context()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Extism version string.
|
||||
/// </summary>
|
||||
public static string GetExtismVersion()
|
||||
{
|
||||
var pointer = LibExtism.extism_version();
|
||||
return Marshal.PtrToStringUTF8(pointer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set Extism's log file and level. This is applied for all <see cref="Context"/>s.
|
||||
/// </summary>
|
||||
/// <param name="logPath">Log file; can be 'stdout' or 'stderr' to write logs to the console.</param>
|
||||
/// <param name="level">The log level to write at.</param>
|
||||
public static bool SetExtismLogFile(string logPath, LogLevel level)
|
||||
{
|
||||
var logLevel = level switch
|
||||
{
|
||||
LogLevel.Error => LibExtism.LogLevels.Error,
|
||||
LogLevel.Warning => LibExtism.LogLevels.Warn,
|
||||
LogLevel.Info => LibExtism.LogLevels.Info,
|
||||
LogLevel.Debug => LibExtism.LogLevels.Debug,
|
||||
LogLevel.Trace => LibExtism.LogLevels.Trace,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
return LibExtism.extism_log_file(logPath, logLevel);
|
||||
}
|
||||
}
|
||||
138
dotnet/src/Extism.Sdk/CurrentPlugin.cs
Normal file
138
dotnet/src/Extism.Sdk/CurrentPlugin.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using Extism.Sdk.Native;
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace Extism.Sdk
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the current plugin. Can only be used within <see cref="HostFunction"/>s.
|
||||
/// </summary>
|
||||
public class CurrentPlugin
|
||||
{
|
||||
internal CurrentPlugin(nint nativeHandle)
|
||||
{
|
||||
NativeHandle = nativeHandle;
|
||||
}
|
||||
|
||||
internal nint NativeHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pointer to the memory of the currently running plugin.
|
||||
/// NOTE: this should only be called from host functions.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public nint GetMemory()
|
||||
{
|
||||
return LibExtism.extism_current_plugin_memory(NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string from a memory block using UTF8.
|
||||
/// </summary>
|
||||
/// <param name="pointer"></param>
|
||||
/// <returns></returns>
|
||||
public string ReadString(nint pointer)
|
||||
{
|
||||
return ReadString(pointer, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string form a memory block.
|
||||
/// </summary>
|
||||
/// <param name="pointer"></param>
|
||||
/// <param name="encoding"></param>
|
||||
/// <returns></returns>
|
||||
public string ReadString(nint pointer, Encoding encoding)
|
||||
{
|
||||
var buffer = ReadBytes(pointer);
|
||||
|
||||
return encoding.GetString(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a span of bytes for a given block.
|
||||
/// </summary>
|
||||
/// <param name="pointer"></param>
|
||||
/// <returns></returns>
|
||||
public unsafe Span<byte> ReadBytes(nint pointer)
|
||||
{
|
||||
var mem = GetMemory();
|
||||
var length = (int)BlockLength(pointer);
|
||||
var ptr = (byte*)mem + pointer;
|
||||
|
||||
return new Span<byte>(ptr, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a string into the current plugin memory using UTF-8 encoding and returns the pointer of the block.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public nint WriteString(string value)
|
||||
=> WriteString(value, Encoding.UTF8);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a string into the current plugin memory and returns the pointer of the block.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="encoding"></param>
|
||||
public nint WriteString(string value, Encoding encoding)
|
||||
{
|
||||
var bytes = encoding.GetBytes(value);
|
||||
var pointer = AllocateBlock(bytes.Length);
|
||||
WriteBytes(pointer, bytes);
|
||||
|
||||
return pointer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte array into a block of memory.
|
||||
/// </summary>
|
||||
/// <param name="pointer"></param>
|
||||
/// <param name="bytes"></param>
|
||||
public unsafe void WriteBytes(nint pointer, Span<byte> bytes)
|
||||
{
|
||||
var length = BlockLength(pointer);
|
||||
if (length < bytes.Length)
|
||||
{
|
||||
throw new InvalidOperationException("Destination block length is less than source block length.");
|
||||
}
|
||||
|
||||
var mem = GetMemory();
|
||||
var ptr = (void*)(mem + pointer);
|
||||
var destination = new Span<byte>(ptr, bytes.Length);
|
||||
|
||||
bytes.CopyTo(destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees a block of memory belonging to the current plugin.
|
||||
/// </summary>
|
||||
/// <param name="pointer"></param>
|
||||
public void FreeBlock(nint pointer)
|
||||
{
|
||||
LibExtism.extism_current_plugin_memory_free(NativeHandle, pointer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocate a memory block in the currently running plugin.
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="length"></param>
|
||||
/// <returns></returns>
|
||||
public nint AllocateBlock(long length)
|
||||
{
|
||||
return LibExtism.extism_current_plugin_memory_alloc(NativeHandle, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the length of an allocated block.
|
||||
/// NOTE: this should only be called from host functions.
|
||||
/// </summary>
|
||||
/// <param name="pointer"></param>
|
||||
/// <returns></returns>
|
||||
public long BlockLength(nint pointer)
|
||||
{
|
||||
return LibExtism.extism_current_plugin_memory_length(NativeHandle, pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
dotnet/src/Extism.Sdk/Extism.Sdk.csproj
Normal file
28
dotnet/src/Extism.Sdk/Extism.Sdk.csproj
Normal file
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<LangVersion>10</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>Extism.Sdk</PackageId>
|
||||
<Version>0.7.0</Version>
|
||||
<Authors>Extism Contributors</Authors>
|
||||
<Description>Extism SDK that allows hosting Extism plugins in .NET apps.</Description>
|
||||
<Tags>extism, wasm, plugin</Tags>
|
||||
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
40
dotnet/src/Extism.Sdk/ExtismException.cs
Normal file
40
dotnet/src/Extism.Sdk/ExtismException.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Represents errors that occur during calling Extism functions.
|
||||
/// </summary>
|
||||
public class ExtismException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExtismException"/> class.
|
||||
/// </summary>
|
||||
public ExtismException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExtismException"/> class with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error .</param>
|
||||
public ExtismException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExtismException"/> class
|
||||
/// with a specified error message and a reference to the inner exception
|
||||
/// that is the cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="innerException">
|
||||
/// The exception that is the cause of the current exception, or a null reference
|
||||
/// (Nothing in Visual Basic) if no inner exception is specified.
|
||||
/// </param>
|
||||
public ExtismException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
146
dotnet/src/Extism.Sdk/HostFunction.cs
Normal file
146
dotnet/src/Extism.Sdk/HostFunction.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using Extism.Sdk.Native;
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Extism.Sdk
|
||||
{
|
||||
/// <summary>
|
||||
/// A host function signature.
|
||||
/// </summary>
|
||||
/// <param name="plugin">Plugin Index</param>
|
||||
/// <param name="inputs">Input parameters</param>
|
||||
/// <param name="outputs">Output parameters, the host function can change this.</param>
|
||||
/// <param name="userData">A data passed in during Host Function creation.</param>
|
||||
public delegate void ExtismFunction(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> outputs, IntPtr userData);
|
||||
|
||||
/// <summary>
|
||||
/// A function provided by the host that plugins can call.
|
||||
/// </summary>
|
||||
public class HostFunction : IDisposable
|
||||
{
|
||||
private const int DisposedMarker = 1;
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Registers a Host Function.
|
||||
/// </summary>
|
||||
/// <param name="functionName">The literal name of the function, how it would be called from a <see cref="Plugin"/>.</param>
|
||||
/// <param name="inputTypes">The types of the input arguments/parameters the <see cref="Plugin"/> caller will provide.</param>
|
||||
/// <param name="outputTypes">The types of the output returned from the host function to the <see cref="Plugin"/>.</param>
|
||||
/// <param name="userData">An opaque pointer to an object from the host, accessible to the <see cref="Plugin"/>.
|
||||
/// NOTE: it is the shared responsibility of the host and <see cref="Plugin"/> to cast/dereference this value properly.</param>
|
||||
/// <param name="hostFunction"></param>
|
||||
public HostFunction(
|
||||
string functionName,
|
||||
Span<ExtismValType> inputTypes,
|
||||
Span<ExtismValType> outputTypes,
|
||||
IntPtr userData,
|
||||
ExtismFunction hostFunction) :
|
||||
this(functionName, "", inputTypes, outputTypes, userData, hostFunction)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a Host Function.
|
||||
/// </summary>
|
||||
/// <param name="functionName">The literal name of the function, how it would be called from a <see cref="Plugin"/>.</param>
|
||||
/// <param name="namespace">Function namespace.</param>
|
||||
/// <param name="inputTypes">The types of the input arguments/parameters the <see cref="Plugin"/> caller will provide.</param>
|
||||
/// <param name="outputTypes">The types of the output returned from the host function to the <see cref="Plugin"/>.</param>
|
||||
/// <param name="userData">An opaque pointer to an object from the host, accessible to the <see cref="Plugin"/>.
|
||||
/// NOTE: it is the shared responsibility of the host and <see cref="Plugin"/> to cast/dereference this value properly.</param>
|
||||
/// <param name="hostFunction"></param>
|
||||
unsafe public HostFunction(
|
||||
string functionName,
|
||||
string @namespace,
|
||||
Span<ExtismValType> inputTypes,
|
||||
Span<ExtismValType> outputTypes,
|
||||
IntPtr userData,
|
||||
ExtismFunction hostFunction)
|
||||
{
|
||||
fixed (ExtismValType* inputs = inputTypes)
|
||||
fixed (ExtismValType* outputs = outputTypes)
|
||||
{
|
||||
NativeHandle = LibExtism.extism_function_new(functionName, inputs, inputTypes.Length, outputs, outputTypes.Length, CallbackImpl, userData, IntPtr.Zero);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(@namespace))
|
||||
{
|
||||
LibExtism.extism_function_set_namespace(NativeHandle, @namespace);
|
||||
}
|
||||
|
||||
void CallbackImpl(
|
||||
nint plugin,
|
||||
ExtismVal* inputsPtr,
|
||||
uint n_inputs,
|
||||
ExtismVal* outputsPtr,
|
||||
uint n_outputs,
|
||||
IntPtr data)
|
||||
{
|
||||
var outputs = new Span<ExtismVal>(outputsPtr, (int)n_outputs);
|
||||
var inputs = new Span<ExtismVal>(inputsPtr, (int)n_inputs);
|
||||
|
||||
hostFunction(new CurrentPlugin(plugin), inputs, outputs, data);
|
||||
}
|
||||
}
|
||||
|
||||
internal IntPtr NativeHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Host Function.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _disposed, DisposedMarker) == DisposedMarker)
|
||||
{
|
||||
// Already disposed.
|
||||
return;
|
||||
}
|
||||
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throw an appropriate exception if the Host Function has been disposed.
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
protected void CheckNotDisposed()
|
||||
{
|
||||
Interlocked.MemoryBarrier();
|
||||
if (_disposed == DisposedMarker)
|
||||
{
|
||||
ThrowDisposedException();
|
||||
}
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
private static void ThrowDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(HostFunction));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Host Function.
|
||||
/// </summary>
|
||||
unsafe protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Free up any managed resources here
|
||||
}
|
||||
|
||||
// Free up unmanaged resources
|
||||
LibExtism.extism_function_free(NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructs the current Host Function and frees all resources used by it.
|
||||
/// </summary>
|
||||
~HostFunction()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
347
dotnet/src/Extism.Sdk/LibExtism.cs
Normal file
347
dotnet/src/Extism.Sdk/LibExtism.cs
Normal file
@@ -0,0 +1,347 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
/// <summary>
|
||||
/// A union type for host function argument/return values.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct ExtismValUnion
|
||||
{
|
||||
/// <summary>
|
||||
/// Set this for 32 bit integers
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public int i32;
|
||||
|
||||
/// <summary>
|
||||
/// Set this for 64 bit integers
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public long i64;
|
||||
|
||||
/// <summary>
|
||||
/// Set this for 32 bit floats
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public float f32;
|
||||
|
||||
/// <summary>
|
||||
/// Set this for 64 bit floats
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public double f64;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents Wasm data types that Extism can understand
|
||||
/// </summary>
|
||||
public enum ExtismValType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Signed 32 bit integer. Equivalent of <see cref="int"/> or <see cref="uint"/>
|
||||
/// </summary>
|
||||
I32,
|
||||
|
||||
/// <summary>
|
||||
/// Signed 64 bit integer. Equivalent of <see cref="long"/> or <see cref="ulong"/>
|
||||
/// </summary>
|
||||
I64,
|
||||
|
||||
/// <summary>
|
||||
/// Floating point 32 bit integer. Equivalent of <see cref="float"/>
|
||||
/// </summary>
|
||||
F32,
|
||||
|
||||
/// <summary>
|
||||
/// Floating point 64 bit integer. Equivalent of <see cref="double"/>
|
||||
/// </summary>
|
||||
F64,
|
||||
|
||||
/// <summary>
|
||||
/// A 128 bit number.
|
||||
/// </summary>
|
||||
V128,
|
||||
|
||||
/// <summary>
|
||||
/// A reference to opaque data in the Wasm instance.
|
||||
/// </summary>
|
||||
FuncRef,
|
||||
|
||||
/// <summary>
|
||||
/// A reference to opaque data in the Wasm instance.
|
||||
/// </summary>
|
||||
ExternRef
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `ExtismVal` holds the type and value of a function argument/return
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ExtismVal
|
||||
{
|
||||
/// <summary>
|
||||
/// The type for the argument
|
||||
/// </summary>
|
||||
public ExtismValType t;
|
||||
|
||||
/// <summary>
|
||||
/// The value for the argument
|
||||
/// </summary>
|
||||
public ExtismValUnion v;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Functions exposed by the native Extism library.
|
||||
/// </summary>
|
||||
internal static class LibExtism
|
||||
{
|
||||
/// <summary>
|
||||
/// A `Context` is used to store and manage plugins.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct ExtismContext { }
|
||||
|
||||
/// <summary>
|
||||
/// Host function signature
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
/// <param name="inputs"></param>
|
||||
/// <param name="n_inputs"></param>
|
||||
/// <param name="outputs"></param>
|
||||
/// <param name="n_outputs"></param>
|
||||
/// <param name="data"></param>
|
||||
unsafe internal delegate void InternalExtismFunction(nint plugin, ExtismVal* inputs, uint n_inputs, ExtismVal* outputs, uint n_outputs, IntPtr data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pointer to the memory of the currently running plugin.
|
||||
/// NOTE: this should only be called from host functions.
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism", EntryPoint = "extism_current_plugin_memory")]
|
||||
internal static extern IntPtr extism_current_plugin_memory(nint plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Allocate a memory block in the currently running plugin
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
/// <param name="n"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism", EntryPoint = "extism_current_plugin_memory_alloc")]
|
||||
internal static extern IntPtr extism_current_plugin_memory_alloc(nint plugin, long n);
|
||||
|
||||
/// <summary>
|
||||
/// Get the length of an allocated block.
|
||||
/// NOTE: this should only be called from host functions.
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
/// <param name="n"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism", EntryPoint = "extism_current_plugin_memory_length")]
|
||||
internal static extern long extism_current_plugin_memory_length(nint plugin, long n);
|
||||
|
||||
/// <summary>
|
||||
/// Get the length of an allocated block.
|
||||
/// NOTE: this should only be called from host functions.
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
/// <param name="ptr"></param>
|
||||
[DllImport("extism", EntryPoint = "extism_current_plugin_memory_free")]
|
||||
internal static extern void extism_current_plugin_memory_free(nint plugin, IntPtr ptr);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new host function.
|
||||
/// </summary>
|
||||
/// <param name="name">function name, this should be valid UTF-8</param>
|
||||
/// <param name="inputs">argument types</param>
|
||||
/// <param name="nInputs">number of argument types</param>
|
||||
/// <param name="outputs">return types</param>
|
||||
/// <param name="nOutputs">number of return types</param>
|
||||
/// <param name="func">the function to call</param>
|
||||
/// <param name="userData">a pointer that will be passed to the function when it's called this value should live as long as the function exists</param>
|
||||
/// <param name="freeUserData">a callback to release the `user_data` value when the resulting `ExtismFunction` is freed.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism", EntryPoint = "extism_function_new")]
|
||||
unsafe internal static extern IntPtr extism_function_new(string name, ExtismValType* inputs, long nInputs, ExtismValType* outputs, long nOutputs, InternalExtismFunction func, IntPtr userData, IntPtr freeUserData);
|
||||
|
||||
/// <summary>
|
||||
/// Set the namespace of an <see cref="ExtismFunction"/>
|
||||
/// </summary>
|
||||
/// <param name="ptr"></param>
|
||||
/// <param name="namespace"></param>
|
||||
[DllImport("extism", EntryPoint = "extism_function_set_namespace")]
|
||||
internal static extern void extism_function_set_namespace(IntPtr ptr, string @namespace);
|
||||
|
||||
/// <summary>
|
||||
/// Free an <see cref="ExtismFunction"/>
|
||||
/// </summary>
|
||||
/// <param name="ptr"></param>
|
||||
[DllImport("extism", EntryPoint = "extism_function_free")]
|
||||
internal static extern void extism_function_free(IntPtr ptr);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new context.
|
||||
/// </summary>
|
||||
/// <returns>A pointer to the newly created context.</returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern ExtismContext* extism_context_new();
|
||||
|
||||
/// <summary>
|
||||
/// Remove a context from the registry and free associated memory.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern void extism_context_free(ExtismContext* context);
|
||||
|
||||
/// <summary>
|
||||
/// Load a WASM plugin.
|
||||
/// </summary>
|
||||
/// <param name="context">Pointer to the context the plugin will be associated with.</param>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="wasmSize">The length of the `wasm` parameter.</param>
|
||||
/// <param name="functions">Array of host function pointers.</param>
|
||||
/// <param name="nFunctions">Number of host functions.</param>
|
||||
/// <param name="withWasi">Enables/disables WASI.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern int extism_plugin_new(ExtismContext* context, byte* wasm, int wasmSize, IntPtr* functions, int nFunctions, bool withWasi);
|
||||
|
||||
/// <summary>
|
||||
/// Update a plugin, keeping the existing ID.
|
||||
/// Similar to <see cref="extism_plugin_new"/> but takes an `plugin` argument to specify which plugin to update.
|
||||
/// Memory for this plugin will be reset upon update.
|
||||
/// </summary>
|
||||
/// <param name="context">Pointer to the context the plugin is associated with.</param>
|
||||
/// <param name="plugin">Pointer to the plugin you want to update.</param>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="wasmSize">The length of the `wasm` parameter.</param>
|
||||
/// <param name="functions">Array of host function pointers.</param>
|
||||
/// <param name="nFunctions">Number of host functions.</param>
|
||||
/// <param name="withWasi">Enables/disables WASI.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern bool extism_plugin_update(ExtismContext* context, int plugin, byte* wasm, long wasmSize, Span<IntPtr> functions, long nFunctions, bool withWasi);
|
||||
|
||||
/// <summary>
|
||||
/// Remove a plugin from the registry and free associated memory.
|
||||
/// </summary>
|
||||
/// <param name="context">Pointer to the context the plugin is associated with.</param>
|
||||
/// <param name="plugin">Pointer to the plugin you want to free.</param>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern void extism_plugin_free(ExtismContext* context, int plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Remove all plugins from the registry.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern void extism_context_reset(ExtismContext* context);
|
||||
|
||||
/// <summary>
|
||||
/// Update plugin config values, this will merge with the existing values.
|
||||
/// </summary>
|
||||
/// <param name="context">Pointer to the context the plugin is associated with.</param>
|
||||
/// <param name="plugin">Pointer to the plugin you want to update the configurations for.</param>
|
||||
/// <param name="json">The configuration JSON encoded in UTF8.</param>
|
||||
/// <param name="jsonLength">The length of the `json` parameter.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern bool extism_plugin_config(ExtismContext* context, int plugin, byte* json, int jsonLength);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if funcName exists.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="plugin"></param>
|
||||
/// <param name="funcName"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern bool extism_plugin_function_exists(ExtismContext* context, int plugin, string funcName);
|
||||
|
||||
/// <summary>
|
||||
/// Call a function.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="plugin"></param>
|
||||
/// <param name="funcName">The function to call.</param>
|
||||
/// <param name="data">Input data.</param>
|
||||
/// <param name="dataLen">The length of the `data` parameter.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern int extism_plugin_call(ExtismContext* context, int plugin, string funcName, byte* data, int dataLen);
|
||||
|
||||
/// <summary>
|
||||
/// Get the error associated with a Context or Plugin, if plugin is -1 then the context error will be returned.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="plugin">A plugin pointer, or -1 for the context error.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern IntPtr extism_error(ExtismContext* context, nint plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Get the length of a plugin's output data.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="plugin"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern long extism_plugin_output_length(ExtismContext* context, int plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Get the plugin's output data.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="plugin"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern IntPtr extism_plugin_output_data(ExtismContext* context, int plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Set log file and level.
|
||||
/// </summary>
|
||||
/// <param name="filename"></param>
|
||||
/// <param name="logLevel"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
internal static extern bool extism_log_file(string filename, string logLevel);
|
||||
|
||||
/// <summary>
|
||||
/// Get the Extism version string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism", EntryPoint = "extism_version")]
|
||||
internal static extern IntPtr extism_version();
|
||||
|
||||
/// <summary>
|
||||
/// Extism Log Levels
|
||||
/// </summary>
|
||||
internal static class LogLevels
|
||||
{
|
||||
/// <summary>
|
||||
/// Designates very serious errors.
|
||||
/// </summary>
|
||||
internal const string Error = "Error";
|
||||
|
||||
/// <summary>
|
||||
/// Designates hazardous situations.
|
||||
/// </summary>
|
||||
internal const string Warn = "Warn";
|
||||
|
||||
/// <summary>
|
||||
/// Designates useful information.
|
||||
/// </summary>
|
||||
internal const string Info = "Info";
|
||||
|
||||
/// <summary>
|
||||
/// Designates lower priority information.
|
||||
/// </summary>
|
||||
internal const string Debug = "Debug";
|
||||
|
||||
/// <summary>
|
||||
/// Designates very low priority, often extremely verbose, information.
|
||||
/// </summary>
|
||||
internal const string Trace = "Trace";
|
||||
}
|
||||
}
|
||||
32
dotnet/src/Extism.Sdk/LogLevel.cs
Normal file
32
dotnet/src/Extism.Sdk/LogLevel.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
/// <summary>
|
||||
/// Extism Log Levels
|
||||
/// </summary>
|
||||
public enum LogLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Designates very serious errors.
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// Designates hazardous situations.
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// Designates useful information.
|
||||
/// </summary>
|
||||
Info,
|
||||
|
||||
/// <summary>
|
||||
/// Designates lower priority information.
|
||||
/// </summary>
|
||||
Debug,
|
||||
|
||||
/// <summary>
|
||||
/// Designates very low priority, often extremely verbose, information.
|
||||
/// </summary>
|
||||
Trace
|
||||
}
|
||||
210
dotnet/src/Extism.Sdk/Plugin.cs
Normal file
210
dotnet/src/Extism.Sdk/Plugin.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a WASM Extism plugin.
|
||||
/// </summary>
|
||||
public class Plugin : IDisposable
|
||||
{
|
||||
private const int DisposedMarker = 1;
|
||||
|
||||
private readonly Context _context;
|
||||
private readonly HostFunction[] _functions;
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Create a and load a plug-in
|
||||
/// Using this constructor will give the plug-in it's own internal Context
|
||||
/// </summary>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="functions">List of host functions expected by the plugin.</param>
|
||||
/// <param name="withWasi">Enable/Disable WASI.</param>
|
||||
public static Plugin Create(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi) {
|
||||
var context = new Context();
|
||||
return context.CreatePlugin(wasm, functions, withWasi);
|
||||
}
|
||||
|
||||
internal Plugin(Context context, HostFunction[] functions, int index)
|
||||
{
|
||||
_context = context;
|
||||
_functions = functions;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the native Plugin struct.
|
||||
/// </summary>
|
||||
internal int Index { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Update a plugin, keeping the existing ID.
|
||||
/// </summary>
|
||||
/// <param name="wasm">The plugin WASM bytes.</param>
|
||||
/// <param name="withWasi">Enable/Disable WASI.</param>
|
||||
unsafe public bool Update(ReadOnlySpan<byte> wasm, bool withWasi)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
var functions = _functions.Select(f => f.NativeHandle).ToArray();
|
||||
fixed (byte* wasmPtr = wasm)
|
||||
{
|
||||
return LibExtism.extism_plugin_update(_context.NativeHandle, Index, wasmPtr, wasm.Length, functions, 0, withWasi);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update plugin config values, this will merge with the existing values.
|
||||
/// </summary>
|
||||
/// <param name="json">The configuration JSON encoded in UTF8.</param>
|
||||
unsafe public bool SetConfig(ReadOnlySpan<byte> json)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
fixed (byte* jsonPtr = json)
|
||||
{
|
||||
return LibExtism.extism_plugin_config(_context.NativeHandle, Index, jsonPtr, json.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a specific function exists in the current plugin.
|
||||
/// </summary>
|
||||
unsafe public bool FunctionExists(string name)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
return LibExtism.extism_plugin_function_exists(_context.NativeHandle, Index, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls a function in the current plugin and returns a status.
|
||||
/// If the status represents an error, call <see cref="GetError"/> to get the error.
|
||||
/// Othewise, call <see cref="OutputData"/> to get the function's output data.
|
||||
/// </summary>
|
||||
/// <param name="functionName">Name of the function in the plugin to invoke.</param>
|
||||
/// <param name="data">A buffer to provide as input to the function.</param>
|
||||
/// <returns>The exit code of the function.</returns>
|
||||
/// <exception cref="ExtismException"></exception>
|
||||
unsafe public ReadOnlySpan<byte> CallFunction(string functionName, ReadOnlySpan<byte> data)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
fixed (byte* dataPtr = data)
|
||||
{
|
||||
int response = LibExtism.extism_plugin_call(_context.NativeHandle, Index, functionName, dataPtr, data.Length);
|
||||
if (response == 0)
|
||||
{
|
||||
return OutputData();
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMsg = GetError();
|
||||
if (errorMsg != null)
|
||||
{
|
||||
throw new ExtismException(errorMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExtismException("Call to Extism failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the length of a plugin's output data.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
unsafe internal int OutputLength()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
return (int)LibExtism.extism_plugin_output_length(_context.NativeHandle, Index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the plugin's output data.
|
||||
/// </summary>
|
||||
internal ReadOnlySpan<byte> OutputData()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
var length = OutputLength();
|
||||
|
||||
unsafe
|
||||
{
|
||||
var ptr = LibExtism.extism_plugin_output_data(_context.NativeHandle, Index).ToPointer();
|
||||
return new Span<byte>(ptr, length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the error associated with the current plugin.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
unsafe internal string? GetError()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
var result = LibExtism.extism_error(_context.NativeHandle, Index);
|
||||
return Marshal.PtrToStringUTF8(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Plugin.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _disposed, DisposedMarker) == DisposedMarker)
|
||||
{
|
||||
// Already disposed.
|
||||
return;
|
||||
}
|
||||
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throw an appropriate exception if the plugin has been disposed.
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
protected void CheckNotDisposed()
|
||||
{
|
||||
Interlocked.MemoryBarrier();
|
||||
if (_disposed == DisposedMarker)
|
||||
{
|
||||
ThrowDisposedException();
|
||||
}
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
private static void ThrowDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Plugin));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Plugin.
|
||||
/// </summary>
|
||||
unsafe protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Free up any managed resources here
|
||||
}
|
||||
|
||||
// Free up unmanaged resources
|
||||
LibExtism.extism_plugin_free(_context.NativeHandle, Index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructs the current Plugin and frees all resources used by it.
|
||||
/// </summary>
|
||||
~Plugin()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
2
dotnet/src/Extism.Sdk/README.md
Normal file
2
dotnet/src/Extism.Sdk/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
## Extism.Sdk
|
||||
Extism SDK that allows hosting Extism plugins in .NET apps.
|
||||
73
dotnet/test/Extism.Sdk/BasicTests.cs
Normal file
73
dotnet/test/Extism.Sdk/BasicTests.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Extism.Sdk.Native;
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Extism.Sdk.Tests;
|
||||
|
||||
public class BasicTests
|
||||
{
|
||||
[Fact]
|
||||
public void CountHelloWorldVowelsWithoutContext()
|
||||
{
|
||||
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
|
||||
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code.wasm"));
|
||||
using var plugin = Plugin.Create(wasm, Array.Empty<HostFunction>(), withWasi: true);
|
||||
|
||||
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
|
||||
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CountHelloWorldVowels()
|
||||
{
|
||||
using var context = new Context();
|
||||
|
||||
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
|
||||
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code.wasm"));
|
||||
using var plugin = context.CreatePlugin(wasm, Array.Empty<HostFunction>(), withWasi: true);
|
||||
|
||||
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
|
||||
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CountVowelsHostFunctions()
|
||||
{
|
||||
using var context = new Context();
|
||||
|
||||
var userData = Marshal.StringToHGlobalAnsi("Hello again!");
|
||||
|
||||
using var helloWorld = new HostFunction(
|
||||
"hello_world",
|
||||
"env",
|
||||
new[] { ExtismValType.I64 },
|
||||
new[] { ExtismValType.I64 },
|
||||
userData,
|
||||
HelloWorld);
|
||||
|
||||
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
|
||||
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code-functions.wasm"));
|
||||
using var plugin = context.CreatePlugin(wasm, new[] { helloWorld }, withWasi: true);
|
||||
|
||||
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
|
||||
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
|
||||
|
||||
void HelloWorld(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> outputs, nint data)
|
||||
{
|
||||
Console.WriteLine("Hello from .NET!");
|
||||
|
||||
var text = Marshal.PtrToStringAnsi(data);
|
||||
Console.WriteLine(text);
|
||||
|
||||
var input = plugin.ReadString(new nint(inputs[0].v.i64));
|
||||
Console.WriteLine($"Input: {input}");
|
||||
|
||||
var output = new string(input); // clone the string
|
||||
outputs[0].v.i64 = plugin.WriteString(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
dotnet/test/Extism.Sdk/Extism.Sdk.Tests.csproj
Normal file
42
dotnet/test/Extism.Sdk/Extism.Sdk.Tests.csproj
Normal file
@@ -0,0 +1,42 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\..\wasm\code.wasm" Link="code.wasm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\..\..\wasm\code-functions.wasm" Link="code-functions.wasm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Extism.runtime.win-x64" Version="0.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
28
dune-project
28
dune-project
@@ -11,7 +11,7 @@
|
||||
|
||||
(maintainers "Extism Authors <oss@extism.org>")
|
||||
|
||||
(license BSD-3)
|
||||
(license BSD-3-Clause)
|
||||
|
||||
(documentation https://github.com/extism/extism)
|
||||
|
||||
@@ -19,6 +19,30 @@
|
||||
(name extism)
|
||||
(synopsis "Extism bindings")
|
||||
(description "Bindings to Extism, the universal plugin system")
|
||||
(depends ocaml dune ctypes-foreign bigstringaf ppx_yojson_conv base64)
|
||||
(depends
|
||||
(ocaml (>= 4.14.1))
|
||||
dune
|
||||
(ctypes (>= 0.18.0))
|
||||
(ctypes-foreign (>= 0.18.0))
|
||||
(bigstringaf (>= 0.9.0))
|
||||
(ppx_yojson_conv (>= v0.15.0))
|
||||
(extism-manifest (= :version))
|
||||
(ppx_inline_test (>= v0.15.0))
|
||||
(cmdliner (>= 1.1.1))
|
||||
)
|
||||
(tags
|
||||
(topics wasm plugin)))
|
||||
|
||||
(package
|
||||
(name extism-manifest)
|
||||
(synopsis "Extism manifest bindings")
|
||||
(description "Bindings to the Extism manifest format")
|
||||
(depends
|
||||
(ocaml (>= 4.14.1))
|
||||
dune
|
||||
(ppx_yojson_conv (>= v0.15.0))
|
||||
(ppx_inline_test (>= v0.15.0))
|
||||
(base64 (>= 3.5.0))
|
||||
)
|
||||
(tags
|
||||
(topics wasm plugin)))
|
||||
|
||||
4
elixir/.formatter.exs
Normal file
4
elixir/.formatter.exs
Normal file
@@ -0,0 +1,4 @@
|
||||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
28
elixir/.gitignore
vendored
Normal file
28
elixir/.gitignore
vendored
Normal 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/
|
||||
27
elixir/Makefile
Normal file
27
elixir/Makefile
Normal file
@@ -0,0 +1,27 @@
|
||||
.PHONY: test
|
||||
|
||||
prepare:
|
||||
mix deps.get
|
||||
mix compile
|
||||
|
||||
test: prepare
|
||||
mix test
|
||||
|
||||
clean:
|
||||
mix clean
|
||||
|
||||
publish: clean prepare
|
||||
mix hex.build
|
||||
mix hex.publish --yes
|
||||
|
||||
format:
|
||||
mix format
|
||||
|
||||
lint:
|
||||
mix format --check-formatted
|
||||
|
||||
docs:
|
||||
mix docs
|
||||
|
||||
show-docs: docs
|
||||
open doc/index.html
|
||||
73
elixir/README.md
Normal file
73
elixir/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Extism
|
||||
|
||||
Extism Host SDK for Elixir and Erlang
|
||||
|
||||
## Docs
|
||||
|
||||
Read the [docs on hexdocs.pm](https://hexdocs.pm/extism/).
|
||||
|
||||
## Installation
|
||||
|
||||
You can find this package on [hex.pm](https://hex.pm/packages/extism).
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:extism, "~> 0.1.0"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Example
|
||||
|
||||
```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)
|
||||
```
|
||||
|
||||
### Modules
|
||||
|
||||
The two primary modules you should learn are:
|
||||
|
||||
* [Extism.Context](Extism.Context.html)
|
||||
* [Extism.Plugin](Extism.Plugin.html)
|
||||
|
||||
#### Context
|
||||
|
||||
The [Context](Extism.Context.html) can be thought of as a session. You need a context to interact with the Extism runtime. The context holds your plugins and when you free the context, it frees your plugins. It's important to free up your context and plugins when you are done with them.
|
||||
|
||||
```elixir
|
||||
ctx = Extism.Context.new()
|
||||
# frees all the plugins
|
||||
Extism.Context.reset(ctx)
|
||||
# frees the context and all its plugins
|
||||
Extism.Context.free(ctx)
|
||||
```
|
||||
|
||||
#### Plugin
|
||||
|
||||
The [Plugin](Extism.Plugin.html) represents an instance of your WASM program from the given manifest.
|
||||
The key method to know here is [Extism.Plugin#call](Extism.Plugin.html#call/3) which takes a function name to invoke and some input data, and returns the results from the plugin.
|
||||
|
||||
```elixir
|
||||
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
|
||||
```
|
||||
5
elixir/lib/extism.ex
Normal file
5
elixir/lib/extism.ex
Normal file
@@ -0,0 +1,5 @@
|
||||
defmodule Extism do
|
||||
def set_log_file(filepath, level) do
|
||||
Extism.Native.set_log_file(filepath, level)
|
||||
end
|
||||
end
|
||||
31
elixir/lib/extism/cancel_handle.ex
Normal file
31
elixir/lib/extism/cancel_handle.ex
Normal file
@@ -0,0 +1,31 @@
|
||||
defmodule Extism.CancelHandle do
|
||||
@moduledoc """
|
||||
A CancelHandle is a handle generated by a plugin that allows it to be cancelled from another
|
||||
thread while running.
|
||||
"""
|
||||
defstruct [
|
||||
# The actual NIF Resource. PluginIndex and the context
|
||||
handle: nil
|
||||
]
|
||||
|
||||
def wrap_resource(handle) do
|
||||
%__MODULE__{
|
||||
handle: handle
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Cancel plugin execution
|
||||
"""
|
||||
def cancel(handle) do
|
||||
Extism.Native.plugin_cancel(handle.handle)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Extim.CancelHandle do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
concat(["#Extism.CancelHandle<", to_doc(dict.handle, opts), ">"])
|
||||
end
|
||||
end
|
||||
64
elixir/lib/extism/context.ex
Normal file
64
elixir/lib/extism/context.ex
Normal file
@@ -0,0 +1,64 @@
|
||||
defmodule Extism.Context do
|
||||
@moduledoc """
|
||||
A Context is needed to create plugins. The Context is where your plugins
|
||||
live. Freeing the context frees all of the plugins in its scope.
|
||||
"""
|
||||
|
||||
defstruct [
|
||||
# The actual NIF Resource. A pointer in this case
|
||||
ptr: nil
|
||||
]
|
||||
|
||||
def wrap_resource(ptr) do
|
||||
%__MODULE__{
|
||||
ptr: ptr
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new context.
|
||||
"""
|
||||
def new() do
|
||||
ptr = Extism.Native.context_new()
|
||||
Extism.Context.wrap_resource(ptr)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Resets the context. This has the effect of freeing all the plugins created so far.
|
||||
"""
|
||||
def reset(ctx) do
|
||||
Extism.Native.context_reset(ctx.ptr)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Frees the context from memory and all of its plugins.
|
||||
"""
|
||||
def free(ctx) do
|
||||
Extism.Native.context_free(ctx.ptr)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create a new plugin from a WASM module or manifest
|
||||
|
||||
## Examples:
|
||||
|
||||
iex> ctx = Extism.Context.new()
|
||||
iex> manifest = %{ wasm: [ %{ path: "/Users/ben/code/extism/wasm/code.wasm" } ]}
|
||||
iex> {:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
|
||||
|
||||
## Parameters
|
||||
|
||||
- ctx: The Context to manage this plugin
|
||||
- manifest: The String or Map of the WASM module or [manifest](https://extism.org/docs/concepts/manifest)
|
||||
- wasi: A bool you set to true if you want WASI support
|
||||
|
||||
"""
|
||||
def new_plugin(ctx, manifest, wasi \\ false) do
|
||||
{: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
|
||||
23
elixir/lib/extism/native.ex
Normal file
23
elixir/lib/extism/native.ex
Normal file
@@ -0,0 +1,23 @@
|
||||
defmodule Extism.Native do
|
||||
@moduledoc """
|
||||
This module represents the Native Extism runtime API and is for internal use.
|
||||
Do not use or rely on this this module.
|
||||
"""
|
||||
use Rustler,
|
||||
otp_app: :extism,
|
||||
crate: :extism_nif
|
||||
|
||||
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()
|
||||
def plugin_cancel_handle(_ctx, _plugin_id), do: error()
|
||||
def plugin_cancel(_handle), do: error()
|
||||
|
||||
defp error, do: :erlang.nif_error(:nif_not_loaded)
|
||||
end
|
||||
104
elixir/lib/extism/plugin.ex
Normal file
104
elixir/lib/extism/plugin.ex
Normal file
@@ -0,0 +1,104 @@
|
||||
defmodule Extism.Plugin do
|
||||
@moduledoc """
|
||||
A Plugin represents an instance of your WASM program from the given manifest.
|
||||
"""
|
||||
defstruct [
|
||||
# The actual NIF Resource. PluginIndex and the context
|
||||
plugin_id: nil,
|
||||
ctx: nil
|
||||
]
|
||||
|
||||
def wrap_resource(ctx, plugin_id) do
|
||||
%__MODULE__{
|
||||
ctx: ctx,
|
||||
plugin_id: plugin_id
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new plugin
|
||||
"""
|
||||
def new(manifest, wasi \\ false, context \\ nil) do
|
||||
ctx = context || Extism.Context.new()
|
||||
{:ok, manifest_payload} = JSON.encode(manifest)
|
||||
|
||||
case Extism.Native.plugin_new_with_manifest(ctx.ptr, manifest_payload, wasi) do
|
||||
{:error, err} -> {:error, err}
|
||||
res -> {:ok, Extism.Plugin.wrap_resource(ctx, res)}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Call a plugin's function by name
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:ok, plugin} = Extism.Plugin.new(manifest, false)
|
||||
iex> {:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
|
||||
# {:ok, "{\"count\": 4}"}
|
||||
|
||||
## Parameters
|
||||
|
||||
- plugin: The plugin
|
||||
- name: The name of the function as a string
|
||||
- input: The input data as a string
|
||||
|
||||
## Returns
|
||||
|
||||
A string representation of the functions output
|
||||
|
||||
"""
|
||||
def call(plugin, name, input) do
|
||||
case Extism.Native.plugin_call(plugin.ctx.ptr, plugin.plugin_id, name, input) do
|
||||
{:error, err} -> {:error, err}
|
||||
res -> {:ok, res}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the manifest of the given plugin
|
||||
|
||||
## Parameters
|
||||
|
||||
- ctx: The Context to manage this plugin
|
||||
- manifest: The String or Map of the WASM module or [manifest](https://extism.org/docs/concepts/manifest)
|
||||
- wasi: A bool you set to true if you want WASI support
|
||||
|
||||
|
||||
"""
|
||||
def update(plugin, manifest, wasi) when is_map(manifest) do
|
||||
{:ok, manifest_payload} = JSON.encode(manifest)
|
||||
|
||||
case Extism.Native.plugin_update_manifest(
|
||||
plugin.ctx.ptr,
|
||||
plugin.plugin_id,
|
||||
manifest_payload,
|
||||
wasi
|
||||
) do
|
||||
{:error, err} -> {:error, err}
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Frees the plugin
|
||||
"""
|
||||
def free(plugin) do
|
||||
Extism.Native.plugin_free(plugin.ctx.ptr, plugin.plugin_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given plugin responds to the given function name
|
||||
"""
|
||||
def has_function(plugin, function_name) do
|
||||
Extism.Native.plugin_has_function(plugin.ctx.ptr, plugin.plugin_id, function_name)
|
||||
end
|
||||
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
|
||||
BIN
elixir/logo.png
Normal file
BIN
elixir/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
59
elixir/mix.exs
Normal file
59
elixir/mix.exs
Normal file
@@ -0,0 +1,59 @@
|
||||
defmodule Extism.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :extism,
|
||||
version: "0.5.1",
|
||||
elixir: "~> 1.12",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps(),
|
||||
package: package(),
|
||||
aliases: aliases(),
|
||||
docs: docs()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger]
|
||||
]
|
||||
end
|
||||
|
||||
defp deps do
|
||||
[
|
||||
{:rustler, "~> 0.29.1"},
|
||||
{:json, "~> 1.4"},
|
||||
{:ex_doc, "~> 0.21", 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 .formatter.exs mix.exs README.md LICENSE),
|
||||
links: %{"GitHub" => "https://github.com/extism/extism"}
|
||||
]
|
||||
end
|
||||
|
||||
defp docs do
|
||||
[
|
||||
main: "Extism",
|
||||
logo: "./logo.png",
|
||||
main: "readme",
|
||||
extras: ["README.md"]
|
||||
]
|
||||
end
|
||||
end
|
||||
12
elixir/mix.lock
Normal file
12
elixir/mix.lock
Normal file
@@ -0,0 +1,12 @@
|
||||
%{
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.30.5", "aa6da96a5c23389d7dc7c381eba862710e108cee9cfdc629b7ec021313900e9e", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "88a1e115dcb91cefeef7e22df4a6ebbe4634fbf98b38adcbc25c9607d6d9d8e6"},
|
||||
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
|
||||
"json": {:hex, :json, "1.4.1", "8648f04a9439765ad449bc56a3ff7d8b11dd44ff08ffcdefc4329f7c93843dfa", [:mix], [], "hexpm", "9abf218dbe4ea4fcb875e087d5f904ef263d012ee5ed21d46e9dbca63f053d16"},
|
||||
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
|
||||
"rustler": {:hex, :rustler, "0.29.1", "880f20ae3027bd7945def6cea767f5257bc926f33ff50c0d5d5a5315883c084d", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "109497d701861bfcd26eb8f5801fe327a8eef304f56a5b63ef61151ff44ac9b6"},
|
||||
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
|
||||
}
|
||||
15
elixir/native/extism_nif/.cargo/config
Normal file
15
elixir/native/extism_nif/.cargo/config
Normal 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
|
||||
18
elixir/native/extism_nif/Cargo.toml
Normal file
18
elixir/native/extism_nif/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "extism_nif"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
authors = ["Benjamin Eckel <bhelx@simst.im>"]
|
||||
|
||||
[lib]
|
||||
name = "extism_nif"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
# need this to be here and be empty
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
rustler = "0.29.1"
|
||||
extism = "0.5.2"
|
||||
log = "0.4"
|
||||
199
elixir/native/extism_nif/src/lib.rs
Normal file
199
elixir/native/extism_nif/src/lib.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
use extism::{Context, Plugin};
|
||||
use rustler::{Atom, Env, ResourceArc, Term};
|
||||
use std::mem;
|
||||
use std::path::Path;
|
||||
use std::str;
|
||||
use std::str::FromStr;
|
||||
use std::sync::RwLock;
|
||||
|
||||
mod atoms {
|
||||
rustler::atoms! {
|
||||
ok,
|
||||
error,
|
||||
unknown // Other error
|
||||
}
|
||||
}
|
||||
|
||||
struct ExtismContext {
|
||||
ctx: RwLock<Context>,
|
||||
}
|
||||
unsafe impl Sync for ExtismContext {}
|
||||
unsafe impl Send for ExtismContext {}
|
||||
|
||||
struct ExtismCancelHandle {
|
||||
handle: RwLock<extism::CancelHandle>,
|
||||
}
|
||||
|
||||
unsafe impl Sync for ExtismCancelHandle {}
|
||||
unsafe impl Send for ExtismCancelHandle {}
|
||||
|
||||
fn load(env: Env, _: Term) -> bool {
|
||||
rustler::resource!(ExtismContext, env);
|
||||
rustler::resource!(ExtismCancelHandle, env);
|
||||
true
|
||||
}
|
||||
|
||||
fn to_rustler_error(extism_error: extism::Error) -> rustler::Error {
|
||||
rustler::Error::Term(Box::new(extism_error.to_string()))
|
||||
}
|
||||
|
||||
#[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 mut plugin = unsafe { Plugin::from_id(plugin_id, context) };
|
||||
let result = match plugin.call(name, input) {
|
||||
Err(e) => Err(to_rustler_error(e)),
|
||||
Ok(result) => match str::from_utf8(result) {
|
||||
Ok(output) => Ok(output.to_string()),
|
||||
Err(_e) => Err(rustler::Error::Term(Box::new(
|
||||
"Could not read output from plugin",
|
||||
))),
|
||||
},
|
||||
};
|
||||
// this forget should be safe because the context will clean up
|
||||
// all it's plugins when it is dropped
|
||||
mem::forget(plugin);
|
||||
result
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
fn plugin_update_manifest(
|
||||
ctx: ResourceArc<ExtismContext>,
|
||||
plugin_id: i32,
|
||||
manifest_payload: String,
|
||||
wasi: bool,
|
||||
) -> Result<(), rustler::Error> {
|
||||
let context = &ctx.ctx.read().unwrap();
|
||||
let mut plugin = unsafe { Plugin::from_id(plugin_id, context) };
|
||||
let result = match plugin.update(manifest_payload, [], wasi) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => Err(to_rustler_error(e)),
|
||||
};
|
||||
// this forget should be safe because the context will clean up
|
||||
// all it's plugins when it is dropped
|
||||
mem::forget(plugin);
|
||||
result
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
fn plugin_cancel_handle(
|
||||
ctx: ResourceArc<ExtismContext>,
|
||||
plugin_id: i32,
|
||||
) -> Result<ResourceArc<ExtismCancelHandle>, rustler::Error> {
|
||||
let context = &ctx.ctx.read().unwrap();
|
||||
let plugin = unsafe { Plugin::from_id(plugin_id, context) };
|
||||
let handle = plugin.cancel_handle();
|
||||
Ok(ResourceArc::new(ExtismCancelHandle {
|
||||
handle: RwLock::new(handle),
|
||||
}))
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
fn plugin_cancel(handle: ResourceArc<ExtismCancelHandle>) -> bool {
|
||||
handle.handle.read().unwrap().cancel()
|
||||
}
|
||||
|
||||
#[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) => {
|
||||
if extism::set_log_file(path, Some(level)) {
|
||||
Ok(atoms::ok())
|
||||
} else {
|
||||
Err(rustler::Error::Term(Box::new(
|
||||
"Did not set log file, received false from the API.",
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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_cancel_handle,
|
||||
plugin_cancel,
|
||||
plugin_free,
|
||||
set_log_file,
|
||||
],
|
||||
load = load
|
||||
);
|
||||
83
elixir/test/extism_test.exs
Normal file
83
elixir/test/extism_test.exs
Normal file
@@ -0,0 +1,83 @@
|
||||
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}}
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "🌎hello🌎world🌎")
|
||||
assert JSON.decode(output) == {:ok, %{"count" => 3}}
|
||||
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
|
||||
1
elixir/test/test_helper.exs
Normal file
1
elixir/test/test_helper.exs
Normal file
@@ -0,0 +1 @@
|
||||
ExUnit.start()
|
||||
34
extism-manifest.opam
Normal file
34
extism-manifest.opam
Normal file
@@ -0,0 +1,34 @@
|
||||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
synopsis: "Extism manifest bindings"
|
||||
description: "Bindings to the Extism manifest format"
|
||||
maintainer: ["Extism Authors <oss@extism.org>"]
|
||||
authors: ["Extism Authors <oss@extism.org>"]
|
||||
license: "BSD-3-Clause"
|
||||
tags: ["topics" "wasm" "plugin"]
|
||||
homepage: "https://github.com/extism/extism"
|
||||
doc: "https://github.com/extism/extism"
|
||||
bug-reports: "https://github.com/extism/extism/issues"
|
||||
depends: [
|
||||
"ocaml" {>= "4.14.1"}
|
||||
"dune" {>= "3.2"}
|
||||
"ppx_yojson_conv" {>= "v0.15.0"}
|
||||
"ppx_inline_test" {>= "v0.15.0"}
|
||||
"base64" {>= "3.5.0"}
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
["dune" "subst"] {dev}
|
||||
[
|
||||
"dune"
|
||||
"build"
|
||||
"-p"
|
||||
name
|
||||
"-j"
|
||||
jobs
|
||||
"@install"
|
||||
"@runtest" {with-test}
|
||||
"@doc" {with-doc}
|
||||
]
|
||||
]
|
||||
dev-repo: "git+https://github.com/extism/extism.git"
|
||||
474
extism.go
474
extism.go
@@ -5,17 +5,177 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime/cgo"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo pkg-config: libextism.pc
|
||||
#cgo CFLAGS: -I/usr/local/include
|
||||
#cgo LDFLAGS: -L/usr/local/lib -lextism
|
||||
#include <extism.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int64_t extism_val_i64(ExtismValUnion* x){
|
||||
return x->i64;
|
||||
}
|
||||
|
||||
int32_t extism_val_i32(ExtismValUnion* x){
|
||||
return x->i32;
|
||||
}
|
||||
|
||||
float extism_val_f32(ExtismValUnion* x){
|
||||
return x->f32;
|
||||
}
|
||||
|
||||
double extism_val_f64(ExtismValUnion* x){
|
||||
return x->f64;
|
||||
}
|
||||
|
||||
|
||||
void extism_val_set_i64(ExtismValUnion* x, int64_t i){
|
||||
x->i64 = i;
|
||||
}
|
||||
|
||||
|
||||
void extism_val_set_i32(ExtismValUnion* x, int32_t i){
|
||||
x->i32 = i;
|
||||
}
|
||||
|
||||
void extism_val_set_f32(ExtismValUnion* x, float f){
|
||||
x->f32 = f;
|
||||
}
|
||||
|
||||
void extism_val_set_f64(ExtismValUnion* x, double f){
|
||||
x->f64 = f;
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Context is used to manage Plugins
|
||||
type Context struct {
|
||||
pointer *C.ExtismContext
|
||||
}
|
||||
|
||||
type ValType = C.ExtismValType
|
||||
|
||||
type Val = C.ExtismVal
|
||||
|
||||
type Size = C.ExtismSize
|
||||
|
||||
var (
|
||||
I32 ValType = C.I32
|
||||
I64 ValType = C.I64
|
||||
F32 ValType = C.F32
|
||||
F64 ValType = C.F64
|
||||
V128 ValType = C.V128
|
||||
FuncRef ValType = C.FuncRef
|
||||
ExternRef ValType = C.ExternRef
|
||||
)
|
||||
|
||||
// Function is used to define host functions
|
||||
type Function struct {
|
||||
pointer *C.ExtismFunction
|
||||
userData cgo.Handle
|
||||
}
|
||||
|
||||
// Free a function
|
||||
func (f *Function) Free() {
|
||||
C.extism_function_free(f.pointer)
|
||||
f.pointer = nil
|
||||
f.userData.Delete()
|
||||
}
|
||||
|
||||
// NewFunction creates a new host function with the given name, input/outputs and optional user data, which can be an
|
||||
// arbitrary `interface{}`
|
||||
func NewFunction(name string, inputs []ValType, outputs []ValType, f unsafe.Pointer, userData interface{}) Function {
|
||||
var function Function
|
||||
function.userData = cgo.NewHandle(userData)
|
||||
cname := C.CString(name)
|
||||
ptr := unsafe.Pointer(function.userData)
|
||||
var inputsPtr *C.ExtismValType = nil
|
||||
if len(inputs) > 0 {
|
||||
inputsPtr = (*C.ExtismValType)(&inputs[0])
|
||||
}
|
||||
var outputsPtr *C.ExtismValType = nil
|
||||
if len(outputs) > 0 {
|
||||
outputsPtr = (*C.ExtismValType)(&outputs[0])
|
||||
}
|
||||
function.pointer = C.extism_function_new(
|
||||
cname,
|
||||
inputsPtr,
|
||||
C.uint64_t(len(inputs)),
|
||||
outputsPtr,
|
||||
C.uint64_t(len(outputs)),
|
||||
(*[0]byte)(f),
|
||||
ptr,
|
||||
nil,
|
||||
)
|
||||
C.free(unsafe.Pointer(cname))
|
||||
return function
|
||||
}
|
||||
|
||||
func (f *Function) SetNamespace(s string) {
|
||||
cstr := C.CString(s)
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
C.extism_function_set_namespace(f.pointer, cstr)
|
||||
}
|
||||
|
||||
func (f Function) WithNamespace(s string) Function {
|
||||
f.SetNamespace(s)
|
||||
return f
|
||||
}
|
||||
|
||||
type CurrentPlugin struct {
|
||||
pointer *C.ExtismCurrentPlugin
|
||||
}
|
||||
|
||||
func GetCurrentPlugin(ptr unsafe.Pointer) CurrentPlugin {
|
||||
return CurrentPlugin{
|
||||
pointer: (*C.ExtismCurrentPlugin)(ptr),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) Memory(offs uint) []byte {
|
||||
length := C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs))
|
||||
data := unsafe.Pointer(C.extism_current_plugin_memory(p.pointer))
|
||||
return unsafe.Slice((*byte)(unsafe.Add(data, offs)), C.int(length))
|
||||
}
|
||||
|
||||
// Alloc a new memory block of the given length, returning its offset
|
||||
func (p *CurrentPlugin) Alloc(n uint) uint {
|
||||
return uint(C.extism_current_plugin_memory_alloc(p.pointer, C.uint64_t(n)))
|
||||
}
|
||||
|
||||
// Free the memory block specified by the given offset
|
||||
func (p *CurrentPlugin) Free(offs uint) {
|
||||
C.extism_current_plugin_memory_free(p.pointer, C.uint64_t(offs))
|
||||
}
|
||||
|
||||
// Length returns the number of bytes allocated at the specified offset
|
||||
func (p *CurrentPlugin) Length(offs uint) uint {
|
||||
return uint(C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs)))
|
||||
}
|
||||
|
||||
// NewContext creates a new context, it should be freed using the `Free` method
|
||||
func NewContext() Context {
|
||||
p := C.extism_context_new()
|
||||
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 {
|
||||
id int32
|
||||
ctx *Context
|
||||
id int32
|
||||
functions []Function
|
||||
}
|
||||
|
||||
type WasmData struct {
|
||||
@@ -31,11 +191,11 @@ type WasmFile struct {
|
||||
}
|
||||
|
||||
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"`
|
||||
Url string `json:"url"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
}
|
||||
|
||||
type Wasm interface{}
|
||||
@@ -43,11 +203,23 @@ type Wasm interface{}
|
||||
type Manifest struct {
|
||||
Wasm []Wasm `json:"wasm"`
|
||||
Memory struct {
|
||||
Max uint32 `json:"max,omitempty"`
|
||||
MaxPages uint32 `json:"max_pages,omitempty"`
|
||||
} `json:"memory,omitempty"`
|
||||
Config map[string]string `json:"config,omitempty"`
|
||||
Config map[string]string `json:"config,omitempty"`
|
||||
AllowedHosts []string `json:"allowed_hosts,omitempty"`
|
||||
AllowedPaths map[string]string `json:"allowed_paths,omitempty"`
|
||||
Timeout uint `json:"timeout_ms,omitempty"`
|
||||
}
|
||||
|
||||
func makePointer(data []byte) unsafe.Pointer {
|
||||
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)
|
||||
@@ -57,69 +229,295 @@ func SetLogFile(filename string, level string) bool {
|
||||
return bool(r)
|
||||
}
|
||||
|
||||
func register(data []byte, wasi bool) (Plugin, error) {
|
||||
plugin := C.extism_plugin_register(
|
||||
(*C.uchar)(unsafe.Pointer(&data[0])),
|
||||
C.uint64_t(len(data)),
|
||||
C._Bool(wasi),
|
||||
)
|
||||
|
||||
if plugin < 0 {
|
||||
return Plugin{id: -1}, errors.New("Unable to load plugin")
|
||||
}
|
||||
|
||||
return Plugin{id: int32(plugin)}, nil
|
||||
// ExtismVersion gets the Extism version string
|
||||
func ExtismVersion() string {
|
||||
return C.GoString(C.extism_version())
|
||||
}
|
||||
|
||||
func LoadManifest(manifest Manifest, wasi bool) (Plugin, error) {
|
||||
func register(ctx *Context, data []byte, functions []Function, wasi bool) (Plugin, error) {
|
||||
ptr := makePointer(data)
|
||||
functionPointers := []*C.ExtismFunction{}
|
||||
for _, f := range functions {
|
||||
functionPointers = append(functionPointers, f.pointer)
|
||||
}
|
||||
plugin := C.int32_t(-1)
|
||||
|
||||
if len(functions) == 0 {
|
||||
plugin = C.extism_plugin_new(
|
||||
ctx.pointer,
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
nil,
|
||||
0,
|
||||
C._Bool(wasi))
|
||||
} else {
|
||||
plugin = C.extism_plugin_new(
|
||||
ctx.pointer,
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
&functionPointers[0],
|
||||
C.uint64_t(len(functions)),
|
||||
C._Bool(wasi),
|
||||
)
|
||||
}
|
||||
|
||||
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, functions: functions}, nil
|
||||
}
|
||||
|
||||
func update(ctx *Context, plugin int32, data []byte, functions []Function, wasi bool) error {
|
||||
ptr := makePointer(data)
|
||||
functionPointers := []*C.ExtismFunction{}
|
||||
for _, f := range functions {
|
||||
functionPointers = append(functionPointers, f.pointer)
|
||||
}
|
||||
|
||||
if len(functions) == 0 {
|
||||
b := bool(C.extism_plugin_update(
|
||||
ctx.pointer,
|
||||
C.int32_t(plugin),
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
nil,
|
||||
0,
|
||||
C._Bool(wasi),
|
||||
))
|
||||
|
||||
if b {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
b := bool(C.extism_plugin_update(
|
||||
ctx.pointer,
|
||||
C.int32_t(plugin),
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
&functionPointers[0],
|
||||
C.uint64_t(len(functions)),
|
||||
C._Bool(wasi),
|
||||
))
|
||||
|
||||
if b {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
||||
// NewPlugin creates a plugin in its own context
|
||||
func NewPlugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
|
||||
ctx := NewContext()
|
||||
return ctx.Plugin(module, functions, wasi)
|
||||
}
|
||||
|
||||
// NewPlugin creates a plugin in its own context from a manifest
|
||||
func NewPluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
|
||||
ctx := NewContext()
|
||||
return ctx.PluginFromManifest(manifest, functions, wasi)
|
||||
}
|
||||
|
||||
// PluginFromManifest creates a plugin from a `Manifest`
|
||||
func (ctx *Context) PluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
|
||||
data, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return Plugin{id: -1}, err
|
||||
}
|
||||
|
||||
return register(data, wasi)
|
||||
return register(ctx, data, functions, wasi)
|
||||
}
|
||||
|
||||
func LoadPlugin(module io.Reader, wasi bool) (Plugin, error) {
|
||||
// Plugin creates a plugin from a WASM module
|
||||
func (ctx *Context) Plugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
|
||||
wasm, err := io.ReadAll(module)
|
||||
if err != nil {
|
||||
return Plugin{id: -1}, err
|
||||
}
|
||||
|
||||
return register(wasm, wasi)
|
||||
return register(ctx, wasm, functions, wasi)
|
||||
}
|
||||
|
||||
// Update a plugin with a new WASM module
|
||||
func (p *Plugin) Update(module io.Reader, functions []Function, wasi bool) error {
|
||||
wasm, err := io.ReadAll(module)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.functions = functions
|
||||
return update(p.ctx, p.id, wasm, functions, wasi)
|
||||
}
|
||||
|
||||
// Update a plugin with a new Manifest
|
||||
func (p *Plugin) UpdateManifest(manifest Manifest, functions []Function, wasi bool) error {
|
||||
data, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.functions = functions
|
||||
return update(p.ctx, p.id, data, functions, wasi)
|
||||
}
|
||||
|
||||
// Set configuration values
|
||||
func (plugin Plugin) SetConfig(data map[string][]byte) error {
|
||||
s, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
C.extism_plugin_config(C.int(plugin.id), (*C.uchar)(unsafe.Pointer(&s[0])), C.uint64_t(len(s)))
|
||||
ptr := makePointer(s)
|
||||
C.extism_plugin_config(plugin.ctx.pointer, C.int(plugin.id), (*C.uchar)(ptr), C.uint64_t(len(s)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
|
||||
// FunctionExists returns true when the named function is present in the plugin
|
||||
func (plugin Plugin) FunctionExists(functionName string) bool {
|
||||
name := C.CString(functionName)
|
||||
rc := C.extism_call(
|
||||
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)(unsafe.Pointer(&input[0])),
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(input)),
|
||||
)
|
||||
C.free(unsafe.Pointer(name))
|
||||
|
||||
if rc != 0 {
|
||||
error := C.extism_error(C.int32_t(plugin.id))
|
||||
if error != nil {
|
||||
return nil, errors.New(
|
||||
fmt.Sprintf("ERROR (extism plugin code: %d): %s", rc, C.GoString(error)),
|
||||
)
|
||||
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_output_length(C.int32_t(plugin.id))
|
||||
buf := make([]byte, length)
|
||||
C.extism_output_get(C.int32_t(plugin.id), (*C.uchar)(unsafe.Pointer(&buf[0])), length)
|
||||
length := C.extism_plugin_output_length(plugin.ctx.pointer, C.int32_t(plugin.id))
|
||||
|
||||
return buf, nil
|
||||
if length > 0 {
|
||||
x := C.extism_plugin_output_data(plugin.ctx.pointer, C.int32_t(plugin.id))
|
||||
return unsafe.Slice((*byte)(x), C.int(length)), nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// ValGetI64 returns an I64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetI64(v unsafe.Pointer) int64 {
|
||||
return int64(C.extism_val_i64(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValGetUInt returns a uint from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetUInt(v unsafe.Pointer) uint {
|
||||
return uint(C.extism_val_i64(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValGetI32 returns an int32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetI32(v unsafe.Pointer) int32 {
|
||||
return int32(C.extism_val_i32(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValGetF32 returns a float32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetF32(v unsafe.Pointer) float32 {
|
||||
return float32(C.extism_val_f32(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValGetF32 returns a float64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetF64(v unsafe.Pointer) float64 {
|
||||
return float64(C.extism_val_i64(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValSetI64 stores an int64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
||||
func ValSetI64(v unsafe.Pointer, i int64) {
|
||||
C.extism_val_set_i64(&(*Val)(v).v, C.int64_t(i))
|
||||
}
|
||||
|
||||
// ValSetI32 stores an int32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
||||
func ValSetI32(v unsafe.Pointer, i int32) {
|
||||
C.extism_val_set_i32(&(*Val)(v).v, C.int32_t(i))
|
||||
}
|
||||
|
||||
// ValSetF32 stores a float32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
||||
func ValSetF32(v unsafe.Pointer, i float32) {
|
||||
C.extism_val_set_f32(&(*Val)(v).v, C.float(i))
|
||||
}
|
||||
|
||||
// ValSetF64 stores a float64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
||||
func ValSetF64(v unsafe.Pointer, f float64) {
|
||||
C.extism_val_set_f64(&(*Val)(v).v, C.double(f))
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) ReturnBytes(v unsafe.Pointer, b []byte) {
|
||||
mem := p.Alloc(uint(len(b)))
|
||||
ptr := p.Memory(mem)
|
||||
copy(ptr, b)
|
||||
ValSetI64(v, int64(mem))
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) ReturnString(v unsafe.Pointer, s string) {
|
||||
p.ReturnBytes(v, []byte(s))
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) InputBytes(v unsafe.Pointer) []byte {
|
||||
return p.Memory(ValGetUInt(v))
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) InputString(v unsafe.Pointer) string {
|
||||
return string(p.InputBytes(v))
|
||||
}
|
||||
|
||||
type CancelHandle struct {
|
||||
pointer *C.ExtismCancelHandle
|
||||
}
|
||||
|
||||
func (p *Plugin) CancelHandle() CancelHandle {
|
||||
pointer := C.extism_plugin_cancel_handle(p.ctx.pointer, C.int(p.id))
|
||||
return CancelHandle{pointer}
|
||||
}
|
||||
|
||||
func (c *CancelHandle) Cancel() bool {
|
||||
return bool(C.extism_plugin_cancel(c.pointer))
|
||||
}
|
||||
|
||||
17
extism.opam
17
extism.opam
@@ -4,18 +4,21 @@ 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"
|
||||
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"
|
||||
"ocaml" {>= "4.14.1"}
|
||||
"dune" {>= "3.2"}
|
||||
"ctypes-foreign"
|
||||
"bigstringaf"
|
||||
"ppx_yojson_conv"
|
||||
"base64"
|
||||
"ctypes" {>= "0.18.0"}
|
||||
"ctypes-foreign" {>= "0.18.0"}
|
||||
"bigstringaf" {>= "0.9.0"}
|
||||
"ppx_yojson_conv" {>= "v0.15.0"}
|
||||
"extism-manifest" {= version}
|
||||
"ppx_inline_test" {>= "v0.15.0"}
|
||||
"cmdliner" {>= "1.1.1"}
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
@@ -33,3 +36,5 @@ build: [
|
||||
]
|
||||
]
|
||||
dev-repo: "git+https://github.com/extism/extism.git"
|
||||
build-env: [EXTISM_TEST_NO_LIB = ""]
|
||||
post-messages: ["See https://extism.org/docs/install/ for information about installing libextism"]
|
||||
|
||||
2
extism.opam.template
Normal file
2
extism.opam.template
Normal file
@@ -0,0 +1,2 @@
|
||||
build-env: [EXTISM_TEST_NO_LIB = ""]
|
||||
post-messages: ["See https://extism.org/docs/install/ for information about installing libextism"]
|
||||
184
extism_test.go
Normal file
184
extism_test.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package extism
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func manifest(functions bool) Manifest {
|
||||
path := "./wasm/code.wasm"
|
||||
if functions {
|
||||
path = "./wasm/code-functions.wasm"
|
||||
}
|
||||
|
||||
return Manifest{
|
||||
Wasm: []Wasm{
|
||||
WasmFile{
|
||||
Path: path,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func expectVowelCount(plugin Plugin, input string, count int) error {
|
||||
out, err := plugin.Call("count_vowels", []byte(input))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var result map[string]int
|
||||
json.Unmarshal(out, &result)
|
||||
if result["count"] != count {
|
||||
return fmt.Errorf("Got count %d but expected %d", result["count"], count)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCreateAndFreeContext(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
ctx.Free()
|
||||
}
|
||||
|
||||
func TestCallPlugin(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := expectVowelCount(plugin, "this is a test again", 7); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := expectVowelCount(plugin, "this is a test thrice", 6); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFreePlugin(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// free this specific plugin
|
||||
plugin.Free()
|
||||
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err == nil {
|
||||
t.Fatal("Expected an error after plugin was freed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextReset(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// reset the context dropping all plugins
|
||||
ctx.Reset()
|
||||
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err == nil {
|
||||
t.Fatal("Expected an error after plugin was freed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanUpdateAManifest(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
plugin.UpdateManifest(manifest(false), []Function{}, false)
|
||||
|
||||
// can still call the plugin
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionExists(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !plugin.FunctionExists("count_vowels") {
|
||||
t.Fatal("Was expecting to find the function count_vowels")
|
||||
}
|
||||
if plugin.FunctionExists("i_dont_exist") {
|
||||
t.Fatal("Was not expecting to find the function i_dont_exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorsOnUnknownFunction(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = plugin.Call("i_dont_exist", []byte("someinput"))
|
||||
if err == nil {
|
||||
t.Fatal("Was expecting call to unknown function to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancel(t *testing.T) {
|
||||
manifest := Manifest{
|
||||
Wasm: []Wasm{
|
||||
WasmFile{
|
||||
Path: "./wasm/loop.wasm",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest, []Function{}, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cancelHandle := plugin.CancelHandle()
|
||||
|
||||
go func(handle CancelHandle) {
|
||||
time.Sleep(time.Second * 1)
|
||||
handle.Cancel()
|
||||
}(cancelHandle)
|
||||
|
||||
_, err = plugin.Call("infinite_loop", []byte(""))
|
||||
if err == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user