vgo (Versioned Go) に関する覚え書き

Go 言語の次のバージョン(v1.11)から vgo (Versioned Go) を実装する計画があるようで, vgo 関連のドキュメントが公開されている。

vgo は新しいパッケージのバージョン管理機能で,vendoring 機能を使った depglide のような仕組みとは異なるアプローチらしい。 まず v1.11 で試験的に導入し, v1.12 で正式に導入することを目指しているようだ。 最終的に vgo が従来の go コマンドから完全に置き換えられることになれば go get を削除することも考えてるみたい。

I would like Go 1.11 to ship with preliminary support for Go modules, as a kind of technology preview, and then I'd like Go 1.12 to ship with official support. In some later release, we'll remove support for the old, unversioned go get. That's an aggressive schedule, though, and if getting the functionality right means waiting for later releases, we will.

お試し vgo

vgo のプロトタイプ版があるようなのでちょっとだけ試してみる。 なお,以下は “A Tour of Versioned Go” からの拝借なのでご注意を。

vgo のプロトタイプ版は go get で取得できる。

$ go get -u golang.org/x/vgo

次に以下のコードを用意する1

package main // import "github.com/spiegel-im-spiegel/hello"

import (
    "fmt"

    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Hello())
}

ファイル名は hello.go とする。 次に hello.go を置いているフォルダに空の go.mod ファイルを作る。

$ echo>go.mod

これで準備完了。 それじゃあ,いきなり vgo build してみよう。

$ vgo build
vgo: resolving import "rsc.io/quote"
vgo: finding rsc.io/quote (latest)
vgo: adding rsc.io/quote v1.5.2
vgo: finding rsc.io/quote v1.5.2
vgo: finding rsc.io/sampler v1.3.0
vgo: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
vgo: downloading rsc.io/quote v1.5.2
vgo: downloading rsc.io/sampler v1.3.0
vgo: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c

v1.5.2 といったバージョンはパッケージのリポジトリのタグ情報から取得する。 バージョンを示すタグ情報がない場合は v0.0.0-20170915032832-14c0d48ead0c のような感じで仮バージョンが付与される。

では,作成した実行バイナリを動かしてみる(この記事では Windows 環境なので悪しからず)。

$ hello.exe
Hello, world.

$ SET LANG=ja

$ hello.exe
こんにちは世界。

おおー。 ちゃんと動いてる。

ビルド後,空の go.mod ファイルに以下のように記述が加えられた。

module "github.com/spiegel-im-spiegel/hello"

require "rsc.io/quote" v1.5.2

vgo のパッケージ管理

ビルドの様子を見ればわかると思うが,依存関係を調べて各パッケージを全てダウンロードしている。 実は golang.org/x/text パッケージは GOPATH 配下にダウンロード済みだったのだが,これを使ってはいないようだ。

じゃあ,ダウンロードしたパッケージは何処にあるかというと $GOPATH/src/v フォルダ以下に展開されていた2

$GOPATH/src/v
├─cache
│  ├─golang.org
│  │  └─x
│  │      └─text
│  │          └─@v
│  └─rsc.io
│      ├─quote
│      │  └─@v
│      └─sampler
│          └─@v
├─golang.org
│  └─x
│      └─text@v0.0.0-20170915032832-14c0d48ead0c
└─rsc.io
    ├─quote@v1.5.2
    └─sampler@v1.3.0

更に quote@v1.5.2 フォルダと sampler@v1.3.0 フォルダにもそれぞれ go.mod ファイルがあって,以下のような記述になっている。

module "rsc.io/quote"

require "rsc.io/sampler" v1.3.0
module "rsc.io/sampler"

require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c

このように go.mod ファイルの情報を元にして依存パッケージのバージョンを管理するわけだ。 ちなみに各パッケージの全てのバージョンを列挙するには以下のコマンドが使える。

$ vgo list -t rsc.io/sampler
rsc.io/sampler
        v1.0.0
        v1.2.0
        v1.2.1
        v1.3.0
        v1.3.1
        v1.99.99

また hello.go 以下の依存パッケージのバージョン情報は以下のコマンドで見ることができる。

$ vgo list -m -u
MODULE                               VERSION                             LATEST
github.com/spiegel-im-spiegel/hello  -                                   -
golang.org/x/text                    v0.0.0-20170915032832-14c0d48ead0c  v0.0.0-20171214130843-f21a4dfb5e38
rsc.io/quote                         v1.5.2 (2018-02-15 00:44)           -
rsc.io/sampler                       v1.3.0 (2018-02-14 04:05)           v1.99.99 (2018-02-14 07:20)

dep みたいに依存関係を可視化できるといいんだけどねえ。

Semantic Versioning と後方互換性

vgo が管理するバージョンは Semantic Versioning に従うことが期待されている。 また同じ import パスで取得するパッケージは後方互換性を持つことも期待されている。

たとえば, my/thing パッケージの v2 が後方互換性のない構成になっていた場合は my/thing/v2 という感じに import パスを変えるわけだ3

Semantic Import Versioning

現行の Go 言語コンパイラはパッケージのバージョンを意識していないが(バージョン管理は dep のような外部ツールが担っている), vgo が正式に組み込まれればより厳密な(Semantic Versioning に基づいた)バージョン管理が要求されることになると思う。 なので,今からそれを意識した運用を考えておくべきかもしれない。

とはいえ,まだ先の話

とはいえ,次の v1.11 が出るのは早くても半年後(2018年8月頃)だし,正式対応するという v.1.12 など鬼が笑う話である。 今後 Semantic Versioning は意識したほうがいいかもしれないが,当面は dep などを用いた運用ができていればいいと思う。

ブックマーク


  1. ソースコードの先頭部分 package main に続くコメント // import ... を正しく書かないとビルド時にエラーになる。 ↩︎

  2. このパスは正式版までに変更されると考えられる。 go.mod ファイルにパッケージへのフルパスが記述されるため,わざわざ GOPATH 配下にパス構成を統合する必要が無いからである。最終的には GOPATH の削除も視野に入れているかもしれない。 ↩︎

  3. あるいは gopkg.in のような API を使う手もある。 ↩︎