フューチャー技術ブログ

Go 1.16のgo installについて

はじめに

TIG真野です。

Go 1.16連載の2つ目となるこの記事では、Go 1.16で機能追加された go install について説明します。

go installの新機能

Go 1.16から go installに新機能が追加されました。go install 自体は従来から存在しているので全く新しいコマンドが増えたわけではなく、機能拡張されたイメージです。

  • 現時点のリリースノートの記載はこちら、元のIssueはこちらです。起票されてから半年弱でリリースに含まれました。

go installの機能拡張部分ですが簡単に言うと、バージョン指定できるようになりました。言ってみればそれだけです。嬉しいこととして、それによってgo.modの書き換え無しにツールなどのインストールを行いやすくなりました。ちなみに、インストールとはコードをコンパイルして、$GOPATH/binとか$HOME/go/binにビルドしたバイナリを配備してくれることです。

従来はgo getでツールのインストールも行えていましたが、同時にgo.modも書き換わってしまいました。メインで開発するモジュールで利用するライブラリならgo.modに追記されることが自然ですが、例えばGoのLanguage Serverであるgoplsや、stringer といった開発系のコマンドラインツールの場合は少し困りました。

どういうことかと言うと、メインで開発するモジュール本体のgo.modで、例えばgo get golang.org/x/tools/cmd/stringerでインストールすると一時的に go.mod に追加されてしまうのです。もちろん、コードの中にはimportされていないため、go mod tidyすると消すことができますが、新規参画者の最初の環境構築で手順を間違えると不要な差分が出てきて少し手間でした。参画タイミングによって新しいバージョンが出たりすると、go getgo.modのバージョンが書き換わるのもまた問題でした。また、逆に go mod tidy するとgo.modから削除されますが、つまり開発系ツール自体のバージョン管理ができなくなる問題もありました。そちらについては後述します。

今回追加された go install を用いればこういったストレスからは開放されるかと思います。なんとなく go getの機能が分割され、今後はGo Module追加編集のためのgo get、ツールなどのバイナリインストールのgo installと住み分けることができそうです。

利用方法

go install example.com/cmd@v1.0.0 の形式で利用可能です。go getと同じ指定方法ですね。

例えば、stringerであれば

go get golang.org/x/tools/cmd/stringer@v0.1.0です。バージョンは必須とのこと。最新で良い場合は@latestをつけます。

動かしてみる

Go 1.16のRelease Candidate1でgo installを動かしてみます。

>go version
go version go1.16rc1 windows/amd64

適当なフォルダで go mod init して、go get golang.org/x/tools/cmd/stringer@v0.1.0go install golang.org/x/tools/cmd/stringer@v0.1.0 の実行結果を比較してみます。

go_get版
>go get golang.org/x/tools/cmd/stringer@v0.1.0
go get: added golang.org/x/tools v0.1.0

>type go.mod
module github.com/ma91n/go116

go 1.14

require golang.org/x/tools v0.1.0 // indirect

>stringer
Usage of stringer:
stringer [flags] -type T [directory]
stringer [flags] -type T files... # Must be a single package
(省略)

Go 1.16だとまだ go get でインストールもできるようですね。(将来的に消える可能性があるので注意です)

続いて、go install で動かしてみます。

go_install版
>go install golang.org/x/tools/cmd/stringer@v0.1.0

>type go.mod
module github.com/ma91n/go116

go 1.14

>stringer
Usage of stringer:
stringer [flags] -type T [directory]
stringer [flags] -type T files... # Must be a single package
(省略)

リリースノート通り、go.mod への副作用はありませんでした。

ローカルリポジトリに対してのgo install

こちらは従来どおり、そのまま利用可能です。

mycmd.go
package main

import "fmt"

func main() {
fmt.Println("Happy Go 1.16 !!")
}

以下のような適当なmainパッケージなファイルを作成してinstallします。

$ go install mycmd.go
$ mycmd
Happy Go 1.16 !!
````

ローカルリポジトリ及び、リモートリポジトリ両方に同じように操作ができるようになったと考えると、なんとなく直感的にうまくコマンドが整理されたのかなと感じました。正確にはGo 1.16時点でも使えたのですが、`go get`をヘビーユースして`go install`はほとんど使わなかった..。


# Go Moduleで開発系ツールを管理するハックとの関係は?

[ここ](https://qiita.com/nirasan/items/2bdbf0ada7b4182d56ce)にあるように、開発系ツールをGo Modulesでバージョン管理するために、`// +build`のビルドタグ+ブランクインポートするようなハックがありました。

```go tools.go
// +build tools

package main

import _ "golang.org/x/tools/cmd/stringer"

また、巷では、toolsフォルダをつくって、そこでgo mod init toolsして、上記の記事のようなブランクインポートする流派もあります。こうすると開発しているモジュールが依存するパッケージと、ツールのバージョン別に管理できるプラクティスとして一定の広がりがあったと思います。

どちらにしても開発ツールのバージョンをgo.modに記載し、そのディレクトリでgo install golang.org/x/tools/cmd/stringer などとしていました。複数あるとそのまま複数個installコマンドを愚直に打つか、cat tools.go | grep _ | awk -F'"' '{print $2}' | xargs -tI % go install % などでシェル技が炸裂していました。

Go 1.16での変化ですが、RC1時点だと go getでインストールはまだ行ってくれます。しかし将来的に使えなくなる可能性があるので、素直に go install を使いましょう。従来のgo installはバージョン指定ができなかったため、わざわざGo Module管理するためにハックを繰り返していましたが、Go 1.16だとそもそも不要です。

素直に make install でセットアップするが正解になる気がします。

Makefile
.PHONY: install

install:
go install golang.org/x/tools/gopls@v0.6.5
go install golang.org/x/tools/cmd/stringer@v0.1.0

このあたりのプラクティスは、Go WikiのHow can I track tool dependencies for a module? がどう変わるかをウォッチしていこうと思います。(多分、変わるはず..)

//go:generateディレクティブ

Go Modules が導入されてから、公開されている Go 製のツールは go run によるダウンロード・ビルド・実行が一度にできるようになっていたのですが、Go1.16だとgo.modが自動で更新されない影響か、//go:generate go run golang.org/x/tools/cmd/stringer -type=Pill でインストール無しで go generate するだけでコード生成するハックが使えなくなりました。

pill.go
package generate

//go:generate go run golang.org/x/tools/cmd/stringer -type=Pill
type Pill int

const (
Placebo Pill = iota
Aspirin
Ibuprofen
Paracetamol
)

上記のようにすると、各開発者が事前準備無しにgo generateさえ実行するだけで済んだのですが、Go1.16だとこうなります。

>go generate
no required module provides package golang.org/x/tools/cmd/stringer; to add it:
go get golang.org/x/tools/cmd/stringer
pill.go:3: running "go": exit status 1

同じ開発体験を守りたいなら、複数行に記載することになると思います。

複数行に記載
package generate

//go:generate go install golang.org/x/tools/cmd/stringer@latest
//go:generate stringer -type=Pill
type Pill int

const (
Placebo Pill = iota
Aspirin
Ibuprofen
Paracetamol
)
>go generate

>type pill_string.go
// Code generated by "stringer -type=Pill"; DO NOT EDIT.

package generate

import "strconv"

func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[Placebo-0](
(省略)

無事生成はできました。//go:generateディレクティブに go run コマンドを書くのはちょっとしたハック感があって好きだったのですが、初見では混乱するので消えて良かったのかもしれませんね。

余談

go buildとかgo testで自動的にgo.modが更新されない変わりに、go mod tidyしてねってメッセージがでるようなりました。どのタイミングで表示してくれるのか細かくは良く分かりませんが、気がついたらバージョンを上げると良いかと思います。

まとめ

  • go installでバージョンを指定ができるようになった
  • いくつかのハックが陳腐化したり、使えなくなったが、試行錯誤した経験は我々の中にずっと残り続ける