mrandersonで依存関係の衝突を回避する話
https://gyazo.com/e2d8ea9c489205ab7f6956370cee8416
動機
開発環境として使うライブラリであるので、本来開発したいものが依存するライブラリに +α する形でこのライブラリが dependencies に加わることになるのですが、ver 0.2.12 時点では iced-nrepl 自体にそれなりの依存ライブラリが存在しています。
https://gyazo.com/c4f710d6c0be832cad07c01ae303b3b4
こうなっていると、例えば本来開発したいものが org.clojure/data.json の 0.2.6 以外のバージョンを使いたい時に、以下のように confusing dependencies が発生してしまい、開発補助のためのライブラリが開発を妨げるという本末転倒なことが起きてしまっていました。
code:console
$ lein deps :tree
Possibly confusing dependencies found:
overrides
...
これを解決したいというのが本記事の主旨です。
mranderson
この問題を Clojure の開発環境として有名な CIDER の nREPL ミドルウェア cider-nrepl ではどうしているのかというと、mranderson という Leiningen プラグインを使って解決しています。 何をしてくれるプラグインかというと、依存するライブラリを ソースとして自プロジェクトの一部に取り込む ということをしてくれます。
最初、読み方がわからなかったのですが Mr. Anderson (ミスターアンダーソン)です。
私は世代的にど真ん中ですが、映画 The Matrix の主人公ネオの本名ですね。エージェントに尋問されるシーンが目に浮かびます。
At the end he really gets back to the source, does not he?
カコイイ..
こういうネーミングセンス好きです。
使用例
ソースとして取り込むと言われてもイマイチどんな感じになるのかイメージしづらいかと思います。
ここは実際に使った結果どうなるのかを見るのが一番早いので試してみましょう。
使い方は至極単純で :plugins に mranderson を追加して、ソースとして取り込みたい依存ライブリに :source-dep メタデータをつけるだけです。
唯一注意する点としては Clojure 自体には :source-dep メタデータを指定してはいけないということです。
note you should not mark clojure itself as a source dependency: there is a limit for everything.
code:project.clj
(defproject foo "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:license {:name "Eclipse Public License"
:plugins thomasa/mranderson "0.4.9")
ソースは適当にこんな感じにしておきます。
code:src/foo/core.clj
(ns foo.core
(defn -main []
(println (json/write-str {:hello "world"})))
実際に依存ライブラリをソースとして取り込むには lein source-deps を実行します。
/icons/alert.icon 2019/03/26追記: バージョン 0.5.0 からは lein inline-deps に変更 今回は org.clojure/data.json だけなのですぐ終わりますが、依存ライブラリが多いとめちゃめちゃ時間がかかるのでご注意ください。
code:console
$ lein source-deps
project prefix: mranderson049
retrieve dependencies and munge clojure source files
retrieving data.json artifact.
prefixing imports in clojure files in 'target/srcdeps/clojure/data' ...
取り込んだ結果は target/srcdeps 配下に保存されます。
code:console
$ find target -type f
target/srcdeps/mranderson049/datajson/v0v2v6/clojure/data/json_compat_0_1.clj
target/srcdeps/mranderson049/datajson/v0v2v6/clojure/data/json.clj
target/srcdeps/META-INF/maven/org.clojure/data.json/pom.xml
target/srcdeps/META-INF/maven/org.clojure/data.json/pom.properties
target/srcdeps/META-INF/MANIFEST.MF
target/srcdeps/foo/core.clj
取り込まれたソースを覗いてみるとネームスペースが良い感じに変換されていることがわかります。
code:target/srcdeps/mranderson049/datajson/v0v2v6/clojure/data/json.clj
(ns ^{:author "Stuart Sierra"
:doc "JavaScript Object Notation (JSON) parser/generator.
mranderson049.datajson.v0v2v6.clojure.data.json
(:refer-clojure :exclude (read))
(:import (java.io PrintWriter PushbackReader StringWriter
Writer StringReader EOFException)))
code:target/srcdeps/foo/core.clj
(ns foo.core
(defn -main []
(println (json/write-str {:hello "world"})))
この結果を使ってローカルリポジトリにインストールしたり、clojars にデプロイするときには Leiningen でplugin.mranderson/config というプロファイルを指定する必要があります。
code:console
# install
$ lein with-profile +plugin.mranderson/config install
# deploy
$ lein with-profile +plugin.mranderson/config deploy clojars
結果
まだ検証中で SNAPSHOT ではありますが ver 0.3.0 で mranderson を使った結果が以下になります。ドーン
https://gyazo.com/c4f710d6c0be832cad07c01ae303b3b4 → https://gyazo.com/cb2b441a2d389238cacd23b57e9622f3
かなりスッキリしました。
なお nrepl 本体は cider-nrepl/refactor-nrepl が source-dep を指定していないから、
refactor-nrepl はそれ自体も mranderson が使われていることからソースとして取り込んでいません。
(試しに refactor-nrepl をソースとして取り込んでみると、死ぬほど時間がかかるのでおすすめしません)
eastwood ではまった話
こんな感じで便利に使える mranderson ですが、うまく取り込めないケースもまだ存在します。
型ヒントの変換漏れ
code:target/srcdeps/mranderson049/eastwood/v0v3v4/eastwood/analyze_ns.clj
(defn wrapped-exception? result (if (instance? mranderson049.eastwood.v0v3v4.eastwood.copieddeps.dep2.clojure.tools.analyzer.jvm.ExceptionThrown result)
(.e ^eastwood.copieddeps.dep2.clojure.tools.analyzer.jvm.ExceptionThrown result)))
result の型ヒントとして ^eastwood.copieddeps.dep2.clojure.tools.analyzer.jvm.ExceptionThrown が指定されていますが、正しくは ^mranderson049.eastwood.v0v3v4.eastwood.copieddeps.dep2.clojure.tools.analyzer.jvm.ExceptionThrown です。
取り込み自体はエラーにならず、実行時に java.lang.ClassNotFoundException が投げられるのでなかなか厄介です。
特に lein source-deps に時間がかかるケースだと検証に時間がかかるのでかなりダルいです。
関数名が変換されてしまうケースがある
code:target/srcdeps/mranderson049/eastwood/v0v3v4/eastwood/lint.clj
(defn mranderson049.eastwood.v0v3v4.eastwood
(opts (mranderson049.eastwood.v0v3v4.eastwood opts (reporting/printing-reporter opts))) ...))
元々は eastwood という関数名なのですが、変換ルールがミスっているようで mranderson049.eastwood.v0v3v4.eastwood に変換されてしまっています。
これも実行時に例外が投げられて、 lein source-deps に時間がかかるケースだと(略
変換されないリソースパス
code:target.org/srcdeps/mranderson049/eastwood/v0v3v4/eastwood/util.clj
(defn builtin-config-to-resource name (io/resource (str "eastwood/config/" name)))
これは変換するためのルールが決めづらいので致し方ない気もしますが、
正しくは (io/resource (str "mranderson049/eastwood/v0v3v4/eastwood/config/" name) になります。
これも実行時に例外が(略
その他ツラミポイント
検証目的で自プロジェクトのソース修正した場合でも lein source-deps し直さないといけないのが地味に辛いです。。(target/srcdeps 配下の自プロジェクトのソースに変更が反映されないため)
総じて検証に時間がかかるのが一番のツラミポイントです。
最後に
ウェブアプリの開発ではまず利用する機会がないかもしれませんが、ライブラリの開発では有用なことがあるかもしれないので、記憶の片隅に置いておいていただければと思います。
p.s. 本日はクリスマスですね!Clojure でクリスマスを盛り上げる(雑)プログラムを用意してあるのであなたのターミナルにもクリスマスツリーを植えてみてください!メリークリスマス!
https://gyazo.com/da6e1e79595080a3d63b65917d985959