From cd5c598821315f811dadef7d5616c0a551611ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= Date: Thu, 19 Jun 2025 11:25:57 +0200 Subject: [PATCH] Allow implementing different WIT worlds in wasip2 target As mentioned in [this comment](https://github.com/tinygo-org/tinygo/issues/4843#issuecomment-2988622249), we are also interested in Wasm components with custom worlds (for [Grafbase extensions](https://grafbase.com/docs/gateway/extensions)). I have succesfully compiled and used examples based on the changes in this branch. They are mostly from @daedric's patch in the issue. So this PR is meant as a starting point for discussions, a working branch for anyone else looking at trying this, and to discuss how best to test it (I've made attempts, but they added up to a lot of code, and I wasn't too happy with them). I understand it's work to review and merge something like this, and even more to provide guidance on a proper test setup, so while feedback would be much appreciated, I understand if it's not a priority. fixes #4843 --- builder/build.go | 35 +++++++++++++++++++++++++++++++++++ compileopts/options.go | 1 + compileopts/target.go | 1 + main.go | 12 +++++++----- targets/wasip2.json | 1 + 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/builder/build.go b/builder/build.go index cd650cfc49..d5f36467e3 100644 --- a/builder/build.go +++ b/builder/build.go @@ -914,6 +914,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } // Run wasm-tools for component-model binaries + wasiPackage := strings.ReplaceAll(config.Target.WASIPackage, "{root}", goenv.Get("TINYGOROOT")) + if config.Options.WASIPackage != "" { + wasiPackage = config.Options.WASIPackage + } + witPackage := strings.ReplaceAll(config.Target.WITPackage, "{root}", goenv.Get("TINYGOROOT")) if config.Options.WITPackage != "" { witPackage = config.Options.WITPackage @@ -922,7 +927,37 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if config.Options.WITWorld != "" { witWorld = config.Options.WITWorld } + if witPackage != "" && witWorld != "" { + if wasiPackage != "" { + + fmt.Println("Embedding WASI component. Wasi package: ", wasiPackage) + + // wasm-tools component embed -w wasi:cli/command + // $$(tinygo env TINYGOROOT)/lib/wasi-cli/wit/ main.wasm -o embedded.wasm + componentEmbedInputFile := result.Binary + result.Binary = result.Executable + ".wasm-component-embed-1" + args := []string{ + "component", + "embed", + wasiPackage, + componentEmbedInputFile, + "-o", result.Binary, + } + + wasmtools := goenv.Get("WASMTOOLS") + if config.Options.PrintCommands != nil { + config.Options.PrintCommands(wasmtools, args...) + } + cmd := exec.Command(wasmtools, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() + if err != nil { + return fmt.Errorf("`wasm-tools component embed` failed: %w", err) + } + } // wasm-tools component embed -w wasi:cli/command // $$(tinygo env TINYGOROOT)/lib/wasi-cli/wit/ main.wasm -o embedded.wasm diff --git a/compileopts/options.go b/compileopts/options.go index 517664db2c..739f51dedf 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -56,6 +56,7 @@ type Options struct { Monitor bool BaudRate int Timeout time.Duration + WASIPackage string // pass trough to wasm-tools component first embed invocation WITPackage string // pass through to wasm-tools component embed invocation WITWorld string // pass through to wasm-tools component embed -w option ExtLDFlags []string diff --git a/compileopts/target.go b/compileopts/target.go index 9aeb8d5776..a67be85372 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -65,6 +65,7 @@ type TargetSpec struct { JLinkDevice string `json:"jlink-device,omitempty"` CodeModel string `json:"code-model,omitempty"` RelocationModel string `json:"relocation-model,omitempty"` + WASIPackage string `json:"wasi-package,omitempty"` WITPackage string `json:"wit-package,omitempty"` WITWorld string `json:"wit-world,omitempty"` } diff --git a/main.go b/main.go index a854a75073..1861a0fb24 100644 --- a/main.go +++ b/main.go @@ -1285,19 +1285,19 @@ microcontrollers, it is common to use the .elf file extension to indicate a linked ELF file is generated. For Linux, it is common to build binaries with no extension at all.` - usageRun = `Run the program, either directly on the host or in an emulated environment + usageRun = `Run the program, either directly on the host or in an emulated environment (depending on -target).` usageFlash = `Flash the program to a microcontroller. Some common flags are described below. - -target={name}: + -target={name}: Specifies the type of microcontroller that is used. The name of the microcontroller is given on the individual pages for each board type listed under Microcontrollers (https://tinygo.org/docs/reference/microcontrollers/). Examples: "arduino-nano", "d1mini", "xiao". - -monitor: + -monitor: Start the serial monitor (see below) immediately after flashing. However, some microcontrollers need a split second or two to configure the serial port after flashing, and @@ -1347,7 +1347,7 @@ instead of the expected \n (also known as ^J, NL, or LF) to indicate end-of-line. You may be able to get around this problem by hitting Control-J in tinygo monitor to transmit the \n end-of-line character.` - usageGdb = `Build the program, optionally flash it to a microcontroller if it is a remote + usageGdb = `Build the program, optionally flash it to a microcontroller if it is a remote target, and drop into a GDB shell. From there you can set breakpoints, start the program with "run" or "continue" ("run" for a local program, continue for on-chip debugging), single-step, show a backtrace, break and resume the program @@ -1631,8 +1631,9 @@ func main() { flag.StringVar(&outpath, "o", "", "output filename") } - var witPackage, witWorld string + var witPackage, witWorld, wasiPackage string if command == "help" || command == "build" || command == "test" || command == "run" { + flag.StringVar(&wasiPackage, "wasi-package", "", "wasi package for wasm component embedding") flag.StringVar(&witPackage, "wit-package", "", "wit package for wasm component embedding") flag.StringVar(&witWorld, "wit-world", "", "wit world for wasm component embedding") } @@ -1720,6 +1721,7 @@ func main() { Monitor: *monitor, BaudRate: *baudrate, Timeout: *timeout, + WASIPackage: wasiPackage, WITPackage: witPackage, WITWorld: witWorld, } diff --git a/targets/wasip2.json b/targets/wasip2.json index 66e79eda5a..88cf4d67da 100644 --- a/targets/wasip2.json +++ b/targets/wasip2.json @@ -28,6 +28,7 @@ "src/runtime/asm_tinygowasm.S" ], "emulator": "wasmtime run --wasm component-model -Sinherit-network -Sallow-ip-name-lookup --dir={tmpDir}::/tmp {}", + "wasi-package": "{root}/lib/wasi-cli/wit/", "wit-package": "{root}/lib/wasi-cli/wit/", "wit-world": "wasi:cli/command" }