MJHD

エモさ駆動開発

golangとbazelで作るいい感じなビルド環境

Makefileに一部間違いがあったため、修正しました

 

bazelはビルドシステムの一つで、Googleが開発し社内でも使用している。makeなどの従来のビルドシステムと比べると、簡潔な設定で幅広い言語・ビルド対象に対応していて、ビルドが必要なものはほとんどbazelで解決できる。

Bazel - a fast, scalable, multi-language and extensible build system" - Bazel

 

例えば、Goのビルドと同時にdockerイメージもビルドしたい、protoもビルドしたいといった場合、通常であれば個別にビルドコマンドを打つ必要があるのだが、この作業をbazelに一本化でき、とても便利。

必要な物

以下のものを使用する:

1. bazel

2. rules_go

3. gazelle

4. rules_docker

rules_goはGo言語向けbazelルール。go_libraries、go_test、go_binaryなどを提供し、Go言語に関するビルド、テスト処理を記述できるようになる。

gazelleはrules_goを用いたビルド設定を自動生成するツール。importなどを見て、自動で依存を解決し、ビルド設定を吐き出してくれる。

rules_dockerは、bazelを用いてdockerイメージをビルドしたり、プッシュすることができるルール。今回はgoのバイナリを含めたイメージをビルドし、実行する。

オススメの構成

以下にサンプルのレポジトリを作成した。

GitHub - mjhd-devlion/bazel-gazelle-sample

ディレクトリ構造としては以下のようになっており、cmd以下のsampleが、今回動かす対象のプログラム。pkg/sampleパッケージを読み込み、Hello, Worldを出力する。depによるvendoringも行いたかったため、いつもよりキレイめにHello, Worldを表示している。

./
├── BUILD.bazel
├── Gopkg.lock
├── Gopkg.toml
├── Makefile
├── WORKSPACE
├── cmd
│   └── sample
│       ├── BUILD.bazel
│       └── main.go
├── pkg
│   └── sample
│       ├── BUILD.bazel
│       ├── sample.go
│       └── sample_test.go
└── vendor

また、以下のようなMakefileを用意している。make gazelleコマンドを実行することでプロジェクト全体のBUILD.bazelファイルを更新し、その後make runすることによってプログラムを実行することができる。

.PHONY: run
run:
	bazel run //cmd/sample:go_image

.PHONY: build
build:
	bazel build //cmd/sample:go_image

.PHONY: test
test:
	bazel test //...

.PHONY: gazelle
gazelle:
bazel run //:gazelle
bazel run //:gazelle -- update-repos -from_file ./Gopkg.lock

ここで一つ注意点があり、Makefileのgazelleの項に、update-reposという記述をしている。

gazelleとvendoringは相性が悪く、特にprotoをビルドする際など、バージョンの違いによる問題が発生したりする。

ここ(go_proto_library dependency overhaul · Issue #1548 · bazelbuild/rules_go · GitHub)に詳しく載っているのだが、protoをビルドする際にrules_goが提供する依存のバージョンがvendoringしているものと衝突した場合、ビルド時、または実行時のエラーが発生する。

これを解決するためには、WORKSPACEにgo_repositoryルールを用いて依存するライブラリのバージョン指定をする必要があるのだが、手作業でやるのは流石に辛い。

ということで、実はgazelleはupdate-reposという「Gopkg.lockとgo_repositoryを同期する」プログラムを用意してくれている。(つい先日知った)

サンプルでは、このプログラムを用いてdepの解決した依存に、bazelの依存を同期し、安全にビルドが行えるよう、update-reposをgazelle実行時に呼ぶような設定を行っている。

微妙な点

実はvendorディレクトリを削除しても動く。依存はbazelがビルド時にダウンロードし、その後も管理をしてくれるため、vendorディレクトリの中身はビルド時に使用されていない。二重管理になってしまうため、vendorディレクトリを削除したいのだが、vendorディレクトリが無いとエディタが真っ赤になる…。

何かもっと良い方法をご存じの方、是非コメントいただけると嬉しいです。