令和時代のRubyコア開発

Ruby Core Development 2019というタイトルでRubyKaigiのCFPにプロポーザルを書いたのだが、 もう一つ書いた方の話が採択されたのでその話はしなかった。

さて、今日はRubyコア*1の開発がSubversionからGitに移った節目でもあったので、そっちのトークで言いたかったことの一部を記事にしておこうと思う。

Subversion → Git 移行 [Misc #14632]

去年くらいから @hsbt さんが cgit というGitフロントエンドを使ってGitリポジトリの準備を始め Misc #14632、ついに今日正式にcgitの方がupstreamになった。平成の時代でSubversionでのtrunkのRubyコア開発は幕を閉じた。

この辺を進めているのは主に @hsbt さんな中、僕がこれを偉そうに書いたり今回のRubyKaigiで壇上でアナウンスをやったりしていたのは、必要な作業量が多そうで半年くらい前から僕も移行作業を手伝っていて、割と多くのパートで貢献していたため。

そもそも何故Gitにするのか

他の場所でのSubversionの用途は知らないが、Rubyコミッタの間では、大体以下の話が散見された。

  • 最近は多くのコミッタがgit-svnを使っているが、git-svnを使うのが微妙に面倒くさい
    • そもそも一からgit-svnでcheckoutするのに無限の時間がかかるので、そのsnapshotのtarを落としてくるのが地味に面倒
    • rXXXXX なSubversionのリビジョンをgit-svnのshaに変換するのが面倒くさい
    • Rubyコアの開発以外でgit-svnを使わないので、git svnの使い方をこれのためだけに記憶していないといけないし、たまに忘れる
    • MinGWではgit-svnが破滅的に壊れているので使えず、そこでだけsvnの方を使う必要がある
  • Subversionだとコミッタ以外のパッチをマージする時にauthor情報がコミットメッセージにしか残らない

GitよりSubversionの方が優れている点も当然あるが、Git化したいという話は作者のまつもとさんが時おり言っており、 MatzがGoと言えばGoなのである。

何故GitHubではなくcgitなのか

コミットhookが svn.ruby-lang.org のサーバー上で動くことを多少前提にしているのでいきなりGitHubに持っていくと少し大変という話もあるが、ブロッカーなのは以上の一点しかないという理解をしている。

今のところ彼はメンテナをやめる意向をたまにRedmineに書いているがおそらくまだ公式にそうなったという感じではない。僕らがGitHubでマージボタンを押せず、自分たちでgitサーバーを運用するコストに見合うくらい彼の貢献や活動力は貴重なので、すぐにGitHubに移行しよう、とはならない気がする。特に、auto-fiberと呼ばれている目玉機能は彼が作ったがまだコミットされていないのである。

Git化に必要だった作業達

最初の2つを僕がやった。半年前にガッと進めて、かつ今回のRubyKaigi中もガッとやってやっと終わった程度には、作業量があった。

懸念した点

  • リビジョンがインクリメンタルな数字ではなくなる
    • chat botのbisectとかCIがこれに依存していたりするので移行が必要になる。が、git bisect通りの挙動をすればまあいいはず
    • RUBY_REVISION の型がIntegerがStringに変わってしまう。これはどうしようもない。すみません。
  • 我々のcgitの運用方法が共有ユーザーでsshしてコミット、なので署名しない限りあるコミットを誰がコミットしたのか信頼できない
    • authorized_keys で鍵ごとの環境変数をセットする機能があり、それでユーザーを判定してコミットのCommitterをverifyする、という機能を @mame さんが作った。
  • GitHubのマージボタンをどうにか使えるように双方向同期するか?
    • GitHubへの移行を諦めるならいつかやりたい気はする。実装が面倒そうなのでいつか。
  • trunk を master に変えるか?
    • Gitでtrunkブランチを使うのは不自然で間違えるのでやめたい。が、CIとかが決めうちしてそうなので、一度に壊しまくらないようそれの移行はタイミングを分ける
    • @yugui さんが git symbolic-ref refs/remotes/origin/master refs/remotes/origin/trunk というのを共有していた
  • 古いバージョンのブランチをGit化するとそれ自体が非互換になりそう
    • なので 2.6 までのブランチはSubversionのままやることになった
  • Subversion用のViewVCでクローラーがサーバーに負荷をかけており、同居しているcgitがたまに使えなくなる
    • ViewVCは今日落とされた。あと、git.ruby-lang.org は信頼性が高い構成ではないので、コミッター以外はcgitのかわりにGitHubを使って欲しいというポリシーにしている。

Git化してよかったこと

  • git-svnを使わずにgitで普通〜に開発できる。
    • git svn dcommitするのに比べ、git pushが速い。cgitが軽量なのと、git.ruby-lang.org のサーバーが日本にあるので日本からだと特に速い。
    • 全体のgit cloneは1分で終わるし、--depth=1だと数秒で開発が始められる。
    • (僕は) svnコマンドやgit svnコマンドの使い方を忘れてよくなった。
  • コミットすると https://github.com/ruby/ruby/graphs/contributors に名前が残るのでcontributorのモチベーションが上がる?
    • 僕はコミッターになる前の機能のgit blameが自分じゃないのはちょっと寂しいなと思っていた
    • まあでも実際そこに名前が残らないから何もしない、という人はどの道コミットしないような気もする :p
  • 将来GitHub化するなりGitHubからも同期するなりでPull Requestのマージボタンが押せる世界が近付いた
    • 今は多くのコミッター(僕も含む)はPull Requestなしでいきなりtrunkに突っ込んで何かあったらrevertするという原始的な開発手法を採用しているが、trunkのCIが破滅しがちで辛いので、文化を変えた方がよさそう

C99化 [Misc #15347]

古いVisual StudioSolarisで動かないことに懸念があり、Rubyコアは去年まで長らくの間C89/C90で開発されていた。 どういうことかというと、 // コメントが書けないとか、boolが使えないとか、関数途中で変数を宣言できないとか、arrayやstructの便利な初期化シンタックスが使えないとか、そういう感じになる。

2019年にこれはしんどい。C99というのは1999年だからC99なんだけど、もう20年たったのだ。もう平成は終わりなのだ。

記事が長くなってきて疲れたので適当にまとめるが、Ruby 2.7からplatformの要求バージョンを上げるなどで対応した。また、C99を全てはサポートしていないplatformがあるが、それら用に重点的にCIを回すことで対策をするなどした。詳細に興味がある人は https://github.com/ruby/ruby/pull/2064 をどうぞ。このプロジェクトは主に僕が主導していた。

インデントのハードタブ廃止 [Bug #14246]

去年の途中まで、Rubyコアはインデントが4つまではスペース、8つに達するとハードタブ、その次の4つはスペース、といった方法でインデントされていた。 僕のエディタ(Vim)で普通にRubyコアのリポジトリを開くとデフォルトではインデントがめちゃくちゃになりがちで、vim-cruby をいれ、cloneする度に .vimrc.local を書きそれを有効にする、といった苦労があった。また、GCCやClangがVMのコードをpreprocessしてJIT用のヘッダを生成する時にタブだけ1スペに変換されるという挙動があり、gdbデバッグするのがものすごく辛い、という実害があった。

Rubyの二大コミッタのうち、 @nobu さんが上記の方法でコミットし、 @akr さんが常にスペースだけでコードを書いていてインデント方法陣取り合戦みたいになっていて、@shyouhei さんがどっちかにしてくれという話をしたのが Bug #14246。結果全部スペースに倒すことになった。

今はいじった行や新しい行にハードタブが含まれているとbotが勝手にspaceに変換してくるというフックが入っており、revertする時とかに鬱陶しいという話があるが、一気に置換すると、git blameの歴史が一層積まれる他にbackportのconflictが辛そう、という話があり妥協でそのままになっている。まあ、なんか困ったらファイルごとに一括置換していくなりしたらいいと思う。

僕は他に misc/ruby-style.el の更新とかをやったりした。

MinGW, JIT 向けCIの追加

RubyJITは最初のWindows platformとしてMinGWに対応したため、MinGWのCIが欲しくなった。 AppVeyorでMinGWが動かせるので、AppVeyorにこれを追加した。これは確か僕が最初にやって @nobu さんが修正した感じだった気がする。

また、JITは普通は非同期にコンパイルを行なうが、JITがリクエストされた瞬間同期的に必ずメソッドをコンパイルするようにすると、結構シビアなJITのテストができる。なので、これをWerckerといういままで使ってなかったCIで実行することによって、JITはテストされている。これのおかげでJIT向けにがんばってテストを書かなくても、既存のテスト資産全てがJITの開発に利用できている。すごい楽!!! これは僕用なので一人でメンテしている。

Werckerはコミットごとに1回なんだけど、ランダムで発生する失敗を見つけまくるため、 @ko1 さんが http://ci.rvm.jp/ というCIをメンテしていて、そこでJITのCIが24時間ぶん回っていて、これが2.6のバグ減らしにかなり貢献していたりする。

bundler, bundled-gems 向けCIの追加

@hsbt さんがAzure Pipelineに新たなCIを設定した。僕もちょっと手伝った。 これは標準ライブラリであるがRubyコアに直接存在していないbundled-gemsや、 存在はするが時間がかかるのでCIに参加させにくいbundlerのCIを、並列度が高く新設のAzure Pipelinesで実行しようというもの。

Azure Pipelinesが新しすぎて通知周りがいろいろいけてないとか、単純にbundled-gemsのCIが不安定などでtrunk限定で実行している。 Ruby trunkでbundlerやbundled-gemsが動かなかった時にすぐ気付けて大変便利。 最近 @nobu さんがおもむろに加えた変更でbundlerのテストがコケたので僕がbundlerにパッチを送って直した、ということがあった。

まとめ

Rubyの開発はとても快適になったので今すぐ https://github.com/ruby/ruby にPull Requestしよう!!! *3

*1:この記事ではRuby言語の参照実装であるインタプリタ、つまりMRI, CRubyのことをRubyコアと表記している。これはruby-coreというメーリングリストの名前から取っている。

*2:Unicornやgit-svnの作者。GVLやTimer threadの他低レイヤーの難しい奴をメンテしていた。Webrickもメンテナだった。

*3:ちなみにPull RequestはGit化もGitHub移行も何も関係なく受けつけているが、Pull Requestを送る側が何が快適になっているかは記事に書いたつもり