Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
examples/sdk/pkg/bindings/imports/** linguist-generated=true
examples/sdk/pkg/bindings/exports/wit_exports/** linguist-generated=true
examples/sdk/pkg/wit/deps/** linguist-generated=true
44 changes: 44 additions & 0 deletions examples/sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Building SDKs for componentize-go applications

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.

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).

## componentize-go.toml

```toml
# componentize-go.toml file structure

# The FULL names of the default WIT worlds
worlds = ["example:sdk/test", "foo:bar/baz"]
# The paths in which the WIT files are stored.
wit_paths = ["wit", "../other_wit_path"]
```

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.

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.

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.

## Defining Imports

Imports are straightforward since componentize-go generates the bindings. To see how imports are abstracted, see the [sockets package](pkg/sockets/sockets.go).

## Defining Exports

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.

**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))

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.

**Step 2: The SDK wrapper** ([`pkg/cli/cli.go`](pkg/cli/cli.go))

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.

Note the required blank import of `wit_exports`, as the component will not compile without it.

**Step 3: The application** ([`component/main.go`](component/main.go))

The application implements the required function export(s) and calls `RegisterExports` from `init()`. The `main()` function is left empty to make the compiler happy.
7 changes: 7 additions & 0 deletions examples/sdk/component/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: run build

build:
componentize-go build

run: build
wasmtime run -S p3,inherit-network -W component-model-async main.wasm
18 changes: 18 additions & 0 deletions examples/sdk/component/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# `sdk` example

## Usage

### Prerequisites

- [**componentize-go**](https://github.com/bytecodealliance/componentize-go) - Latest version
- [**go**](https://go.dev/dl/) - v1.25.9
- [**wasmtime**](https://github.com/bytecodealliance/wasmtime) - v44.0.1

### Run

```sh
make run

# Invoke the application
curl localhost:6767
```
9 changes: 9 additions & 0 deletions examples/sdk/component/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module example

go 1.25.5

replace pkg => ../pkg

require pkg v0.0.0-00010101000000-000000000000

require go.bytecodealliance.org/pkg v0.2.1 // indirect
2 changes: 2 additions & 0 deletions examples/sdk/component/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go.bytecodealliance.org/pkg v0.2.1 h1:TdRagooIcCW3UmlKqVO4cDR3GNDyfDnbiBzGI6TOvyg=
go.bytecodealliance.org/pkg v0.2.1/go.mod h1:OjA+V8g3uUFixeCKFfamm6sYhTJdg8fvwEdJ2GO0GSk=
66 changes: 66 additions & 0 deletions examples/sdk/component/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import (
"fmt"
"io"
wasiExports "pkg/cli"
wasiSockets "pkg/sockets"
)

const numWorkers = 16

type Component struct{}

func (c *Component) Run() error {
socket, err := wasiSockets.NewSocket(wasiSockets.IpAddressFamilyIpv4)
if err != nil {
return err
}

if err := socket.Bind("0.0.0.0:6767"); err != nil {
return err
}

listener, err := socket.Listen()
if err != nil {
return err
}
defer listener.Close()

connCh := make(chan *wasiSockets.TcpSocket, numWorkers)

for range numWorkers {
go func() {
for conn := range connCh {
handleConn(conn)
}
}()
}

for {
conn, err := listener.Accept()
if err != nil {
close(connCh)
if err == io.EOF {
return nil
}
return err
}
connCh <- conn
}
}

func handleConn(conn *wasiSockets.TcpSocket) {
body := "Hello from Go + wasi:sockets!"
response := fmt.Sprintf(
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s",
len(body), body,
)
conn.Write([]byte(response))
}

func init() {
wasiExports.RegisterExports(&Component{})
}

func main() {}
14 changes: 14 additions & 0 deletions examples/sdk/pkg/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
generate-bindings:
@rm -rf bindings/imports
@rm -rf bindings/exports/wit_exports
# The export_wasi_cli_run files are hand-written, and thus are not removed

@componentize-go \
--ignore-toml-files \
bindings \
--output bindings/imports \
--pkg-name pkg/bindings/imports \
--export-pkg-name pkg/bindings/exports \
--format

@mv bindings/imports/wit_exports bindings/exports
3 changes: 3 additions & 0 deletions examples/sdk/pkg/bindings/exports/export_wasi_cli_run/empty.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file exists for testing this package without WebAssembly,
// allowing empty function bodies with a //go:wasmimport directive.
// See https://pkg.go.dev/cmd/compile for more information.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Note: This file was NOT generated by componentize-go

package export_wasi_cli_run

import (
witTypes "go.bytecodealliance.org/pkg/wit/types"
)

var Exports struct {
Run func() witTypes.Result[witTypes.Unit, witTypes.Unit]
}

func Run() witTypes.Result[witTypes.Unit, witTypes.Unit] {
return Exports.Run()
}
52 changes: 52 additions & 0 deletions examples/sdk/pkg/bindings/exports/wit_exports/wit_exports.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions examples/sdk/pkg/bindings/imports/wasi_cli_exit/empty.s

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions examples/sdk/pkg/bindings/imports/wasi_cli_exit/wit_bindings.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions examples/sdk/pkg/bindings/imports/wasi_cli_stderr/empty.s

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading