パッケージを一つ追加しただけなのに、直接指定していないライブラリのエラーが出る。 依存関係を調べると、二つのパッケージが同じライブラリの別バージョンを要求している。
ダイヤモンド依存関係とは、このような場面で現れる構造です。 名前は難しそうですが、構造は複雑ではありません。
ダイヤモンド依存関係とは何か
アプリケーションがパッケージBとパッケージCを利用しているとします。 BとCは、どちらも内部でライブラリDを利用しています。
Application
/ \
Package B Package C
\ /
Library D
依存関係を線で結ぶと、途中で二つに分かれ、Dで再び合流します。 この形がひし形に見えるため、ダイヤモンド依存関係と呼ばれます。
ここで問題になるのは、ひし形そのものではありません。 BとCが同じバージョンのDを使えるなら、そのまま動きます。 BとCが異なるバージョンのDを必要とするときに、競合が起こります。
問題はDのどのバージョンを使うか
BはDの1系を必要とし、CはDの2系を必要としているとします。 アプリケーションは、Dの1系と2系のどちらを使えばよいのでしょうか。
Dの1系だけを使うと、Cが必要とする機能が見つからない可能性があります。 Dの2系だけを使うと、今度はBが新しいAPIに対応していない可能性があります。
新しいバージョンを選べば解決するとは限りません。 メジャーバージョンが変わると、関数の名前や引数、戻り値などが変わることがあるからです。
パッケージマネージャーは、BとCのコードが本当に動くかまでは判断しません。 宣言されたバージョン条件をもとに、インストールするDを決めます。
npmとBundlerでは結果が異なる
npmは、Dの1系と2系を別々の場所へインストールできます。
一方のパッケージにはDの1系を、もう一方にはDの2系を使わせる仕組みです。
公式ドキュメントでも、条件が両立しないパッケージを別のnode_modulesへ配置する例が示されています。
この方法なら、バージョンが違ってもインストールは成功します。 ただし、同じライブラリが複数入るため、容量が増え、更新対象もわかりにくくなります。
Bundlerは、同じgemについて一つのバージョンを選びます。 開発環境に複数バージョンがインストールされていても、一つのbundleが同時に使うバージョンは一つです。 Bundlerの公式FAQにも、すべてのグループとプラットフォームを通じて、一つのバージョンへ解決するとあります。
BとCが要求する範囲に重なりがあれば、Bundlerはその中からDを選べます。
重なりがなければ、bundle installは依存関係を解決できずに止まります。
npmは複数バージョンを共存させられますが、Bundlerは一つのバージョンへそろえます。
競合している依存関係を見つける
調べたいのは、Dを使っている二つの経路です。 アプリケーションが直接追加したパッケージだけを見ても、BからD、CからDというつながりはわかりません。
npmでは、次のコマンドでDが入った経路を確認できます。
npm explain <package-name>
インストールされているDのバージョンをまとめて見る場合は、npm ls <package-name> --allを使います。
Bundlerでは、まずbundle installのエラーを確認します。
どのgemが、どのバージョン範囲を要求しているかが表示されます。
インストール済みの依存関係は、Gemfile.lockのGEMセクションからたどれます。
package-lock.jsonやGemfile.lockには、最終的に選ばれたバージョンが記録されています。
ロックファイルは解決結果を固定するものであり、競合自体を直すものではありません。
依存元を更新して解消する
競合を見つけたら、Dを無理に固定する前にBとCを確認します。 対処の順序は次の三つです。
- Dを要求しているBとCを特定する
- BまたはCを更新し、両方が使えるDの範囲を作る
- 更新できなければ、BかCの置き換えを検討する
Bundlerなら、対象を絞ってbundle update <gem-name>を実行できます。
すべてのgemを更新するbundle update --allは変更範囲が広いため、最初から使う必要はありません。
Bundlerも、Gemfileを変更した後はまずbundle installで保守的に更新する手順を案内しています。
最終手段として、GemfileでDのバージョンを直接指定したり、npmのoverridesで上書きしたりする方法もあります。
ただし、指定によってインストールに成功しても、BとCがそのDで動くとは限りません。
テストを実行し、ロックファイルの差分まで確認します。
ダイヤモンド依存関係を直すとき、最初に見るべきなのは合流先のDではありません。 Dへ異なる条件を持ち込んだBとCを見つけ、更新によって条件をそろえられるかを確認するのが出発点です。
まとめ
- ダイヤモンド依存関係:二つのパッケージが同じライブラリへ依存し、経路がひし形になる構造です
- npmとBundlerの違い:npmは複数バージョンを共存させられますが、Bundlerは一つのバージョンへそろえます
- 対処の順序:競合したライブラリを直接固定する前に、異なる条件を持ち込んだ二つの依存元を更新できるか確認します
