なんとな~くしあわせ?の日記

「そしてそれゆえ、知識そのものが力である」 (Nam et ipsa scientia potestas est.) 〜 フランシス・ベーコン

leiningenで作るuberjarがmavenでできたらいいと思ったら出来なかった話

Leiningenでuberjarを作る

Clojureの日本語ガイドにあるように -> Part7: どのようにして Heroku へデプロイするか Leiningenから lein uberjar と打てばいわゆるFAT Jarができる。これは依存ライブラリを全て含んでいるのでJavaさえあれば実行できる。

Mavenからそれはできないか

似たようなものはできた。以下、設定したproject.clj

  • 肝心な部分は maven-shade-plugin
    • こいつが依存ライブラリとコンパイル済みのClojureからできたクラスファイルをjarに入れてくれる
  • ManifestResourceTransformerMaven側でManifest.mfを書き換えて、実行するメインクラスを定めてくれる
  • finalName
    • これを ${project.artifactId}-${project.version}-standalone で設定すると、leiningenがデフォルトで出力するuberjarの名前になるハズ

実行は lein pom してから mvn clojure:compile package

(defproject uber "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :uberjar-name "uber-clj.jar"
  :min-lein-version "2.5.3"
  :dependencies [[org.clojure/clojure "1.8.0"]]
  :pom-plugins [[com.theoryinpractise/clojure-maven-plugin "1.3.8"
                 {:configuration ([:mainClass "uber.core"]
                                  [:sourceDirectories [:sourceDirectory "src/main/clj"]])}]
                [org.apache.maven.plugins/maven-shade-plugin "2.4.3"
                 [:executions [:execution ([:phase "package"]
                                           [:goals [:goal "shade"]]
                                           [:configuration
                                            [:transformers
                                             [:transformer
                                              {:implementation "org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer"}]
                                             [:transformer
                                              {:implementation "org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer"}]
                                             [:transformer
                                              {:implementation "org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"}
                                              [:mainClass "uber.core"]]
                                             ]
                                            ;[:finalName "${project.artifactId}-${project.version}-standalone"]
                                            [:finalName "uber-clj"]
                                            [:filters
                                             [:filter
                                              [:includes
                                               [:include "**/*.js"]
                                               [:include "**/*.class"]
                                               [:include "**/*.xml"]]]]])]]]]
  :source-paths ["src/main/clj"]
  :test-paths ["src/test/clj"]
  :resource-paths ["resources"]
  :profiles
  {:dev {:env {:dev true }}
   :uberjar {:aot :all
             :main uber.core}})

動かない理由

ただし、この方法で作ったjarは動かない。それにはどうやらClojureのAOTコンパイルが関係しているらしい。

ClojureのAOTコンパイルは、タイムスタンプによってAOTコンパイル済みか判定する。maven-shade-pluginはこれをおかしくさせてしまうようだ。 むーん、maven-shade-pluginにプルリクする?

追記

簡単なスクリプト程度だと、Mavenから動かしても動くバイナリができるようだ。ringなどのライブラリを混ぜると動かない感じになる。

Clojureを使ってみる

たまたまClojureを触ってみて、なんだか良さそうだと感じたのでいろいろ書き散らしている。
環境構築が比較的容易で、WEBアプリが作りやすそうなのがよかった。あとScalaほど四角四面ではなく、Groovyほど壊れにくい*1

環境構築

yoppi.hatenablog.com

まずこれでMavenと連携させてみる

Leiningen

そしてLeiningenを入れる
Windowsの場合、Msys2上でUNIX用のスクリプトを動かしたほうが早い

> wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein -O /usr/bin/lein
> lein self-install
> lein --version
Leiningen 2.7.1 on Java 1.8.0_112 Java HotSpot(TM) 64-Bit Server VM

Maven

プロジェクトを作ってMaven

$ lein new wiki

project.clj ができるので、そこからpomを作る

$ lein pom

leinが出力するpom.xmlは、全部project.cljが元である。Mavenに慣れているならパズルゲームみたいな感じで設定出来ると思う。

Hello, World!!

ここからHello, Worldまではこのサイトがわかりやすい

qiita.com

core.cljを書き換えて・・・最終的にこうなった

(ns wiki.core
  (:gen-class main true))

(defn -main
  "I don't do a whole lot."
  [& args]
  (println "Hello, World!"))

Emacs

適当にこんな感じ

;; Clojure
(unless (package-installed-p 'clojure-mode)
  (package-refresh-contents) (package-install 'clojure-mode))
(require 'clojure-mode)
(add-to-list 'auto-mode-alist '("\\.clj$" . clojure-mode))

EmacsでモダンClojure開発環境構築 - Qiita
新: Emacs を使うモダンな Clojure 開発環境 - Qiita

cider-modeはmsys2では動かないかもしれない。

コレクション操作

Ruby

見慣れた形、データが先で関数で回す
# listの中身をトレース
[1, 2, 3].each do |n|
    puts n
end

# mapの中身をトレース
{ 1 => "a", 2 => "b", 3 => "c" }.each do |k, v|
    puts "#{k}, #{v}"
end

Clojure

Lispなのでデータが後で関数が先

mapについては doseq, reduce-kv 2つの関数を見つけた

; listの中身をトレース
(map #(println %) [1 2 3])
(map #(prn %) [1 2 3])

; mapの中身をトレース
(doseq [[k v] {1 "a" 2 "b" 3 "c"}] (prn k v))
(reduce-kv #(println %2 %3) 0 {1 "a" 2 "b" 3 "c"})

マクロとかLispっぽさ、関数型言語っぽい書き方もできたりするのだろうか。
チュートリアルを見ているとClojureは結構実用的に感じた。

Clojure の日本語ガイド — Clojure の日本語ガイド

プログラミングClojure 第2版

プログラミングClojure 第2版

*1:偏見…?