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:

  1. It kept saying that blueooth.DefaultAdapter didn’t exist.
  2. time module was erroring with package 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.

Image