ゆるいBazelルールの歩き方

Bazelは再現性に優れた、強力なビルドツールとして利用することができます。実際、会社ではビルド環境としてBazelが使われています。弊社では使用されている実績は以下の記事が詳しいです。

- [Bazel で複数の values.yaml のイメージタグを一括置換する](https://medium.com/mixi-developers/replace-values-yaml-with-bazel-977d0ceffc09)
- [Bazelとモノレポ](https://medium.com/mixi-developers/bazel%E3%81%A8%E3%83%A2%E3%83%8E%E3%83%AC%E3%83%9D-b901ffba61ce)
- [GoとBazel](https://medium.com/mixi-developers/go-project-with-bazel-ad807ba19f5c)

私もプロジェクト内でシェルスクリプトであったり、MakefileにやらせていたことをBazelのルールに一気に置き換えたりしてBazelに慣れてきました。
これを読んだみなさんが、Bazel ルールを書きたいけど、どこから手を付ければいいのか？となった時に参考になれば幸いです。

## Bazelで出来ること

ソフトウェアのビルドに使われることの多いBazelですが、以下のことがまず基本的にできます。

- ファイル/アーカイブのダウンロード
  - [repository_rule](https://docs.bazel.build/versions/master/repo/)
- ファイルの実行
  - [actions.run](https://docs.bazel.build/versions/master/skylark/lib/actions.html#run)
- シェルスクリプトの実行
 -  [expand_template](https://docs.bazel.build/versions/master/skylark/lib/actions.html#expand_template) を使って、アクションが実行された際にマクロに沿ってシェルスクリプトを実行することが多いです。

これが出来ることに伴って、これらを組み合わせてビルドルールを記述することで何かを生成してそれを含めるようなことをBazelにやらせることができます。

## BazelルールのQ&A

### 作ったルールが合っているか分からない、どうすれば？

Bazelルールはテストを書くことができます。書き方は[公式ドキュメント](https://docs.bazel.build/versions/master/skylark/testing.html#minimal-example)がとても分かりやすい内容で取り上げているのでそちらを参考にしてみてください。

出力を元にしたテストを書くのは難しいので、最初はルールが自分の予期したものであるか（作成されたシェルスクリプトが合っているかどうか）を確認するところからはじめて良いと思います。

### ルールのドキュメントを書きたい、どうすれば？

[bazelbuild/stardoc](https://github.com/bazelbuild/stardoc)を使いましょう。Starlarkで記述したルールのコメントや、`attrs`の`doc`などで明示したドキュメントを元にドキュメントを生成することができます。

### ルールを作るために見た方が良い場所は？

自分の知っているプロジェクトでBazelが使われていたらそれを参考にしましょう。

もしない場合は、[bazelbuild/examples](https://github.com/bazelbuild/examples)に様々なBazel ルールが存在します。主に**ビルドツール**として使う際のルールが多いです。これらのルールにあるように、きちんと出力を定義することで、Bazelの特徴であるキャッシュなどの恩恵を十分に受けることができます。

実際に私が書いたルールはMakefileの代替として使うぐらいの気持ちで作成したもの（例: [atpons/rules_helm3](https://github.com/atpons/rules_helm3)）が多いです。

### ワークスペース内に書き込みしたいんだけど？

Bazelの良いところは再現性ですが、ビルドツールとして出力を使い回すのではなく、リポジトリにその出力を含めたい場合などにはどうすれば良いでしょうか？

`bazel run`で実行されるexecutableなoutputは、Bazelによって`BUILD_WORKSPACE_DIRECTORY`という環境変数がつきます。これを使ってサンドボックス内からワークスペースに入ることができます。

そこで何かを生成し、さらにそれをまたBazelで使用する場合はBUILDファイルにも手を加えてBazelに認識させる必要があります。（手で書いてもいいですし、それ用のツールを書くのもありだと思います）

## Bazelの情報を集めるには？

私がBazelを主にGoで使っているので、GoでBazelを使っている大規模OSSを読んでいます。

Bazel自体の最新情報はGitHubの[bazelbuild](https://github.com/bazelbuild)を見ています。[Slackコミュニティ](https://slack.bazel.build/)もあるのでそれを参考にするのも良いと思います。

### awesome-bazel

https://awesomebazel.com/

Bazelを利用しているプロジェクトや、ビルドルールを眺めることができます。

### kubernetes/kubernetes

KubernetesはBazelを使ってビルドされています。`hack/`ディレクトリにBazelルール用のシェルスクリプトが入っています。

Go Modulesのvendorや、外部バイナリの外部からの注入などが参考になりました。Buildozerの使い方なども参考になります。

https://github.com/kubernetes/kubernetes/blob/61dc69ac2cf936ccafb51e9d946097b62b4ead07/hack/update-bazel.sh


### atlassian/bazel-tools

Atlassianが使用しているルールやツールのリポジトリです。主にGolangで使用されているようです。

Bazelの不便なところとして、`bazel run`のターゲットを並列に実行する機能がまだありません。（[issue](https://github.com/bazelbuild/bazel/issues/10855)）
Bazelを依存が少ないMakefileの代替としてゆるく使っている時には不便です。

それを解決する[multirun](https://github.com/atlassian/bazel-tools/tree/master/multirun)というルールを作ってくれています。私は[rules_docker](https://github.com/bazelbuild/rules_docker)と組み合わせて、複数のコンテナレジストリにプッシュするターゲットを作った時に、同時に実行もできるようにしておきたいときに使いました。

```python
container_push(
    name = "push-region-1",
    format = "Docker",
    image = ":image",
    registry = "asia.gcr.io",
    repository = "example/example",
    stamp = True,
    tag = "{BUILD_TIMESTAMP}",
)

container_push(
    name = "push-region-2",
    format = "Docker",
    image = ":image",
    registry = "asia.gcr.io",
    repository = "example/example",
    stamp = True,
    tag = "{BUILD_TIMESTAMP}",
)


multirun(
    name = "push",
    commands = [
        "//tool:push-region-1",
        "//tool:push-region-2",
    ],
    parallel = True,
)
```

## 最後に

Bazelを全体に導入するのはなかなか難しいので、最初は特定の部分で使用していって、そのうちCIをBazelだけで回せるようにしていくやり方が一番導入しやすいと思います。

その上で、今までMakefileなどで行っていたワークフローをBazelに移行していくことでビルドの再現性や、外部への依存を少しでもわかりやすくしていくことが良いと思います。

そのために"ゆるい"Bazelルールを書いてBazelに慣れていくことをおすすめします。この記事がその参考になれば幸いです。