Skip to content

Commit c156889

Browse files
authored
feat(plugin): support native Go plugins with WASI (wasip1) (#71)
* deps: update Go version and wazero to v1.9.0 - Upgrade Go version from 1.22 to 1.24.0 - Update wazero dependency from v1.7.0 to v1.9.0 - Update corresponding go.sum entries * refactor: migrate from TinyGo to standard Go Wasm support This commit updates the project to use standard Go's WebAssembly support instead of TinyGo: - Replace TinyGo-specific build constraints with `wasip1` - Update Makefile to use standard Go Wasm compilation - Modify plugin initialization to use `init()` instead of `main()` - Update memory management functions - Replace `//export` with `//go:wasmexport` - Update README and documentation references - Regenerate protobuf files with updated protoc version The changes enable broader compatibility with Go's native Wasm support while maintaining the existing plugin architecture. * test(host-functions): add test for standard Go Wasm plugin with JSON parsing This commit adds a new test case for a WebAssembly plugin using standard Go's JSON parsing and host function capabilities: - Implement a new test function `TestStd` in host_functions_test.go - Create a new plugin implementation in plugin-std/plugin.go - Demonstrate JSON unmarshaling and string formatting in a Wasm plugin - Use `WithStdout` to enable standard output handling - Verify plugin interaction with a sample greeting scenario * docs: remove JSON parsing references for TinyGo Clean up README by removing outdated JSON parsing notes specific to TinyGo, including: - Remove section about JSON parsing limitations - Delete links to JSON parsing libraries - Remove reference to JSON parsing example link
1 parent d8d4205 commit c156889

File tree

102 files changed

+396
-297
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+396
-297
lines changed

.github/workflows/test.yaml

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,20 @@ on:
99
- 'LICENSE'
1010
pull_request:
1111
env:
12-
GO_VERSION: "1.22"
1312
PROTOC_VERSION: "21.12"
1413

1514
jobs:
1615
test:
17-
name: Test (TinyGo ${{ matrix.tinygo-version }})
16+
name: Test
1817
runs-on: ubuntu-latest
1918
strategy:
2019
fail-fast: false
21-
matrix:
22-
tinygo-version:
23-
- "0.31.2"
24-
- "0.32.0"
25-
- "0.33.0"
26-
- "0.34.0"
2720

2821
steps:
29-
- uses: actions/checkout@v3
22+
- uses: actions/checkout@v4
3023

3124
- name: Set up Go
32-
uses: actions/setup-go@v4
25+
uses: actions/setup-go@v5
3326
with:
3427
go-version-file: go.mod
3528

@@ -48,10 +41,5 @@ jobs:
4841
env:
4942
PB_REL: https://github.com/protocolbuffers/protobuf/releases
5043

51-
- name: Install TinyGo
52-
run: |
53-
wget https://github.com/tinygo-org/tinygo/releases/download/v${{ matrix.tinygo-version }}/tinygo_${{ matrix.tinygo-version }}_amd64.deb
54-
sudo dpkg -i tinygo_${{ matrix.tinygo-version }}_amd64.deb
55-
5644
- name: Run unit tests
57-
run: make test
45+
run: make test

Makefile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ build: $(GOBIN)/protoc-gen-go-plugin
1212
$(GOBIN)/protoc-gen-go-plugin: $(go_sources)
1313
go build ${LDFLAGS} -o $(GOPATH)/bin/protoc-gen-go-plugin cmd/protoc-gen-go-plugin/main.go
1414

15-
tinygo_examples := $(shell find examples -path "*/plugin*/*.go")
15+
go_examples := $(shell find examples -path "*/plugin*/*.go")
1616
.PHONY: build.examples
17-
build.examples: $(tinygo_examples:.go=.wasm)
17+
build.examples: $(go_examples:.go=.wasm)
1818

19-
tinygo_tests := $(shell find tests -path "*/plugin*/*.go")
19+
go_tests := $(shell find tests -path "*/plugin*/*.go")
2020
.PHONY: build.tests
21-
build.tests: $(tinygo_tests:.go=.wasm)
21+
build.tests: $(go_tests:.go=.wasm)
2222

2323
%.wasm: %.go $(GOBIN)/protoc-gen-go-plugin
24-
tinygo build -o $@ -scheduler=none --no-debug --target=wasi $<
24+
GOOS=wasip1 GOARCH=wasm go build -o $@ -buildmode=c-shared $<
2525

2626
proto_files := $(shell find . -name "*.proto")
2727
.PHONY: protoc

README.md

Lines changed: 24 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -49,38 +49,31 @@ This is useful when interface signatures are changing, protocol level changes ar
4949
When a protocol version is incompatible, a human friendly error message is shown to the end user.
5050

5151
## Architecture
52-
`go-plugin` generates Go SDK for a host and TinyGo SDK for plugins.
53-
As the Wasm support in Go is not mature, plugins need to be compiled to Wasm by [TinyGo][tinygo], which is an alternative compile for Go source code, at the moment.
52+
`go-plugin` generates Go SDK for a host and plugins.
5453
The plugin system works by loading the Wasm file and communicating over exporting/exported methods.
5554

5655
This architecture has a number of benefits:
5756

5857
- Plugins can't crash your host process: a panic in a plugin is handled by the Wasm runtime and doesn't panic the plugin user.
59-
- Plugins are very easy to write: just write a Go application and `tinygo build`.
60-
- Plugins are very easy to distribute: just compile the TinyGo source code to the Wasm binary once and distribute it.
58+
- Plugins are very easy to write: just write a Go application and `GOOS=wasip1 GOARCH=wasm go build`.
59+
- Plugins are very easy to distribute: just compile the Go source code to the Wasm binary once and distribute it.
6160
- Plugins are very easy to install: just put the Wasm binary in a location where the host will find it.
6261
- Plugins can be secure: the plugin is executed in a sandbox and doesn't have access to the local filesystem and network by default.
6362

6463
## Installation
6564

6665
Download a binary [here][releases] and put it in `$PATH`.
6766

68-
## Support Policy
67+
## Requirements
68+
- Go 1.24+
6969

70+
## Support Policy
7071
`go-plugin` is based on [Wazero][wazero] runtime and has Support Policy which follows [same rules](https://github.com/tetratelabs/wazero/?tab=readme-ov-file#go):
7172
> wazero follows the same version policy as Go's [Release Policy](https://go.dev/doc/devel/release): two versions. wazero will ensure these versions work and bugs are valid if there's an issue with a current Go version.
7273
73-
For example, if current version of Go is `go1.23`, `go-plugin` is ensured to work with Go versions:
74-
- `go1.22`
75-
- `go1.23`
76-
77-
as well as support corresponding `tinygo` versions (having most recent patch versions according to [Semver][semver]):
78-
- `v0.31.2`
79-
- `v0.32.0`
80-
- `v0.33.0`
81-
- `v0.34.0`
82-
83-
Mapping between different versions of Go and TinyGo can be found on [Go compatibility matrix](https://tinygo.org/docs/reference/go-compat-matrix/) and [Releases](https://github.com/tinygo-org/tinygo/releases) page.
74+
For example, if current version of Go is `go1.25`, `go-plugin` is ensured to work with Go versions:
75+
- `go1.24`
76+
- `go1.25`
8477

8578
## Usage
8679
To use the plugin system, you must take the following steps.
@@ -106,7 +99,7 @@ Install the following tools:
10699

107100
- `knqyf263/go-plugin` (See `Installation`)
108101
- [protoc][protoc]
109-
- [TinyGo][tinygo-installation]
102+
- [Go][go-installation]
110103

111104
### Choose the interface you want to expose for plugins
112105
Create `greeting.proto`.
@@ -176,7 +169,7 @@ A plugin author needs to implement `Greeter` and registers the struct via `Regis
176169
In this tutorial, we use `plugin.go` as a file name, but it doesn't matter.
177170

178171
```go
179-
//go:build tinygo.wasm
172+
//go:build wasip1
180173

181174
package main
182175

@@ -186,8 +179,10 @@ import (
186179
"github.com/path/to/your/greeting"
187180
)
188181

189-
// main is required for TinyGo to compile to Wasm.
190-
func main() {
182+
// main is required for Go to compile to Wasm.
183+
func main() {}
184+
185+
func init() {
191186
greeting.RegisterGreeter(MyPlugin{})
192187
}
193188

@@ -200,10 +195,10 @@ func (m MyPlugin) SayHello(ctx context.Context, request greeting.GreetRequest) (
200195
}
201196
```
202197

203-
Then, compile it to Wasm by TinyGo.
198+
Then, compile it to Wasm by Go.
204199

205200
```shell
206-
$ tinygo build -o plugin.wasm -scheduler=none -target=wasi --no-debug plugin.go
201+
$ GOOS=wasip1 GOARCH=wasm go build -o plugin.wasm -buildmode=c-shared plugin.go
207202
```
208203

209204
### Implement a host
@@ -341,14 +336,6 @@ API version mismatch, host: 2, plugin: 1
341336
### File access
342337
Refer to [this example][wasi-example].
343338

344-
### JSON parsing
345-
TinyGo currently doesn't support `encoding/json`.
346-
https://tinygo.org/docs/reference/lang-support/stdlib/
347-
348-
You have to use third-party JSON libraries such as [gjson][gjson] and [easyjson][easyjson].
349-
350-
Also, you can export a host function. The example is available [here][json-example].
351-
352339
### Logging
353340
`fmt.Printf` can be used in plugins if you attach `os.Stdout` as below. See [the example][wasi-example] for more details.
354341

@@ -392,9 +379,6 @@ Pull:
392379
$ oras pull ghcr.io/knqyf263/my-plugin:latest
393380
```
394381

395-
### Other TinyGo tips
396-
You can refer to https://wazero.io/languages/tinygo/.
397-
398382
## Under the hood
399383
`go-plugin` uses [wazero][wazero] for Wasm runtime.
400384
Also, it customizes [protobuf-go][protobuf-go] and [vtprotobuf][vtprotobuf] for generating Go code from proto files.
@@ -409,13 +393,16 @@ It is not schema-driven like Protocol Buffers and can easily break signature.
409393

410394
### Why not using [protobuf-go][protobuf-go] directly?
411395

412-
TinyGo [doesn't support Protocol Buffers](https://github.com/tinygo-org/tinygo/issues/2667) natively as of today.
413-
`go-plugin` generates Go code differently from [protobuf-go] so that TinyGo can compile it.
396+
`go-plugin` used to rely on TinyGo, which [doesn't support Protocol Buffers](https://github.com/tinygo-org/tinygo/issues/2667) natively.
397+
`go-plugin` generates Go code differently from [protobuf-go] so that TinyGo could compile it.
398+
Now that Go supports WASI (wasip1), I haven't fully verified if Protocol Buffers works properly when Go code is compiled to Wasm.
399+
This is an area that needs further testing and validation.
414400

415401
### Why replacing known types with custom ones?
416402
You might be aware that your generated code imports [github.com/knqyf263/go-plugin/types/known][go-plugin-known], not [github.com/protocolbuffers/protobuf-go/types/known][protobuf-go-known] when you import types from `google/protobuf/xxx.proto` (a.k.a well-known types) in your proto file.
417403
As described above, `TinyGo` cannot compile `github.com/protocolbuffers/protobuf-go/types/known` since those types use reflection.
418404
`go-plugin` provides well-known types compatible with TinyGo and use them.
405+
With the release of Go 1.24, which [has improved Wasm support](https://go.dev/blog/wasmexport), these workarounds might no longer be necessary. However, I haven't fully verified this yet.
419406

420407
### Why using `// go:plugin` for parameters rather than [protobuf extensions][protobuf-extensions]?
421408
An extension must be registered in [Protobuf Global Extension Registry][protobuf-registry] to issue a unique extension number.
@@ -428,7 +415,7 @@ You can see other reasons [here][wazero-go].
428415
We might be able to add support for Go as an experimental feature.
429416

430417
### What about other languages?
431-
`go-plugin` currently supports TinyGo plugins only, but technically, any language that can be compiled into Wasm can be supported.
418+
`go-plugin` currently supports Go plugins only, but theoretically, any language that can be compiled into Wasm can be supported.
432419
Welcome your contribution :)
433420

434421
## TODO
@@ -512,15 +499,12 @@ Welcome your contribution :)
512499
[protoc]: https://grpc.io/docs/protoc-installation/
513500
[vtprotobuf]: https://github.com/planetscale/vtprotobuf
514501
[plugin]: https://pkg.go.dev/plugin
515-
[gjson]: https://github.com/tidwall/gjson
516-
[easyjson]: https://github.com/mailru/easyjson
517502
[wazero-go]: https://wazero.io/languages/go/
518503

519504
[protobuf-go]: https://github.com/protocolbuffers/protobuf-go
520505
[protobuf-go-known]: https://github.com/protocolbuffers/protobuf-go/tree/master/types/known
521506

522-
[tinygo]: https://tinygo.org/
523-
[tinygo-installation]: https://tinygo.org/getting-started/install/
507+
[go-installation]: https://go.dev/doc/install
524508

525509
[hello-world]: https://github.com/knqyf263/go-plugin/tree/1ebeeca373affc319802989c0fe6304f014861c4/examples/helloworld
526510
[go-plugin-known]: https://github.com/knqyf263/go-plugin/tree/1ebeeca373affc319802989c0fe6304f014861c4/types/known
@@ -530,7 +514,6 @@ Welcome your contribution :)
530514

531515
[wasi-example]: https://github.com/knqyf263/go-plugin/tree/main/examples/wasi
532516
[host-functions-example]: https://github.com/knqyf263/go-plugin/tree/main/examples/host-functions
533-
[json-example]: https://github.com/knqyf263/go-plugin/tree/main/tests/host-functions
534517

535518
[releases]: https://github.com/knqyf263/go-plugin/releases
536519

examples/helloworld/greeting/greet.pb.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/helloworld/greeting/greet_host.pb.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/helloworld/greeting/greet_options.pb.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/helloworld/greeting/greet_plugin.pb.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/helloworld/greeting/greet_vtproto.pb.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/helloworld/main.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ import (
44
"context"
55
"fmt"
66
"log"
7-
)
8-
9-
//go:generate tinygo build -o plugin-morning/morning.wasm -scheduler=none -target=wasi --no-debug plugin-morning/morning.go
10-
//go:generate tinygo build -o plugin-evening/evening.wasm -scheduler=none -target=wasi --no-debug plugin-evening/evening.go
117

12-
import (
138
"github.com/knqyf263/go-plugin/examples/helloworld/greeting"
149
)
1510

11+
//go:generate go build -o plugin-morning/morning.wasm -buildmode=c-shared plugin-morning/morning.go
12+
//go:generate go build -o plugin-evening/evening.wasm -buildmode=c-shared plugin-evening/evening.go
13+
1614
func main() {
1715
if err := run(); err != nil {
1816
log.Fatal(err)

examples/helloworld/plugin-evening/evening.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build tinygo.wasm
1+
//go:build (js && wasm) || wasip1
22

33
package main
44

@@ -9,8 +9,9 @@ import (
99
"github.com/knqyf263/go-plugin/examples/helloworld/greeting"
1010
)
1111

12-
// main is required for TinyGo to compile to Wasm.
13-
func main() {
12+
func main() {}
13+
14+
func init() {
1415
greeting.RegisterGreeter(GoodEvening{})
1516
}
1617

0 commit comments

Comments
 (0)