2012-08-29

chromiumの継続的インテグレーション

最近はどうもJenkinsとかTravisCIとかいうのが話題みたいなのだが、使ったことがないのでよくわからない。だがどうも漏れ聞く話を見ていると、こういうのは継続的インテグレーション(CI)と呼ばれていて、だいたい自分の社内プロジェクトでも似たようなことをやっているらしい。そこで、Chromiumがどういう環境でCIしているか、ということを簡単にまとめてみたい。あらかじめ書いておくと、名前が違うだけでだいたい普通です。

BuildBot
Chromiumは普通のクライアントプログラムなので、ビルド環境の想定がけっこう複雑だ。Windows/Mac/Linux/ChromeOS(最近はAndroidなどのモバイル環境)のようにプラットフォームは多岐にわたるし、同じプラットフォームでも様々なビルドコンフィグレーションがある。テストも数が多く、ローカルに走らせておくのは時間がかかる。

BuildBotは様々なビルド環境で実際にビルドし、テストを実行するフレームワークだ。複数のマシンインスタンスがそれぞれ微妙に違ったコンフィグレーションでビルドを行い、テストを実行してくれる。どこかに問題があれば即座に問題を報告し、ツリーを閉じる。

ツリーが閉じられると基本的にはだれもコミットができなくなる。問題のレポートは、問題となりそうな……つまりそれまでビルドが通っていたのに新しくビルドが壊れる状態になるあいだにコミットされたパッチの作者(たち)に送られる。問題を引き起こした開発者は、単純ミスですぐ直せそうなら自分で直すが、たいていの場合はrevertする。

鵜飼さんの講演にもあったように、Chromiumでは平日では平均で7分半にひとつといった頻度でパッチがコミットされている。このため、一つ一つのパッチをすべて段階的には確認するほどのリソースはかけず、複数のリビジョンをある程度はまとめてBuildBotで確認している。頻度が高いというのは、ツリーが閉じられたらものの数分で誰かが気づくということだ。ツリーを壊すと「お前の変更で壊れてるっぽいんだけど」と文句を言われ、せっつかれることになる。そんなときは冷や汗を書きながら、ごめんごめん、いまrevert中なんだ、などと答えたりする……。

sheriff
たいていの場合、ツリーの閉じられ方からはだれのどのパッチが問題なのかはすぐわかる。開発者は、壊れた状況から自分ぽいな、ということはすぐわかるし、壊したらすぐ自分で対処する。……とはいえ、完全に開発者の自治のみというわけでもなく、BuildBotとツリーの状態を守る専任もおり、sheriffという。

sheriffは専任といっても、実際にはローテーション制が採用されていて、担当になると数日間、ツリーの監視を行う。デベロッパは世界中にいるので、ローテーションも在籍地の時差を考慮して複数人が割り当てられており、さすがに夜寝てるあいだとかにはほかのsheriffが仕事をしてくれる。

sheriffの仕事は実際問題としてはそんなに忙しくはない。ふだんはのんびりと、メインの仕事をしていたりする。だが、ひとたびツリーが閉じたら問題を調べ、だれのどのパッチが問題を引き起こしたのか同定する。そしてその開発者とコンタクトを取って、修正が容易なら修正するし、ダメならrevertする。

炭鉱の庭師という記事を読んで、gardenerはsheriffより大変だなと思った。ツリーのcloseは自動的なためsheriffの仕事はリアクティブだし、上にも書いたように、多くの場合には何がツリーを閉じたのかは自明なので、負荷は大きくない。ごくまれにコンパイラのバグが顕在化して大混乱に陥ることもあるけれど……。sheriffはどちらかといえば、「ツリーが閉じててコミットできん!」と怒れるほかの開発者の代理に文句をいう仕事であり、仮に当該の開発者はもう家に帰ってしまったとかいった理由で反応がない、などの理由で連絡が取れない場合に責任をもってrevertする仕事だ。

trybots
BuildBotとは別にtrybotというのもある。trybotは、自分のパッチをコミットに確認するための仕組みだ。trybotも同じように、様々なビルド環境とコンフィグレーションを持っていて、それぞれ名前がついている。winとかmacとかlinuxとかlinux_chromeosとか。そしてそれぞれ適当なリビジョンのソースツリーを手元に持っている。開発者がコマンドを発行すると、パッチを適用し、実際にビルドしてテストを走らせ、結果を教えてくれる。

コミット前に適宜trybotを走らせておけば、パッチの健全さはわかる。Chromiumの使っているRietveldには改造が施されていて、trybotを走らせた場合にはその実行結果が表示され、わかるようになっている。

trybotはBuildBotほどバリエーション豊かじゃないし、ごくまれにtrybotをすり抜ける問題もある。だが99%の問題はtrybotでふるい落とすことができると思う。パーセンテージの数字は適当 :-P

commit queue
trybotは確認のためのインフラであって、それ自体は自動化はされていない。trybotの手間を惜しんでしまうひとがいることもありうるし、まれに必要なtrybotを走らせておらず、うっかりビルドが壊れるということがある。たとえば、ChromeOSでしか使わないと思っていたら、とある特殊なコンフィギュレーションのMacでも使われることがあることを見落としていたとか、標準的なC++を自分では書いているつもりがVCでだけコンパイルエラーになるとか。私もいちど、ごく普通にあるクラスにkInvalidIDというクラス変数を足したところ、実はCarbon内でkInvalidIDは#defineされているためMacでだけコンパイルエラーになる、などという自体に遭遇してひっくり返りそうになったことがある。そんなの知らんがな……。

閑話休題。

そんなわけで、commit queue (CQ)がある。開発者がパッチにフラグをつけるとCQはそのパッチをひと通りのtrybotにかける。それで全部でうまく行ったものだけがコミットされる。これによってくだらない問題を回避できる。上で書いたMacの問題も、まさにCQによって発見された問題であって、これがなければうっかりビルドを壊してしまっただろう。タイミングによっては直せず、revertされてしまったかもしれない。

もっともCQにも弱点はある。というのは、一般的なtrybotでの確認しかしないから、特殊なビルド環境で開発している場合は自己責任でtrybotを走らせるしかない。それに、特定のプラットフォーム以外ではビルドすらされないような変更では、CQを走らせるのはたんに時間と計算リソースの無駄でしかない。

コードレビューとコーディングスタイル
コードレビューいろいろでも紹介されているように、Chromiumのコードレビューはコミット前に行う。デベロッパは自分でレビュワーを見つけ出し、指定する。レビューは(trybot等の表示の改造が施された)Rietveldを使っている。

レビューは基本的にだれがやっても良いし、だれに依頼しても構わない。ただし、ディレクトリごとにオーナーがおり、そのパッチで関連するファイルのすべてのディレクトリのオーナーからの許可なしにはコミットはできない。とはいえ、オーナーの数は限られていることも多いから(たとえばui直下のオーナーは3人しかいない)、そうした場合には、そのファイルやパッチの内容に詳しいレビュワーにまずレビューをお願いし、そちらのレビューがひと通り完了した段階でオーナーにレビューをお願いし最終確認をもらう、というパターンも多い。

たまに、ふだんあまりいじらないあたりのファイルをいじっていると、だれにレビューをお願いしたらいいのかわからないこともある。そのときは適当にログを調べて、最近そのファイルをいじってたりそのファイルの変更をレビューしている人をピックアップしたりする。比較的最近、Chromiumで使っているgit-clでは、チェンジの内容からレビュアーをサジェストする機能が導入された。experimentalだが、わりとよいサジェスチョンをしてくれることも多い気がする。

ところで、Chromiumではコーディングスタイルを定めていろいろ細々と決めている。ベースはGoogleのコーディングスタイルなどだが、さらに細々としたことも決めている。このあたり、人によっては意見もあるだろうけれど、コーディングスタイルを決めることには、このタイプのレビューと親和性があると思っている。

レビューでは、いろんな人が勝手にいろんなことを言う。細かく、どちらでもいいことであっても、いったんこうと決めておくことでレビューが進められる。たとえば、Foo* barであってFoo *barでない、などのスタイルは、本来はどっちでもいいが、いずれにせよどちらかに統一すべきものだ。だからすぱっと決めてしまえばいい。そうすることで、機械的に問題をチェックすることすら可能かもしれない。瑣末なことを決めてしまえば、本質的なポイントにフォーカスしたレビューができる……まぁ理想的には……と思っている。

goma / ninja
コードレビューの話はいささか本題から外れてた気がするので話を戻す。

ChromiumはほぼC++なので、ビルドの高速化は至上命題だ。gomaはChromiumで使われるクラウドコンパイラで、ローカルのファイルをデータセンターのインスタンスに送り、コンパイル結果を返す。コンパイル結果はキャッシュされている。大半の場合はファイルの変更はないから、gomaのキャッシュにヒットするとコンパイル時間は凄まじく高速化される。gomaはもちろん開発者の生活も幸せにしたが、実際にはBuildBotやtrybotでも使われており、これらのチェックの高速化に寄与している。

ninjaは平たく言えばmakeの代替物。いわゆるビルドシステムだ。ChromiumではビルドにはGYPというソフトウェアを使っている。GYPは.gypファイルを読み込むと各種のビルドシステム用のファイルを生成する。WindowsならVCのプロジェクト、MacならXcodeプロジェクト、LinuxならMakefileといった具合。GYPはninjaサポートを済ませており、簡単な設定でこれらのファイルの代わりに(あるいは加えて)ninjaファイルを生成する。

ninjaはプロジェクトの目的に高速であることを第一に掲げていて、実際makeより格段に高速である。なぜそんなに速いのか、実際のところはよく知らないのだけど……。

まとめ
Chromiumでの開発環境をひと通り解説した。

正直なことを言うと、少しも特別なことはしていないと思う。当たり前のことを当たり前にやっているだけだ。だから、こういう文章がどれぐらい参考になるのかはよくわからない。

また改めて書くまでもなく、こうした手法やツールは相互に依存し合っている。trybotみたいな仕組みが必要なのはプラットフォームが多岐に渡っているからだし、またコミット前レビューの仕組みとも関わっている。Rietveldはコミット前レビューが前提なため、他のレビュープロセスなプロダクトで導入すると面倒なことになるかもしれない。gomaが大きな意味を持つのは、開発言語が主にC++であることも大きい。

開発プロセスに正解はない、と思う。あるプロジェクトでうまく行っている、というのを聞きつけて、その一部だけ取り出してもうまくはいかないかもしれない。どちらでもいいが一方を選択している、ということもある。ChromiumでRietveldが使われているのはたぶんGuidoが作ったシステムだからという理由でしかなく、WebKitにRietveldを導入したい、という意見はあまり興味を持たれないらしい。WebKitはbugzilla上でコードレビューも行うため、issueと正確に一対一対応されるが、Rietveldはバグトラッカーではないため、issue管理は別にやる必要がある。Chromiumではissueの管理はcode.google.comで行い、Rietveld上でissueと関連付けられるようになっている。レビューツールとissueツールが別、というのはぼくにとっては自然だけど、そこはまぁ好き好きかもしれないし、ぼくがなにかに洗脳されているだけかもしれない。

ちょっと話がそれたが、それでもまぁ、こういう文章もある種のケーススタディとして何かの参考になるとうれしいかな、と思う。

images: