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