Goでリポジトリ単位でモジュールのバージョン管理をする方法
はじめに
こんにちは @glassmonkeyです。
先日めでたく1.22がリリースされましたね。おめでとうございます。
個人的にはfor loopの修正が無限にはまってたので嬉しい限りです。
今回は記事ではJavaScriptでいうところのdevDependenciesに相当することをGoでできないか?ということを調べて同僚に共有することがあったので、そのモチベーションでまとめました。
もしかしたら一部情報が古い可能性がありキャッチアップ不足の可能性があるので、その際はご指摘いただけると幸いです。1.22での情報です。
基本的には公式docのHow can I track tool dependencies for a module?に記載されている内容となります。
モチベーション
Goでの開発の際に開発用CLIをinstall することは度々あると思います。
よくある例だと
mockgenやgoimportsなどが挙げられます。
基本的にはgo installでローカルマシンにインストールして活用するで問題ないと思います。
しかし、開発者が複数いる場合やCI/CDでの利用を考えると、バージョン管理をしておくことが望ましいです。
またgqlgenのようにCLIとライブラリ活用を同時に行いたい場合もあります。
また、リポジトリ単位でバージョン管理をしておくとDependabotなどを使って常に最新のバージョンを利用することができます。
方法
サンプルにglassmonkey/go-module-sampleも用意してるので良かったらご活用ください。
blank importをする
以下のな形でblank importをすることで、go.modに依存関係を追加することができます。
//go:build tools
package tool
import _ "golang.org/x/tools"
go:buildは1.17から導入されたもので、昔は// +buildという形でした。
このフラグはBuild constraintsと呼ばれるものでBuild条件を変更することができます。
今回の場合だとgo:build toolsこの指定により通常のビルド時には無視されるようになります。(go build -tags toolsとtagを指定することでビルドに含まれるようになります)
詳しくはBuild constraintsをご覧ください。
有名どころだと、このテクニックはopen-telemetry/opentelemetry-collector-contribなどでも使われいます。
import (
_ "github.com/Khan/genqlient"
_ "github.com/client9/misspell/cmd/misspell"
_ "github.com/daixiang0/gci"
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
_ "github.com/google/addlicense"
_ "github.com/jcchavezs/porto/cmd/porto"
_ "github.com/jstemmer/go-junit-report"
_ "go.opentelemetry.io/build-tools/checkfile"
_ "go.opentelemetry.io/build-tools/chloggen"
_ "go.opentelemetry.io/build-tools/crosslink"
_ "go.opentelemetry.io/build-tools/issuegenerator"
_ "go.opentelemetry.io/build-tools/multimod"
_ "go.opentelemetry.io/collector/cmd/builder"
_ "golang.org/x/tools/cmd/goimports"
_ "golang.org/x/vuln/cmd/govulncheck"
_ "gotest.tools/gotestsum"
)
Makefile中ではgrepコマンドを使ってパッケージ名を取得しています。
TOOLS_PKG_NAMES := $(shell grep -E $(TOOLS_MOD_REGEX) < $(TOOLS_MOD_DIR)/tools.go | tr -d " _\"")
実行する
blank importを追加したら、その配下でgo runコマンドを実行することで、go.modに依存関係配下でのバージョンで実行することができます。
例えばgolang.org/x/toolsを管理下に入れた場合でstringerを使う場合は以下のようになります。
go run golang.org/x/tools/cmd/stringer -type=Pill
(+α) go generateで管理する
Makefileなどで実行コマンドを各種用意することもあるかと思いますが、個人的にはgo generateで標準化することもおすすめです。
例えば以下のように実行コマンドを適当なファイルに記載しておくとgo generate ./...で一元化できるので便利です。
//go:generate go run golang.org/x/tools/cmd/stringer -type=Pill
私の場合だとgomockを使う場合にファイルごとに以下のような記述をしておくと go generate ./...で一括で実行できるようにすることが多いです。
//go:generate go run go.uber.org/mock/mockgen -source=$GOFILE -package=mock_$GOPACKAGE -destination=mock_$GOPACKAGE/mock_$GOFILE
例えば上記のように記述しておくと、以下のようなディレクトリ構造になります。
├── foo
│ ├── bar.go
│ ├── baz.go
│ └── mock_foo
│ └── mock_bar.go
おわりに
さすがにhackすぎるのでtrack tool dependencies in go.modというプロポーサルが出ており、1.23での対応が期待できそうで楽しみです。
もし参考になったならいいねや宣伝していただけると励みになります。
あまりGoのことはつぶやかないのですが、@glassmonkeyもフォローしていただけると嬉しいです。
Discussion