概要
Bazelを導入する過程で学んだTipsをまとめます。
環境
- Bazel v4.2.2
Tips
bazeliskを使う
Bazelのバージョンを環境別に管理したい場合、bazelbuild/bazeliskを使うのが良いです。
.bazelversion
に以下のようなバージョンを書いておけばそのバージョンでビルドしてくれます。
4.2.2
インストールはhomebrewでできます。
$ brew install bazelisk
macOSであればbazel
のエイリアスが勝手につくので、bazel
コマンドで中身はbazelisk
が実行されます。
bash completion
1点課題なのがbash completion(Tab補完)の設定が大変な点です。
通常のBazelであれば
Command-line completion - Bazel main
の通りにすればいいですが、bazeliskはbazel自体をビルドしてbash completeファイルを生成する必要があります。
$ git clone https://github.com/bazelbuild/bazel $ bazel build //scripts:bazel-complete.bash
このビルドがめちゃくちゃ長いです。
参考
.bazelrcの活用
bazelで毎回多くのオプションを指定しなくて済むよう、常にセットしたいオプションは.bazelrc
に書くようにします。
過去の解説でもちょこちょこ使っていますね。
継承という概念
Bazelを使ってみる その5(リモートキャッシュ) - Carpe Diem
でも軽く説明しましたが、以下のコマンドはすべてbuildを継承しているので、別で追記する必要はありません。
- test
- run
- clean
- mobile-install
- info
- print_action
- config
- cquery
- aquery
なので.bazelrc
に
build --show_timestamps
と書けば、
bazel build
だけでなくbazel run
やbazel test
の際もtimestampが表示されるようになります。
coverage
についてはtest
を継承しています。
CIのときだけ実行したい
例えば
- CIの時だけremote cacheを使いたい
- CIはLinuxなのでplatformパラメータを指定したい
といったケースがあると思います。
その場合は
# for Local build --show_timestamps --verbose_failures test --test_output=errors --test_verbose_timeout_warnings # for CI build:ci --platforms=@io_bazel_rules_go//go/toolchain:linux_amd64 build:ci --remote_cache=https://storage.googleapis.com/xxx --google_credentials=xxx.json
のように:
の後に任意の文字列を付けておきます。今回だと:ci
ですね。
そして実行時にci
の文字列をconfig
フラグで指定すると、:ci
が付いたオプションのみ適用してくれます。
$ bazel build --config=ci //...
参考
Stamping(ビルド成果物への埋め込み)
過去の記事でも書いたことがありますが、Goはldflags
を使うことでビルド時に変数に値を埋め込む事が可能です。
一般にこれをStampingと言い、Bazelでもその仕組みが用意されています。
例えば
. ├── BUILD.bazel ├── Makefile ├── WORKSPACE ├── alive │ ├── BUILD.bazel │ └── alive.go ├── cmd │ ├── BUILD.bazel │ └── main.go └── go.mod
という構成でalive packageに以下の変数を用意していた場合、
package alive var ( BuildVersion = "none" Timestamp = "none" )
BUILD.bazelの設定
そのalive packageのBUILD.bazel
にx_defs
を設定するか、
go_library( name = "alive", srcs = ["alive.go"], importpath = "github.com/jun06t/bazel-sample/ldflags/alive", visibility = ["//visibility:public"], x_defs = { "BuildVersion": "{GIT_COMMIT}", "Timestamp": "{DATE}", }, )
もしくはcmd/BUILD.bazel
の方でpathをきちんと付けてx_defs
を設定します。
go_binary( name = "cmd", embed = [":cmd_lib"], pure = "on", visibility = ["//visibility:public"], x_defs = { "github.com/jun06t/bazel-sample/ldflags/alive.BuildVersion": "{GIT_COMMIT}", "github.com/jun06t/bazel-sample/ldflags/alive.Timestamp": "{DATE}", }, )
どちらか片方でOKです。
もしかしたらcmd/main.go
だけいじってalive/BUILD.bazel
の方でx_defs
を指定すると、timestamp変わらない?と思いましたがちゃんと新しい値で埋め込み直してくれました。
スクリプトの用意
次にスクリプトを用意します。
#!/bin/bash echo GIT_COMMIT $(git rev-parse --verify HEAD) echo DATE $(date '+%Y/%m/%d %H:%M:%S %Z')
実行して動作確認しておきます。
$ ./tag.sh GIT_COMMIT 53be3f01ff58e5ed1c973f107f96020f1069ae13 DATE 2022/01/18 14:54:17 JST
ビルド方法
最後にビルド時に--stamp
フラグを付けます。
$ bazel build --stamp --workspace_status_command=$(pwd)/tag.sh
これで埋め込むことができました。
動作確認
$ bazel run --stamp --workspace_status_command=$(pwd)/tag.sh //cmd
APIを叩いてみます。
$ curl localhost:8080/alive {"version":"53be3f01ff58e5ed1c973f107f96020f1069ae13","timestamp":"2022/01/18 14:55:02 JST"}
期待する値が埋め込まれていることが確認できました。
ちなみにtag.sh
の方を修正しても肝心のビルド対象が変更されていないので、キャッシュを使ってしまい期待する挙動を確認できないため都度bazel clean
した方がよいです。
サンプルコード
ldflagsのサンプルコードはこちら↓
ビルド対象をフィルタする
Bazelはキャッシュを活用して差分ビルドする特徴がありますが、例えば「goバイナリはビルドしたいけど、protobufは生成したくない」というように単純にビルド対象を絞りたいケースがあると思います。
ディレクトリ分けで頑張る方法もありますが、Bazelはbazel query
というコマンドが便利です。
例えばgo_binary
だけ抽出したい場合は以下のように書きます。
$ bazel query 'kind("go_binary", //...)' //server:server //client:client
queryで使える関数はkind, attr, labelsなど多数ありますが、詳細は以下を確認してください。
コミット差分のみ対象に
さらにコミットした差分から絞りたい場合はBazel公式のスクリプトを参考に以下のようなスクリプトを用意すると良いでしょう。
#!/usr/bin/env bash set -u COMMIT_RANGE=${COMMIT_RANGE:-$(git merge-base origin/main HEAD)".."} files=() for file in $(git diff --name-only ${COMMIT_RANGE} ); do files+=($(bazelisk query --keep_going --noshow_progress $file)) done buildables=$(bazelisk query \ --keep_going \ "kind(${TARGET:-go_binary}, rdeps(//..., set(${files[*]})))") echo ${buildables}
Makefile
を以下のようにし
build: bazel build -- ${BUILDABLE}
実行時に指定します。
$ target=`TARGET=go_binary ./scripts/buildable.sh` $ make build BUILDABLE="${target}"
参考
goarchなどを設定するとgo_binaryで出てこない?
go_binaryでクロスコンパイルのためのプロパティであるgoarch
やgoos
、pure
などを入れているとbazel query 'kind(go_binary, //...)'
では出てこなくなりました。
この場合は
$ bazel query 'attr(goarch, "amd64", //...)'
など別の関数で一覧化することができます。
依存解決できない時はvendorディレクトリも含めてしまう
gazelleはgo.modがあるライブラリについては適切に依存関係を解決してくれますが、
- 古くてgo.modに対応していない
- go.modのバージョン規定を満たしていない
といったライブラリについては依存関係がおかしくなるケースがあります。
その場合手動で1つ1つの依存関係を解決する必要があるのですが、うまく行かない時は中々抜け出せない沼にハマってしまいます。
そうなった場合は一度
$ go mod vendor
でvendorディレクトリを入れて、vendorディレクトリをrulesの依存関係に組み込んでしまうと良いです。
最終手段のようなものではありますが、こうすることでずっと解決できない依存関係と戦うよりは前に進めることができます。
go_repositoryのsumとかはどうやって出すの?
go_repositoryで手動で特定のバージョンを指定したい場合は
$ go mod download -json <module>@<version>
とするとsumなどが返ってきます。
$ go mod download -json go.uber.org/zap@v1.20.0 { "Path": "go.uber.org/zap", "Version": "v1.20.0", "Info": "/Users/jun06t/.go/pkg/mod/cache/download/go.uber.org/zap/@v/v1.20.0.info", "GoMod": "/Users/jun06t/.go/pkg/mod/cache/download/go.uber.org/zap/@v/v1.20.0.mod", "Zip": "/Users/jun06t/.go/pkg/mod/cache/download/go.uber.org/zap/@v/v1.20.0.zip", "Dir": "/Users/jun06t/.go/pkg/mod/go.uber.org/zap@v1.20.0", "Sum": "h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc=", "GoModSum": "h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=" }
参考
まとめ
BazelのTipsをまとめてみました。
運用を続けてまた新たに見つけたら追記していこうと思います。