以下の GIF のように、 PyPortal (atsamd51j20a) の設定で gopls を動かせるようになりました。
正しく board_pyportal.go 等に飛べているのが確認できます。
もちろん、メソッド等の補完も動きます。
はじめに
TinyGo は、以下の理由により gopls がほとんど動きませんでした。
- 独自のビルトインパッケージを持っている
- 例: machine や device など
- buildtag による分岐
buildtag による分岐については、 gopls でも issue として上がっている内容になりますが、現時点でも gopls 全体で ある単一の buildtag を持つこと
は可能なので何とかなりました。
ということで、 LSP を動かすために以下の 3 つを実施していきます。
- 開発しているフォルダの go.mod に replace を書く
- tinygo ディレクトリの各パッケージフォルダに go.mod (空で良い) を置く
- 環境変数 GOOS + GOARCH + GOFLAGS を設定する
環境
この記事を作るにあたり、以下の環境で確認しました。
- Windows 10
- Go version go1.14.1 windows/amd64
- tinygo version 0.13.1 windows/amd64 (using go version go1.14.1 and LLVM version 10.0.1)
- gopls ead0a569305d87def8dc4ad3899a7d78432b12c6
- Vim 8.2.147
- vim-go 13af5df6a1b3bc4bdfd03e3c05fa600d1dd16de6
多分、 Windows じゃなくても Vim じゃなくても同じように設定可能だと思います。
やり方
1. 開発しているフォルダの go.mod に replace を書く
背景
TinyGo では、以下のパッケージ以外のものは GOROOT にあるものを使っています。
以下のパッケージは tinygo をインストールしたディレクトリ直下のものが使われます。
- machine
- os
- reflect
- runtime
- runtime/interrupt
- runtime/volatile
- sync
- testing
- internal/reflectlite
- internal/task
- device/*
- examples/*
例えば fmt パッケージは GOROOT にあるものが使われるので、インストールしている Go の fmt パッケージが使われます。fmt.Printf()
はほとんどのマイコンボードにおいて、 USBCDC (USB の SerialPort) への書き込みに使われますが、 fmt パッケージ自体は Go (not TinyGo) の物が使われます。
Go の fmt パッケージを使っているのにどうやって USBCDC に出力するかというと、 TinyGo 内で os.Stdout
を USBCDC への書き込みにマッピングして実現しています。
この 特別扱い
は以下のコードにより実現しています。
そして、この 特別扱い
が gopls がうまく動かない状態を作っています。
// tinygo/compiler/compiler.gofuncCompile(pkgNamestring,machinellvm.TargetMachine,config*compileopts.Config)(llvm.Module,[]string,[]error){// ...lprogram:=&loader.Program{// ...OverlayPath:func(pathstring)string{// Return the (overlay) import path when it should be overlaid, and// "" if it should not.ifstrings.HasPrefix(path,tinygoPath+"/src/"){// Avoid issues with packages that are imported twice, one from// GOPATH and one from TINYGOPATH.path=path[len(tinygoPath+"/src/"):]}switchpath{case"machine","os","reflect","runtime","runtime/interrupt","runtime/volatile","sync","testing","internal/reflectlite","internal/task":returnpathdefault:ifstrings.HasPrefix(path,"device/")||strings.HasPrefix(path,"examples/"){returnpath}elseifpath=="syscall"{for_,tag:=rangec.BuildTags(){iftag=="baremetal"||tag=="darwin"{returnpath}}}}return""},// ...}// ...}
対策
ということで、上記の特別扱いされたフォルダに対して go.mod で replace ディレクティブを書くことにより解決できます。
Windows での標準的なインストール場所に合わせた設定は以下になります。
module tinygo.org/x/drivers
go 1.14
replace (
device/sam => C:\tinygo\src\device/sam
internal/reflectlite => C:\tinygo\src\internal/reflectlite
internal/task => C:\tinygo\src\internal/task
machine => C:\tinygo\src\machine
os => C:\tinygo\src\os
reflect => C:\tinygo\src\reflect
runtime => C:\tinygo\src\runtime
runtime/interrupt => C:\tinygo\src\runtime/interrupt
runtime/volatile => C:\tinygo\src\runtime/volatile
sync => C:\tinygo\src\sync
testing => C:\tinygo\src\testing
)
なお、 replace ディレクティブで指し示す先は、空でも良いので go.mod ファイルが必要になります。
後述の 各パッケージフォルダに go.mod を置く
を実行しないと、この時点ではうまく動作しません。
2. tinygo ディレクトリの各パッケージフォルダに go.mod (空で良い) を置く
背景
以下に記載の通り、 replace される側については go.mod ファイルが必要となります。
空でも良いので作成しておく必要があります。
Note: if the right-hand side of a replace directive is a filesystem path, then the target must have a go.mod file at that location. If the go.mod file is not present, you can create one with go mod init.
https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive
対策
touch 等を使ってからファイルを置いてください。
git-bash 等が使える場合は以下を実行することで go.mod を簡単に作成することができます。
touch C:/tinygo/src/device/sam/go.mod
touch C:/tinygo/src/internal/reflectlite/go.mod
touch C:/tinygo/src/internal/task/go.mod
touch C:/tinygo/src/machine/go.mod
touch C:/tinygo/src/os/go.mod
touch C:/tinygo/src/reflect/go.mod
touch C:/tinygo/src/runtime/go.mod
touch C:/tinygo/src/runtime/interrupt/go.mod
touch C:/tinygo/src/runtime/volatile/go.mod
touch C:/tinygo/src/sync/go.mod
touch C:/tinygo/src/testing/go.mod
3. 環境変数 GOOS + GOARCH + GOFLAGS を設定する
背景
TinyGo では以下を使用しています。
これらを gopls に伝える必要があります。
- GOOS
- GOARCH
- buildtag (マイコンやボードなどの分岐)
対策
buildtag については、環境変数 GOFLAGS で設定できます。
それぞれの値をどう設定すべきかは tinygo info
で調べることができます。
例えば、 PyPortal というターゲットに対しては tinygo flash -target pyportal .
というようなコマンドでビルドしますが、その際の -target pyportal
の部分を tinygo info
に設定します。
$ C:\tinygo\bin\tinygo.exe info -target pyportal
LLVM triple: armv7em-none-eabi
GOOS: linux
GOARCH: arm
build tags: cortexm baremetal linux arm sam atsamd51 atsamd51j20 atsamd51j20a pyportal tinygo gc.conservative scheduler.tasks
garbage collector: conservative
scheduler: tasks
上記を調べることができたので、後は環境変数を設定します。
GOFLAGS はカンマ区切りなので注意が必要です。
set GOOS=linux
set GOARCH=arm
set GOFLAGS=-tags=cortexm,baremetal,linux,arm,sam,atsamd51,atsamd51j20,atsamd51j20a,pyportal,tinygo,gc.conservative,scheduler.tasks
bash 等では以下のように設定します。
export GOOS=linux
export GOARCH=arm
export GOFLAGS=-tags=cortexm,baremetal,linux,arm,sam,atsamd51,atsamd51j20,atsamd51j20a,pyportal,tinygo,gc.conservative,scheduler.tasks
おまけ
3. 環境変数 GOOS + GOARCH + GOFLAGS を設定する
の部分だけをヘルプする小さな自分用 CLI ツールとして tinygo-edit を作りました。
GOOS + GOARCH + GOFLAGS を設定しつつ --editor で指定した editor で開きます。
https://github.com/sago35/tinygo-edit
以下でインストールできます。
$ go get github.com/sago35/tinygo-edit
以下のように使用できます。
$ cd ./examples/blinky1
# feather-m4 の設定で gvim を立ち上げ
$ tinygo-edit --editor gvim --target feather-m4
# pyportal の設定で vim を立ち上げ
$ tinygo-edit --editor vim --target pyportal
まとめ
以下を実施することで、快適な TinyGo 環境になりました。
今後、 (主に gopls 側の version up により) 以下の設定は不要になるかと思いますが、しばらくはこの方法を使っていくことになりそうです。
- 開発しているフォルダの go.mod に replace を書く
- tinygo ディレクトリの各パッケージフォルダに go.mod (空で良い) を置く
- 環境変数 GOOS + GOARCH + GOFLAGS を設定する
リンク
- sago35/tinygo-edit: Add an environment variable for tinygo and open the editor
- x/tools/gopls: improve handling for build tags · Issue #29202 · golang/go