Clojureコマンドラインツールとその中核ライブラリであるclojure/tools.depsは、2016年のClojure/ConjキーノートSpec-ulationで述べられているように、Rich HickeyにとってClojure、Datomicに続く新たな開発テーマとなっています。
現時点のclojure CLIはLeiningenと比較すると遥かにシンプルです。Leiningenの代替として、日常の開発で普段使いができるか、試してみることにしました。Tipsを順次追加していく予定です。
Gitプライベートリポにアクセスするための設定
LeiningenはMavenと同様、アーティファクトをJarにパッケージングし、Mavenリポジトリ(またはClojars)にJarをアップロードするというモデルになっています。Clojure CLIは、Jarにパッケージすることなく、Gitリポジトリに直接アクセスすることで依存関係を解決する手段を提供しています。
{:deps {
com.fzakaria/slf4j-timbre {:mvn/version "0.3.6"}
my-group/my-artifact {:git/url "git@github.com:my-group/my-artifact.git"
:rev "3a2a5ef8"}
com.rpl/specter {:mvn/version "1.0.2"}
...}}
- パブリックGitリポの場合は
https
スキームで、プライベートの場合はssh
スキームでgit/url
を記述します。 -
ssh
を利用する場合、sshキーがssh-agentに追加されていないと認証エラーが発生します。GitHubのヘルプを参照して、ssh-agentへの追加を行ってください。 - Gitによる参照の場合、参照元のプロジェクトにも
deps.edn
が設定されている必要があります。現時点では、Gitで参照しているプロジェクトのpom.xml
やproject.clj
を解析して、再帰的な依存関係解決はサポートされてないようです。
nREPLの起動
REPLの起動時に読み込まれるuser.clj
内でnREPLサーバを起動し、ポート番号を.nrepl-port
ファイルに出力することで、CIDERやfireplace.vimが自動的にnREPLに接続してエディタからREPL操作ができるようになります。
(ns user
(:require [clojure.tools.nrepl.server :refer [start-server stop-server]]))
(defn- available-port []
(with-open [s (java.net.ServerSocket. 0)]
(.getLocalPort s)))
(let [port (available-port)]
(defonce server (start-server :port port))
(spit ".nrepl-port" port))
依存関係衝突の解決方法
Leiningenではlein deps :tree
で再帰的依存関係木構造を表示すると同時に衝突しているライブラリの表示をしてくれます。deps.tree.alpha
には-Stree
オプションがありますが、衝突を表示しないようなので、
clj -Spom
でpom.xml
を生成し、mvn dependency:tree -Dverbose
で衝突したライブラリを含めて表示することができます。
ローカルrepoの指定
標準では$HOME/.m2
にJarファイルなどのアーティファクトがダウンロードされますが、Dockerで依存関係Jarファイルを全てコピーして、実行時に依存関係をダウンロードせずに実行したい場合など、必要な依存関係を別のディレクトリに保存したい場合があります。
deps.edn
に、:mvn/local-repo "ディレクトリパス"
を指定することでダウンロード先を変更することができます。
依存関係解決順序
tools.depsとMavenでは依存関係の解決順序が異なることに留意してください。
同一ライブラリの複数バージョンが依存関係に見つかった場合の解決方法は、
- 直接定義されている場合はそのバージョンが勝つ
- 間接的に依存している場合は、新しいバージョンが勝つ
- 2次的に依存している場合、たとえ新しいバージョンがあったとしても、その親の依存関係が採用されていない場合は無視される。
となります。
依存関係の除外
Leiningenの:dependencies
と同様に、依存関係のJarから除外したいパッケージ名や名前空間を指定することができます。
{:deps {
org.clojure/clojure {:mvn/version "1.8.0"}
...
org.apache.maven/maven-aether-provider
{:mvn/version "3.3.9"
:exclusions [org.eclipse.aether/aether-api org.eclipse.aether/aether-spi
org.eclipse.aether/aether-util org.eclips.aether/aether-impl]}}}
1.9.0.317で、clj -Spom
で出力されたpom.xml
にもこの指定が反映されるようになりました。clojure CLIを最新版に更新してください。JIRAチケット
変更したファイルの自動リローディングと差分テスト
下記のコードを:dev aliases内のextra-paths
で指定したディレクトリdev/clj
に入れておくことで、(reload)
を実行すると、変更された名前空間とそこから参照されている名前空間がリロードされ、その後関連するテストだけが選択的に再実行されます。
{:aliases {:dev {:extra-paths ["dev/clj" "test"]
:extra-deps {cljfmt {:mvn/version "0.5.7"}
ns-tracker {:mvn/version "0.3.1"}
eftest {:mvn/version "0.4.2"}}}}}
(ns user
(:require [eftest.runner :refer [find-tests run-tests]]
[ns-tracker.core :refer [ns-tracker]]))
(def modified-namespaces
(ns-tracker ["src/clj" "resources" "test"]))
(defn reload []
(let [updated-ns (modified-namespaces)]
(doseq [ns-sym updated-ns]
(require ns-sym :reload))
(when updated-ns (run-tests (find-tests updated-ns)))))
[kenji@k2n-mbp13: ] clj -R:dev -C:dev
Clojure 1.9.0
(エディタでソースコードを変更し、保存する)
user=> (reload)
2/2 100% [==================================================] ETA: 00:00
Ran 2 tests in 0.021 seconds
2 assertions, 0 failures, 0 errors.
{:test 2, :pass 2, :fail 0, :error 0, :type :summary, :duration 21.093365}
(変更したソースコードがREPLに反映されると同時に、変更したコードを参照しているテストだけが選択的に実行される)
ファイルを保存した際に(reload)
をトリガーすることも可能です。Vim/NeoVimの場合は下記の行を.vimrc
か.nvimrc
に保存します。
:autocmd BufWritePost *.clj :Eval (user/reload)
UberjarやAWS Lambdaのパッケージング
-
juxt/packを使うと、
deps.edn
で定義した情報を元にAWS Lambda用のZipファイルや単体で実行可能なuberjarファイルを生成することができます。 - luchiniatwork/cambadaを使うと、jar, uberjarに加え、Graal VMを用いたnative imageの作成が可能です。
Private Maven Repoへのアクセス
- 現時点では、パスワードで保護されたmaven repoへのアクセスができないようです。JIRAチケットでパッチが提案されています。
- 依存関係のライブラリが、ClojarsかMaven Central以外のリポジトリから、Leiningenのproject.cljに明示的にリポジトリを指定して、そこからライブラリをダウンロードしている場合、
tools.deps
はその情報を利用できないのでダウンロードに失敗します。一旦lein deps :tree
を実行し、ダウンロードに失敗しているライブラリを利用しているプロジェクトを特定し、そのproject.clj
で記述されているリポジトリをdeps.edn
に追加する必要があります。
clj -Spath
Error building classpath. Could not find artifact com.jezhumble.javasysmon:javasysmon:jar:0.3.5 in central (https://repo1.maven.org/maven2/)
org.eclipse.aether.resolution.ArtifactResolutionException: Could not find artifact com.jezhumble.javasysmon:javasysmon:jar:0.3.5 in central (https://repo1.maven.org/maven2/)
at org.eclipse.aether.internal.impl.DefaultArtifactResolver.resolve(DefaultArtifactResolver.java:422)
...
javasysmon
が標準のmaven repoからダウンロードできていません。今回は既存のLeiningen用project.clj
があるので、lein deps :tree
で依存ツリーを取得します。ない場合は、一つ一つ確認していくしかありませんね... 上記のpom.xml
を出力する回避策も、clj -Spom
が失敗するのでここでは利用不可です。
lein deps :tree
...
[lambdacd "0.13.5"]
[cheshire "5.4.0" :exclusions [[org.jvnet.winp/winp]]]
[com.fasterxml.jackson.core/jackson-core "2.4.4"]
[com.fasterxml.jackson.dataformat/jackson-dataformat-cbor "2.4.4"]
[com.fasterxml.jackson.dataformat/jackson-dataformat-smile "2.4.4"]
[tigris "0.1.1"]
[clj-time "0.9.0" :exclusions [[org.jvnet.winp/winp]]]
[joda-time "2.6"]
[clj-timeframes "0.1.0" :exclusions [[org.jvnet.winp/winp]]]
[com.gfredericks/test.chuck "0.2.1"]
[com.andrewmcveigh/cljs-time "0.3.11"]
[org.clojure/math.combinatorics "0.1.1"]
[org.clojure/test.check "0.9.0"]
[cljsjs/moment "2.10.6-4" :exclusions [[org.jvnet.winp/winp]]]
[com.jezhumble/javasysmon "0.3.6" :exclusions [[org.jvnet.winp/winp]]] ;; <=== このファイルがダウンロードできていない
[compojure "1.1.8" :exclusions [[org.jvnet.winp/winp]]]
...
依存関係ツリーをみると、lambdacd
が要求しているので、Githubでそのproject.cljを見てみます。
(defproject lambdacd "0.14.0-SNAPSHOT"
:description "A library to create a continous delivery pipeline in code."
:url "http://github.com/flosell/lambdacd"
:license {:name "Apache License, version 2.0"
:url "http://www.apache.org/licenses/LICENSE-2.0.html"}
:min-lein-version "2.5.0"
:deploy-repositories [["clojars" {:creds :gpg}]
["releases" :clojars]]
:repositories [["gocd" "https://dl.bintray.com/gocd-maven-repo/generic/gocd"]] ; for jezhumbles javasysmon
:source-paths ["src/clj" "src/cljs"]
...
"gocd"という名のリポジトリが定義されているのがわかります。そこで、このURLをdeps.edn
に追記します。
{:mvn/repos {"gocd" {:url "https://dl.bintray.com/gocd-maven-repo/generic/gocd"}}
:deps
{lambdacd {:mvn/version "0.13.5"}
lambdacd-git {:mvn/version "0.4.0"}
...
これでダウンロードできるようになりました。
JIRAチケット
複数のリポジトリを更新しながらの開発
gitによる依存関係の取得が可能になったことで、例えばDuctコンポーネントを独立したgit repoにし、アプリケーションからそのコンポーネントをgit経由で利用する、という使い方が容易になりました。
しかし、開発中、いちいち変更をコミットし、githubにプッシュするのは開発フローの重荷になるので、回避策を考えます。
local/root
を活用する
tools.deps
はローカルにあるJarファイルを依存関係に組み込むためにlocal/root
を提供しています。これをgitリポに対しても適用させることができます。もちろん依存するリポジトリもdeps.edn
を持っていることが条件となります。Transitive dependencyもきちんと動作しているようです。
{:deps {;;clj-nakano/my-duct-component {:git/url "git@github.com:clj-nakano/my-duct-component.git"
;; :sha "8ab0541fda5a28ca3ede6dc6e0b89cf9379d328b"}
clj-nakano/my-duct-component {:local/root "/Users/kenji/ghq/github.com/clj-nakano/my-duct-component"}}
依存関係の解決結果をグラフィカルに表示する
2019年11月23日にtools.deps.graphが公開されました。
最新の動向
Alex MillerがClojure/conj 2019でComposable Toolsで最新機能の追加と今後の開発方針について公演しています。