依存関係解決における最大バージョン選択と最小バージョン選択について
この記事では Bundler (Ruby) や npm (JS)、Cargo (Rust)、vgo (Golang) 等のパッケージマネージャの依存関係解決における最大バージョン選択 (Maximal Version Selection) と最小バージョン選択 (Minimal Version Selection) [1] の違いについて説明します。また、それぞれの長所や短所、最大バージョン選択において現状発生している問題について書きます。
最大バージョン選択 (Maximal Version Selection)
最大バージョン選択は Bundler, npm 等をはじめとする多くのプログラミング言語のパッケージマネージャで採用されています。指定されたなかで最大の(=最新の)バージョンを使います。
例) v1.0 以上のバージョンが指定されていて現在の最新バージョンが v1.1 の場合、v1.1 がインストールされます。
最小バージョン選択 (Minimal Version Selection)
最小バージョン選択はその逆です。指定されたなかで最小の(=最も古い)バージョンを使います。
例) v1.0 以上のバージョンが指定されていて現在の最新バージョンが v1.1 の場合、(最大バージョンの v1.1 ではなく)v1.0 がインストールされます。
最小バージョン選択をするパッケージマネージャーに Golang の Go Modules (vgo) があります。また、Cargo (Rust) はデフォルトでは最大バージョン選択ですが オプションで -Z minimal-versions を指定することで最小バージョン選択 にすることもできます。
Cargo が最小バージョン選択のオプションをサポートした経緯
デフォルトでは最大バージョン選択を行う Cargo が最小バージョン選択のオプションをサポートした経緯は、最大バージョン選択には以下のような問題があるからです。
パッケージ A に依存するパッケージ B があったとします。パッケージ B の作者は依存関係としてパッケージ A のバージョン v1.0 以上を指定しました。インストールを実行した際に、パーケージ A の最新バージョンが v1.1 だったとします。最大バージョン選択の場合、v1.1 がインストールされロックファイルにも記録されます。パッケージ B の作者は、意図せずパッケージ A の v1.1 で導入された新機能に依存したコードを書いていました。しかし v1.1 がインストールされているためローカルでもテストは通るし CI も成功してしまいます。この場合、パッケージ A の v1.0 を使用しているユーザーがパッケージ B をインストールした場合、パッケージ B は期待通りに動作しません。
CI では最大バージョン選択に加えて最小バージョン選択でもテストを実行するように設定することで、このように意図せず指定したバージョンよりも新しいバージョンの新機能に依存したコードを含むパッケージをリリースしてしまうことを防ぐことができます。[2]
最小バージョン選択の長所
- ロックファイルが不要
- そのためパッケージマネージャーの実装もシンプル
- 概念的にも簡単になるため理解も容易
- 選択されるバージョンが外部要因(=新しいパッケージのリリース)の影響を受けない
- 依存関係解決の再現性が保証される
最大バージョン選択の長所
- パッケージ群のエコシステム全体で、CI によって最新バージョン同士の組み合わせのテストが常に相互に行われていることを期待できる
- 多くのユーザーが最新バージョンの組み合わせを利用しているため、最新バージョンの組み合わせで問題が発生した場合に素早く対応される可能性が高い
- 古いバージョンでは存在しているバグは最新バージョンを入手することで修正済みになっている可能性が高い
- 既に修正済みのバグをユーザーが踏む可能性が相対的に低い
参考資料
- Command to update Cargo.lock to minimal versions · Issue #4100 · rust-lang/cargo
- Dependencies resolution with
--minimal-versions
· Issue #5657 · rust-lang/cargo - research!rsc: Go += Package Versioning (Go & Versioning, Part 1)
Discussion