proxy.golang.org の挙動調査メモ

goのモジュールをリリースした際に proxy.golang.org がどうふるまうか調査したメモ

TL;DR

  • 前回の記事 の調査結果が正しいことを実験で確認した
  • go.mod を作って v1 (B) 系の最新のタグを打てば、v2 以降の +incompatible リリースは闇に葬れる
  • v2 以降の compatible リリースはモジュール名の変更 (例 /v2 サフィックス) を免れない

随記

調査中 https://pkg.go.dev/ソースコードが公開されたが公開されたが、関係なく https://proxy.golang.org (以下単にproxy) を調査した。 proxy のほうもソースコード公開されねぇかなぁ…

本調査の観点は go のモジュールをリリースした際に proxy ではどう見えるか。

go のバージョンは相変わらず 1.14.4 である。

また前回の記事を読んでいる前提。 ネタバレ: 本調査は前回の記事でわかったことを実地で確認するだけなので「ああやっぱそうだよね」以上のものはない。

シナリオとしては、歴史のあるモジュールで go.mod 導入前にリリースを重ねタグ的には(いわゆるA系による)メジャーバージョンアップをしている状態を経由し、 そこから go.mod を追加してリリースし mattn/go-sqlite3 のように go get では最新リリースを取れなくなってしまった状態を経て、 前回の調査で見出した復帰する手段を実践してみる。

実験に使用したレポジトリは https://github.com/koron/proxyexam (以下単にexam) である。

exam の _bin/release スクリプトを $ ./_bin/release v1.0.1 のように実行すると v1.0.1 がリリースされ proxy にて利用可能になる。 あとはひたすらリリースを繰り返し proxy でどのようにみえるかを記録すればよい。なお記録もこのスクリプトはその記録もしてくれるようになっている。

ここでちょっと脇道なのだが、goのモジュールをリリース後すなわちgitのタグをつけた直後に go get できなくて困ったことはないだろうか。 これは proxy が新規リリースを認識せずキャッシュしてないことで生じる事態であることがこの調査中にわかった。

これを回避するには GOPROXY=direct go get ... で直接VCSへ取りに行けばよいのだが、proxyの恩恵にあずかれないのはやや物足りない。

proxy へ強制的に認識させるには /@v/{version}.info (以下 /@v/info) を叩けばよいことが分かった。 ここで {version} には v1.0.1 などのバージョンが入る。 例: https://proxy.golang.org/github.com/koron/proxyexam/@v/v1.0.1.info

/@v/info を取得すると /@v/listバージョン一覧に該当バージョンが追加される。 なお上記スクリプトでは安全のため5秒待ってから /@v/list を取るようにしている。 /@v/list に記載されたバージョンは go get からアクセス可能であることは前回の記事に書いた通り。

閑話休題

exam においては go.mod なしで v1.9.0 (A)までリリースした。 そこから v2, v3 (B) に突入したのだが v2.0.0+incompatible (C)といった感じで以降は +incompatible となった。

この時点で /@v/info はC系で問い合わせた時に /@v/list を更新することがわかった。 例: https://proxy.golang.org/github.com/koron/proxyexam/@v/v2.0.0+incompatible.info

ただしA系で問い合わせた場合にもレスポンスは返され、JSONの Version プロパティが正しいC系のバージョンを示している。 例: https://proxy.golang.org/github.com/koron/proxyexam/@v/v2.0.0.info

よって機械的にproxyに読ませるならば一度A系で /@v/info へ問い合わせて Version プロパティの値と等しくなかったらそのC系の値で問い合わせるということで良いだろう。

そのままリリースを重ね v3.1.0 まで到達したところで go.mod を追加して v3.2.0 をリリースした。 これの /@v/info はエラーになり取得できない。

$ curl "https://proxy.golang.org/github.com/koron/proxyexam/@v/v3.2.0.info"
not found: github.com/koron/proxyexam@v3.2.0: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v3

このエラーは、go.mod のある v2 以降のリリースは module aware ということでモジュール名に /v3 サフィックスがあることが厳密に求められる、ことを示している。

こうなってしまうともとの github.com/koron/proxyexam というモジュール名では v3.2.0 以降のリリースを取得することは絶対にできない。 (go.modを消すという手段はあるが現実的な選択肢ではないため棚に上げる)

なので v3 (B) としてリリースを継続したい場合は素直にモジュール名を github.com/koron/proxyexam/v3 に変更しよう。 リポジトリ内のルートに v3 を置くか v3/ ディレクトリを掘るかは好きにすればよい。

さてここで「実は breaking changes ないんだよね。雰囲気でメジャーバージョンアップしただけだから」ということであるならば v2, v3 を破棄して v1 に戻る手が選択肢に入る。 (breaking changesがあっても v1 に戻る自由は存在するが強く反対しておく)

ここで v1.10.0 をリリースしよう。すると新規ユーザーの go get github.com/koron/proxyexam は v1.10.0 を返すようになる。 以降 v1.11.0 (C) など v1 (B) の更新があった場合には go get github.com/koron/proxyexam だけで最新版を取得できる。 もちろん v3 や +incompatible が混入することはない。

v3.1.0+incompatible などの +incompatible を使っている既存ユーザーは go get github.com/koron/proxyexam ではこのアップデートに追従できない。 一旦 go get github.com/koron/proxyexam@mastergo get github.com/koron/proxyexam@v1.10.0 のようにして v1.10.0 という compatible の世界へ戻ってくる必要がある。 以降は上記の新規ユーザーのケースと同様 go get github.com/koron/proxyexam だけで最新版に追従できる。

proxy の /@latest は更新頻度が低い(30分に1回程度と推測している)のだが、こちらも +incompatible の世界から compatible の世界へいずれは戻ってくる。

つまり go.mod を作って v1 (B) 系の最新のタグを打てば、v2 以降の +incompatible リリースは闇に葬れる。

ちなみにだが +incompatible になったバージョンタグ(A)を消したらどうなるのか…実は /@v/info は取れてしまう。

exam において v2.0.1 というタグがあったのだが実験のために消した。 結果 https://proxy.golang.org/github.com/koron/proxyexam/@v/v2.0.1+incompatible.info は本記事執筆時点でもレスポンスを返すが、 https://proxy.golang.org/github.com/koron/proxyexam/@v/v2.0.1.info は返さなくなった。 後者も消した直後は返していた記憶(あてにならない)があるので、時間によって消えた可能性がある。 前者も消えるかもしれないがまだ確認できていない。

その意味で本当になかったことにするのは難しいのかもしれない。 リリースの時には公開してはいけない情報を厳に含めないよう。

以上、あと一本くらい go.mod とバージョン付けのチャートみたいなのを記事にするかもしれない。 しないかもしれない。