TL;DR
-
go generate
で使うものや linter などの開発用ツールもバージョン固定したいよね - gex を使えば簡単にできるよ
- 独自の管理機構を持たない(dep / Modules に任せる)から既存のワークフローへの導入も楽だよ
依存管理ツールと開発用ツール
Go における依存管理ツールは現状,dep が広く使われています.また,Go 1.11 からは Experimental ですが Modules も利用可能になっています(Modules については『Go 1.11 の modules・vgo を試す - 実際に使っていく上で考えないといけないこと』という記事で軽く感想を書いたので,よければそちらも御覧ください).
これらの依存管理ツールはコード中で import
するパッケージをいい感じに管理・バージョンロックをしてくれます.
開発用ツールの管理
一方で,開発時に利用するような実行可能ツールについては関心の外です.
よくある例としては
あたりでしょうか.これらが正しくバージョン固定されないことで,いろいろな問題が発生します.
- チーム開発時に
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/<cmd> <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で頒布予定なので,そちらもよろしくおねがいします