Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

A Tour of Versioned Go (vgo)

(Go & Versioning、パート 2)

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

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


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

今、vgogo get golang.org/x/vgo を実行することでダウンロードでき、試せる。Vgogo コマンドの完全な互換品で (あり、フォークしたコピーでも) ある。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 を使い、タイトルの先頭に「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/samplergolang.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/quotersc.io/sampler に依存していることが分かったが、これは golang/x/textlanguage matching のために使っているのだ。

$ 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/textrsc.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/quotersc.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.modexclude 命令を追加することで行うことができる。

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/quotego.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 をフォークして、何かしら変更を 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 に、タイトルの先頭に「x/vgo:」を付けた上で投稿して欲しい。明日は更に投稿する予定だ。それでは!


訳註

翻訳ミス、誤字等の指摘は、編集リクエストやこのページへのコメントで行って頂けると幸いです。
記事の内容そのものに対する指摘は、Russ による元の記事へのコメントや、Go subreddit への投稿、golang-nuts への投稿をお願いします。意見が Russ に届くことが重要なので、日本語では意味が薄そうです。

ライセンス表示: この記事は、Russ Cox によって書かれたものを @nekketsuuu が翻訳・公開したものです。CC BY 4.0 ライセンスの元で公開します。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした