Skip to content

Commit 1771387

Browse files
authored
Merge pull request #54 from asteurer/example-sdk
feat: add sdk example
2 parents 55ab77a + 9ac6589 commit 1771387

68 files changed

Lines changed: 13203 additions & 5 deletions

File tree

Some content is hidden

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

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
examples/sdk/pkg/bindings/imports/** linguist-generated=true
2+
examples/sdk/pkg/bindings/exports/wit_exports/** linguist-generated=true
3+
examples/sdk/pkg/wit/deps/** linguist-generated=true

examples/sdk/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Building SDKs for componentize-go applications
2+
3+
While feasible, it can be tedious to work directly with WIT types in Go. In most cases, it's better to have an SDK layer that handles the conversions from WIT types to idiomatic Go types. This example demonstrates the ways that we handle WIT `imports` and `exports` in order to have shared code feel natural for Go developers to use.
4+
5+
The [application](component/main.go) that's being demonstrated imports the TCP portion of `wasi:sockets` and defines a Run export for `wasi:cli`, both of which have been wrapped in an SDK (see the `pkg` directory).
6+
7+
## componentize-go.toml
8+
9+
```toml
10+
# componentize-go.toml file structure
11+
12+
# The FULL names of the default WIT worlds
13+
worlds = ["example:sdk/test", "foo:bar/baz"]
14+
# The paths in which the WIT files are stored.
15+
wit_paths = ["wit", "../other_wit_path"]
16+
```
17+
18+
Notice in the [component/Makefile](component/Makefile) how the build command is simply `componentize-go build`. Contrast this with the other examples which explicitly specify WIT worlds and paths to the WIT files in their build commands. The [pkg/componentize-go.toml](pkg/componentize-go.toml) file is what enables this behavior.
19+
20+
When building a component, componentize-go will search the go.mod file's dependencies' respective repositories for a componentize-go.toml file in the root. This file indicates where the WIT files are stored and the default worlds that are to be used.
21+
22+
You can override the default worlds via the command line. Note that doing so causes componentize-go to ignore all componentize-go.toml world definitions. You will need to explicitly list every WIT world the component requires.
23+
24+
## Defining Imports
25+
26+
Imports are straightforward since componentize-go generates the bindings. To see how imports are abstracted, see the [sockets package](pkg/sockets/sockets.go).
27+
28+
## Defining Exports
29+
30+
Exports require a bit more structure than imports because we need to have a translation layer for idiomatic, user-defined export functions and the raw WIT function signatures the compiler will recognize.
31+
32+
**Step 1: Define an Exports variable** ([`pkg/bindings/exports/export_wasi_cli_run/wit_bindings.go`](pkg/bindings/exports/export_wasi_cli_run/wit_bindings.go))
33+
34+
We hand-write an `Exports` variable that contains an uninitialized slot for each function export. At startup, something will need to assign a function to each slot; otherwise, the export panics at runtime. We do it this way because the [generated export bindings](pkg/bindings//exports/wit_exports/wit_exports.go) expect a `Run` function in the `bindings/export_wasi_cli_run` package, and we want to avoid making manual edits to any of the generated files.
35+
36+
**Step 2: The SDK wrapper** ([`pkg/cli/cli.go`](pkg/cli/cli.go))
37+
38+
The SDK defines a `RegisterExports` function that both assigns to the generated `Exports` slots and handles conversion between idiomatic Go types (e.g. `error`) and the raw WIT types (e.g. `witTypes.Option[string]`) the bindings expect.
39+
40+
Note the required blank import of `wit_exports`, as the component will not compile without it.
41+
42+
**Step 3: The application** ([`component/main.go`](component/main.go))
43+
44+
The application implements the required function export(s) and calls `RegisterExports` from `init()`. The `main()` function is left empty to make the compiler happy.

examples/sdk/component/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.PHONY: run build
2+
3+
build:
4+
componentize-go build
5+
6+
run: build
7+
wasmtime run -S p3,inherit-network -W component-model-async main.wasm

examples/sdk/component/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# `sdk` example
2+
3+
## Usage
4+
5+
### Prerequisites
6+
7+
- [**componentize-go**](https://github.com/bytecodealliance/componentize-go) - Latest version
8+
- [**go**](https://go.dev/dl/) - v1.25.9
9+
- [**wasmtime**](https://github.com/bytecodealliance/wasmtime) - v44.0.1
10+
11+
### Run
12+
13+
```sh
14+
make run
15+
16+
# Invoke the application
17+
curl localhost:6767
18+
```

examples/sdk/component/go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module example
2+
3+
go 1.25.5
4+
5+
replace pkg => ../pkg
6+
7+
require pkg v0.0.0-00010101000000-000000000000
8+
9+
require go.bytecodealliance.org/pkg v0.2.1 // indirect

examples/sdk/component/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
go.bytecodealliance.org/pkg v0.2.1 h1:TdRagooIcCW3UmlKqVO4cDR3GNDyfDnbiBzGI6TOvyg=
2+
go.bytecodealliance.org/pkg v0.2.1/go.mod h1:OjA+V8g3uUFixeCKFfamm6sYhTJdg8fvwEdJ2GO0GSk=

examples/sdk/component/main.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
wasiExports "pkg/cli"
7+
wasiSockets "pkg/sockets"
8+
)
9+
10+
const numWorkers = 16
11+
12+
type Component struct{}
13+
14+
func (c *Component) Run() error {
15+
socket, err := wasiSockets.NewSocket(wasiSockets.IpAddressFamilyIpv4)
16+
if err != nil {
17+
return err
18+
}
19+
20+
if err := socket.Bind("0.0.0.0:6767"); err != nil {
21+
return err
22+
}
23+
24+
listener, err := socket.Listen()
25+
if err != nil {
26+
return err
27+
}
28+
defer listener.Close()
29+
30+
connCh := make(chan *wasiSockets.TcpSocket, numWorkers)
31+
32+
for range numWorkers {
33+
go func() {
34+
for conn := range connCh {
35+
handleConn(conn)
36+
}
37+
}()
38+
}
39+
40+
for {
41+
conn, err := listener.Accept()
42+
if err != nil {
43+
close(connCh)
44+
if err == io.EOF {
45+
return nil
46+
}
47+
return err
48+
}
49+
connCh <- conn
50+
}
51+
}
52+
53+
func handleConn(conn *wasiSockets.TcpSocket) {
54+
body := "Hello from Go + wasi:sockets!"
55+
response := fmt.Sprintf(
56+
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s",
57+
len(body), body,
58+
)
59+
conn.Write([]byte(response))
60+
}
61+
62+
func init() {
63+
wasiExports.RegisterExports(&Component{})
64+
}
65+
66+
func main() {}

examples/sdk/pkg/Makefile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
generate-bindings:
2+
@rm -rf bindings/imports
3+
@rm -rf bindings/exports/wit_exports
4+
# The export_wasi_cli_run files are hand-written, and thus are not removed
5+
6+
@componentize-go \
7+
--ignore-toml-files \
8+
bindings \
9+
--output bindings/imports \
10+
--pkg-name pkg/bindings/imports \
11+
--export-pkg-name pkg/bindings/exports \
12+
--format
13+
14+
@mv bindings/imports/wit_exports bindings/exports
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// This file exists for testing this package without WebAssembly,
2+
// allowing empty function bodies with a //go:wasmimport directive.
3+
// See https://pkg.go.dev/cmd/compile for more information.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Note: This file was NOT generated by componentize-go
2+
3+
package export_wasi_cli_run
4+
5+
import (
6+
witTypes "go.bytecodealliance.org/pkg/wit/types"
7+
)
8+
9+
var Exports struct {
10+
Run func() witTypes.Result[witTypes.Unit, witTypes.Unit]
11+
}
12+
13+
func Run() witTypes.Result[witTypes.Unit, witTypes.Unit] {
14+
return Exports.Run()
15+
}

0 commit comments

Comments
 (0)