またね、Bazel 。ただいま、シェルスクリプト。

この記事は株式会社ビットキー Advent Calendar 2022 17日目の記事です。
BKP&HubCoreModule でソフトウェアエンジニアをやっている @nrnrk が担当します。

## まえがき

Bazel はクールな理念を持ったナイスなツールです。大好きです。
ただ、私のチームにおいては、ミスマッチな点があったため、泣く泣くお別れしました。その話について書きます。

## 想定読者

* Bazel を入れてみたけどしっくり来ていない人
* Bazel の導入を検討中の人

## Bazel とは

[Bazel](https://bazel.build/) とは、多言語のマルチプラットフォームのプロジェクトをビルド・テストできるツールです。ビルドの **再現性を高い精度で保ち** つつ、 **ビルド時間もキャッシュを用いて短く** できるというのが強みです。

つまり、Java、C++、Go、Android、iOS などを Windows、macOS、Linux 上で Bazel によりビルド・テストが可能ですし、高度なキャッシュによって、同じビルドの再実行を減らすことができます。例えば、分散キャッシュ(Remote caching)の機構では、チーム内の別の人が実行したビルド結果さえもキャッシュとして共有することもできたりなんかします。

## Bazel 導入までの経緯

当時、私のチームでは、次の問題を抱えていました。

### 問題1: レポジトリが多く管理が大変

開発メンバーは少ないときには2,3人になるくらい少なかったのですが、レポジトリの数は10以上ありました。また、レポジトリ間の依存もあったため、これらの管理コストが高くなっていました。

### 問題2: ビルドが遅い

レポジトリの多くはGo言語のプロジェクトでした。開発初期には気にならなかったビルド時間がだんだん気になるようになってきました。特に `go generate` でコード生成する箇所は非常に遅く開発速度を下げる要因になっていました。

### 問題3: 開発環境構築が難しい

レポジトリの数も増え、それぞれで利用するツールや言語の数も増えたことで開発環境構築の難易度も上がってしまっていました。 [Ansible](https://www.ansible.com/) を利用した開発環境構築を試したときもありましたが、検証を含めたメンテナンスコストが高く、開発メンバーの微妙に違う好みを反映するのが難しい問題があり、長続きはしませんでした。

### Bazel の導入

Bazel でこれらの課題を解決できるのではないかと思い、導入に踏み切りました。


大部分のレポジトリをモノレポ化[^monorepo]し、依存があってもレポジトリを管理しやすくしました。そして、Bazel でのビルドを利用することでキャッシュを効くようにし、また、(一部の例外を除き)Bazelコマンドだけがあればビルドができる状態になりました。


## なぜ Bazel を外すことにしたのか

Bazel の導入は一定の成果を上げましたが、最終的に利用しないことにしました。

### さよならの理由1: ビルドは想定よりも速くならなかった

これは完全に私の計画ミスでした。当時のビルド時間のボトルネックは、 [go-swagger](https://github.com/go-swagger/go-swagger) のコード生成でした。Bazel のキャッシュによるビルド高速化の恩恵が特に得られず、全体のビルドの高速化は小さなものになってしまいました。[^go-swagger-rule]

### さよならの理由2: rule がない場合は自分で作る必要があった

[rule](https://bazel.build/extending/rules) というのは、Bazel におけるビルドを構成するアクションで、通常のコンパイルもこの rule の一種として扱われます。[gomock](https://github.com/golang/mock) や [go-swagger](https://github.com/go-swagger/go-swagger)　などには、デファクトスタンダードな rule が存在していませんでした。したがって、これらを自ら作る必要があり、メンテナンスをする必要もありました。

### さよならの理由3: キャッチアップすることが多い

Bazel はハードルが高い、と感じる一つの原因ですが、Bazel には独自の概念がたくさんあります。少し使う分には気にならなくても、メンテナンスしていくレベルになると途端にハードルが上がります。よく整理された概念で勉強にはなるのですが、キャッチアップコストはやはり高くなってしまいます。[^bazel-hard]

### さよならの理由4: 開発メンバーの開発PCが同じ

開発メンバーは、現状 M1 Mac を全員が利用しており、開発プラットフォームの差分がありません。そのため、Bazel の強みである異なるプラットフォームでも同じビルドを手軽に再現できる、という強みも享受することができませんでした。


## after Bazel

Bazel を辞めた後は、ビルドに必要な処理はシェルスクリプトで管理しています。Bazel 自体や関連する rule のメンテナンスがなくなり、ビルド関連がとてもシンプルになりました。[^why-shell]

今は[devcontainer](https://code.visualstudio.com/docs/devcontainers/containers) などのコンテナ開発を検討中で、これが私のチームにとっては適切な解決策になるのではないかと期待しています。モノレポ化になったことで、[問題1: レポジトリが多く管理が大変](https://qiita.com/nrnrk/private/99ba34726240f12acd55#%E5%95%8F%E9%A1%8C1-%E3%83%AC%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%8C%E5%A4%9A%E3%81%8F%E7%AE%A1%E7%90%86%E3%81%8C%E5%A4%A7%E5%A4%89) は解消され、[問題3: 開発環境構築が難しい](https://qiita.com/nrnrk/private/99ba34726240f12acd55#%E5%95%8F%E9%A1%8C3-%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E3%81%8C%E9%9B%A3%E3%81%97%E3%81%84)もコンテナ開発で解消されそうです。[問題2: ビルドが遅い](https://qiita.com/nrnrk/private/99ba34726240f12acd55#%E5%95%8F%E9%A1%8C2-%E3%83%93%E3%83%AB%E3%83%89%E3%81%8C%E9%81%85%E3%81%84)は、個別のコード生成についてそれぞれ別のアプローチが必要になりそうです。

## まとめ

* Bazel は超かっこよくて素晴らしいツール
* 適材適所で考えると、Bazel が too much になるケースがある
    * マルチプラットフォームでのビルドは重要か？
    * rule のメンテンナンスコストは見合っているか？
    * チームメンバーが Bazel をキャッチアップするモチベーションがあるか？
* 開発環境を揃えることが目的ならコンテナ開発も選択肢として良いかもしれない

Bazel とまた出会える日を夢見て記事の終わりとさせていただきます。

明日の18日目の株式会社ビットキー Advent Calendar 2022は、Connect Firmwareの@NaotoFujihiroが担当します。


[^monorepo]: モノレポ化に関しては、[Go Conference での発表](https://speakerdeck.com/bitkey/goconference-ren-sheng-iroiromofalserehomoiroiro)でお話させていただきました。
[^go-swagger-rule]: もしかしたら、go-swagger の rule を非常に上手く作成することで部分的なキャッシュなども可能かもしれません。
[^bazel-hard]: 自分がキャッチアップコストが高いと感じた点について記載します。Bazel 導入を検討されている方は、実際に小さなプロジェクトで試してみるとより具体的に体感できるかと思います。 ① 汎用的でスコープが広く、[用語や概念](https://bazel.build/reference/glossary)が多い ② ビルド再現性のために、ビルド時には切り出せれた環境(サンドボックス環境と呼ぶ)でビルドが走るので、トライアンドエラーの際の体験が通常のビルドと違う(ログの見方も注意が必要) ③ 自分がやりたいビルドをやっている先人が少ないことが多い(Googlability が高くない)
[^why-shell]: Make も検討しましたが、以前から利用していたシェルスクリプトでも特に困ったことはないという理由でシェルスクリプトにしています。ご意見やご提案などあればコメントいただけると嬉しいです。