LoginSignup
24
11

More than 3 years have passed since last update.

clojure CLI (tools.deps)を使いやすくするためのTips

Last updated at Posted at 2018-01-19

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.edn
{: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.xmlproject.cljを解析して、再帰的な依存関係解決はサポートされてないようです。

nREPLの起動

REPLの起動時に読み込まれるuser.clj内でnREPLサーバを起動し、ポート番号を.nrepl-portファイルに出力することで、CIDERやfireplace.vimが自動的にnREPLに接続してエディタからREPL操作ができるようになります。

user.clj
(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では依存関係の解決順序が異なることに留意してください。
同一ライブラリの複数バージョンが依存関係に見つかった場合の解決方法は、
1. 直接定義されている場合はそのバージョンが勝つ
2. 間接的に依存している場合は、新しいバージョンが勝つ
3. 2次的に依存している場合、たとえ新しいバージョンがあったとしても、その親の依存関係が採用されていない場合は無視される。
となります。

依存関係の除外

Leiningenの:dependenciesと同様に、依存関係のJarから除外したいパッケージ名や名前空間を指定することができます。

deps.edn
{: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)を実行すると、変更された名前空間とそこから参照されている名前空間がリロードされ、その後関連するテストだけが選択的に再実行されます。

deps.edn
{: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"}}}}}

user.clj
(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)))))
REPL
[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を見てみます。

lambdacd/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に追記します。

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で最新機能の追加と今後の開発方針について公演しています。

24
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
11