読者です 読者をやめる 読者になる 読者になる

日曜プログラミング

休日趣味でやってるプログラミング関連記事をダラダラと

JavaFX アプリ用 leiningen テンプレートを作ってみる

leiningen JavaFX Clojure

公式ドキュメントを斜め読みしてもイマイチピンとこないので実際に自分で試してみる。日本語で読めるお試し記事がこちらにあり、記事では謙遜してるけど実際にはこちらの記事の方が大いにお世話になった。

やりたい事

JavaFX スタンドアロンアプリ用の Clojure テンプレートを作る。

も少し具体的には依存関係としてデフォルトで Maven のローカルリポジトリに入れた JavaFX ランタイムを追加して、main がある名前空間は gen-class して Application を extend させたプロジェクトテンプレートを作る。

作ろうと思った経緯

半ばグチ的なものなのでここは流してもらっていいです。

今回テンプレート化させたのは JavaFX で簡易的なラッパなりなんなりで使えるようにできないかとちょいちょい調べてたがいい方法が思いつかなかった事がまずあった。それと、結局ラッパ化できないのはエントリポイントのフレームワーク部分であり、その流れも少し面倒になってる程度なので、それなならいっその事テンプレートにすりゃいいやと思ったのもある。upshot ではフレームワーク強制記述は回避されてるが、こちらは作者自身が実験的なものだよとやたら強調してたのと、JavaFX 用スレッドと異なる Swing 用スレッドで走らせるようなハックになっていたのがちょっとなーってのがあった。

構文確認

> lein help new

で構文やらビルトインテンプレート(template/default/app/plugin)の説明やらを見ることができる。

テンプレートのテンプレートを作る

テンプレートのプロジェクト名は fxapp とする。これがイコール lein new 時のテンプレート指定名になる。

> lein new template fxapp
Generating fresh 'lein new' template project.

公式ドキュメントだといきなり --to-dir でプロジェクト名と異なるディレクトリを指定するような方法になってるけど特にそうしたい理由もないので今回は --to-dir オプション指定はなし。ただ、何となくフォルダには慣習的に -template と suffix を付けるっぽい。

また、以後生成されたファイルの編集やら新規追加やらを行う事になるが、project.clj を初めとした同一ファイル名だけど別の中身のものを別の場所に置くことになるので項目の最初にテンプレートプロジェクトフォルダ以下の場所を記述する。

leiningen テンプレート用 project.clj の編集

場所: プロジェクトフォルダ直下

ここの記事によるとプロジェクトバージョン表記に -SNAPSHOT が入っていると何かと参照が面倒になるそうなのでそれに従う。リンク先の記事にも書かれているが、この project.clj は leiningen がテンプレートからプロジェクトのひな形を作る為のものなので、デフォルトの依存関係追記などは後述の new 以下のフォルダに別途 project.clj を追加して編集する。

(defproject fxapp/lein-template "0.1.0"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :eval-in-leiningen true)

デフォルトで使う依存関係を別の project.clj として新規追加

場所: src/leiningen/new/fxapp/project.clj

こちらが何というか lein new [template-name] 指定して生成するファイルのひな形のイメージ。最初の追加場所でも書いたがファイルを追加する場所には注意。

今回は JavaFX スタンドアロンアプリのテンプレを目論んでいるのでビルトインで含まれている app テンプレートに書かれてるのをベースとして拝借する。拝借元は公式のこちら

(defproject {{raw-name}} "0.1.0"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [local.oracle/javafxrt "2.2.45"]]
  :aot [{{namespace}}]
  :main {{namespace}}
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}})

独自な部分は :dependencies に Maven ローカルリポジトリに入れた JavaFX を追加したのと、:main と同じ名前空間が展開されるよう :aot にも {{namespace}} を設定した部分。
Maven に登録がないライブラリ(JavaFX とか Oracle 絡みが多い)をローカルリポジトリに入れる方法は以前記事にも書いた

{{raw-name}} だか {{namespace}} はテンプレート変数みたいなもんだと思ってもらうといいかと。これら変数の設定は src/leiningen/new/fxapp.clj で行うがそのサンプルはまた後述する。

起動クラスとしての core.clj テンプレートを追加

場所: src/leiningen/new/fxapp/core.clj

フォルダ fxapp はテンプレートプロジェクト作成時の指定名によって変わるので適当に読み替えて下さい。ここはわりかし JavaFX 向けに特化した感じで、JavaFX を起動するのに最低限必要なコード記述をテンプレートにした。

(ns {{namespace}}
  (:import javafx.application.Application
           javafx.stage.Stage
           javafx.scene.Scene)
  (:gen-class
   :extends javafx.application.Application))

(defn -start [this stage]
  "JavaFX エントリポイント。"
  (doto stage
    (.setTitle "タイトルをどうぞ")
    (.show)))

(defn -main
  "最終的な起動はこっち。"
  [& args]
  (Application/launch {{sanitized}} args))

ついでにデフォルトの app テンプレートにある .gitignore, README.md, LICENSE, test.clj を同じ場所に追加。今回中身は全く編集なしなので割愛。

テンプレート展開ファイルと展開変数の指定

場所: src/leiningen/new/fxapp.clj

ここがテンプレートのキモとなる部分になるが、自動生成されたものだとやや情報が物足りなかったのでビルトインの app テンプレートを流用した。サンプルは resources 下でなくソースの中に含まれておりこちらを参考にした。

(ns leiningen.new.fxapp           ; <== 1-1
  "Generate a basic application project."
  (:require [leiningen.new.templates :refer [renderer year project-name
                                             ->files sanitize-ns name-to-path
                                             multi-segment sanitize]]  ; <== 2-1
            [leiningen.core.main :as main]))

(defn fxapp                       ; <== 1-2
  "An application project template."
  [name]
  (let [render (renderer "fxapp") ; <== 1-3
        main-ns (multi-segment (sanitize-ns name))
        data {:raw-name name
              :sanitized (multi-segment (sanitize name)) ; <== 2-2
              :name (project-name name)
              :namespace main-ns
              :nested-dirs (name-to-path main-ns)
              :year (year)}]
    (main/info "Generating a project called" name "based on the 'fxapp' template.")
    (->files data
             ; 3 こちらに追加ファイルを指定
             ["project.clj" (render "project.clj" data)]
             ["README.md" (render "README.md" data)]
             [".gitignore" (render ".gitignore" data)]
             ["src/{{nested-dirs}}.clj" (render "core.clj" data)]
             ["test/{{nested-dirs}}_test.clj" (render "test.clj" data)]
             ["LICENSE" (render "LICENSE" data)]
             "resources")))

公式の中身をまるまるコピペした上でやったのは

  1. ns と defn で付けられている app を fxapp へ変更(1-1, 1-2, 1-3)
  2. JavaFX の Application/launch 関数引数用のテンプレート変数 :sanitized を設定
    • 何やら余り見ない関数だがソース見るとは '-' から '_' へ変換したり、.core を付けたりする単純な関数。ちょうど自分がしたい事だったのでこれも使わせてもらった。
  3. ここでプロジェクト作成時のテンプレートファイルリストを指定する。
    • 2 要素のベクタを並べ、右がテンプレートファイル指定で左が展開先
      • テンプレートファイル指定時は render 関数を使いテンプレート変数を設定したマップを渡す形みたい。
    • あまり強調してなかったがこれらファイルは src/leiningen/new/fxapp/ 以下に全て置く形になる。
    • また、自動生成された foo.clj とか言うファイルは今回特にテンプレ化する内容がなかったので削除。

ローカルリポジトリへインストール

プロジェクトフォルダ下で lein install するのみ。

>lein install
Created C:\path\to\lein_projects\fxapp\target\lein-template-0.1.0.jar
Wrote C:\path\to\lein_projects\fxapp\pom.xml

どこに入ったんだろと .m2 フォルダ以下のぞいてみると fxapp ってフォルダがあった。更にその下を掘っていくと通常のライブラリとは異なって lein-template ってフォルダがあるのと jar が lein-template-0.1.0.jar ってなってた。この辺りで leiningen はテンプレートって見るのかな?

使ってみる

lein new と叩いた時に出るメッセージに追加される訳でもなく、leiningen が認識してるのかどうか install しただけでは良く分からないので実際に使ってみる。

> lein new fxapp fx-basis
Generating a project called fx-basis based on the 'fxapp' template.

> cd fx-basis

fx-basis>lein run
Compiling fx-basis.core

これで一旦起動する。ウィンドウの塗りつぶしすらない外枠だけの Window なのでキャプチャ画像は載せない。

uberjar しても同じ。

fx-basis>lein uberjar
Compiling fx-basis.core
[fx-basis.core] and :all have a type mismatch merging profiles.
Created \fx-basis\target\uberjar\fx-basis-0.1.0.jar
Created \fx-basis\target\fx-basis-0.1.0-standalone.jar

fx-basis>java -jar target\fx-basis-0.1.0-standalone.jar

作ってみて

割と面倒だが一度作ってしまえば既存プロジェクトフォルダをコピペして必要部分を修正していくよりは間違いが減るのでやる価値はあるかと。外へ向けてデプロイする事もできるみたいだが、こちらもこちらでやり方良く分からないので上げない。