rust-lang/rustへのcode contributionをはじめからていねいに

2020/12/11に公開

降り積もる雪はすべての罪を覆い隠してくれるので初投稿です

はじめに

rust-lang/rustはいわゆるrustcをビルドするためにそのソースコードだけでなく、CIコンフィグや各種ツール群、ブートストラッピングのあれこれなどを含んでおり、なかなかに巨大です
そのような巨大なコードベース上だと、簡単なbugfixでもPRを提出するまでにいくつかのroadblockに出くわすことがあります

また、日本語圏内でのRustの知名度も上がってきており、rustcにPR投げてみてぇ~という人もそれにつれて増加していることが予想できます
しかし、上述した複雑さ、あるいは日本語が使えない場でのコミュニケーションに対して奥手になり、したくてもできない状態がそこそこの頻度で発生していそうです
この記事では、その後の貢献を促せるといいなあという意図のもと、とにかくはじめての貢献を手助けすることを主眼においています

なお、題名にもありますが、ここではcontribution(貢献)の範囲をcode contribution、言い換えるとGitHub上でのPRを通したやり取りに限定しています
簡単のため、code contributionについては単に貢献と表記します
また、ここに記載している情報はいつでもoutdatedになる可能性があるので、常に今はどうなっているかをよく確認するようにしてください

rustc-dev-guide

日本語での解説ということで、まずは英語ドキュメントの紹介をします
ズコーッという感じなのですが、基本的にこれにしたがって貢献を試みれば大体失敗しない程度には、詳細にあれこれが文書化されています
その名も"rustc-dev-guide"(https://rustc-dev-guide.rust-lang.org/)です
個人的には、rust-lang/rustの(ややこしい)ブートストラップ構造の解説やその中でビルド時間を短縮するためのおすすめフラグ、rust-lang/rust独特のレビュー文化の説明はnewcomerにとっては特に目を通す価値があると思います

できるだけ最新の情報を提供するために、この記事内のいくつかの箇所ではrustc-dev-guideの参照を促すことにします
その他、何かcontributionについてここで説明されていない箇所で詰まったなどあれば、ひとまずrustc-dev-guideを参照してみてください

(もしrustc-dev-guideを読んでいて「これ書いてあると嬉しそう」や「ここ分かりづらい~」、「ここ間違ってる~」となった場合には遠慮なくhttps://github.com/rust-lang/rustc-dev-guide にissueを立ててください!障壁を感じる場合にはこちらの記事へのコメントでも結構です!)

overview

貢献の流れは基本以下の通りです

  1. 取り組むissueを見つける
  2. コードを書く
  3. PRを提出する
  4. レビューを受ける・修正する
  5. merge :tada:

大体の流れは他のGitHub repositoryと大きく変わりませんが、いくつかrust-lang/rust特有の文化などがあります

初めのうちはissueをfixする形でPRを投げることをおすすめします
そうすれば、issueを片付けるという大義名分のもとPRを投げられるのでrejectされづらいです

取り組むissueを見つける

まずは取り組むissueを見つけましょう!

取り組みたいissueを見つけたら、@rustbot claimとissue上でコメントすることをおすすめします
これにより、botくんがあなたをそのissueにアサインしてくれ、他の貢献者とのrace conditionを防ぐことができます(様子:https://github.com/rust-lang/rust/issues/79792#issuecomment-740233649
assignを外したい場合には@rustbot release-assignmentとコメントすればOKです

基本的に初心者が貢献する上で取り組みやすそうな分野は以下の通りです:

  • documentation: typo fixやminor improvementなどは取り組みやすい(関連label:T-doc
  • diagnostics: diagnostics(rustcのエラーメッセージ)周りの文言を微調整するissueなどは取り組みやすい(関連label:A-diagnostics
  • rustdoc: チームメンバーにより詳細が説明されているissueが比較的多い(関連label:T-rustdoc
  • test: テストを書くだけなのでコンパイラのコード自体に触る必要がない(ラベルについては後述)

例えばこれはdiagnosticsのtypoを指摘するissue例です:https://github.com/rust-lang/rust/issues/78787
linkedされたPRを見ると分かりますが、typoを修正して関連するtest(のoutput)を修正すれば作業は終了です
これくらいなら手を出せそう!という気分になりませんか?
そうですその意気です

これらの分野の他に、rust-lang/rustにはnewcomer向けのissueを示すラベルも用意されています:

E-easy

E-easyはそのissueに取り組むための必要知識が比較的少ないことを意味します
Rustについての基本知識があれば特に詰まらずに進めるはずです
ちなみにE-mediumE-hardというラベルもあります
難易度についてのラベルは気付いた人(主にチームメンバー)がどうしたら解決できるかをラフに考えて付けていることが多いです
そのため、ラベルが付いていないissueは難易度が高いというわけではないです(未分類というだけ)
また、よくよく実装してみたら結構難しかった~ということもまれによくあるので注意してください

また、同じく似たようなラベルにE-mentorというものがありますが、これはRustチームメンバーがissueをfixするためのinstructionをissueに残しているという印です
E-easyに取り組む中で「でも具体的にどのように実装すればいいんや~」とパニパニパニックになったときは、落ち着いてE-mentorを眺めてみるのもよさそうです

ちなみにE-easyE-mentorは同時につけられることもよくあります

E-*のラベルはチームメンバー、もっと言えばその分野について詳しい人間が基本的には分類しているので、何か困ったことがあればラベルをつけた人やinstructionを書いてくれている人に尋ねると良さそうです

E-needs-test

これはその名の通り、テストを追加するだけの簡単なお仕事です
簡単と言っても、テストはrust-lang/rustにおいてもめちゃくちゃ大事で、誰かがやってくれるととても助かります

E-easyなどは他の貢献者にも人気であり、手頃でフリーなissueを見つけることができないやもしれません
E-needs-testは上に挙げたラベルに比べると見る人が少なく、フリーであることが多いです

例えばこれはnightlyで修正されたICE(Internal Compiler Error)に対するregression testを追加するPRです:https://github.com/rust-lang/rust/pull/78898

置くべきディレクトリやテスト用のフラグについては https://rustc-dev-guide.rust-lang.org/tests/adding.html を参照してください

コードを書く

取り組むissueを決めたら実際に修正していきます
具体的なコードの触り方はissueによるのでここでは触れないことにします
困ったときにはrustc-dev-guideを読むか、英語でのコミュニケーションを苦としないのであれば、Zulipというチャットプラットフォームの#t-compiler/helpという部屋で質問することをおすすめします

お世話になるディレクトリ

よく触ることになるであろうrust-lang/rustのいくつかのディレクトリについて簡単に触れておきます

compiler

コンパイラの実装を担うcrateの集まりです
このページではこれらのcrate群のrustdocを提供していて、実装時にかなり使うことになると思います
一部crateについての詳細はrustc-dev-guideで文書化されています
これらについてひとつひとつ取り上げていると僕のholy nightがsilent nightになるので泣く泣く割愛します
が、なんとここにrustcのコードリーディングを綴っている記事が!→ https://qiita.com/0yoyoyo/items/eba97a019d0e60324263

library

これもその名の通り、stdやcore、allocといったライブラリと呼ばれるcrateの集まりです
例えば標準ライブラリに不安定API生やしてぇ~となったらここを触ることになります

ブートストラップの構造上、ここで完結するPRの場合はビルド時間がめちゃくちゃ短くて済みます(後述)

src/test

その名の通り、テストを集めたディレクトリです
src/test/uiや、rustdocを触るならsrc/test/rustdoc(-*)と仲良くすることになると思います

ビルドとテスト

diagnosticsの改善やrustdocのissueなど、コンパイラの動作に影響する変更を加えた場合には、コンパイラをビルドしてテストを実行してみる必要があります
基本的には https://rustc-dev-guide.rust-lang.org/building/how-to-build-and-run.html 辺りを見れば問題なくビルドとテストが行えると思います
実際のビルド・テストはx.pyというPythonで書かれたブートストラッピングツールを通して行います(参考:https://rustc-dev-guide.rust-lang.org/building/how-to-build-and-run.html#what-is-xpy

ただ、rustc-dev-guideにも書いてあるのですが、ビルドやテストにはコーヒーどころか軽いお食事を済ませられるくらいの時間がかかります(特に初回はデフォルトではLLVMもビルドしようとするので余計時間がかかります)
そして結構リソースを食うのでちょっと古めのマシンだと余計時間がかかります(関連:https://rust-lang.zulipchat.com/#narrow/stream/122651-general/topic/Computer.20Specs
そのためrustc-dev-guideでは、自前でビルドするのが厳しい人たちに、CIを使ってビルドとテストを行うことを提案しています(参考:https://rustc-dev-guide.rust-lang.org/contributing.html#pull-requests

以下はビルド・テスト時に自分がよく使うコマンドたちです:

x.py check

cargo checkのようにコンパイルが通るかどうかのみ確認します
きちんとビルド・テストするより圧倒的に早く終わるので、一通りコードを書いてみて、まずは変更についてコンパイルが通るかどうか確認したいときに便利です

x.py test src/test/ui (--bless)

実際のコードを使ってコンパイルが通るかやコンパイル失敗時のdiagnosticsを確認するために、UIテストを実行します(参考:https://rustc-dev-guide.rust-lang.org/tests/adding.html#ui
大体の変更についてはこのUIテストを実行することになると思います
既存テストのoutputを上書きする際には--blessを渡します、これは例えばdiagnosticsのwordingを変更した際に必要です

記事執筆時点(2020/12)では1万を超えるUIテストが存在していて、それらを都度全部実行しているとえらい時間がかかります
なので、例えばproc-macro周りを触っているときにはsrc/test/ui/proc-macroに絞って、最後に全部走らせる、あるいはCIに任せるというようにすると変に時間を使わずにPR提出までこぎつけることができます

また、標準ライブラリのみを触っている場合にはx.py test --stage 0 library/stdとするとめちゃんこぶーテストが早く終わります(ブートストラップのステージについてはhttps://rustc-dev-guide.rust-lang.org/building/bootstrapping.html#stages-of-bootstrappingに詳しいです)

その他のテストについてはhttps://rustc-dev-guide.rust-lang.org/tests/intro.htmlを参照してください

x.py test tidy (--bless)

code styleを確認します(行数は大きすぎないか、trailing whitespaceはないか、など)
--blessを渡すとフォーマットもやってくれます(x.py fmtと同等)
これが通らないとCIをgreenにできないので、PR提出前にはこれを走らせることをおすすめします
rustc-dev-guideでは、Git hookを通してtidyのかけ忘れを防ぐtipsが紹介されています:https://rustc-dev-guide.rust-lang.org/building/suggested.html#installing-a-pre-commit-hook

PRを提出する

レビューを受けられる程度の変更が出来上がったらPRを提出します
提出時には@rust-highfiveというbotちゃんが自動でreviewerをassignしてくれます
レビューをお願いしたい人がいる場合にはr? @<相手のユーザー名>とPRのdescriptionかコメントに書けばそちらを優先してくれます(参考:https://github.com/rust-lang/rust/pull/79575
特に何も指定しなければここのコンフィグを参照してassignするようになっています
触っている部分に精通している人たちに割り当てられるようになっているので、r?する相手が分からないからといって気にすることはないです

レビューを受ける・修正する

レビューを受けて修正して~の流れはよくあるGitHub上でのreviewと変わりません
ただ、rust-lang/rustでは"no merge policy"といって、Gitによるconflictを解消する際にはmergeではなくrebaseしてくださいねというポリシーがあります(参考:https://rustc-dev-guide.rust-lang.org/git.html#no-merge-policy
また、reviewerによってはcommit logをきれいにするためにsquashをお願いされることもあります

また、rust-lang/rustではPRをmergeするのに"bors"というbotを利用しています
approveされたPRはmerge queueに突っ込まれ、順番にPR上のCIよりも厳密なテストを実行し、それが通ればmergeとなります(queueはここから閲覧できます:https://bors.rust-lang.org/queue/rust
borsの仕組みやmerge queueについてはこのスレッドに詳しいです:https://twitter.com/qnighy/status/1013048477218045952
個人的には、GitHubのbranch protectionの"Require branches to be up to date before merging"よりborsの方がきれいな解決法のように思うので気に入っています

以下に、borsの文脈を理解しないと意味が取りづらいコメントをまとめました:

r+

r+はPRをmerge queueに入れるためのコマンドです(例:https://github.com/rust-lang/rust/pull/79795#issuecomment-741183130
ちなみにこれらのコマンドは特定のチームメンバーしか使うことができず、権限のない人がコメントするとborsに「権限ないやん!」と怒られます

r=me

これは厳密にはborsに向けたコマンドではなく、「これさえ修正してくれればr+するよ~」というレビューコメントの一種です
もともとborsにはsomeoneをapproverとして設定してmerge queueに突っ込むためのr=someoneというコマンドがあり、r=meはその派生であると思われます(もっと言えば、r+はr=commenterの糖衣構文です)
(r=someoneの例:https://github.com/rust-lang/rust/pull/79286#issuecomment-738796246

また、PRのauthorもreviewerも権限のある人の場合だと、軽微な修正後にreviewerが確認してr+していては時間の無駄になります、authorが修正後に自分でr=reviewerすれば済む話です(この場合、authorはmergeする権限を持っているため、ある程度の信頼が置けます)
r=meはそのようなレビュー・修正のラリーを半往復省略する利点もあります

しかしreviewerは割とauthorに権限があるかどうかを区別せずr=meと言いがちで、権限のないauthorが見様見真似でr+やr=reviewerをしてborsに怒られるケースが発生しがちです(例:https://github.com/rust-lang/rust/pull/76876#issuecomment-695359090
もしあなたが権限のあるチームメンバーでないのにr=meと言われた場合は、素直に修正した旨をreviewerに伝えるといいと思います

delegate

権限がなかったらreviewerにpingしろと上に書きましたが、実は一時的にreviewerがauthorに権限を付与することがあります(例:https://github.com/rust-lang/rust/pull/78616#pullrequestreview-521877499
例えば、authorが信頼に足る人物であることをreviewerが知っていて、修正後に自分がすぐレビューできない場合、delegateしてauthorに「後はよろしく!」をするreviewerもいます
これは割とレアケースなのでそうそうdelegateされることはないとは思いますが、もし上に挙げたPRのようにr=me after ... @bors delegate+されたなら、review pointをしっかり修正して@bors r=reviewerすれば大丈夫です
もし不安な場合にはその旨を添えてreviewerをpingすれば問題ないと思います

merge :tada:

PRがmergeされればあなたも立派なcontributorです!
もしこの一連の流れに楽しさを見出したのなら、ぜひ日常的に貢献してみてください
貢献するにつれコンパイラ内部により詳しくなったり、色々な人の関わりが見えてきたり、よりたくさんの楽しさがそこにはあるはずです

おわりに

書き始めた当初は適当でも書けるでしょう~と思っていましたが、rustc-dev-guideとの区別化・内容の重複などに苦労しました
rust-lang/rust特有の文化や構造についてはそこそこ日本語を使って文書化できたような気がしていますが、どうでしょう

また注意として、これからのホリデーシーズン(クリスマスなど)はPRを回す速度が落ちることが予想されます
世の中には休暇中に新しい言語触ってみるか~という人もいれば、この機会に家族と過ごそう~という人もいるというやつです
少ないメンテナでPRを無理に回そうとすると思わぬアクシデントを生んでしまうこともあります
なので、reviewerから反応がなくてもpingするのは年明けにするなど、ちょっとだけそこらの事情を気にかけておくとよいと思います

ちょっぴり長くなりましたが読んでいただきありがとうございました
分かりづらい箇所については何らかの方法で僕にお伝えいただけるとありがたいです

٩(๑òωó๑)۶

Discussion