(define -ayalog '())

括弧に魅せられて道を外した名前のないプログラマ

Clojure で依存関係の解決に潜むワナ?

お仕事中に盛大にハマって、最終的に一緒にやってる Clojure チョットデキルマンに助けてもらった。

Clojure のライブラリを Leiningen や Boot に含める場合、次のような表記を用いる。

[group-id/name version]

Leiningen や Boot はこれを group-id/name でひとつのライブラリとみなし、もし数あるライブラリの内部で使われているライブラリが重複した場合には、後勝ちで依存性が解決されていく*1

なので例えば foo/piyo というライブラリが a/nyan 0.9, a/nyun 0.8 というライブラリを依存関係に含んでいて、 bar/puyo というライブラリが a/nyan 0.1, a/nyun 1.2 というライブラリを依存関係に含んでいる場合に、 foo/piyo -> bar/puyo という順番で依存性が解決される*2とすると bar/puyo の中で使われているライブラリのバージョンが使われることになる。つまり a/nyan 0.1, a/nyun 1.2 になると思う*3

このとき単純にバージョンが競合しているだけならまだ救いがある*4。 lein deps :tree を使えばどのライブラリからどういう依存があって、競合しているかが一目で分かる。 Boot の場合は boot show -p で競合をどう解決したかが確認出来る(単純な依存性グラフは boot show -d で確認出来ます)。

上述した通り、 Leiningen も Boot も group-id/name というのをひとつのライブラリとみなす。つまり、どういうことかというと、既に気付いた人もいると思うけど、もし「同じライブラリなのに名前が違うとコンフリクトしない」んです。そんなことあるわけがない、って思うかもしれませんが極稀に起こります。例えば ring-anti-forgery 。 ring シリーズのひとつとして group-id に ring をつけるようになったのは 0.3.1 以降です。そして、 0.3.0 以前のものを依存性に持っているライブラリが幾らか存在したりします。そうするとこの問題にあたる可能性が多少あるわけですね。幸運にも依存性の解決の順番が ring-anti-forgery -> ring/ring-anti-forgery だった場合、何も起こりません*5

更に厄介なのが lein ancient のようなツールを使ったとしても、更新を発見することが出来ないため上の例の場合だと ring-anti-forgery 0.3.0 で止まるんですね*6。恐らくライブラリ開発者などで「 lein ancient を使ってるから依存ライブラリは全て最新だ、うぇーい」みたいなことをやっていると、こういう風になってしまうんでしょうね…。

とまぁ、そんなこんなで、この問題だけはツールで解決する方法が恐らくないので、ハマったときの為に頭の片隅にでも入れておくと良いのではないでしょうか*7

*1:多分

*2:この順番がどういう風に実際決まっているのかはよく分からない

*3:多分!!

*4:as usual!!

*5:奇跡!!

*6:ヒュ~

*7:ちなみに僕の場合はなぜか wrap-anti-forgery 関数の引数がひとつしかない(つまり古いはず)のに、依存性で ring/ring-anti-forgery は別に競合していないというつらい感じの状態でハマってました。結局 ring-anti-forgery 時代の 0.2.1 と ring/ring-anti-forgery の 1.0.0 が入っているという状態で面白おかしくハマってました