-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4f8cef6
commit 0aba0c2
Showing
1 changed file
with
163 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,167 @@ | ||
# Extism Zig PDK | ||
|
||
For more information about the Zig PDK, please [visit the docs](https://extism.org/docs/write-a-plugin/zig-pdk). | ||
|
||
This library can be used to write [Extism Plug-ins](https://extism.org/docs/concepts/plug-in) in Zig. | ||
|
||
## Install | ||
|
||
Generate a `lib` project with Zig: | ||
|
||
```bash | ||
mkdir my-plugin | ||
cd ./my-plugin | ||
zig init-lib | ||
``` | ||
|
||
Add the library as a dependency: | ||
|
||
```bash | ||
mkdir -p libs | ||
cd libs | ||
git clone https://github.com/extism/zig-pdk.git | ||
``` | ||
|
||
Change your `build.zig` so that it references `extism-pdk`: | ||
|
||
```zig | ||
const std = @import("std"); | ||
const builtin = @import("builtin"); | ||
pub fn build(b: *std.Build) void { | ||
comptime { | ||
const current_zig = builtin.zig_version; | ||
const min_zig = std.SemanticVersion.parse("0.12.0-dev.64+b835fd90c") catch unreachable; // std.json.ArrayHashMap | ||
if (current_zig.order(min_zig) == .lt) { | ||
@compileError(std.fmt.comptimePrint("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); | ||
} | ||
} | ||
const optimize = b.standardOptimizeOption(.{}); | ||
const target = b.standardTargetOptions(.{ | ||
.default_target = .{ .abi = .musl, .os_tag = .freestanding, .cpu_arch = .wasm32 }, | ||
}); | ||
const pdk_module = b.addModule("extism-pdk", .{ | ||
.source_file = .{ .path = "libs/zig-pdk/src/main.zig" }, | ||
}); | ||
var basic_example = b.addExecutable(.{ | ||
.name = "my-plugin", | ||
.root_source_file = .{ .path = "src/main.zig" }, | ||
.target = target, | ||
.optimize = optimize, | ||
}); | ||
basic_example.addModule("extism-pdk", pdk_module); | ||
basic_example.rdynamic = true; | ||
b.installArtifact(basic_example); | ||
const basic_example_step = b.step("my-plugin", "Build my-plugin"); | ||
basic_example_step.dependOn(b.getInstallStep()); | ||
} | ||
``` | ||
|
||
## Getting Started | ||
|
||
The goal of writing an [Extism plug-in](https://extism.org/docs/concepts/plug-in) is to compile your Zig code to a Wasm module with exported functions that the host application can invoke. The first thing you should understand is creating an export. Let's write a simple program that exports a `greet` function which will take a name as a string and return a greeting string. Zig has excellent support for this through the `export` keyword: | ||
|
||
```zig | ||
const std = @import("std"); | ||
const extism_pdk = @import("extism-pdk"); | ||
const Plugin = extism_pdk.Plugin; | ||
const allocator = std.heap.wasm_allocator; | ||
export fn greet() i32 { | ||
const plugin = Plugin.init(allocator); | ||
const name = plugin.getInput() catch unreachable; | ||
defer allocator.free(name); | ||
const output = std.fmt.allocPrint(allocator, "Hello, {s}!", .{name}) catch unreachable; | ||
plugin.output(output); | ||
return 0; | ||
} | ||
``` | ||
|
||
Then run: | ||
```sh | ||
zig build | ||
``` | ||
|
||
This will put your compiled wasm in `zig-out/bin`. | ||
We can now test it using the [Extism CLI](https://github.com/extism/cli)'s `run` | ||
command: | ||
|
||
```bash | ||
extism call ./zig-out/bin/my-plugin.wasm greet --input "Benjamin" | ||
# => Hello, Benjamin! | ||
``` | ||
|
||
> **Note**: We also have a web-based, plug-in tester called the [Extism Playground](https://playground.extism.org/) | ||
### More Exports: Error Handling | ||
Suppose want to re-write our greeting module to never greet Benjamins. We can use `plugin.setError`: | ||
|
||
```zig | ||
export fn greet() i32 { | ||
const plugin = Plugin.init(allocator); | ||
const name = plugin.getInput() catch unreachable; | ||
defer allocator.free(name); | ||
if (std.mem.eql(u8, name, "Benjamin")) { | ||
plugin.setError("Sorry, we don't greet Benjamins!"); | ||
return 1; | ||
} | ||
const output = std.fmt.allocPrint(allocator, "Hello, {s}!", .{name}) catch unreachable; | ||
plugin.output(output); | ||
return 0; | ||
} | ||
``` | ||
|
||
Now when we try again: | ||
|
||
```bash | ||
extism call ./zig-out/bin/my-plugin.wasm greet --input="Benjamin" | ||
# => Error: Sorry, we don't greet Benjamins! | ||
echo $? # print last status code | ||
# => 1 | ||
extism call ./zig-out/bin/my-plugin.wasm greet --input="Zach" | ||
# => Hello, Zach! | ||
echo $? | ||
# => 0 | ||
``` | ||
|
||
### Json | ||
|
||
Extism export functions simply take bytes in and bytes out. Those can be whatever you want them to be. A common and simple way to get more complex types to and from the host is with json: | ||
|
||
```zig | ||
export fn add() i32 { | ||
const Add = struct { | ||
a: i32, | ||
b: i32, | ||
}; | ||
const Sum = struct { | ||
sum: i32, | ||
}; | ||
const plugin = Plugin.init(allocator); | ||
const input = plugin.getInput() catch unreachable; | ||
defer allocator.free(input); | ||
const params = std.json.parseFromSlice(Add, allocator, input, std.json.ParseOptions{}) catch unreachable; | ||
const sum = Sum{ .sum = params.value.a + params.value.b }; | ||
const output = std.json.stringifyAlloc(allocator, sum, std.json.StringifyOptions{}) catch unreachable; | ||
plugin.output(output); | ||
return 0; | ||
} | ||
``` | ||
|
||
```bash | ||
extism call ./zig-out/bin/my-plugin.wasm add --input='{"a": 20, "b": 21}' | ||
# => {"sum":41} | ||
``` | ||
|
||
Join the [Discord](https://discord.gg/cx3usBCWnc) and chat with us! |