和訳: A Tour of Versioned Go (vgo) (Go & Versioning, Part 2)

# A Tour of Versioned Go (vgo)

([Go & Versioning][vgo]、パート 2)

※訳註1: この記事は、[Russ Cox][]によって 2018 年 2 月 20 日に投稿された記事 “[A Tour of Versioned Go (vgo)](https://research.swtch.com/vgo-tour)” の和訳版です。筆者に許可をとり、@nekketsuuu が和訳・公開しました。シリーズ物ですので、気になる方は[全体をご覧ください][vgo]。

※訳註2: 以下の内容は Go 1.9 以下では動作しません。Go 1.10 以降を使ってください。

----

私にとって設計することとは、作って、壊して、また作って、それを繰り返すことだ。[新しいバージョン付けの提案][vgo-intro]を書くにあたって、私はプロトタイプとして `vgo` を作り、細かい詳細を詰めた。この投稿はどのような感じで `vgo` が使えるのかを示す。

今、`vgo` は `go get golang.org/x/vgo` を実行することでダウンロードでき、試せる。`Vgo` は `go` コマンドの完全な互換品で (あり、フォークしたコピーでも) ある。`Go` コマンドの代わりに `vgo` コマンドを走らせると、`vgo` は既に `$GOROOT` へインストールされている標準のコンパイラとライブラリを使う (Go 1.10beta1 以降に対応)。

何が上手くいっていて何がそうでないのかを学んだ結果として、`vgo` の意味論やコマンドラインの詳細が変わるかもしれない。しかし、我々は `go.mod` ファイル・フォーマットの後方互換性を破るような変更は避け、今 `go.mod` をプロジェクトに追加しても将来同様に使えるようにしたいと考えている。今回の提案を洗練させた結果として、`vgo` をアップデートすることになるだろう。

## 例

この節は `vgo` の使い方をデモする。あなた独自の環境のもとで以下の実験を試して欲しい。

まずは、`vgo` をインストールしよう。

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

`Vgo` はサッとテストしただけなので、もしかすると面白いバグに遭遇するかもしれない。Issue を報告するには、[Go 本体の issue tracker](https://golang.org/issue) を使い、タイトルの先頭に「x/vgo:」をつけてほしい。

### Hello, world (あるいは、こんにちは世界)

それでは面白い “hello, world” を書いてみよう。GOPATH/src の外側にディレクトリを作り、その中で作業しよう。

```
$ cd $HOME
$ mkdir hello
$ cd hello
```

次に `hello.go` ファイルを作る。

```
package main // import "github.com/you/hello"

import (
	"fmt"
	"rsc.io/quote"
)

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

あるいは、ダウンロードもできる。

```
$ curl -sS https://swtch.com/hello.go >hello.go
```

空の `go.mod` ファイルを作り、この module のルートを示す。そうしたらこのプログラムをビルドして実行してみよう。

```
$ echo >go.mod
$ 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
$ ./hello
Hello, world.    (訳註: 「こんにちは世界」と出るかもしれない。)
$ 
```

明示的に `vgo get` をしなかったことに注意してほしい。単に `vgo build` をした場合、まだ知らない import に出会う度、それを有する module を探して、その最新バージョンを現在の module の必要要件として追加している。

任意の `vgo` コマンドは副作用として、必要ならば `go.mod` を書き換える。今回の場合、`vgo build` は新しく `go.mod` を書き換えている。

```
$ cat go.mod
module "github.com/you/hello"

require "rsc.io/quote" v1.5.2
$ 
```

このように `go.mod` が作られたので、次の `vgo build` は再度 import 解決をすることはなく、たくさん出力することもしない。

```
$ vgo build
$ ./hello
Hello, world.
$ 
```

もし `rsc.io/quote` v1.5.3 や v1.6.0 が明日リリースされたとしても、明示的にアップグレードしない限り、このディレクトリにおけるビルドは v1.5.2 を使い続ける (下の方参照)。

この `go.mod` ファイルは最小限の必要条件をリストアップしており、既にリストされているものから分かるものは省略されている。今回の場合、`rsc.io/quote` v1.5.2 は `rsc.io/sampler` と `golang.org/x/text`  の特定のバージョンを必要としているが、これは既に書かれているためこの `go.mod` ファイルで繰り返すのは冗長であり、省略されている。

ただし `vgo list -m` を使うと module がビルドに必要としている全てのものを出力させることができる。

```
$ vgo list -m
MODULE                VERSION
github.com/you/hello  -
golang.org/x/text     v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote          v1.5.2
rsc.io/sampler        v1.3.0
$ 
```

ここで、もしかするとなぜこの単純な “hello world” が `golang.org/x/text` を使っているのか不思議に思ったかもしれない。先程 `rsc.io/quote` が `rsc.io/sampler` に依存していることが分かったが、これは `golang/x/text` を [language matching](https://blog.golang.org/matchlang) のために使っているのだ。

```
$ LANG=fr ./hello
Bonjour le monde.
$ 
```

### Upgrade

先程、新しい module のビルドに新しい import の解決が必要なら、`vgo` は最新版を取ることを見た。さっきは `rsc.io/quote` が必要とされ、v1.5.2 が最新だった。しかし新しい import を解決するときを除いて、`vgo` は単に `go.mod` に書かれたバージョンを使用する。今回の例では、`rsc.io/quote` は間接的に特定のバージョンの `golang.org/x/text` と `rsc.io/sampler` に依存していた。実は、先程の `vgo list` コマンドに `-u` (check for updated packages) を加えることで、それら両方のパッケージにはより新しいリリースがあることが分かる。

```
$ vgo list -m -u
MODULE                VERSION                             LATEST
github.com/you/hello  -                                   -
golang.org/x/text     v0.0.0-20170915032832-14c0d48ead0c  v0.0.0-20180208041248-4e4a3210bb54
rsc.io/quote          v1.5.2 (2018-02-14 10:44)           -
rsc.io/sampler        v1.3.0 (2018-02-13 14:05)           v1.99.99 (2018-02-13 17:20)
$
```

両方のパッケージが新しいリリースをしているので、`hello` プログラムのためにこれらをアップグレードしたいとしよう。

それではまず `golang.org/x/text` をアップグレードしてみよう。

```
$ vgo get golang.org/x/text
vgo: finding golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54
vgo: downloading golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54
$ cat go.mod
module "github.com/you/hello"

require (
	"golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54
	"rsc.io/quote" v1.5.2
)
$ 
```

この `vgo get` コマンドは与えられた module の最新版を探し、`go.mod` を書き換えることでそれを今の module の必要条件として追記する。さて、これで次のビルドでは新しい text module が使われることとなった。

```
$ vgo list -m
MODULE                VERSION
github.com/you/hello  -
golang.org/x/text     v0.0.0-20180208041248-4e4a3210bb54
rsc.io/quote          v1.5.2
rsc.io/sampler        v1.3.0
$
```

もちろん、アップグレードした後ちゃんと上手くいくことをテストすべきだ。今回の依存 module `rsc.io/quote` と `rsc.io/sampler` はまだ新しい text module と一緒にテストされていなかった。今作った設定のもとで、次のようにするとテストを走らせることができる。

```
$ vgo test all
?   	github.com/you/hello	[no test files]
?   	golang.org/x/text/internal/gen	[no test files]
ok  	golang.org/x/text/internal/tag	0.020s
?   	golang.org/x/text/internal/testtext	[no test files]
ok  	golang.org/x/text/internal/ucd	0.020s
ok  	golang.org/x/text/language	0.068s
ok  	golang.org/x/text/unicode/cldr	0.063s
ok  	rsc.io/quote	0.015s
ok  	rsc.io/sampler	0.016s
$ 
```

元々の `go` コマンドでは、パッケージに `all` と書くと GOPATH から見つかる全てのパッケージのことを意味していた。多くの場合それは多すぎるので、`vgo` では `all` の意味を「今の module 中の全てのパッケージと、それらが再帰的に import している全てのパッケージ」に狭めた。さて、`rsc.io/quote` module のバージョン 1.5.2 はバグのあるパッケージを含んでいる。

```
$ vgo test rsc.io/quote/...
ok  	rsc.io/quote	(cached)
--- FAIL: Test (0.00s)
	buggy_test.go:10: buggy!
FAIL
FAIL	rsc.io/quote/buggy	0.014s
(exit status 1)
$
```

我々の module に含まれる何かにバグがあったわけだが、しかしこれは我々には関係ないので、これは `all` には含まれていなかったというわけである。どのテストでも、アップデートされた `x/test` は上手く動くようだ。この時点で、`go.mod` をコミットして良いだろう。

別のオプションとして、ビルドで必要になる全ての module をアップグレードすることもできる。これには `vgo get -u` を用いる。

```
$ vgo get -u
vgo: finding golang.org/x/text latest
vgo: finding rsc.io/quote latest
vgo: finding rsc.io/sampler latest
vgo: finding rsc.io/sampler v1.99.99
vgo: finding golang.org/x/text latest
vgo: downloading rsc.io/sampler v1.99.99
$ cat go.mod
module "github.com/you/hello"

require (
	"golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54
	"rsc.io/quote" v1.5.2
	"rsc.io/sampler" v1.99.99
)
$ 
```

ここで `vgo get -u` は text module をアップグレードされたままにし、同様に `rsc.io/sampler` も最新版の v1.99.99 にアップグレードされた。

もう一度テストを走らせてみよう。

```
$ vgo test all
?   	github.com/you/hello	[no test files]
?   	golang.org/x/text/internal/gen	[no test files]
ok  	golang.org/x/text/internal/tag	(cached)
?   	golang.org/x/text/internal/testtext	[no test files]
ok  	golang.org/x/text/internal/ucd	(cached)
ok  	golang.org/x/text/language	0.070s
ok  	golang.org/x/text/unicode/cldr	(cached)
--- FAIL: TestHello (0.00s)
	quote_test.go:19: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
FAIL
FAIL	rsc.io/quote	0.014s
--- FAIL: TestHello (0.00s)
	hello_test.go:31: Hello([en-US fr]) = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
	hello_test.go:31: Hello([fr en-US]) = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Bonjour le monde."
FAIL
FAIL	rsc.io/sampler	0.014s
(exit status 1)
$ 
```

どうやら `rsc.io/sampler` v1.99.99 の何かがおかしいようだ。確かにそうだ。

```
$ vgo build
$ ./hello
99 bottles of beer on the wall, 99 bottles of beer, ...
$ 
```

この `vgo get -u` が依存する全てのものを最新版に引き上げる挙動は、`go get` が GOPATH にないパッケージをダウンロードしてくるときのものと一緒だ。もし GOPATH になにも無いシステムであれば、下のようになる。

```
$ go get -d rsc.io/hello
$ go build -o badhello rsc.io/hello
$ ./badhello
99 bottles of beer on the wall, 99 bottles of beer, ...
$ 
```

とても重要な差異として **`vgo` はデフォルトではこのように動作しない**。また、ダウングレードによってアンドゥをすることもできる。

### Downgrade

パッケージをダウングレードするには、まず `vgo list -t` を使い、タグ付けされていて利用可能なバージョンを調べよう。

```
$ 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
$ 
```

そうしたら `vgo get` によって特定のバージョンにすることができる。たとえば v1.3.1 にしてみよう。

```
$ cat go.mod
module "github.com/you/hello"

require (
	"golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54
	"rsc.io/quote" v1.5.2
	"rsc.io/sampler" v1.99.99
)
$ vgo get rsc.io/sampler@v1.3.1
vgo: finding rsc.io/sampler v1.3.1
vgo: downloading rsc.io/sampler v1.3.1
$ vgo list -m
MODULE                VERSION
github.com/you/hello  -
golang.org/x/text     v0.0.0-20180208041248-4e4a3210bb54
rsc.io/quote          v1.5.2
rsc.io/sampler        v1.3.1
$ cat go.mod
module "github.com/you/hello"

require (
	"golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54
	"rsc.io/quote" v1.5.2
	"rsc.io/sampler" v1.3.1
)
$ vgo test all
?   	github.com/you/hello	[no test files]
?   	golang.org/x/text/internal/gen	[no test files]
ok  	golang.org/x/text/internal/tag	(cached)
?   	golang.org/x/text/internal/testtext	[no test files]
ok  	golang.org/x/text/internal/ucd	(cached)
ok  	golang.org/x/text/language	(cached)
ok  	golang.org/x/text/unicode/cldr	(cached)
ok  	rsc.io/quote	0.016s
ok  	rsc.io/sampler	0.015s
$ 
```

あるパッケージをダウングレードしたら、他のものもダウングレードしないといけないかもしれない。たとえば下のように。

```
$ vgo get rsc.io/sampler@v1.2.0
vgo: finding rsc.io/sampler v1.2.0
vgo: finding rsc.io/quote v1.5.1
vgo: finding rsc.io/quote v1.5.0
vgo: finding rsc.io/quote v1.4.0
vgo: finding rsc.io/sampler v1.0.0
vgo: downloading rsc.io/sampler v1.2.0
$ vgo list -m
MODULE                VERSION
github.com/you/hello  -
golang.org/x/text     v0.0.0-20180208041248-4e4a3210bb54
rsc.io/quote          v1.4.0
rsc.io/sampler        v1.2.0
$ cat go.mod
module "github.com/you/hello"

require (
	"golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54
	"rsc.io/quote" v1.4.0
	"rsc.io/sampler" v1.2.0
)
$
```

ダウングレードの究極の形として、依存しているものを全て削除することもできる。これにはバージョンとして `none` を指定する。

```
$ vgo get rsc.io/sampler@none
vgo: downloading rsc.io/quote v1.4.0
vgo: finding rsc.io/quote v1.3.0
$ vgo list -m
MODULE                VERSION
github.com/you/hello  -
golang.org/x/text     v0.0.0-20180208041248-4e4a3210bb54
rsc.io/quote          v1.3.0
$ cat go.mod
module "github.com/you/hello"

require (
	"golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54
	"rsc.io/quote" v1.3.0
)
$ vgo test all
vgo: downloading rsc.io/quote v1.3.0
?   	github.com/you/hello	[no test files]
ok  	rsc.io/quote	0.014s
$ 
```

それでは、全てが最新版であった状態に戻って、`rsc.io/sampler` も v1.99.99 にしておこう。

```
$ vgo get -u
vgo: finding golang.org/x/text latest
vgo: finding rsc.io/quote latest
vgo: finding rsc.io/sampler latest
vgo: finding golang.org/x/text latest
$ vgo list -m
MODULE                VERSION
github.com/you/hello  -
golang.org/x/text     v0.0.0-20180208041248-4e4a3210bb54
rsc.io/quote          v1.5.2
rsc.io/sampler        v1.99.99
$
```

### Exclude

今回の hello world にとって v1.99.99 が良くないことが分かったので、将来的な問題を避けるために、このことをどこかに記録しておきたいとしよう。これは `go.mod` に `exclude` 命令を追加することで行うことができる。

```
exclude "rsc.io/sampler" v1.99.99
```

するとここからの操作はまるでその module が存在しないかのように振る舞う。

```
$ echo 'exclude "rsc.io/sampler" v1.99.99' >>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 # excluded
$ vgo get -u
vgo: finding golang.org/x/text latest
vgo: finding rsc.io/quote latest
vgo: finding rsc.io/sampler latest
vgo: finding rsc.io/sampler latest
vgo: finding golang.org/x/text latest
$ vgo list -m
MODULE                VERSION
github.com/you/hello  -
golang.org/x/text     v0.0.0-20180208041248-4e4a3210bb54
rsc.io/quote          v1.5.2
rsc.io/sampler        v1.3.1
$ cat go.mod
module "github.com/you/hello"

require (
	"golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54
	"rsc.io/quote" v1.5.2
	"rsc.io/sampler" v1.3.1
)

exclude "rsc.io/sampler" v1.99.99
$ vgo test all
?   	github.com/you/hello	[no test files]
?   	golang.org/x/text/internal/gen	[no test files]
ok  	golang.org/x/text/internal/tag	(cached)
?   	golang.org/x/text/internal/testtext	[no test files]
ok  	golang.org/x/text/internal/ucd	(cached)
ok  	golang.org/x/text/language	(cached)
ok  	golang.org/x/text/unicode/cldr	(cached)
ok  	rsc.io/quote	(cached)
ok  	rsc.io/sampler	(cached)
$
```

この `exclude` は、今の module をビルドするときにのみ適用される。もし今の module がより大きなビルドによって必要とされたとき、この `exclude` は適用されない。たとえば、`rsc.io/quote` の `go.mod` に書かれた `exclude` は我々の “hello, world” のビルドには適用されなかった。この取り決めによって、module の作者にとっては自身のビルドに対してほぼ何でも制御できるようにする一方、その module に依存する module にとってはほとんどどんな制御も制限しない。

さて、今すべき次の行動は、`rsc.io/sampler` の作者に連絡をとって、v1.99.99 の問題を報告し、それが v1.99.100 で修正されるようにすることだ。しかし残念なことに、その作者はそのバグを直さないというブログ記事を出してしまった。

### Replace

依存関係の問題を特定できたなら、それが修復されたコピーを代わりに使って、一時的に何とかする必要がある。それでは `rsc.io/quote` の何らかの挙動を変えたいとしよう。もしかするとこの問題は `rsc.io/sampler` の方で対処できるかもしれないし、それ以外の方法で何とかなるかもしれない。まずは通常の git コマンドを使って `quote` module を見てみよう。

```
$ git clone https://github.com/rsc/quote ../quote
Cloning into '../quote'...
```

そして `../quote/quote.go` を編集して `func Hello` の何かを変更しよう。たとえばここでは、戻り値を `sampler.Hello()` から `sampler.Glass()` に変更し、もっと面白い挨拶にしてみる。

```
$ cd ../quote
$ <edit quote.go>
$ 
```

フォークを変更したら、変更命令を `go.mod` に書き加えることで、実際のものの代わりに変更したものをビルドで使うことができる。

```
replace "rsc.io/quote" v1.5.2 => "../quote"
```

このようにして、変更されたものを使ってプログラムをビルドできる。

```
$ cd ../hello
$ echo 'replace "rsc.io/quote" v1.5.2 => "../quote"' >>go.mod
$ vgo list -m
MODULE                VERSION
github.com/you/hello  -
golang.org/x/text     v0.0.0-20180208041248-4e4a3210bb54
rsc.io/quote          v1.5.2
 => ../quote
rsc.io/sampler        v1.3.1
$ vgo build
$ ./hello
I can eat glass and it doesn't hurt me.
$
```

変更後のものに異なる module の名前を与えることもできる。たとえば [github.com/rsc/quote](https://github.com/rsc/quote) をフォークして、何かしら変更を push したとする。

```
$ cd ../quote
$ git commit -a -m 'my fork'
[master 6151719] my fork
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git tag v0.0.0-myfork
$ git push https://github.com/you/quote v0.0.0-myfork
To https://github.com/you/quote
 * [new tag]         v0.0.0-myfork -> v0.0.0-myfork
$ 
```

するとそれを変更後のものとして使うことができる。

```
$ cd ../hello
$ echo 'replace "rsc.io/quote" v1.5.2 => "github.com/you/quote" v0.0.0-myfork' >>go.mod
$ vgo list -m
vgo: finding github.com/you/quote v0.0.0-myfork
MODULE                    VERSION
github.com/you/hello      -
golang.org/x/text         v0.0.0-20180208041248-4e4a3210bb54
rsc.io/quote              v1.5.2
 => github.com/you/quote  v0.0.0-myfork
rsc.io/sampler            v1.3.1
$ vgo build
vgo: downloading github.com/you/quote v0.0.0-myfork
$ LANG=fr ./hello
Je peux manger du verre, ça ne me fait pas mal.
$ 
```

### 後方互換性

たとえ `vgo` を自分のプロジェクトで使いたいと思ったとしても、おそらく全てのユーザーに `vgo` を使わせたいとまでは思わないだろう。その代わり、`go` コマンドのユーザーにほとんど同じビルドをさせるためのベンダー・ディレクトリを作成することができる (もちろん GOPATH の中でビルドすることになる)。

```
$ vgo vendor
$ mkdir -p $GOPATH/src/github.com/you
$ cp -a . $GOPATH/src/github.com/you/hello
$ go build -o vhello github.com/you/hello
$ LANG=es ./vhello
Puedo comer vidrio, no me hace daño.
$ 
```

「ほとんど同じ」ビルドだといったが、これはツールチェインから見えている、最終的なバイナリに記録される import path が異なるからだ。ベンダリングされたビルドでは、ベンダー・ディレクトリが見える。

```
$ go tool nm hello | grep sampler.hello
 1170908 B rsc.io/sampler.hello
$ go tool nm vhello | grep sampler.hello
 11718e8 B github.com/you/hello/vendor/rsc.io/sampler.hello
$ 
```

## What's Next?

是非 `vgo` を試して欲しい。あなたのリポジトリをバージョンでタグ付けし始めて欲しい。まず `go.mod` ファイルを作成し、確認してみよう。Issue は [golang.org/issue](https://golang.org/issue) に、タイトルの先頭に「x/vgo:」を付けた上で投稿して欲しい。明日は更に投稿する予定だ。それでは！

----

## 訳註

翻訳ミス、誤字等の指摘は、編集リクエストやこのページへのコメントで行って頂けると幸いです。
記事の内容そのものに対する指摘は、Russ による[元の記事](https://research.swtch.com/vgo-tour)へのコメントや、[Go subreddit](https://www.reddit.com/r/golang/comments/7yy3wa/a_tour_of_versioned_go_vgo_go_amp_versioning_part/) への投稿、[golang-nuts](https://groups.google.com/forum/#!forum/golang-nuts) への投稿をお願いします。意見が Russ に届くことが重要なので、日本語では意味が薄そうです。

**ライセンス表示**: この記事は、[Russ Cox][] によって書かれたものを @nekketsuuu が翻訳・公開したものです。[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) ライセンスの元で公開します。



  [Russ Cox]: https://swtch.com/~rsc/
  [vgo]: https://qiita.com/nekketsuuu/items/36f00484ff7c30fd2007
  [vgo-intro]: https://qiita.com/nekketsuuu/items/60634417e6279ccfd95b