stefafafan の fa は3つです

"すてにゃん" こと id:stefafafan のブログです

OSSコントリビューションへの一歩に悩んでいる方向けにちょっとした事例を紹介

OSSへのちょっとしたコントリビューションに成功したので、どういう流れでコントリビュートしたのかブログに簡単に流れをまとめてみようと思います。OSS活動してみたいけどどういう流れでやれるのか気になっている方の参考になれば幸いです。なお今回の修正Pull Requestはこちらです。

github.com

経緯

GitHub App の権限で Songmu/tagpr を利用する - stefafafan の fa は3つです とかにも書いたのですが、個人的に作ったライブラリにこちらの Songmu/tagpr を導入したら便利になったので、そのままはてなで運用している以下のリポジトリとかでも使ってみてはどうかとブログチームの方に紹介しました(自分はブログチームではないのですが、Slackの雑談チャンネルでおもむろに紹介しました)。

github.com

このあと軽くウォッチしていたら、なぜか次のリリースバージョンが v0.0.1 になってしまう問題があり導入に手間取っていそうということに気づきました。自分で使ったときはこういう困りはなかったので不思議だな〜と思いながら過ごしていました(本来ならいま存在するタグの次のバージョンを判別して切って欲しい)。
github.com

おかしいな〜と思ったのと、紹介した側としてなんか申し訳なさのようなものがあったので自分で原因を軽く調べてみることにしました。

不具合の特定および修正までの流れ

自分も今回はたまたま上手くいきましたが、一般化すると以下のような流れで進めるとOSSに限らず不具合の原因の特定や修正まで持っていけると思います。

  1. 状況の把握
  2. 仮説を立てる
  3. 問題点を絞り込む
  4. 状況を再現できる最小の実装を用意
  5. 失敗するテストを書く
  6. 実装を修正する

状況の把握

今回の場合は、「v1.2.0のタグが最新なため、次にv1.2.1のタグが打たれる想定なのに、v0.0.1になっている」という状況でした。

そしてGitHub Actions上で今回のOSSは動作するので、当該のActionsのログを確認しました。ログをみたところ、「現行バージョンを v0.0.0 と誤判別してしまっている」ということがなんとなくわかりました。また、上記の hatena/hatenablog-workflows リポジトリではこのActionsを再度実行していましたが、何度やっても毎回「現行バージョンを v0.0.0 と誤判別してしまっている」になっていました。

さらに環境についてわかっていることは、自分がもともと Songmu/tagpr を導入した hatena/godash というリポジトリでは、tagpr の v1.1.2 を使っていたが問題はなく、 hatena/hatenablog-workflows では v1.1.3 を使っているということもわかりました。

仮説を立てる

わかっていることから仮説を簡単に立てます。今回は以下のようなことを考えました。

  • hatena/hatenablog-workflows の何かしらの設定が Songmu/tagpr の想定しないものになっている
    • 打たれているタグの形式が何かおかしいのかもしれない? (v1.2.0 みたいな形式で打たれているが実はvが全角だったり、特殊な空白文字が含まれているためにパースに失敗しているとか)
    • v1.2.0 みたいなタグと v1 タグが混合しているけどこれだと何か問題になるのかもしれない?
  • Songmu/tagpr v1.1.3 に何かしらBreaking changeが入っている
    • v1.1.2 では少なくとも他リポジトリでは想定通りの挙動だったため

大きく二分すると、自分たちのリポジトリ側の問題なのか、OSS側なのかとなります。もちろんOSSに不具合が入っていることは普通にありますが、大半の場合は利用させてもらっている側の設定ミスなどだったりする上に、情報不足のままissueなどを立てにいっても迷惑なのでまずは自分たちのリポジトリを疑います。

問題点を絞り込む

上記の仮説をもとに問題点を絞り込みます。

今回すべてGitHub Actions上で動いているので、ログに出力される文字列などから、 Songmu/tagpr のリポジトリを検索しなんとなく該当の処理が実行されているポイントを見つけます。

最終的には以下のコードに辿り着きました。 latestSemverTag という関数の戻り値をもとに現行タグを判別し、判別に失敗したら v0.0.0 とする、という処理がありました。一応この時点で v0.0.0 と書いてあるので相当あやしいですが、リポジトリのコード全部を把握しているわけではないので「あやしさはそこそこ高い」というつもりで覚えておきながらコードの処理を軽く追いました。
github.com

現行のタグのバージョンを判別するコードは、自分が修正を加える前は以下の形となっていました。 Songmu/gitsemvers というさらに別のライブラリでgit tagから対象のバージョン一覧を取得してから、tagpr の設定で vPrefix が有効かどうかをもとに最初にマッチしたバージョンを返すという実装になっていました。
github.com

仮説に書いていた「打たれているタグの形式が何かおかしいのかもしれない」や「v1.2.0 みたいなタグと v1 タグが混合しているけどこれだと何か問題になるのかもしれない」の肝として「Songmu/gitsemvers から正しく値が返っているのか?」というのが気になったので、手元にgo install して様子を確認しました。

$ go install github.com/Songmu/gitsemvers/cmd/git-semvers@latest

$ cd /path/to/hatenablog-workflows

$ git-semvers
v1.2.0
v1.1.3
v1.1.2
v1.1.1
v1.1.0
v1
v1.0.0

少なくとも手元で git-semvers を実行しても何もおかしなところはないので、latestSemverTag の実装の続きをみてみることにしました。特に vPrefix の箇所が読んでいてもよくわからず、Go Playgroundで触りながら実験しました。そして触っている中で閃きました。「vPrefixはConfigに指定できる *bool な設定だけど、今回は設定ファイルを作らずにSongmu/tagprを導入しようとしている。もしかして設定が nil な場合に意図しない挙動になっているのではないか」。

状況を再現できる最小の実装を用意

latestSemverTag の実装がv1.1.3で変わっているのもあってとても怪しいということで、引き続きGo Playgroundで状況を再現できるか確認しました。

まずは v1.1.2 時点の実装です。https://go.dev/play/p/GNga-fRcit7
Config の vPrefix 設定の有無や真偽に関わらず、とにかく常に最新のタグのバージョンを返していることを確認できました。

次に v1.1.3 時点の実装です。https://go.dev/play/p/4cNyuN3lcrt
Config の vPrefix が true の場合はv始まりの最初のタグを返すし、falseの場合はvから始まらない最初のタグを返すというのは v1.1.3 の意図通りの挙動になっていることは確認しました。一方で設定が nil かつ v からはじまるタグしかない場合は空文字を返すことがわかりました。今回の不具合のケースにあたります。

もし Songmu/tagpr セットアップで最初にまず .tagpr ファイルを作って vPrefix をセットしましょうという手順になっているのであればこの実装のままでよいですが、別にREADMEみてもそうはなっていないのと、作らなくても初回セットアップ時は .tagpr ファイルが作られるため、これは恐らく意図しない挙動だと判断しました。

失敗するテストを書く

ここまでわかったので、せっかくなので修正Pull Requestも作ってしまいたいです。幸い今回は前入っているPull Requestがそのまま参考になりました。

github.com

上記Pull Requestで TestLatestSemverTag というテストが生えていたのでここに今回失敗する状況のテストケースを足します。OSSへのContributeに限らず、不具合修正はまず失敗するテストケースを足してCI上でも意図通り失敗することを確認してから修正に入ることをおすすめします。

実装を修正する

上記のテストが失敗することを確認したら実装側を修正します。

今回修正するにあたって、「そもそも vPrefix の値が nil の場合の正常な挙動は何か」が一瞬だけ検討ポイントとなりました。デフォルト値が true なのか false なのか、メンテナに確認が必要なのではないか?

ただしここで思い出して欲しいのは、 v1.1.2 での挙動のことです。もともとの挙動は「バージョンの形式がなんであれ最新の値を返す」でした。今回の変更で突然仕様をさらに変更するのは意味がわからないし、v1.1.2の動いていた頃に戻したいのが本来やりたいことのはず。つまり「vPrefixが nil の場合はバージョンの形式を気にせず最新の値を使う」でよいはず。

そうしてできたのがこのブログの序盤に紹介した修正Pull Requestです。vPrefixが nil でない場合は v1.1.3 の挙動になるが、nilの場合は v1.1.2 の頃の挙動のままになるようにして修正しました。

github.com

何かおかしいな、不便だなと思ったときについでにコントリビュートする

私はもともと新卒の頃などはOSSへコントリビュートすることにハードルがありました。とにかくOSSを何かみつけて、Good First Issue ラベルをみつけないといけないのか?コードを読んでいかないといけないのか?と結構大変だなと思って中々できませんでした。

「普段開発しているソフトウェアが利用しているライブラリのコードをもとにコントリビュートできればよい」みたいなことを言われて「そうか」と思いながらも当初はあまりよく理解できていなかったと思います。ただ今回こうしてみると意味はわかってきたように思えますね。

結局は普通に仕事をしていて、自分の把握しているリポジトリのコードにある不具合は普通に治しているはずで、そこから調査をして実は原因が外部にあるとわかったら「外部のことはわからないや」と諦めるのではなくて外部のOSSもみにいくことがコントリビュートへの一歩だと思います。

別に最初からコード修正はする必要はなく、まず同じように困っている人がいないかissueで検索して、いたらそこの議論を読む。もしかしたら何かしらの意図があってそうなっているかもしれないし、暫定的な対処法もわかっているかもしれません。issueが見当たらない場合は再現方法をまとめて自分でissueを立てに行くのもコントリビュートの一つですね。

なので、コントリビュートできなくても焦ることはなく、違和感を感じたときや困ったときに少し外の世界を見に行くくらいからはじめましょうと改めて思った次第です。このブログ記事は新卒の頃の自分に向けたものでもあるということですね。