go get の動作メモ

go get の動作について少し調べたことのメモ書き

TL;DR

  • module 有効下の go get-u 相当 (バージョン未指定なら)
  • go.mod の module には /v{2以上の値} という suffix でメジャーバージョンを書く必要がある
    • 省略すると v0 もしくは v1 とみなされる
  • 取得時には go get example.org/foo/bar/v2 のようにする
    • パッケージを取ってくるときはこれで良いが、コマンドの時はリリース済みのメジャーバージョンを機械的に知る方法がないのでキツいね

髄記

言わずと知れた go get 、Goのパッケージをインストールしたり、コマンドをインストールしてくれたり、バージョン管理してくれる偉いやつ。

調べてみると module が有効かどうかで振る舞いが変わるらしい。

src/cmd/go 下をみると module かどうかでコマンドそのものが入れ替わってる。 internal/get が昔ながらの実装で、internal/modget が module 対応版。

module 対応版は go get だけかつバージョン指定を省略すると -u フラグを省略しても自動で upgrade 相当らしい。以下は該当箇所

internal/modget/get.go より

// If no version suffix is specified, assume @upgrade.
// If -u=patch was specified, assume @patch instead.
if vers == "" {
    if getU != "" {
        vers = string(getU)
    } else {
        vers = "upgrade"
    }
}

ライブラリ側は v0 と v1 の時は特に気にすることはない。 リリースするたびに普通に semver に従って v1.2.3 みたいにタグを付ければよい。

問題は v2 以降。

go.mod がない時は逆に問題がおこらない。 タグに付けたバージョンに +incompatible というサフィックスが付いた状態でアクセスできるようになる。

例: v2.3.4v2.3.4+incompatible

使うほうは v2.3.4 を取ってくれば勝手に +incompatible が付くので気にすることはなにもない。「あ、こいつpinningしてないな(プププ」とでも思っておけばよい。

本番は go.mod がある時。さらに言うと、ある時までは go.mod が無かったけどあるバージョンから追加されたやつ。

たとえば github.com/koron/foobar というモジュールならばこんな go.mod を書いているかと思う。

module github.com/koron/foobar

ここで v2.3.3 までは go.mod 無しで運用してて v2.3.4 で上記の go.mod を追加したとしよう。

このケースでは残念ながら v2.3.4 は利用できない。一生できない。

仮にバージョン指定で取ってこようとすると以下のようなエラーになる。

$ GO111MODULE=on go get github.com/koron/foobar@v2.3.4
go get github.com/koron/foobar@v2.3.4: github.com/koron/foobar@v2.3.4: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2

なんかエラーメッセージで言ってることがよくわからないが「go.mod に書かれてるメジャーバージョンが v2 じゃないから使えねぇよ」ということらしい。

module github.com/koron/foobar ってのは特殊でこれは v0 もしくは v1 であると見做されるようになってる。たぶん互換性目的かな。

なので v2.x.x としてリリースしたいなら go.mod は以下のようにする必要がある。

module github.com/koron/foobar/v2

そのうえで v2.3.5 としてタグを打ちリリースしよう。

こうするとユーザー側は以下のように /v2 サフィックスで取れるようになる。

$ GO111MODULE=on go get github.com/koron/foobar/v2

…パッケージなら納得感あるけど、コマンドだとちょっとキツくね? リリース済みのメジャーバージョンを全てチェックする方法が無い。

proxy.golang.org/github.com/koron/foobar/@v/list を見ると v0, v1 もしくは go.mod 無し時代のリリースは全部見れる。

以下は mattn/jvgrep における例:

なんか正攻法もしくは回避法ないかもうちょっと探ってみよう。

なお検証には mattn さんに付き合ってもらった。この場でお礼を。