Table of Contents generated with DocToc
Leiningenのタスクはleiningen.$TASK名前空間にある$TASKという名前の関数に過ぎません。ですから、Leiningenのプラグインを記述するのは、単にそのような関数を含むプロジェクトを作るだけのことで、下記の内容の多くは、Leiningenそのものに含まれているタスクにも同様に当てはまります。
プラグインを使用するには、プロジェクトマップの:pluginsにそのプラグインを宣言するだけです。プロジェクトの実行用ではなく、使い勝手を向上させるプラグインは、project.cljファイルに直接記述する代わりに~/.lein/profiles.cljの:userプロファイルに記述してください。
lein new plugin mypluginでプロジェクトを作成するところからはじめ、leiningen.myplugin名前空間のmyplugin関数を編集してください。project.clj内にある:eval-in-leningen trueによって、タスクはサブプロセスではなく、leiningenプロセス内部で動作します。プラグインは、clojureそのものへの依存関係を宣言する必要はありません。
Leiningen本体の依存関係全てがプラグインから利用可能です。
非常に単純なプラグインの例として、 ソースコード内の
lein-pprintディレクトリを参照してください。
プラグインの開発中、プロジェクト内でlein installを再実行し、それからテストプロジェクトに切り替えるのは非常に手間がかかります。一度プラグインをインストールすれば、テストプロジェクト内の.lein-classpathファイルに、プラグインのsrcディレクトリのパスを記述しておくことでこの手間を省くことができます。そのプラグインが別の開発中のライブラリに依存している場合は、.lein-classpathにUNIXでは:、Windowsでは;のクラスパスセパレータで区切ってライブラリのディレクトリを追加することができます。
出力をする場合は、printlnの代わりに、leiningen.core.main/info、leiningen.core.main/warn、leiningen.core.main/debugのいずれかを使用してください。ユーザの出力設定の制御が適用されます。
タスク関数の最初の引数は現在のプロジェクトにしてください。これはproject.cljを元にしたマップですが、:name、:group、:version、:rootなどのキーが追加されています。このプロジェクトマップがどのようなものか確認するためには、lein-pprintプラグインを使ってみてください。プロジェクトと、プロファイルの組み合わせを確認するためにpprintタスクを実行することができます。
コマンドラインからパラメタをとるタスクが必要な場合、ひとつ以上の引数をとる関数を作ることができます。タスクは単なるClojureの関数であることを強調するために、引数は通常、UNIXの伝統的な--dashedスタイルでなく、:keywordsを受け付けるように記述します。引数は全てStringとして渡されることに留意してください。引数をキーワード、シンボル、整数として扱いたい場合にはread-stringを呼び出すことができますが、それはあなたが記述する関数次第です。他のタスクを関数として呼び出す際も、この点を留意してください。
ほとんどのタスクは、他のプロジェクトの内部でのみ実行されます。もしタスクを、プロジェクトディレクトリの外側で実行できるようにしたい場合は、^:no-project-neededメタデータをタスク関数に追加してください。その場合であっても、タスク関数の第一引数はプロジェクトをとるように記述し、プロジェクト外で実行した際に渡されるnilを受け入れるようにしてください。プロジェクト内で実行した場合は、LeiningenはJVMを起動する前に、プロジェクトのルートディレクトにcdしますが、IDE連携など、leiningen-coreライブラリを使用しているツールは同じようにふるまわない可能性があるため、プロジェクトの:rootキーワードを確認し、そのディレクトリを起点とすると最大限の移植性が得られます。
lein helpタスクはdocstringを使っています。名前空間レベルのdocstringが定義されていれば、それを短いサマリとして使用します。なければ、関数のdocstringの第一文が使用されます。フォーマットの都合上、サマリは68文字以内に収めるようにしてください。関数の引数名も表示されるので、明快で説明的な引数名を選ぶようにしてください。ユーザーに見せたくない代替の引数を持つ場合、関数のメタデータに:help-arglistsを設定することができます。その場合は全ての引数について説明するようにしてください。引数は全てStringなので、キーワード、数字、シンボルが必要な場合はread-stringを呼び出す必要があることに留意してください。
複雑なタスクはサブタスクに分割することがよくあります。サブタスクの変数のベクターを含んだ:subtasksメタデータをタスク関数に渡すことによって、lein help $TASK_CONTAINING_SUBTASKSを実行した際に、サブタスクを表示することができます。サブタスク一覧は、それぞれのサブタスクのdocstringの第一文を表示します。サブタスクの完全版のヘルプは、lein help $TASK_CONTAINING_SUBTASKS $SUBTASKで表示することができます。
特別な指定がない場合、Leiningenはlein $MYTASK helpの呼び出しを横取りしてlein help $MYTASKに変換します。タスク内で独自のhelpサブタスクを表示したい場合はタスク関数に^:pass-through-helpメタデータを指定してこのふるまいを無効化することができます。
プラグインの関数は、Leiningenのプロセス内で実行されるので、既存のLeiningenの関数全てにアクセスすることができます。leiningen.core.*名前空間内の、^:internalメタデータが付与されていない関数全てとタスク関数は全て公開されているAPIと考えてください。タスク名前空間のタスク以外の関数は内部用で、マイナーバージョンリリースでも変更される可能性があります。
タスクの多くはプロジェクトのコンテクスト内でコードを実行する必要があります。leiningen.core.eval/eval-in-project関数はこの目的で使用されます。この関数はプロジェクト引数、評価するフォーム、そして最後にオプションとして、メインフォームの前に評価される、初期化用のフォームをとることができます。この最後のフォームはジラルディシナリオを防ぐために、名前空間を事前にrequireするために用いることができます。
eval-in-project関数内ではプロジェクトのクラスパスが有効になっており、Leiningen自体の内部関数とプラグインは無効化されています。
プロジェクトマップはeval-in-projectに渡す前に改変することができますが、プロファイルをマージすることで変更する方法が、ユーザーによるオーバーライドを可能にするので推奨されます。変更を加えるために、leiningen.core.project/merge-profilesを使用してください。
(def swank-profile {:dependencies [['swank-clojure "1.4.3"]]})
(defn swank
"Launch swank server for Emacs to connect. Optionally takes PORT and HOST."
[project port host & opts]
(let [profile (or (:swank (:profiles project)) swank-profile)
project (project/merge-profiles project [profile])]
(eval-in-project project
`(swank.core/-main ~@opts)
'(require 'swank.core))))
swank-clojure依存関係のコードがプロジェクト内で必要なため、独自のプロファイルマップを宣言し、マージしています。しかし、:swankプロファイルがプロジェクトマップに定義されている場合はそれを使うため、ユーザはプラグイン内にハードコードされたバージョンに依存したくない場合は自分で違うバージョンを選択することができます。
上記で用いたコードは単にeval-in-projectとmerge-profilesをラップしただけなので、プラグインの例としてはふさわしくないことに留意してください。もし実現したいことがこれだけなのであれば、プラグインを実装することなく実現することができます。単にwith-profilesと必要な関数を呼び出すrunタスクを使ったエイリアスを定義すればよいのです。
eval-in-projectを実行する前に、Leiningenは全てのJavaコードと実行に必要なClojureコードをバイトコードに事前にコンパイルして、プロジェクトが実行可能な状態になるよう準備しなければなりません。これはプロジェクトの:prep-tasksキーに定義されているタスク全てを実行することで行われます。標準では["javac" "compile"]です。もしあなたのプラグインが他の準備作業を必要とする場合、(例えばプロトコルバッファのコンパイル)、ユーザに別のエントリを:prep-tasksに追加してもらうよう指示することができます。このタスクはeval-in-projectを実行するたびに評価されることに留意してください。前回の実行時から何も変わっていなければ素早く終了するように実装してください。
プラグインは主にタスクを提供するためのものですが、他にもプロファイル、フック、ミドルウェア、wagon(依存関係トランスポートメソッド)、バージョン管理方法を含めることができます。
あなたのプラグインを使用するプロジェクトの多くで必要になりそうなものの、何らかの理由でデフォルトで有効化できない設定があるとき、プラグイン内でプロファイルとして含めることができます。
src/myplugin/profiles.cljというファイルをプラグイン内に作成して、下記のマップを定義してください。
{:default {:x "y and z"}
:extra {:other "settings"}}
マップ内のそれぞれの値がプロファイルで、ユーザーがプロジェクトにマージすることができます。with-profileを用いて、実行時に明示的に指定することができます。
$ lein with-profile plugin.myplugin/extra test
ユーザは:defaultプロファイルを変更することで自動的に有効化することもできます。
:profiles {:default [:base :system :user :provided :dev :plugin.myplugin/default]
:other {...}}
:defaultプロファイル内のエントリは、jar、uberjar、pomなど、下流向けに成果物を生成するタスクと、with-profileを指定したタスクを除いた、他の全てのタスクで有効化されます。
フックを用いて、Leiningen標準タスクのふるまいをある程度変更することができます。フック機能は、Leiningenに含まれている Robert Hookeライブラリによって提供されています。
clojure.testのフィクスチャ機能に着想を得て、フックは他の関数、多くの場合はタスクをラップし、他の変数をバインディングしたり、返り値を改変したり、関数の実行を条件で制御したりすることでふるまいを変えます。add-hook関数は適用するタスクの変数とラッピングする関数を引数にとります。
(ns lein-integration.plugin
(:require [robert.hooke]
[leiningen.test]))
(defn add-test-var-println [f & args]
`(binding [~'clojure.test/assert-expr
(fn [msg# form#]
(println "Asserting" form#)
((.getRawRoot #'clojure.test/assert-expr) msg# form#))]
~(apply f args)))
;; Place the body of the activate function at the top-level for
;; compatibility with Leiningen 1.x
(defn activate []
(robert.hooke/add-hook #'leiningen.test/form-for-testing-namespaces
#'add-test-var-println))
フックは関数合成(compose)しますので、あなたのフックが他のフックの内側で実行される場合があることに気をつけてください。詳細は
Hookeのドキュメンテーションを参照してください。add-hookへの呼び出しは第一、第二引数の両方にVarを用いるべきであることに留意してください。そうすることでフックを重複して追加することなく繰り返しローディング可能になります。これは、Clojureでは関数は同一性が比較できませんが、Varは可能だからです。
もしあなたのフックが、あなたのプラグインを含むプロジェクトで自動的にロードされるようにしたい場合は、plugin-name.plugin/hooks関数で呼び出して有効化してください。上記の例ではプラグインはlein-integrationという名前なので、lein-integration.plugin/hooks関数が、lein-integrationプラグインのロード時に自動的に呼び出されます。
フックは、project.clj内の:hooksキーに有効化するためのVarのシークエンスを設定することで、手動でロードすることもできます。下位互換性のために、:hooks内でVarの代わりに名前空間も指定しすることもできます。その名前空間内のactivate関数が呼び出されます。自動ローディングのフックは手動で指定されたフックより前に有効化されます。
プロジェクトミドルウェアは、プロジェクトマップを引数にとり、新しいプロジェクトマップを返す関数にすぎません。ミドルウェアによって、プラグインはプロジェクトマップを変換することができるようになります。しかしミドルウェアは柔軟で透過的なので、デバッグを難しくするという問題点があります。もしプラグイン内のプロファイルを使って必要なことができるのであれば、そのほうがより宣言的で、ふるまいを観察しやすいので、後で起こる頭痛の種を減らすことができます。
下記のミドルウェアはプロジェクトマップの内容をプロジェクトのリソースフォルダ内に挿入してコードから読み取り可能にします。
(ns lein-inject.plugin)
(defn middleware [project]
(update-in project [:injections] concat
`[(spit "resources/project.clj" ~(prn-str project))]))
フックと同様、ミドルウェアもplugin-name.plugin/middleware内に定義すれば自動的に適用されます。また、project.clj内の:middlewareキーにプロジェクトマップを変換するVarのシークエンスを定義することで手動でローディングすることもできます。ミドルウェアの自動ローディングが手動で定義されたミドルウェアよりも前に適用されることに留意してください。
また、現在有効なミドルウェアは有効になっているプロファイルに依存していることにも注意してください。つまりアクティブなプロファイルが切り替わるたびにミドルウェア関数を再適用する必要があるということです。オリジナルのプロジェクトマップを保存しておき、merge-profiles、unmerge-profiles、set-profilesを呼び出すたびに元の状態から変換することによって実現しています。ミドルウェア関数は繰り返し呼び出される可能性があるので、冪等性(idempotent)のない副作用を含むべきでありません。
Pomegranate (依存解決のためにLeiningenによって用いられているライブラリ)は"wagon"ファクトリの登録をサポートしています。wagonはリポジトリ用の非標準トランスポートプロトコルを扱うために用いられ、リポジトリURLのプロトコルに応じて選択されます。もし、あなたのプラグインがwagonファクトリを登録する必要があるのであれば、プロトコルと、そのプロトコル用のwagonインスタンスを返す関数のマップを含むleiningen/wagons.cljファイルを含めることで実現できます。例えば、下記のwagons.cljはdav:URL用のwagonファクトリを登録します。
{"dav" #(org.apache.maven.wagon.providers.webdav.WebDavWagon.)}
このテクニックを用いたプラグインの例として、S3 wagon privateや lein-webdavを参考にしてください。
Leiningenにはマルチメソッドを用いてリリース関連のバージョン管理タスクを行う、vcsタスクが含まれています。標準では、Git向けの実装が含まれていますが、leiningen.vcs.$SYSTEM名前空間に含めることで他のシステムのサポートを追加することができます。vcsタスクが呼び出される際、leiningen.vcsプレフィックス配下の名前空間全てがローディングされます。これらの名前空間では、leiningen.vcs内で定義されているdefmulti向けのメソッドのみを特定のバージョン管理システム用に定義するようにしてください。
プラグインをプロジェクト内で用いるためには、:dependenciesと同じフォーマットで、:pluginsキーをproject.cljに追加するだけです。:dependenciesで扱えるオプションに加え、:pluginsにはフックやミドルウェアの自動ローディングを無効化するオプションが追加されています。
(defproject foo "0.1.0"
:plugins [[lein-pprint "1.1.1"]
[lein-foo "0.0.1" :hooks false]
[lein-bar "0.0.1" :middleware false]])
Leiningen2.4.0以上はClojure1.6.0を使用しています。もしLeiningenのプラグイン内で別のバージョンのClojureを使う必要がある場合は、eval-in-projectをダミーのプロジェクト引数と共に用いることができます。
(eval-in-project {:dependencies '[[org.clojure/clojure "1.4.0"]]}
'(println "hello from" *clojure-version*))
Leiningenの以前のバージョンはプラグインの動き方に違いがいくつかありますが、アップグレードをするのはそれほど難しくないはずです。
バージョン1.xと2.xの最も大きな違いは:dev-dependenciesがなくなったことです。Leiningenのプロセスとプロジェクトのプロセスの両方に存在する依存関係はもはや存在しません。Leiningenは:pluginsだけを参照し、プロジェクトは:dependenciesだけを参照します。ただしこれらのマップは現在有効化されているプロファイルによって影響を受ける場合があります。
もしあなたのプロジェクトがeval-in-projectを使用する必要が全くないのであれば、移植は比較的容易です。移動したLeiningen関数への参照を更新するだけで十分です。leiningen.util.*名前空間内の関数は全てなくなり、leiningen.coreはleiningen.core.mainに移動しました。
eval-in-projectを使用しているプラグインについては、プラグインの依存関係とソースコードがプロジェクト内では利用可能ではなくなるということに気をつけてください。もしあなたのプラグインが、プラグインとプロジェクトの両方のコンテクストで実行する必要のあるコードを含んでいる場合は、複数のプロジェクトに分割し、それぞれを:pluginsと:dependenciesに登録する必要があります。eval-in-projectの呼び出しで:dependenciesを挿入する方法については上記のlein-swankの例を参照してください。
Leiningenタスクの中にはどのディレクトリからでも実行可能なものがあります。(例:lein repl)。プロジェクトコンテクスト内でのみ有効なタスクもあります。
Leiningenがプロジェクトのコンテクスト内で実行されているかどうか(つまり、現在のディレクトリにproject.cljがあるかどうか)を調べるには、プロジェクトマップの:rootキーを調べます。
(if (:root project)
(comment "Running in a project directory")
(comment "Running standalone"))
もしあなたのプラグインがプロジェクトコンテクストの外側で動作するとしても、プロジェクトマップ用の引数リストの余地を残しておくべきです。プロジェクトが存在しない場合はnilが渡ってくるものと考えてください。^:no-project-neededメタデータを使って、プロジェクトを必要としてないことを示してください。
Leiningen 1.xでは、数値を返すタスク関数はプロセスのexit値を出力する方法として使われていましたが、2.xでは致命的なエラーが起こった際にはleiningen.core.main/abortを呼び出すべきです。leiningen.core.main/*exit-process?*変数にtrueが設定されている場合、この関数の呼び出しはexitを引き起こしますが、with-profilesなど、コンテクストによっては、単に例外をスローして次のタスクに進む場合もあります。
通常は、例えばleiningen.compileという名前空間を持つプラグインを作ったとしても、それはlein compileを実行した時に呼び出されることはありません。標準タスクがあなたのプラグインを上書きします。もし標準タスクを隠したい場合、エイリアスを作るか、プラグインをleiningen.plugin.compile名前空間内に作ることで実現することができます。
ひとたび2.xとの互換性を確保するのに必要な変更点を特定すれば、同一のコードベース内でバージョン1.xと2.xの両方をサポートするかどうかを決めることができます。別のブランチで管理したほうが楽な場合もありますし、両方のバージョンをサポートしたほうが簡単な場合もあります。幸運なことに、:pluginsを用いて、eval-in-projectのためだけに:dependenciesを追加する戦略はLeiningen 1.7上ではうまく動作します。
もし2.xで移動してしまった関数を使いたい場合は、コンパイル時に解決する代わりに実行時に解決し、もし見つからなければ1.xバージョンの関数に縮退する方法を試してみてください。lein-swankプラグインはこの互換性シムを使った例を提供しています。
(defn eval-in-project
"Support eval-in-project in both Leiningen 1.x and 2.x."
[project form init]
(let [[eip two?] (or (try (require 'leiningen.core.eval)
[(resolve 'leiningen.core.eval/eval-in-project)
true]
(catch java.io.FileNotFoundException _))
(try (require 'leiningen.compile)
[(resolve 'leiningen.compile/eval-in-project)]
(catch java.io.FileNotFoundException _)))]
(if two?
(eip project form init)
(eip project form nil nil init))))
もちろん、関数がとる引数の数が変わったり、関数が完全になくなった場合はこの手法は適用できませんが、ほとんどの場合はこれで充分なはずです。
2.xで変更された関数のうち、広く用いられていたものは、1.xと2.xの両方をサポートする互換性シムを提供する、leinjackerプロジェクト経由で利用可能です。
他の重要な変更点として、:source-path、:resources-path、:java-source-path、:test-pathは、:source-paths、:resource-paths、:java-source-paths、:test-pathsに変更され、Stringの代わりにベクターをとるようになりました。以前の:dev-resourcesキーは、:devプロファイルが有効な場合のみ:resource-pathsのエントリの一つとして含まれるようになりました。
後方互換性を保つ方法でタスクをプロジェクトディレクトリの外側で実行させるのは、2.xが単にプロジェクトを引数として渡し、なければnilにしておくのに対し、1.xは必要以上に賢く、引数リストが実際にプロジェクトの引数として渡せるかチェックをするため、簡単ではありません。最初の引数がプロジェクトマップかどうかを判定することはできますが、引数が二つ以上の場合、このチェックは非常に難しくなります。このような状況では、二つのブランチで管理するほうがよいかもしれません。
プロジェクトのコードベースにタスクを含めなければいけない場合もときとしてありますが、これは思ったほど頻繁におこることではありません。コマンドラインから実行する必要があるだけなら、プロジェクト内の-main関数として実行させ、lein garbleのようなエイリアスとして起動するほうがはるかに簡単です。
:aliases {"garble" ["run" "-m" "myproject.garble" "supergarble"]}
エイリアスのベクターは部分適用されたタスク関数になりますから、上記の設定の場合、lein garble seventeenは、lein run -m myproject.garble supergarble seventeen(あるいはreplから(myproject.garble/-main "supergarble" "seventeen")を実行した場合)と同等であることに注意してください。エイリアスの引数は実行時に、あらかじめ渡されている引数の後ろに追加されます。
Leiningenタスクを記述する必要があるのは、例えばeval-in-projectやLeiningenの内部へ直接アクセスするタスクを呼び出す前にプロジェクトマップを調整する必要がある場合など、プロジェクトコンテクストの外側で操作する必要がある場合のみです。エイリアスを使って、プロジェクトマップから値を読み取ることさえできます。
:aliases {"garble" ["run" "-m" "myproject.garble" :project/version]}
この例ではプロジェクトマップの:versionフィールドを引数リストに渡すことで、プロジェクト内で実行される-main関数が値にアクセスできるようにしています。
こういった例の多くは既存のプラグインでカバーされているはずですが、もし類似の例が見つからず、何らかの理由で別のブラグインに分離できない場合、tasks/leiningen/の下に新しいタスクを定義したfoo.cljファイルを作成し、tasksを.lein-classpathに追加することでこのふるまいを実現することができます。
$ ls
README.md project.clj src tasks test
$ ls -R tasks
leiningen
tasks/leiningen:
foo.clj
$ echo -ne ":tasks" | cat >> .lein-classpath
$ lein foo
Hello, Foo!
ただし、ほとんどの場合、別のプラグインプロジェクトにタスクを分離するのが望ましいです。.lein-classpathは主に実験用か、適切なプラグインを作る時間がない緊急の場合のためにあるものだからです。
あなたのプラグインができあがったら、Wiki上のリストに追加してください。
このプラグインシステムが、あなたの思いのままにLeiningenをカスタマイズするための簡単で、柔軟なシステムを提供していることを願っています。
Can you improve this documentation? These fine people already did:
Phil Hagelberg, Daniel Compton & Kenji NakamuraEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |