TL;DR
go generate で使うものや linter などの開発用ツールもバージョン固定したいよね
gex を使えば簡単にできるよ
独自の管理機構を持たない(dep / Modules に任せる)から既存のワークフローへの導入も楽だよ
依存管理ツールと開発用ツール
Go における依存管理ツールは現状,dep が広く使われています.また,Go 1.11 からは Experimental ですが Modules も利用可能になっています(Modules については『Go 1.11 の modules・vgo を試す – 実際に使っていく上で考えないといけないこと』という記事で軽く感想を書いたので,よければそちらも御覧ください).
これらの依存管理ツールはコード中で import するパッケージをいい感じに管理・バージョンロックをしてくれます.
開発用ツールの管理
一方で,開発時に利用するような実行可能ツールについては関心の外です.
よくある例としては
mockgen(モック生成)
statik(static なファイルの埋め込み)
sqlboiler(ORM のコード生成)
protoc のプラグイン
あたりでしょうか.これらが正しくバージョン固定されないことで,いろいろな問題が発生します.
-
- チーム開発時に go generate するたびに diff がでる
mockgen(コード生成ツール)と mock(コード中から利用するライブラリ)のバージョンがずれて動かなくなる
などなど.困る.
dep の required
一応,dep に関しては required に記述することで,import されていないツールも管理はできます.
required = [
"github.com/golang/mock/mockgen",
]
しかしここから正しくビルドしてバイナリを生成しようとすると,もう3手間ほど必要になります:
# `go install` でプロジェクトローカルに出力されるように `GOBIN` を書き換え
$ export GOBIN=$PWD/bin
# `PATH` を通す
$ export PATH=$GOBIN:$PATH
# ビルドする
$ cd vendor/github.com/golang/mock/mockgen
$ go install .
これを毎回やるのはさすがに….
自分は Makefile の define を利用していました.
DEP_SRCS := \
github.com/golang/mock/mockgen \
golang.org/x/lint/golint
DEP_BINS := $(addprefix $(BIN_DIR)/,$(notdir $(DEP_SRCS)))
define dep-bin-tmpl
$(eval OUT := $(BIN_DIR)/$(notdir $(1)))
$(OUT): dep
@echo "--> Installing $(OUT)..."
@cd vendor/$(1) && GOBIN="$(BIN_DIR)" go install .
endef
$(foreach src,$(DEP_SRCS),$(eval $(call dep-bin-tmpl,$(src))))
ただ,「ツールの追加時に Gopkg.toml と Makefile 両方に記述が必要になる冗長さ」と,「そもそも Makefile が複雑過ぎてメンテしづらい」などの問題を抱えています.
その他の開発用ツール管理手法
この問題に対処するためのツールはいくつか存在します.
-
- virtualgo https://github.com/GetStream/vg
Python の virtualenv inspired なツール
retool https://github.com/twitchtv/retool
独自の管理機構を持つ
しかし,いずれも「vendoring したパッケージからバイナリを生成したい」程度の欲求に対し規模が大きすぎる印象を受けました.また,dep や Modules の「他ツールからの migration が容易」というメリットを殺してしまう可能性もあります.
gex による開発用ツール管理
ここまでで「開発用ツールの管理」に欲しいものを整理すると,以下の要件を満たせれば良さそうです.
-
- dep や Modules に相乗りできる(独自の管理機構は持たない)
- とりあえずバイナリを簡単に生成できれば良い
上述の条件を満たすような gex という薄いツールを作りました.
たとえば mockgen を依存に追加したければ次のコマンドを実行するだけです:
$ gex --add github.com/golang/mock/mockgen
mockgen を実行したいときは,gex mockgen … のようにするか,add 時に $PWD/bin に生成されたバイナリを直接叩きます.また,gex –build でバイナリを再生成できます.direnv や export PATH=$PWD/bin:$PATH した Makefile との相性も良いです.
gex がどのようにしてバイナリを管理しているか
gex は cmd/go: clarify best practice for tool dependencies · Issue #25922 · golang/go で紹介されていたものを愚直に実装しています.
gex –add <path/to/cmd> で以下のような挙動をします.
go get … もしくは dep ensure -add … が実行される
go build -o ./bin/ <path/to/cmd> が実行される
tools.go に書き込む
tools.go はツールのパッケージを blank import しているだけのファイルです.build constraint に適当なことを書いているため,ビルド時には参照されないようになっています.
// Code generated by github.com/izumin5210/gex. DO NOT EDIT.
// +build tools
package tools
// tool dependencies
import (
_ "github.com/golang/mock/mockgen"
)
dep や Modules はこのファイルで import されているため,ツールのバージョンも正しく管理してくれます.
まとめ
開発用のツール(CLI)の管理をする gex を紹介しました.覚えるのは gex –add くらいで,管理自体は dep や Modules に丸投げするのでどんな環境にも気軽に導入できるはずです.
こういうのはいずれ Modules にも取り込まれるとは思いますが,それまでのつなぎに是非どうぞ.
また,gex を実際どのように使っているかなど,Wantedly Techbook 5 で紹介しています.技術書典5 き20で頒布予定なので,そちらもよろしくおねがいします