Help us understand the problem. What is going on with this article?

gex で Go プロジェクトの開発用ツールの依存を管理する

More than 1 year has passed since last update.

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 .

これを毎回やるのはさすがに….
自分は Makefiledefine を利用していました.

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.tomlMakefile 両方に記述が必要になる冗長さ」と,「そもそも Makefile が複雑過ぎてメンテしづらい」などの問題を抱えています.

その他の開発用ツール管理手法

この問題に対処するためのツールはいくつか存在します.

しかし,いずれも「vendoring したパッケージからバイナリを生成したい」程度の欲求に対し規模が大きすぎる印象を受けました.また,dep や Modules の「他ツールからの migration が容易」というメリットを殺してしまう可能性もあります.

gex による開発用ツール管理

ここまでで「開発用ツールの管理」に欲しいものを整理すると,以下の要件を満たせれば良さそうです.

  • dep や Modules に相乗りできる(独自の管理機構は持たない)
  • とりあえずバイナリを簡単に生成できれば良い

上述の条件を満たすような gex という薄いツールを作りました.

たとえば mockgen を依存に追加したければ次のコマンドを実行するだけです:

$ gex --add github.com/golang/mock/mockgen

mockgen を実行したいときは,gex mockgen ... のようにするか,add 時に $PWD/bin に生成されたバイナリを直接叩きます.また,gex --build でバイナリを再生成できます.direnvexport PATH=$PWD/bin:$PATH した Makefile との相性も良いです.

gex がどのようにしてバイナリを管理しているか

gex は cmd/go: clarify best practice for tool dependencies · Issue #25922 · golang/go で紹介されていたものを愚直に実装しています.

gex --add <path/to/cmd> で以下のような挙動をします.

  1. go get ... もしくは dep ensure -add ... が実行される
  2. go build -o ./bin/<cmd> <path/to/cmd> が実行される
  3. 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で頒布予定なので,そちらもよろしくおねがいします :v:

wantedly
「シゴトでココロオドル」ためのビジネスSNS「Wantedly」の開発・運営をしています。
https://wantedlyinc.com/ja/presentations
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away