Carpe Diem

備忘録

go modulesでコマンドラインツールのバージョン管理をする

概要

goはModulesリポジトリのライブラリのバージョン管理を行えます。
ただコマンドラインツールに関してはgo getしてgo.modに追加されても、goファイルで扱っているわけではないのでgo mod tidyすると消えてしまいます。

しかしながら「この機能は最新のmasterにしかない。かと言ってgo get -u@masterで常に最新にしてしまうと互換性が壊れるかもしれない。だからバージョン管理できないと困る」というシチュエーションはよくあります。

その対策としては現状tools.goにblank importするのがベストプラクティスとなっています。

環境

  • go 1.13.6

現状の問題

入ったはずのパッケージがgo mod tidyで消える

$ mkdir hello
$ cd hello
$ go mod init example.com/hello

としてから、stringerを使いたいと考えインストールしてみます。

$ go install golang.org/x/tools/cmd/stringer

するとgo.modにそのライブラリ及びインストールしたバージョンが追加されます。

$ cat go.mod
module example.com/hello

go 1.13

require golang.org/x/tools v0.0.0-20200110213125-a7a6caa82ab2 // indirect

しかしこれは直接goファイルで扱っているわけではないので、go mod tidyすると消えてしましいます。

$ go mod tidy
$ cat go.mod
module example.com/hello

go 1.13

blank importでgo.modに残す

そこでtools.goといった適当なファイルを用意し、_importを使います。

// +build tools

package tools

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

こうするとgo mod tidyしてもgo.modに残り続けます。

$ go install golang.org/x/tools/cmd/stringer
$ go mod tidy
$ cat go.mod
module example.com/hello

go 1.13

require golang.org/x/tools v0.0.0-20200110213125-a7a6caa82ab2

// +build toolsを付けるのはなぜか?

単純に

package tools

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

としてしまうと、通常のビルド時やgo get時に

tools.go:6:2: import "golang.org/x/tools/cmd/stringer" is a program, not an importable package

と怒られてしまいます。

なので通常のビルドに含まれないように付けます。

インストールする時は?

go installを使う

go getはライブラリが更新されていると勝手にバージョンが上がるので基本的にgo installを使います。
go installであればgo.modに書かれたバージョンをインストールしてくれるのでコマンドラインツールのバージョン固定が可能です。

複数インストールしたい

import (
        _ "github.com/golang/mock/mockgen"
        _ "golang.org/x/tools/cmd/stringer"
)

こんな感じで複数のコマンドラインツールがある場合は

$ cat tools.go | awk -F'"' '/_/ {print $2}' | xargs -tI {} go install {}

ちょっと泥臭いですがこのようにシェルコマンドで対応します。
Makefileに書くときは$2のところを$$2にするよう注意してください。

install-go-tools:
        cat tools.go | awk -F'"' '/_/ {print $$2}' | xargs -tI {} go install {}

その他

Wikiにも載っているくらいなので現状このやり方が推奨されています。

有名どころだとzapがやっています。

github.com

まとめ

go modulesでコマンドラインツールのバージョン固定方法を紹介しました。

ソース