Go
バージョン管理
versioning
vgo

和訳: Go += Package Versioning (Go & Versioning, Part 1)

Go += Package Versioning

(Go & Versioning、パート 1)

※訳註: この記事は、Russ Coxによって 2018 年 2 月 20 日に投稿された記事 “Go += Package Versioning” の和訳版です。筆者に許可をとり、@nekketsuuu が和訳・公開しました。この記事では新しく提案されている Go のバージョン管理方法について、提案するに至った経緯が説明されています。かなり長いので、サクッと知りたい方や具体的なコマンドを知りたい方は、続くパート 2 の記事をご覧下さい。


Go にはパッケージのバージョン付けが必要だ。

より正確には、Go を使う人と Go のツール双方に共通したやり方でパッケージのバージョンを捉えられるようになり、互いにやりとりする際、厳密にどのプログラムがビルドされ、実行され、解析されるのかをしっかり把握できるようにすべきだ。つまり、go コマンドが使われるときはどのパッケージのどのバージョンがビルドされるのか開発者へ表示される必要があるし、その逆に開発者はそれを伝えられる必要がある。

バージョンを付ければ、ビルドが再現可能になる。つまり、自分のプログラムの最新版を誰かに試してもらうとき、その人が単に自分のコードの最新版を使っているということだけではなく、自分のコードが依存している全てのパッケージのバージョンが同一であるということまで分かるのだ。結果として自分とその人がビルドしたバイナリは完全に一致する。

また、バージョンを付ければ、今日ビルドできたプログラムが明日ビルドできることも保証してくれる。もし依存したパッケージの新しいバージョンが出ていたとしても、明示しない限り go コマンドはそれらを使わない。

ただし、バージョンを追加したいといっても、今の go コマンドが持つ簡便性、速度、そして分かりやすさなどの長所はそのままにしておきたい。最近では多くのプログラマは大抵バージョンに注意をはらっておらず、そして大抵すべてのことはそれで上手くいく。もし我々がそのモデルとデフォルトを正しく捉えられているなら、プログラマが 今まで同様 バージョンに注意をはらわなくて済むような形でバージョン付けの仕組みを追加でき、その結果すべてがより良くなり、分かりやすくなるはずだ。既存のワークフローへの変更は最小限になるはずだ。新しいバージョンをリリースすることも簡単であろう。一般に、バージョン管理は後ろで勝手に行われるようなものでなければならず、常に気にするようなものであってはならない。

まとめると、パッケージのバージョン付けは必要だが、go get を壊さずに行う必要があるということだ。この投稿では、まさにそれを行うための叩き台を紹介する。プロトタイプのデモも用意してあり、すぐに試せる。上手くいけば、これが最終的な go コマンドに組み込まれる素地となるだろう。私はこの投稿を、何ができて何ができないかの生産的な議論の開始点としたい。この議論に基づいて提案とプロトタイプを調整し、Go 1.11 にオプション機能として組み込めるよう、公式の Go proposal を出そうと思う。

この提案は go get の長所を保ち、再現可能なビルドができるようにし、semantic versioning を採用し、ベンダリングを排除し、プロジェクト基準なワークフローでの GOPATH を廃止し、そして、dep やその先輩ソフトウェアたちからスムーズに移行できるようにする。とはいえ、この提案はまだ初期段階だ。細かい部分が上手くいっていなければ、Go 本体に実装するより前に直す時間をとろうと思う。

背景

提案そのものを見る前に、今現在どうやっているのかを見てみよう。ちょっと長くなるが、この歴史を振り返ることは重要な学びになるし、今回の提案が現在行われていることの何を変えるのかを理解する助けになるだろう。忙しい人は、提案本体まで飛ばすか、例を用いて説明する次の投稿を読むようにして欲しい。

Makefile、goinstall、そして go get

2009 年 11 月、Go が最初にリリースされたとき配布されたのはコンパイラ、リンカ、そしていくつかのライブラリだった。プログラムをコンパイルしてリンクするために 6g6l を実行していて、サンプルの makefile を同梱していた。小さいラッパー gobuild があって、ひとつのパッケージをビルドしたり、大抵適切な makefile を書くことができていた。他人とコードを共有するための確立された方法はなかった。我々はもっと良い方法が求められていると分かってはいたが、ひとまず作ったものをリリースし、後はコミュニティと一緒に考えようとしていた。

2010 年 2 月、我々は goinstall を提案した。これは Bitbucket や GitHub のようにソースコードを管理するリポジトリからパッケージをダウンロードするもので、追加設定なしで動く新しいコマンドだった。Goinstall は、現在でも使われている import パスのルールを作った。導入されたときはそのようなルールに従っていなかったので、最初 goinstall は標準ライブラリ以外何もインポートしていないパッケージでのみ動作した。しかし開発者は彼らが使っていた名前の付け方から現在知られている付け方へ素早く移植し、公開されている Go パッケージたちはこの統一されたシステムのもとで発展していった。

また、goinstall は makefile を不要にし、そして同時に、ユーザーが定義した複雑なビルド方法も消し去った。それぞれのビルドでコードを生成できないことはパッケージの作者にとって時々不便ではあったものの、パッケージの ユーザー にとっては、この単純化がとてつもなく重要だった。ユーザーは、パッケージをビルドする前にパッケージ作者と同じツールセットをインストールしなくてもよくなったのだ。更に、この単純化はツールにとっても大事なことだった。Makefile は命令的で、パッケージをコンパイルするための手順を順番に記述する。したがって同じパッケージに go vet やコード補完のような異なるツールを適用するためには makefile のリバース・エンジニアリングが必要で、これは時にとても難しい。必要なパッケージを必要なときにだけビルドするために用いる、パッケージの依存性を解析することすら、任意の makefile が考えられる場合は充分難しい課題となる。この時柔軟性が無くなるからといって導入に反対する人がいたが、振り返ってみれば、得られる利益の方が不便さを上回っていたことは明らかだ。

2011 年 12 月、Go 1 の準備の一部として、我々は go コマンドを導入しgoinstallgo get で置き換えた。

全体的に見て、go get は革命的だった。Go 開発者は go get によって、ソースコードを共有して互いに互いの作ったものをビルドできるようになったし、ツールにおいても go コマンドの内部はどんなビルドシステムになっているのかを考えなくてよくなった。ただし、go get にはバージョン付けの概念は一切無い。goinstall についての最初の話し合いで、明らかにバージョン付けについて何か必要だ、という話になったのだが、残念なことに、少なくとも我々 Go チームの面々には、それでは何をすべきなのかが明らかではなかった。パッケージをインストールする際、go get は必ず最新のコピーをとるようにしており、ダウンロードやアップデートの操作は Git や Mercurial などのバージョン管理システムに任せている。このようにパッケージのバージョン付けを無視したことにより、少なくとも 2 つの大きな短所が生まれた。

バージョン付けと API の安定性

go get が持つ 1 つ目の大きな短所は、バージョン付けの概念が無いと、アップデートによって何が変わるのかをユーザーへ警告できないという点だ。

2013 年 11 月、Go 1.2 でパッケージのバージョン付けに関する FAQ が追加された。それは以下の基本的なものだ (Go 1.10 でも変わっていない):

誰かに使ってもらうためのパッケージは、後方互換性を守るべきだ。Go 1 の互換性ガイドラインが参考になるだろう: export された名前を削除しない、タグ付きの composite literal を使う、など。もし新しい機能が必要なら、古いものを変えるかわりに新しい名前を付けよう。もし完全に非互換とする必要があるなら、新しい import path で新しいパッケージを作ろう。

2014 年 3 月、Gustavo Niemeyer は「Go 言語のための安定した API」を謳って gopkg.in を立ち上げた。これはバージョンを元に GitHub へリダイレクトするもので、gopkg.in/yaml.v1gopkg.in/yaml.v2 などといった import path を使って 1 つの Git リポジトリの異なるコミット (あるいは異なるブランチ) を参照するという仕組みだ。セマンティック・バージョニングに従えば、互換性を破る変更をするときは新しいメジャー・バージョンを導入するだろうし、その後、v1 の import path は以前のものを完全に互換するものとして扱われ、v2 の import path は全く違う API があるかもしれないものとして扱われるだろう。

2015 年 8 月、Dave Cheney がセマンティック・バージョニングを採用する提案を出した。これはその後数ヶ月興味深い議論を呼んだ。そこではコードにセマンティック・バージョンを付けることには皆賛成しているようだったが、ではそのバージョンを元にツールが何をすべきかという問題には誰も答えられなかった。

また、セマンティック・バージョニングに関するどんな議論も、Hyrum の法則由来の反論は避けられない。これは以下のようなものだ。

API を使用するユーザーが充分な数存在するなら、あなたがそれにどんな仕様を約束しようと関係ない。観測可能な動作であれば全て、誰かがその動作に依存しているからだ。

たとえ Hyrum の法則が経験的に正しいとしても、リリース間にどんな関係があるか予想できるようにするためにセマンティック・バージョニングは有用だ。1.2.3 から 1.2.4 にアップデートしても自分のコードは動くだろうが、1.2.3 から 2.0.0 にアップデートすると動かなくなるかもしれない。もし 1.2.4 へのアップデートで動かなくなってしまったら、おそらくバグ・レポートが望まれているし、1.2.5 で修正されるだろう。もし 2.0.0 へのアップデートで動かなくなったら (あるいはコンパイルすらできなくなったら)、おそらく自分のコード内部を見てやる必要があるだろうし、2.0.1 等でそれが修正される可能性は低いだろう。

結論として、Hyrum の法則からセマンティック・バージョニングを否定するのではなく、必要がない限り、依存するパッケージを作者がビルドしたときと全く同じバージョンに揃えてビルドすべきなのだ。つまり、ビルドというものはデフォルトで最大限再現性を上げておくべきだ。

ベンダリングと、再現可能なビルド

go get が持つ 2 つ目の大きな短所は、バージョン付けの概念が無いと、ビルドの再現可能性を担保できなかったり、あるいは設定することすらできないという点だ。依存するパッケージたちのバージョンが自分のときと同じ状況でコンパイルされているか確かめる方法が無いのだ。2013 年 11 月、Go 1.2 FAQ にはこういう内容も追加された:

もし外部パッケージを使っていて、それが意図しない方法によって変わるかもしれないと思っているのなら、それをローカルのリポジトリへコピーしてしまうのが最も単純な解決策だ。(これは Google 内部でとられている方法と同じである。) コピーを新しい import path に置いて、それがローカルのコピーだと分かるようにしよう。たとえば、"original.com/pkg""you.com/external/original.com/pkg" にコピーするような感じだ。Keith Rarick の作った goven などのツールが、この作業を自動化するのに役立つだろう。

Keith Rarick が 2012 年 3 月に始めた goven は、依存するパッケージを自分のリポジトリへコピーし、関係する import path が全て新しいものになるよう更新するというものだった。ビルドを通すためにはこの方法で依存パッケージのソースコードを改変しなければいけないが、これはあまり適切ではなかった。改変したことで、新しいコピーや依存する他のコピーのアップデートを比較したり組み込んだりするのが難しくなったのだ。

2013 年 9 月、「パッケージの依存関係を固定するための新しいツール」として Keith は godep を発表したGodep の主な改善点は、今でいうところの Go ベンダリングを導入した点だ。つまり、依存パッケージをソースコードを 改変することなく プロジェクトへコピーする。このときにはツールチェインの直接のサポートは無く、適当な方法で GOPATH を設定することによって行っていた。

2014 年 10 月、Keith は Go のツールチェインが「外部パッケージ」の概念を扱えるようにする提案をした。こうするとツールがプロジェクトをより上手く扱えるだろうということだった。この頃から、godep に似たことをするものが複数現れた。Matt Farina は “Glide in the Sea of Go Package Managers” というブログ記事を書き、godep と新しいツールたち、特に glide と比較した。

2015 年 4 月、Dave Cheney が「ソース・ベンダリングによって繰り返しビルドが走ることを許す、プロジェクト基準のビルドツール」として gb を導入した。これも import path を書き換えないものだった。 (gb には別の動機として、GOPATH 中の特定のディレクトリにコードを保存することを避けるというものがあった。これは多くの開発者のワークフローにそぐわないからだ。)

同じ春、Jason Buberel は Go のパッケージ管理周辺の状態を調べ上げ、複数の成果をひとつにまとめ、無駄な努力をしないためには何ができるかを明らかにした。彼の調査によって Go チームの我々としては、go コマンドが import パスの書き換えなしにベンダリングすることを直接サポートするのが必要であると明らかになった。同時に、Daniel Theophanes はベンダー・ディレクトリ中のコードがどこのもののバージョンいくつなのかを正確に記述するためのファイル・フォーマット仕様を策定し始めた。2015 年 6 月には Keith の提案を Go 1.5 vendor experiment として承認し、Go 1.5 ではオプション機能、Go 1.6 ではデフォルト機能として使えるようにした。また我々は、共通したメタデータ・ファイル・フォーマットが使えるようにするため、ベンダリング・ツールの全ての作者に Daniel と協力するよう勧めた。

Go ツールチェインにベンダリングの概念を入れたことで、go vet のようなプログラム解析ツールがベンダリングを使ったプロジェクトを上手く扱えるようになり、実際現在たくさんの Go パッケージ・マネジャーやベンダリング・ツールはベンダー・ディレクトリを扱っている。対して、これらのツールはそれぞれ異なるメタデータ・ファイル・フォーマットを用いているため、互いに協力したり、依存性情報を簡単に共有したりすることはできていない。

より根本的には、パッケージのバージョン管理問題に対してベンダリングを適用しても不完全である。ベンダリングは再現可能なビルドができるようにするだけである。ベンダリングはパッケージのバージョンを理解しやすくはしないし、パッケージのどのバージョンを使うべきか決めやすくもしない。Glidedep のようなパッケージ・マネジャーは Go のビルドにバージョン付けの概念を暗に導入したが、これはツールチェインの直接のサポートがあったわけではなく、適当な方法でベンダー・ディレクトリを用意することで行われた。結果として、Go 周辺の多くのツールはバージョンを適切に扱えない。明らかに、パッケージのバージョンをツールチェインが直接扱えることが必要なのだ。

公式のパッケージ管理実験

GopherCon 2016 において、関心のある gopher たちのグループが Hack Day (今の Community Day) に集まり、Go のパッケージ管理について広く議論した。そのひとつの結果が、Go のパッケージ管理のための新しいツールを作ることを目標とした、パッケージ管理のための委員会の結成だ。このツールでは既存のものをまとめ、置き換えることを見据えていたが、一方でツールチェインに頼らず、ベンダー・ディレクトリを使って実装されようとしていた。Peter Bourgon によってまとめられた Andrew Gerrand、Ed Muller、Jessie Frazelle、そして Sam Boyer らの委員会は、仕様のドラフトをまとめ、そして Sam 主導のもと、dep として実装した。この背景については、Sam が 2016 年 2 月に投稿した “So you want to write a package manager”、2016 年 12 月に投稿した “The Saga of Go Dependency Management”、そして 2017 年 7 月に GopherCon で発表された “The New Era of Go Package Management” を参考にされたい。

Dep は多くの目的を果たしている。Dep は実際に使用でき、それまでのものより進歩していて、問題解決のための重要な一歩であり、そしてまた、実験でもある。我々はこれを「公式の実験」と呼び、Go 開発者にとって何が良くて何が良くないのかを知る助けにしている。ただし dep は、最終的な go コマンドに組み込まれるものの直接のプロトタイプではない。Dep はパワフルで、設計にあたってほぼ充分に柔軟なやり方であり、また、Go のプログラムをビルドする方法を知る際には makefile のような役割を果たす。しかし、一旦その設計を理解し、それをいくつかの重要かつ必要不可欠な特徴に分解できてしまうと、その他の機能を削除し、表現力を落とした方が、Go のコード基盤をより統一した分かりやすいものにし、ツールにとってビルドが簡単になるための助けになるのだ。

この投稿は、dep の次の段階のための最初の一歩だ。つまり、最終的に go コマンドに組み込まれることになるプロトタイプの最初のドラフトであり、goinstall と同様のパッケージ管理である。このプロトタイプはスタンドアローンなコマンドであり、我々は vgo と呼んでいる。これは go コマンドと完全に互換し、更にパッケージのバージョン付けをサポートしている。これは新しい実験であり、我々はここから何が学べるか観察するつもりだ。我々が goinstall を導入したときのように、いくらかのコードとプロジェクトは既に vgo に対応しており、他のプロジェクトも対応する必要があるだろう。Makefile を止めたときのように、我々はいくつかの操作と表現力を手放し、システムを簡略化し、ユーザー側の複雑度を下げている。通常通り、我々は vgo による実験をしてくれるアーリー・アダプタを求めており、ユーザーからなるべく多くのことを学ぼうとしている。

この vgo による実験の開始は、dep のサポート終了を意味しない。我々は、go コマンドへの組み込み方法が完全に決まり、実装され、そして広く使われるようになるまでは dep を保全する。また我々は、dep コマンドから go コマンドへの最終的な移行方法をなるべく円滑なものにするつもりだ。そうすることにより、まだ dep に移行していないプロジェクトも恩恵を受けるだろう。 (godepglide はアクティブな開発を終了し、dep への移行を勧めていることに注意。) それ以外のプロジェクトは、もし既に希望要件を満たしているなら、直接 vgo へ移行するだろう。

提案

バージョン付けを go コマンドに追加する提案は、4 ステップから成る。第一に、Go FAQ や gopkg.in に着想を得た import compatibility rule を採用する。つまり、ある import path にあるパッケージの新しいバージョンは、古いバージョンに対して後方互換性を保つという習慣を根付かせる。第二に、ビルドにおいてパッケージのどのバージョンが選ばれるのかについて、簡便かつ新しいアルゴリズムである minimal version selection を使用する。第三に、Go module の概念を導入する。これはひとつの部品としてバージョン付けされたパッケージの集まりで、それらの依存性を満たすために必要な最小の条件が定義されているものだ。第四に、これら全てを、現在使われているワークフローを大きく変えることなく、どのように既存の go コマンドと統合するかを定める。この節の残りの部分では、これら 4 ステップそれぞれについて紹介する。今週投稿される他の記事で、より詳細に入ろうと思う。

The Import Compatibility Rule (import の互換性ルール)

パッケージ管理システムで苦しいところはほぼ、非互換性をどうにかしようとするときに生じる。たとえば、殆どのシステムで、パッケージ B がパッケージ D の 6 以降を要求させており、また、パッケージ C が D の 2、3、4 を要求し 5 以降は対応していないとしよう。もしあなたがパッケージ A を書いていて、そして B と C のどちらもを使いたくなったとき、残念なことになる。どのバージョンの D を選んだとしても、A のために B と C 両方をビルドすることはできない。こうなるともう何もできない。このシステムは B と C のこのような振る舞いを許容しており (あるいは、推奨しており)、したがってどうすることもできない。

巨大なプログラムがビルドできなくなることを避けられないようなシステムを設計する代わりに、この提案ではパッケージの作者に以下の import compatibility rule を要求する。

もし古いパッケージと新しいパッケージが同一の import path を持っているなら、新しいパッケージは古いパッケージと後方互換でないといけない。

このルールは上で引用した Go FAQ での助言を言い換えたものである。引用された FAQ の文章の最後には「もし完全に非互換とする必要があるなら、新しい import path で新しいパッケージを作ろう」と書かれていた。最近の開発者はこのような非互換性を表すのにセマンティック・バージョニングを使うだろうから、我々は提案の中にセマンティック・バージョニングを含めた。具体的には、メジャー・バージョン 2 以降を使うためには、以下のようなパスを指定すれば良い。

import "github.com/go-yaml/yaml/v2"

セマンティック・バージョニングとしては大きな非互換性を意味する v2.0.0 を作ることで、import compatibility が要求するように、新しい import path の新しいパッケージが作られている。それぞれのメジャー・バージョンは異なる import path を持つため、それぞれの Go アプリはその内のどれか 1 つのメジャー・バージョンを保持することになる。これは望ましいやり方だ。こうすることでプログラムが上手くビルドされ続けるし、非常に巨大なプログラムの一部を個別に v1 から v2 へアップデートすることもできる。

パッケージの作者が import compatibility rule を守ると仮定することで、我々は非互換性を扱わなくてもよくなり、システム全体が非常に素朴になり、パッケージの管理方法が断片化しにくくなる。もちろん実際には、たとえ作者が最大限努力したとしても、同じメジャー・バージョン内でのアップデートがユーザーにとっては大きな非互換性を生むときがあるだろう。このため、更新頻度を上げすぎることなくアップグレードする仕組みが重要になる。これが次のステップだ。

Minimal Version Selection (最小バージョンの選択)

Depcargo など、最近のほぼ全てのパッケージ・マネジャーはビルドの際、指定された範囲の中で可能な限り最新のパッケージを使用する。私は主に 2 つの理由から、これが間違ったデフォルト設定であると信じている。第一に、「可能な限り最新のバージョン」の意味は、外部のイベント、つまり新しいバージョンの公開によって変わるということだ。今夜依存パッケージのどれかの最新バージョンを誰かがリリースするかもしれないし、明日には今日打ち込んだコマンド列の挙動が変わるかもしれない。第二に、このデフォルト設定を上書きする際、開発者はパッケージ・マネジャーに「駄目、X を使わないで」と伝えるために時間を費やし、そしてパッケージ・マネジャーは X を使わない方法を探すのに時間を使ってしまうことだ。

今回の提案では、私が minimal version selection と呼んでいる別の方法をとる。この方法は、ビルド中に出てきたそれぞれのパッケージについて、指定された中で 最も古い バージョンを選ぶことをデフォルトとする。より古いバージョンが公開されることはないので、こうやって決められたものが明日になって変わることはない。より良いことに、このデフォルトを上書きするために、開発者はパッケージ・マネジャーに対して「駄目、少なくとも Y を使って」と伝えればよく、するとパッケージ・マネジャーはどのバージョンを使うべきか自明に決めることができる。こうするとバージョンが極小のものが選ばれるし、システム全体としてもおそらく極小になり、既存のシステムのように複雑にならなくなるだろうから、私はこの仕組みを minimal version selection と呼んでいる。

Minimal version selection はモジュールに対して、依存モジュールのバージョン下限のみを制限できるようにしている。そうすることでアップグレードにもダウングレードにもきちんと定義された唯一の解が与えられるし、またこれらの操作の効率的な実装ができる。更にこうすることでモジュール全体の作者に対し、排除すべきバージョンを指定したり、フォークされたコピーで置き換えられるべき特定のバージョンを指定したりできる。このとき、このコピーがローカルのファイルシステムにあろうと自身のモジュールとして公開されていようと関係ない。このように排除したり置き換えたりすることは、そのモジュールが他のモジュールの依存モジュールとしてビルドされるときには適用されない。そうすることで自身のプログラムがどのようにビルドされるのかについて完全な権限をユーザーに与えつつ、他人のプログラムがどうビルドされるかには干渉しないようにしている。

Minimal version selection はデフォルトでビルドの再現性を与える。ロック・ファイルも要らない。

Import compatibility は minimal version selection を単純にするためのキーとなる。ユーザーは「駄目、それは新しすぎる」と言う代わりに、「駄目、それは古すぎる」と言うことしかできない。この場合、解決法は明らかで、(極小的に) より新しいバージョンを使えばよい。そして新しいバージョンは、古いものと互換するはずなのだ。

Go Module の定義

Go module とは、module path と呼ばれる共通の import path を先頭に持つパッケージの集まりだ。Module はバージョン付けの単位であり、module のバージョンはセマンティックなバージョン文字列として書かれる。Git を使って開発するなら、module の新しいセマンティック・バージョンは、module の Git リポジトリにタグを追加することで定義される。また、セマンティック・バージョンが強く推奨されるものの、特定のコミットを指定することもサポートされる予定だ。

Module はそれが依存する他の module の最小バージョン条件を、新しいファイル go.mod の中で定義する。たとえば、以下は単純な go.mod ファイルの例だ。

// My hello, world.

module "rsc.io/hello"

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

このファイルは rsc.io/hello で識別される module を定義しており、他の 2 つの module、golang.org/x/textrsc.io/quote に依存している。Module のビルドは常にこの go.mod ファイルで与えられた具体的なバージョン依存関係を基に行われることになる。巨大なビルドの一部としては、ビルド中に必要とされたもののみ新しいバージョンを使うことになる。

Module の作者はセマンティック・バージョンを使って各リリースにタグ付けすることが期待され、vgo はそのタグ付けされたバージョンを使う。コミットではない。たとえば rsc.io/quote module は github.com/rsc/quote から提供されていて、v1.5.2 などのバージョンでタグ付けされている。タグ付けされていないコミットに名前を付けるためには、擬似的なバージョン v0.0.0-yyyymmddhhmmss-commit が与えられた日時に行われた特定のコミットを識別する。セマンティック・バージョニングではこの文字列は v0.0.0 プレリリースに相当し、プレリリースの識別子 yyyymmddhhmmss-commit を持つ。セマンティック・バージョニングの順番付けルールに従うと、このようなプレリリースは v0.0.0 以降のバージョン全てより前にあることになり、そしてこれらは文字列比較によって順序付けされる。日付を最初に持ってくることで、この疑似バージョン構文において文字列の比較が日時の比較になるようにしてある。

バージョン条件に加え、go.mod ファイルには前の節で書いたような排除や置き換えが指定できる。ただし再度書いておくと、それらはその module を直接ビルドするときにのみ適用され、巨大なプログラムの一部として module をビルドするときには適用されない。このはこれら全てについて述べている。

Goinstall や古い go get は、コードをダウンロードするのに直接 githg などのバージョン管理ツールを呼び出している。これは多くの問題を引き起こしており、特に断片化が起こる。たとえば bzr を持っていないユーザーは Bazaar のリポジトリにあるコードをダウンロードできない。これに対して常に、module は HTTP で提供される zip ファイルである。元々 go get は有名なコード・ホスティング・サイトに対しては特別にバージョン管理のコマンドを選んでおいていた。しかし vgo ではそれらのホスティング・サイトの API を用いて圧縮ファイルを取ってくるようになっている。

Module を zip ファイルとして統一的に扱うことで、module をダウンロードするプロキシのためのプロトコルを自明にし、実装を可能にする。企業や個人はセキュリティや、削除されたもののキャッシュ・コピーをとりたいなどのどんな理由であれ、プロキシを使うことがあるだろう。プロキシが確実に使え、go.mod にどのコードを使うのか書くことで、vendor ディレクトリは最早必要なくなるのだ。

go コマンド

Module を扱えるよう、go コマンドはアップデートしなくてはいけない。ひとつの重要な変更は、go buildgo installgo run、そして go test などの通常のビルドコマンドが新しい依存関係を解決できるようにすることだ。新しい module において golang.org/x/text を使うために必要なことは、Go のソースコードに import を足して、ビルドすることだけだ。

What's Next?

どのように vgo を使うのか示すために、私は別個 “A Tour of Versioned Go” を投稿した。今どうやって vgo をダウンロードして実験するかについては、この投稿を見て欲しい。この投稿で書かなかった詳細を説明するために、今週はもっと別の記事も投稿する。投稿に対してコメントでのフィードバックを歓迎するし、私は Go の subreddit や golang-nuts メーリングリストを見ていようと思う。最後の投稿として、金曜日に FAQ を投稿しようと思っている (今の所は)。来週、正式に Go proposal を出そうと思う。

是非 vgo を試して欲しい。あなたのリポジトリをバージョンでタグ付けし始めて欲しい。なお、もし空の go.mod があって、既に depglideglockgodepgodepsgovendgovendor、または gvt の設定ファイルがあるようなリポジトリで実行した際には、vgo はそれらの設定ファイルを用いて go.mod ファイルを作ろうとする。

私は、Go でバージョンを統一的に扱うという、長年の懸念であった問題に取り組むことにワクワクしている。Go をやろうとした開発者が抱える共通した問題のいくらかは、再現性のあるビルドができなかったり、go get がリリースタグを完全に無視していたり、パッケージの複数のバージョンを扱うために GOPATH を使えなかったり、GOPATH の外部のソースコードを必要としていたりすることだ。ここで提案した設計はこういった問題を全て解決し、更にそれ以上のはたらきを見せるだろう。

ただし、私はきっとどこか細かいミスをしていると思っている。私はユーザーが、新しい vgo プロトタイプを試してこの設計を正しく理解し、生産的な議論を産んでくれると期待する。私は Go 1.11 でテクニカル・プレビューとして Go module のサポートを準備し、Go 1.12 で公式にサポートしたい。その後のリリースでは、古くなった、バージョン付けをしていない go get のサポートを削除するだろう。これは幾らか積極的なスケジュールだが、今回の機能を正しく理解すれば、新しいリリースを待つようになるだろうと思う。

私は古い go get や、数々のベンダリング・ツールから新しい module システムに移行することに非常に気を遣っている。これは私にとって今回の機能を正しく理解してもらうのと同じくらい重要だ。移行が成功すれば、新しいリリースを待つようになるだろう。

Peter Bourgon、Jess Frazelle、Andrew Gerrand、Ed Mueller、そして Sam Boyer に感謝する。彼らはパッケージ管理の委員会で働いてくれ、また今までたくさんの有用な議論を行ってくれた。Dave Cheney、Gustavo Niemeyer、Keith Rarick、そして Daniel Theophanes に感謝する。彼らは Go のパッケージ・バージョニングについて重要な貢献をしてくれた。Dep を作った Sam Boyer に再度感謝し、彼および dep の contributor の dep に対する貢献に感謝する。その他今までにベンダリング・ツールを作り貢献してくれた全ての方に同様に感謝する。最後に、事前にこの提案について手助けしてくれ、間違っている箇所を修正し、なるべく円滑にパッケージ・バージョニングを Go に追加してくれた全ての方に感謝する。


訳註

本文に書いてあるとおり、急なスケジュールであるため、日本語話者も議論に参加しやすいよう和訳しました。

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

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