Go におけるパッケージバージョニングに関するプロポーザル #
A Proposal for Package Versioning in Go by Russ Cox
はじめに #
8年前、Go チームは goinstall
(のちの go get
)や今日の Go 開発者にはお馴染みの分散化された URL のようなインポートパスを導入しました。goinstall
をリリース後、最初に尋ねられた質問の一つにバージョン情報をどのようにして組み込むかというものがありました。私たちは分からなかったことを認めました。長いあいだ、私たちはパッケージのバージョニング問題はアドオンツールで解決するのが一番良いと考えており、人々にアドオンツールを作ることを勧めました。Go コミュニティは異なるアプローチでたくさんのツールを作りました。それにより私たちはその問題についてより深く理解することができ、2015年半ばまでにその問題には多くの解決方法があることが明らかとなりました。私たちは公式のツールをただ一つ採用する必要がありました。
2015年7月の GopherCon から同年秋までに行われたコミュニティでの議論の後、私たちはどのバージョンを使うか決めるために Rust の Cargo におけるタグ付きのセマンティックバージョニングやマニフェスト、ロックファイル、 SAT ソルバー に代表されるパッケージのバージョニングに関するアプローチに従うという結論に至りました。このおおまかな計画に従い、go
コマンドに統合するためのモデルとして役立てることを意図した Dep を作るために Sam Boyer はチームを先導しました。しかし、Cargo/Dep アプローチの論理包含についてより深く理解するにつれて、Go が細部(特に後方互換性に関する)を変更することで恩恵を受けることが明らかとなりました。
互換性への影響 #
Go 1 の最も重要な新しい特徴は言語に関するものではありませんでした。Go 1 は後方互換性を重視していました。それまで約一か月毎に安定板のスナップショットをリリースしており、それぞれのリリースによる変更は互換性を極めて欠くものでした。Go 1 のリリース後すぐに、利益や採用において著しい加速を観測しました。互換性に関する約束事 により、開発者たちがプロダクションユースで Go を採用するとより一層快適になると考えています。またその約束事が、今日 Go の人気が高まっている主な理由だと考えています。2013年以降 Go の FAQ は、パッケージの開発者に自らのユーザーが似たような互換性として期待していることを提供するよう促してきました。私たちはこれを インポート互換性のルール(import compatibility rule) と呼んでいます。「古いパッケージと新しいパッケージが共に同じインポートパスを持つ場合、新しいパッケージは古いパッケージに対する後方互換性がなければならない。」
それとは無関係に、セマンティックバージョニング は Go を含む多くの言語コミュニティにおいてソフトウェアのバージョンを記述するデファクトスタンダードとなりました。セマンティックバージョニングを用いることで、単一のメジャーバージョン内では後方のバージョンは前方のバージョンと後方互換性が保たれていることが期待されます。例えば、v1.2.3 は v1.2.1 や v1.1.5 と互換性がなければならないですが、v2.3.4 はそれらのいずれかと互換性がある必要はありません。
私たちが Go パッケージでセマンティックバージョニングを採用する場合、ほとんどの Go 開発者が期待する通り、インポート互換性のルールは、異なるメジャーバージョンは異なるインポートパスを使っていなければならないということを要求します。この結果、v2.0.0 で始まるバージョンは my/thing/v2/sub/pkg
のようなインポートパス内にメジャーバージョンを含む、といった セマンティックインポートバージョニング が可能となりました。
一年前、インポートパス内にバージョン数を含むかどうかは主に趣味の問題だと私は強く考えており、それを含むことが特別エレガントだったかは懐疑的でした。しかしその決定は論理的には趣味の問題ではないと分かりました。つまり、インポートの互換性とセマンティックバージョニングは共にセマンティックインポートバージョニングを必要としているのです。私はこれに気づいたとき、論理的な必要性に驚きました。
段階的なコードの修正 や部分的なコードのアップグレードといったセマンティックインポートバージョニングとは独立した第2の論理的な考え方があることに気づき私はまた驚きました。大規模なプログラムおいて、特定の依存関係がある v1 から v2 に同時にアップデートすることを、プログラム内の全てのパッケージに期待するのは非現実的です。その代わりにプログラムの一部については v2 にアップグレードでき、その他の部分は v1 を使い続けることができるでしょう。しかしその一方で、ビルドされたバイナリは v1 と v2 の両方の依存関係を含んでいるでしょう。それらに同じインポートパスを与えると混乱につながり、私たちが インポート一意性のルール(inport uniqueness rule) と呼んでいるもの、つまり異なるパッケージは異なるインポートパスを持っていなければならないというルールに違反します。部分的なコードのアップグレード、インポート一意性、そして セマンティックバージョニングを行う唯一の方法はセマンティックインポートバージョニングなどを採用することです。
セマンティックインポートバージョニングなしでセマンティックバージョニングを用いるシステムをビルドすることはもちろん可能ですが、部分的なコードのアップグレードかインポート一意性のどちらかを諦めなければなりません。Cargo はインポート一意性を諦めることにより部分的なコードのアップグレードを可能としています。したがって、所定のインポートパスは大規模なビルドの異なる部分で異なる意味合いを持つことができます。Dep は部分的なコードのアップグレードを諦めることでインポート一意性を保証しています。したがって、大規模なビルドに関する全てのパッケージは所定の依存関係によって定められたただ一つのバージョンを探し出さなければならず、大規模なプログラムほどビルドできない可能性が高まります。Cargo は大規模なソフトウェア開発に不可欠な部分的なコードのアップグレードを主張するのに適しています。Dep は同様にインポート一意性を主張するのに適しています。Go の現在のベンダリングサポートは使用するのが複雑であり、インポート一意性に違反する可能性があります。違反した結果生じた問題は開発者とツールの両方にとってとても理解しにくいものでした。部分的なコードのアップグレードとインポート一意性のどちらかを選択する際は、選択しなかったことによる影響を予測する必要があります。セマンティックインポートバージョニングにより選択を避け、代わりに両方を維持することができます。
どのくらいインポート互換性がバージョン選択を簡素化するかに気づいたときも驚きました。これは、所定のビルドで用いるパッケージのバージョンを決める際の問題です。Cargo と Dep により、バージョン選択することと ブール充足可能性を解決すること は等しく扱うことができ、それは有効なバージョン設定が存在するかどうかを判断するには高くつくかもしれないということ示唆しています。さらに、❝ベスト❞な設定を選択するために明確な基準がないと、有効な設定がたくさん見つかるかもしれません。代わりにインポート互換性に頼ることで Go は自明な線形時間のアルゴリズムを使用して、常に存在する唯一のベストな設定を探し出すことができます。私が 最小バージョン選択(minimal version selection) と呼んでいるこのアルゴリズムは、別々のロックファイルとマニフェストファイルの必要性をなくします。それらは開発者とツールの両方が直接編集した単一の小さな設定ファイルで置き換えられ、再現可能なビルドは引き続きサポートされます。
Dep を使用した体験は互換性への影響を明らかにしています。Cargo とそれ以前のシステムに基づき、セマンティックバージョニングを採用する一環としてインポート互換性を諦めるために、私たちは Dep を設計しました。私はこれを意図的に決めたとは考えていません。私たちはそれらとは別のシステムに従っただけです。Dep を直接使用したことで、互換性のないインポートパスを許可することによってどのくらいの複雑さが生じるか正確に理解するのに役立ちました。セマンティックインポートバージョニングを導入することによってインポート互換性のルールを復活させることで、その複雑さを取り除き、よりシンプルなシステムにします。
進捗、プロトタイプ、プロポーザル #
Dep は2017年1月にリリースされました。そのベーシックモデル、つまり依存関係の要求を明示した設定ファイルに沿うタグ付けされたコードはほとんどの Go のバージョニングツールからの明確な一歩で、Dep 自身に集中することもまた明確な一歩でした。特にコード自体と依存関係の両方に関する Go のパッケージバージョニングについて Go 開発者が考えるのに慣れる助けとなるため、私は全面的にその採用を称えました。Dep が私たちを明らかに正しい方向へ導く中、私は細部に潜む複雑性の悪魔についてずっと心配していました。私は Dep が大規模なプログラム内で段階的にコードをアップグレードするためのサポートが不足していることについて特に心配していました。2017年は Sam Boyer やその他のパッケージ管理ワーキンググループを含む多くの人と話しましたが、誰一人として複雑性を減らす明確な方法が分かる人はいませんでした。(私はそれに加え多くのアプローチを見つけました。)年末に近づいてもそれは依然として SAT ソルバーのように見え、不十分なビルドが私たちができるベストなのかもしれません。
11月中旬、Dep がどのようにして段階的なコードのアップグレードをサポートできるか今一度考えていたとき、私はインポート互換性についての言い伝えがセマンティックインポートバージョニングを暗示していることに気づきました。大きな進歩のような気がしました。私はブログ記事 semantic import versioning に初稿を書き、Dep は慣習を採用することを提案するという結論に至りました。私は以前お話した人達にその草稿を送り、とても大きな反響を呼びました。賛否両論でした。私はアイデアをさらに広める前にセマンティックインポートバージョニングの論理包含をより深く理解する必要があることを認識し、行動に移しました。
12月中旬、私はインポート互換性とセマンティックインポートバージョニングを組み合わせることでバージョン選択を 最小バージョン選択 へと落とし込めることに気づきました。私はそれを理解したか確かめるために基礎的な実装を書き、それがとてもシンプルであったわけの背後に隠された理論を学び、そしてそれを説明する記事の草稿を書きました。それでも、Dep のような実際のツールではそのアプローチが実用的かまだ分かりませんでした。プロトタイプが必要であることは明らかでした。
1月に入り、私はセマンティックインポートバージョニングと最小バージョン選択を実装したシンプルな go
コマンドのラッパーを作り始めました。簡単なテストはうまくいきました。月末に近づき、私のシンプルなラッパーは、多くのバージョニングされたパッケージを用いた実際のプログラムである Dep をビルドすることができました。ラッパーはまだコマンドラインインターフェースを持っておらず、いくつかの文字定数でハードコードされた状態で Dep をビルドしていましたが、そのアプローチは明らかに有効でした。
私は2月の最初の3週間を利用してラッパーを完全なバージョンの go
コマンド vgo
に書き直したり、vgo
について紹介するブログ記事一覧 の草稿を書いたり、Sam Boyer やパッケージ管理ワークグループ、Go チームと議論したりしました。そして2月の最後の週は Go コミュニティ全体に対して vgo
やその背後に隠されたアイデアを共有しました。
インポート互換性やセマンティックインポートバージョニング、最小バージョン選択のアイデアのコアに加え、vgo
のプロトタイプには goinstall
や go get
との8年間の体験によって動機づけられた小さいながらも重要な数々の変更を導入しました。例えば、パッケージのコレクションを一つの単位とする Go モジュール という新しい概念、検証可能ビルド・検証済みビルド、$GOPATH
外での動作や(ほとんどの)vendor
ディレクトリの削除を可能とする go
コマンドを通したバージョンの認知 です。
この全ての成果は先週私が 公式の Go の プロポーザル として提出しました。完璧に実装されているように見えるかもしれませんが、まだプロトタイプの段階で、私たちと一緒にこれから完璧に仕上げていく必要があります。あなたは golang.org/x/vgo から vgo
のプロトタイプをダウンロードして試すことができます。また、vgo
に慣れるために Tour of Versioned Go を読むことができます。
今後の方針 #
私が先週提出したプロポーザルはまさに最初のプロポーザルです。Go 開発者は私たちが知らないとても賢いやり方で Go を使うので、Go チームや私が見つけられない問題があることを知っています。プロポーザルのフィードバックプロセスの目標は、現在のプロポーザルの問題を特定して対処するため、また将来リリースされる Go の最終的な実装ができるだけ多くの開発者にとってうまくいくようにするために私たち全員が協力することです。プロポーザルに関する議論の issue で問題点を指摘してください。私はいただいたフィードバックにより 議論の概要 や FAQ を更新し続ける予定です。
このプロポーザルが成功するためには、Go のエコシステム、特に今日のメジャーな Go プロジェクト全体がインポート互換性のルールやセマンティックインポートバージョニングを採用する必要があるでしょう。それがスムーズに行われるように、新しいバージョニングのプロポーザルをコードベースに組み込む方法や、体験のフィードバックを得る方法について質問があるプロジェクトと共にビデオ会議でユーザーフィードバックセッションを行う予定です。そのようなセッションに参加したい場合は、Steve Francia(spf@golang.org)までメールを下さい。
私たちは(最後に!)Go コミュニティに対し、パッケージのバージョニングを go get
に組み込む方法に関する質問に対し公式の回答を一つだけ提供することを楽しみにしています。これまで私たちを助けてくださったみなさま、そしてこれから私たちを助けてくださるみなさまに感謝申し上げます。私たちはあなたたちの助けを借りて、Go の開発者が気に入るものをリリースできることを願っています。
By Russ Cox