OPAM の依存関係ソルバーと戯れてみた

Posted on January 23, 2015

前置き

最近のモダンなパッケージマネージャは充足可能性理論を利用した優秀な依存関係ソルバーを持っていることがある。これはどういうものかと言うと、依存するパッケージ名およびバージョン制約を与えると、そこから“最適”なインストール手順(解)を導き出すものだ。“最適”というのは場面によって意味が異なり例えば以下のようなものが考えられる。

  1. 現在の状態を極力変化させずにリクエストされたパッケージをインストー ルする解を選択する
  2. 現在の状態は極力変化させないが依存パッケージは最新にアップグレード しつつリクエストされたパッケージをインストールする解を選択する
  3. 与えられた制約を満たす解の内、インストールされるパッケージが最も少 なくなるような解を選択する

素朴なパッケージマネージャーは基本的にパッケージのアップグレードしか考えないため、例えばあるインストール済みパッケージをダウングレードすることで全体の制約を満たすことが可能だとしても、むなしく依存関係解決エラーになって終了したりする。さらに悪いものになると依存関係解決エラーにすらならず、実行時になるまで非互換性に気付かなかったりする。いわゆる“Dependency hell” の一端なのだが、依存関係ソルバーがあればかなり軽減できる。

後発の OPAM は当然ながら依存関係ソルバーを持っている。しかもユーザーによって制御可能だ。本エントリではこの依存関係ソルバーの考え方と基本的な使い方を紹介したいと思う。

インストール戦略

インストール戦略1を指定するには opam install--criteria オプションを渡せば良い。デフォルトでは次のようなインストール戦略が使われる。2

-count(removed),-notuptodate(request),-count(down),-notuptodate(changed),-count(changed),-notuptodate(solution)

これは辞書順で次のように読む。

  1. 削除されるパッケージ数を最小化する
  2. リクエストされた(opam install に与えられた)パッケージで最新版で ないパッケージ数を最小化する。つまりできるだけ最新版をインストールする
  3. ダウングレードされるパッケージ数を最小化する
  4. 最新でない追加・削除パッケージ数を最小化する。つまり追加・削除・アッ プグレード・ダウングレード時にできるだけ最新版をインストールする
  5. 追加・削除パッケージ数を最小化する
  6. 解の内、最新版でないパッケージ数を最小化する。つまりできるだけ最新 版をインストールする

かなり複雑だがやろうとしていることは分かるはずだ。語彙や仕様について詳しくは以下の PDF を参照されたい。

http://www.dicosmo.org/Articles/usercriteria.pdf

この PDF からいくつか例を紹介しよう。

以下はパラノイド向けのインストール戦略である。

-count(removed),-count(changed)

changed は削除および追加されるパッケージの集合である。つまりこれはシステムへの変化を最小限に抑えるインストール戦略なのだ。

以下はパラノイド向けであるができるだけ最新も追うインストール戦略である。

-count(removed),-notuptodate(request),-count(down),-count(changed)

-notuptodate(request) はリクエストされたパッケージが最新版でないものを最小化しようとする。つまり、リクエストされたパッケージについてはできるだけ最新版をインストールしようとするのだ。

他にもいくつか面白い例が挙げられているので興味のある人はぜひ PDF を参照されたい。

戯れる

実際に戯れてみよう。まず実験環境に入る。

$ opam switch -A 4.02.1 criteria
$ eval `opam config env`

実験に使うパッケージは ocamlfindppx_monadic にしよう。ocamlfind の最新版は現時点で 1.5.5ppx_monadic1.0.2 である。 ppx_monadicocamlfind に依存しているが、バージョン制約は特にない。

まず ocamlfind1.5.2 をインストールしてみよう。

$ opam install ocamlfind.1.5.2
...

次に ppx_monadic1.0.0 をインストールしてみよう。

$ opam install ppx_monadic.1.0.0
The following actions will be performed:
 - install   ppx_tools.0.99.2                      [required by ppx_monadic]
 - install   omake.0.9.8.6-0.rc1                   [required by ppx_monadic]
 - install   ppx_monadic.1.0.0
=== 3 to install ===
Do you want to continue ? [Y/n]
...

ocamlfind1.5.5 にアップグレードされなかった。

これはなぜかというとデフォルトのインストール戦略が依存パッケージのバージョンをできるだけ保とうとするからだ。デフォルトのインストール戦略を再掲する。

-count(removed),-notuptodate(request),-count(down),-notuptodate(changed),-count(changed),-notuptodate(solution)

-notuptodate(changed),-count(changed) が該当の部分だ。これを削除すると、 -notuptodate(solution) が強く作用しパッケージを積極的にアップグレードするようになる。実際にやってみよう。

$ opam remove ppx_monadic
$ opam install --criteria="-count(removed),-notuptodate(request),-count(down),-notuptodate(solution)" ppx_monadic.1.0.0
The following actions will be performed:
 - upgrade   ocamlfind from 1.5.2 to 1.5.5         [required by ppx_monadic]
 - recompile ppx_tools.0.99.2                      [uses ocamlfind]
 - recompile omake.0.9.8.6-0.rc1                   [uses ocamlfind]
 - install   ppx_monadic.1.0.0
=== 1 to install | 2 to reinstall | 1 to upgrade ===
Do you want to continue ? [Y/n]
...

期待通り ocamlfind1.5.5 にアップグレードされた。次に普通にppx_monadic をインストールしてみよう。

$ opam install ppx_monadic
[NOTE] Package ppx_monadic is already installed (current version is 1.0.0).

(期待に反して) 1.0.2 にアップグレードされなかった。試しに以下を実行してみる。

$ opam install --criteria="-notuptodate(solution)" ppx_monadic
[NOTE] Package ppx_monadic is already installed (current version is 1.0.0).

どうやらインストール済みのパッケージをバージョン指定なしでインストールしようとすると、インストール済みパッケージのバージョンが指定されたという解釈になるようだ。こういった場合は opam install の代わりに opam upgrade を使うか、バージョンを指定する。

$ opam upgrade --dry-run ppx_monadic
The following actions will be performed:
 - upgrade   ppx_monadic from 1.0.0 to 1.0.2
...
$ opam install --dry-run ppx_monadic.1.0.2

最後に荒技をやってみよう。以下のコマンドは指定されたパッケージが依存しないパッケージをシステムから削除するコマンドである。

$ opam install --criteria="-notuptodate(solution),-count(solution),+count(removed)" base-unix.base base-bigarray.base base-threads.base PACKAGE...

インストール戦略は、できるだけ最新版をインストールしつつ、解は最小化し、削除するパッケージ数を最大化している。

こういう技を臨機応変に繰り出せるのは OPAM の設計の素晴しいところだと思う。ただ、しばらく戯れた印象では --criteria オプションを使うことは滅多にないと思われる。あったとしても正しく指定できる自信がない。結構、(狙って面白い例を作るのが)難しいのだ。

具体的な実践例について続くかもしれない。


  1. 若干語弊があるかもしれないが分かりやすさのためにこの言葉を使う

  2. OPAM 1.2.0 の man ページより