I had trouble getting VSCode using Go lang server to resolve all modules properly for a TinyGo project.
Here’s my simple blinking light and BLE advertising code:
package main
import (
"machine"
"time"
"tinygo.org/x/bluetooth"
)
var adapter = bluetooth.DefaultAdapter
func main() {
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
go func() { // blink forever in a goroutine
for {
led.Set(!led.Get())
time.Sleep(100 * time.Millisecond)
}
}()
must(adapter.Enable())
adv := adapter.DefaultAdvertisement()
must(adv.Configure(bluetooth.AdvertisementOptions{
LocalName: "TinyGoDemo",
Interval: bluetooth.NewDuration(20 * time.Millisecond),
}))
must(adv.Start())
for {
time.Sleep(time.Hour)
} // keep running
}
func must(err error) {
if err != nil {
panic(err)
}
}
Two issues:
- It kept saying that
blueooth.DefaultAdapter
didn’t exist. time
module was erroring withpackage sync/atomic is not in std
.
Background
When you run tinygo
, it creates a Go root that symlinks various modules to either main go std libs or its own depending on what -target
flag is passed. For instance, if you are developing for Seeedstudio’s Xiao BLE board:
$ tinygo info -target=xiao-ble
LLVM triple: thumbv7em-unknown-unknown-eabi
GOOS: linux
GOARCH: arm
build tags: cortexm baremetal linux arm nrf52840 nrf softdevice s140v7 nrf52840_reset_uf2 xiao_ble tinygo purego osusergo math_big_pure_go gc.conservative scheduler.tasks serial.usb
garbage collector: conservative
scheduler: tasks
cached GOROOT: /Users/mansour/Library/Caches/tinygo/goroot-f688a267ff405d09fba72f367f71421a253d93f5af2c0c750eb7861aa20fddee
In order to get the Go lang server in VSCode to use these go envs as well, here’s what you can put in the .vscode/settings.json
of your project dir:
{
"go.toolsEnvVars": {
"GOCACHE": "/Users/mansour/Library/Caches/tinygo",
"GOROOT": "/Users/mansour/Library/Caches/tinygo/goroot-f688a267ff405d09fba72f367f71421a253d93f5af2c0c750eb7861aa20fddee",
"GOFLAGS": "-tags=cortexm,baremetal,linux,arm,nrf52840,nrf,s140v7,nrf52840_reset_uf2,xiao_ble,tinygo,purego,osusergo,math_big_pure_go,gc.conservative,scheduler.tasks,serial.usb,softdevice",
"GOOS": "linux",
"GOARCH": "arm"
}
}
(There’s in fact a VSCode extension for TinyGo that does exactly this.)
Issue number 2 was solved by copying the output of tinygo info
(GOROOT
) to VSCode settings.
The bluetooth
module file that has build tag of softdevice
definitely defines the DefaultAdapter
though:
//go:build softdevice
package bluetooth
// ...
// #include "ble.h"
// #ifdef NRF51
// #include "nrf_soc.h"
// #else
// #include "nrf_nvic.h"
// #endif
import "C"
// ...
// DefaultAdapter is the default adapter on the current system. On Nordic chips,
// it will return the SoftDevice interface.
//
// Make sure to call Enable() before using it to initialize the adapter.
var DefaultAdapter = &Adapter{isDefault: true,
connectHandler: func(device Device, connected bool) {
return
}}
// ...
So what’s up? Notice the import "C"
? Yep, Go lang server is assuming no CGO by default. Just needed to add:
"CGO_ENABLED": "1",
to .vscode/settings.json
.
Running tinygo env
gave me the hint:
$ tinygo env -target=xiao-ble
GOOS="darwin"
GOARCH="arm64"
GOROOT="/opt/homebrew/Cellar/go/1.24.2/libexec"
GOPATH="/Users/mansour/go"
GOCACHE="/Users/mansour/Library/Caches/tinygo"
CGO_ENABLED="1"
TINYGOROOT="/opt/homebrew/Cellar/tinygo/0.37.0"
Investigation
ChatGPT helped me out eventually but in-between the issue arising and finding the solution, I learnt a bit of gopls debugging. Having this as .vscode/settings.json
enabled a tracing log that I could dump into ChatGPT:
{
"go.toolsEnvVars": {},
"go.languageServerFlags": [
"-rpc.trace",
"--debug=localhost:6060",
"-logfile=auto"
]
}
After this, you can visit http://localhost:6060 and find the log file name. It has a bunch of other nice stats in there.