Leiningen: nástroj pro správu projektů napsaných v Clojure (2)

Pavel Tišnovský 19. 2. 2015

Ve druhé části článku o nástroji Leiningen pro správu projektů vytvořených v Clojure se seznámíme s použitím zkompilovaných tříd vytvořených v Javě, s interaktivní smyčkou REPL zabudovanou do Leiningenu a vytvářením javovských archivů (.jar) obsahujících vyvíjenou aplikaci a připravených pro nasazení na další počítače.

Obsah

1. Leiningen: nástroj pro správu projektů napsaných v Clojure (2)

2. Vytvoření nového demonstračního projektu

3. Výpis adresářů, v nichž se hledají přeložené třídy a zdrojové kódy

4. Přidání přeložené javovské třídy do projektu

5. Spuštění projektu s přidanou javovskou třídou

6. Interaktivní smyčka REPL integrovaná do Leiningenu

7. Vytvoření Java archivu s demonstračním projektem

8. Spuštění projektu s využitím Java archivu

9. Vytvoření „megaarchivu“ s projektem i se samotným Clojure

10. Spuštění aplikace z „megaarchivu“

11. Obsah třetí části článku

12. Odkazy na Internetu

1. Leiningen: nástroj pro správu projektů napsaných v Clojure (2)

V první části článku o nástroji Leiningen jsme si ukázali, jakým způsobem je možné tento nástroj jednoduše nainstalovat na prakticky jakýkoli stroj s JDK a JRE skriptem lein a jak lze s využitím tohoto nástroje vytvořit nový projekt, který bude napsán v programovacím jazyce Clojure. Taktéž jsme se seznámili se způsobem automatického stažení knihoven, na nichž projekt závisí i s tím, jak se projekt nakonec spustí. Ve skutečnosti je ovšem možné Leiningen používat i k dalším činnostem. Při vývoji lze s výhodou využít vestavěnou interaktivní smyčku REPL (Read-Eval-Print Loop), která se od běžné smyčky REPL implementované v samotném Clojure v mnoha ohledech odlišuje – existencí historie zadávaných příkazů, automatickým nahráním všech knihoven vyžadovaných pro běh projektu, vylepšeným systémem nápovědy apod.

Leiningen se ovšem v praxi velmi často používá i při vytváření javovských archivů (souborů s koncovkou .jar) obsahujících celý projekt, který tak lze jednoduše distribuovat či nasazovat na další počítače. Vytvořený archiv lze ale například využít i v tomto obrazu pro Docker. Dokonce lze vytvářet i takzvané „megaarchivy“ (uberjar) určené pro spuštění na počítači s JRE (Java Runtime Environment) – megaarchivy totiž obsahují i samotné jádro Clojure, samozřejmě ve verzi používané programátorem při vývoji aplikace. Leiningen podporuje i tvorbu a spouštění jednotkových testů, které sice nejsou ve výchozím nastavení kompatibilní s formátem používaným nástrojem JUnit (lze ovšem využít plugin test2junit), ovšem i tak lze s jejich pomocí relativně snadno vyvíjenou aplikaci, resp. její jednotlivé funkce, průběžně testovat. Způsobem tvorby jednotkových testů se budeme podrobněji zabývat příště.

2. Vytvoření nového demonstračního projektu

Před ukázkami dalších možností nabízených nástrojem Leiningen si ve vhodném adresáři vytvoříme zcela nový projekt, který budeme postupně měnit a v závěru článku také testovat. Již z předchozí části víme, jak se nový projekt vytvoří. Vše zařídí následující příkaz, který musí být spuštěn v adresáři, do něhož má právě aktivní uživatel právo zápisu:

lein new app clojure_test_2

Na standardní výstup by se měla vypsat následující zpráva:

Generating a project called clojure_test_2 based on the 'app' template.

Z hlášení vidíme, že se projekt skutečně podařilo vytvořit. Adresář s projektem by měl mít následující strukturu (stejnou, jako projekt vytvořený minule, samozřejmě až na odlišný název):

.
├── doc
│   └── intro.md
├── LICENSE
├── project.clj
├── README.md
├── resources
├── src
│   └── clojure_test_2
│       └── core.clj
└── test
    └── clojure_test_2
        └── core_test.clj
 
6 directories, 6 files

Do projektu ještě přidáme deklaraci závislosti na externí knihovně, pro jednoduchost stejné, jako tomu bylo i minule, tedy org.clojure.data/json. Změna je provedena v hlavním souboru celého projektu – project.clj:

(defproject clojure_test_2 "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"}
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [org.clojure/data.json "0.2.5"]]
  :main ^:skip-aot clojure-test-2.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}})

Upravíme i zdrojový kód projektu takovým způsobem, aby tuto knihovnu využíval a volal z ní metodu pprint (povšimněte si, že jsme pro jmenný prostor knihovny vytvořili kratší a snadno zapamatovatelný alias json):

(ns clojure-test-2.core
    (:gen-class)
    (:require [clojure.data.json :as json]))
 
(defn -main
    "I don't do a whole lot ... yet."
    [& args]
    (let [article {:title "What's Good About Clojure?"
                   :url   "http://www.catalysoft.com/articles/goodAboutClojure.html"
                   :last-checked (.toString (new java.util.Date))}]
         (json/pprint article)))

3. Výpis adresářů, v nichž se hledají přeložené třídy a zdrojové kódy

Jedním z velmi častých problémů, s nimiž se setkávají jak programátoři vyvíjející aplikace v Javě, tak i administrátoři, je zjištění popř. nastavení classpath, tj. adresářů a Java archivů, v nichž virtuální stroj Javy po svém spuštění hledá přeložené třídy popř. další potřebné soubory (resources). Podobný problém řeší i vývojáři používající programovací jazyk Clojure, kde je situace ještě poněkud komplikovanější kvůli pravidlům pojmenovávání zdrojových souborů, adresářů a jmenných prostorů (zjednodušeně řečeno se pomlčky ve jmenných prostorech převádí na podtržítka). Pokud je však projekt založen nástrojem Leiningen, je možné zjistit aktuální nastavení classpath velmi jednoduše; konkrétně pomocí příkazu lein classpath. Můžeme si to snadno vyzkoušet:

lein classpath

Výstupem tohoto příkazu je jediný řádek, který jsem kvůli vyšší čitelnosti rozdělil na větší množství řádků v místě oddělovače jednotlivých cest:

/home/tester/src/Clojure/clojure_test_2/test:
/home/tester/src/Clojure/clojure_test_2/src:
/home/tester/src/Clojure/clojure_test_2/dev-resources:
/home/tester/src/Clojure/clojure_test_2/resources:
/home/tester/src/Clojure/clojure_test_2/target/base+system+user+dev/classes:
/home/tester/.m2/repository/clojure-complete/clojure-complete/0.2.3/clojure-complete-0.2.3.jar:
/home/tester/.m2/repository/org/clojure/tools.nrepl/0.2.6/tools.nrepl-0.2.6.jar:
/home/tester/.m2/repository/org/clojure/data.json/0.2.5/data.json-0.2.5.jar:
/home/tester/.m2/repository/org/clojure/clojure/1.6.0/clojure-1.6.0.jar

4. Přidání přeložené javovské třídy do projektu

Z výstupu příkazu lein classpath je patrné, že se přeložené třídy, tj. soubory s koncovkou .class budou vyhledávat mj. i v podadresáři resources, který byl automaticky vytvořen v průběhu generování adresářové struktury nového projektu. Ihned si tuto vlastnost otestujeme; přidáme totiž do projektu jeden zdrojový soubor napsaný v programovacím jazyce Java, jehož překladem vznikne kýžený soubor .class obsahující bajtkód přeložené třídy. Zdrojový soubor se bude jmenovat Adder.java a třída v něm deklarovaná bude mít (pochopitelně) název Adder, protože se jedná o veřejnou třídu:

public class Adder {
    public static int add(int x, int y) {
        return x+y;
    }
}

Překlad této třídy do bajtkódu se (prozatím) provede ručním zavoláním standardního překladače javac:

javac Adder.java

Výsledný soubor se jménem Adder.class musí být přesunut do adresáře resources:

mv Adder.class resources/

Struktura projektu nyní vypadá následovně:

.
├── Adder.java
├── doc
│   └── intro.md
├── LICENSE
├── project.clj
├── README.md
├── resources
│   └── Adder.class
├── src
│   └── clojure_test_2
│       └── core.clj
└── test
    └── clojure_test_2
        └── core_test.clj
 
6 directories, 8 files

5. Spuštění projektu s přidanou javovskou třídou

Nyní musíme otestovat, zda vyvíjená aplikace po svém spuštění skutečně třídu Adder „vidí“ a může volat její metody. Upravíme tedy zdrojový kód naší aplikace následujícím způsobem:

(ns clojure-test-2.core
    (:gen-class)
    (:require [clojure.data.json :as json]))
 
(defn -main
    "I don't do a whole lot ... yet."
    [& args]
    (println (Adder/add 1 2)))

Zkusme si nyní takto upravenou aplikaci spustit a otestovat tak, zda se přeložená třída Adder skutečně po spuštění nalezne a zda bude korektně zavolána i metoda Adder.add():

lein run

Výstupem by měla být hodnota 3:

3

Projekt vrátíme do původního stavu vymazáním souborů Adder.javaAdder.class. Následně se upraví i zdrojový kód core.clj tak, jak je vypsán ve druhé kapitole.

6. Interaktivní smyčka REPL integrovaná do Leiningenu

Důležitou součástí nástroje Leiningen je i interaktivní smyčka REPL (Read-Eval-Print Loop), která je oproti standardnímu REPLu integrovanému přímo do Clojure vylepšena. Zejména je implementována historie příkazů, dále je možné se k REPLu připojit přes zvolený port (což dělají některá integrovaná vývojová prostředí), k dispozici je vylepšený systém nápovědy apod. Ovšem nejdůležitější je fakt, že se při inicializaci REPLu správně nastaví i cesty ke všem třídám a knihovnám, takže je možné bez dalších složitostí spouštět a testovat jednotlivé části projektu. Ostatně si to můžeme jednoduše vyzkoušet zadáním následujícího příkazu (příkaz se samozřejmě musí spustit z adresáře projektu):

lein repl

Po inicializaci by se měla na konzoli vypsat následující zpráva (verze JVM atd. samozřejmě může být odlišná):

nREPL server started on port 56416 on host 127.0.0.1 - nrepl://127.0.0.1:56416
REPL-y 0.3.5, nREPL 0.2.6
Clojure 1.6.0
OpenJDK 64-Bit Server VM 1.7.0_75-b13
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e
 
clojure-test-2.core=>

Nyní si můžeme vyzkoušet, jestli se opravdu načetla i knihovna pro práci s formátem JSON:

clojure-test-2.core=> json/pprint
#<json$pprint clojure.data.json$pprint@69f528dc>

Symbol json/pprint je skutečně navázán na funkci, takže si vypišme nápovědu:

clojure-test-2.core=> (doc json/pprint)
-------------------------
clojure.data.json/pprint
([x & options])
  Pretty-prints JSON representation of x to *out*. Options are the
  same as for write except :value-fn, which is not supported.
nil

Funkci je samozřejmě možné ihned zavolat:

clojure-test-2.core=> (json/pprint [:Clojure :is :awesome])
["Clojure", "is", "awesome"]
nil

7. Vytvoření Java archivu s demonstračním projektem

Další důležitou funkcí nabízenou nástrojem Leiningen je možnost vytvořit Java archiv (soubor s koncovkou .jar), který bude obsahovat jak vlastní projekt, tak i všechny knihovny, na kterých tento projekt závisí. Java archiv je posléze možné spustit na jiném počítači, na něm ovšem musí být nainstalovaný Clojure. Opět si tuto funkcionalitu vyzkoušíme. Nejdříve je nutné nepatrně upravit obsah souboru project.clj takovým způsobem, aby se provedl překlad zdrojového kódu clojure-test-2.core do bajtkódu, tedy do souborů .class. Úprava je jednoduchá: na řádku :main stačí vymazat metadata ^:skip-aot (já jsem změnu provedl poněkud odlišně – původní řádek je zakomentovaný a za ním je přidána nová deklarace):

(defproject clojure_test_2 "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"}
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [org.clojure/data.json "0.2.5"]]
  ;:main ^:skip-aot clojure-test-2.core
  :main clojure-test-2.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}})

Vytvoření Java archivu zajistí příkaz lein jar, ještě předtím je však vhodné celý projekt vyčistit příkazem lein clean:

lein clean
lein jar

Struktura projektu se rozšíří, protože vznikne adresář pojmenovaný target, v němž je mj. i náš projekt přeložený do souborů .class:

.
├── doc
│   └── intro.md
├── LICENSE
├── project.clj
├── README.md
├── resources
├── src
│   └── clojure_test_2
│       └── core.clj
├── target
│   ├── classes
│   │   ├── clojure
│   │   │   └── data
│   │   │       ├── json
│   │   │       │   └── JSONWriter.class
│   │   │       ├── json$codepoint_case.class
│   │   │       ├── json$codepoint.class
│   │   │       ├── json$codepoint_clause.class
│   │   │       ├── json_compat_0_1__init.class
│   │   │       ├── json$default_value_fn.class
│   │   │       ├── json$default_write_key_fn.class
│   │   │       ├── json$fn__23.class
│   │   │       ├── json$fn__67.class
│   │   │       ├── json$fn__70.class
│   │   │       ├── json$fn__70$G__65__77.class
│   │   │       ├── json$fn__70$G__66__73.class
│   │   │       ├── json$fn__97.class
│   │   │       ├── json$fn__97$fn__98.class
│   │   │       ├── json__init.class
│   │   │       ├── json$json_str.class
│   │   │       ├── json$loading__4958__auto__.class
│   │   │       ├── json$pprint_array.class
│   │   │       ├── json$pprint_array$fn__105.class
│   │   │       ├── json$pprint.class
│   │   │       ├── json$pprint_dispatch.class
│   │   │       ├── json$pprint_generic.class
│   │   │       ├── json$pprint_generic$fn__133.class
│   │   │       ├── json$pprint_json.class
│   │   │       ├── json$pprint_object.class
│   │   │       ├── json$pprint_object$fn__111.class
│   │   │       ├── json$pprint_object$iter__114__118.class
│   │   │       ├── json$pprint_object$iter__114__118$fn__119.class
│   │   │       ├── json$pprint_object$iter__114__118$fn__119$fn__120.class
│   │   │       ├── json$print_json.class
│   │   │       ├── json$read_array.class
│   │   │       ├── json$_read.class
│   │   │       ├── json$read.class
│   │   │       ├── json$read_decimal.class
│   │   │       ├── json$read_escaped_char.class
│   │   │       ├── json$read_hex_char.class
│   │   │       ├── json$read_integer.class
│   │   │       ├── json$read_integer$fn__44.class
│   │   │       ├── json$read_json.class
│   │   │       ├── json$read_number.class
│   │   │       ├── json$read_number$fn__49.class
│   │   │       ├── json$read_object.class
│   │   │       ├── json$read_quoted_string.class
│   │   │       ├── json$read_str.class
│   │   │       ├── json$write_array.class
│   │   │       ├── json$write_bignum.class
│   │   │       ├── json$write.class
│   │   │       ├── json$write_double.class
│   │   │       ├── json$write_float.class
│   │   │       ├── json$write_generic.class
│   │   │       ├── json$write_json.class
│   │   │       ├── json$write_named.class
│   │   │       ├── json$write_null.class
│   │   │       ├── json$write_object.class
│   │   │       ├── json$write_plain.class
│   │   │       ├── json$write_ratio.class
│   │   │       ├── json$write_str.class
│   │   │       └── json$write_string.class
│   │   ├── clojure_test_2
│   │   │   ├── core.class
│   │   │   ├── core$fn__148.class
│   │   │   ├── core__init.class
│   │   │   ├── core$loading__4958__auto__.class
│   │   │   └── core$_main.class
│   │   └── META-INF
│   │       └── maven
│   │           └── clojure_test_2
│   │               └── clojure_test_2
│   │                   └── pom.properties
│   ├── clojure_test_2-0.1.0-SNAPSHOT.jar
│   └── stale
│       └── extract-native.dependencies
└── test
    └── clojure_test_2
        └── core_test.clj

Podívejme se nyní podrobněji na obsah adresáře target:

ls -la target/

Na standardní výstup by se měla vypsat přibližně následující struktura:

total 108
drwxr-xr-x 4 tester tester  4096 Uno 14 22:25 .
drwxr-xr-x 7 tester tester  4096 Uno 14 22:25 ..
drwxr-xr-x 4 tester tester  4096 úno 15 21:35 base+system+user+dev
drwxr-xr-x 5 tester tester  4096 Uno 14 22:25 classes
-rw-r--r-- 1 tester tester 90184 Uno 14 22:25 clojure_test_2-0.1.0-SNAPSHOT.jar
drwxr-xr-x 2 tester tester  4096 Uno 14 22:25 stale

Nejzajímavější je v tomto případě samozřejmě soubor clojure_test2-0.1.0-SNAPSHOT.jar. Jeho pojmenování vzniklo jednoduše – jedná se o název projektu, za nějž je připojeno číslo verze. Oba tyto údaje lze opět nalézt v souboru project.clj.

Můžeme samozřejmě prozkoumat obsah tohoto souboru, a to konkrétně s využitím nástroje jar:

jar tvf target/clojure_test_2-0.1.0-SNAPSHOT.jar

Nalezneme zde například celou strukturu se zdrojovými kódy projektu:

   453 Sat Feb 14 22:25:56 CET 2015 project.clj
   373 Sat Feb 14 22:18:06 CET 2015 clojure_test_2/core.clj

Dále různé metainformace obsahující mj. i jméno třídy, v níž se má při spuštění hledat statická metoda main:

     0 Sat Feb 14 22:25:52 CET 2015 META-INF/
   126 Sat Feb 14 22:25:56 CET 2015 META-INF/MANIFEST.MF
   453 Sat Feb 14 22:25:56 CET 2015 META-INF/leiningen/clojure_test_2/clojure_test_2/project.clj
   479 Sat Feb 14 22:25:56 CET 2015 META-INF/leiningen/clojure_test_2/clojure_test_2/README.md
 11218 Sat Feb 14 22:25:56 CET 2015 META-INF/leiningen/clojure_test_2/clojure_test_2/LICENSE

Dokonce se zde nachází metainformace použitelné známým nástrojem Maven (v souboru pom.properties jsou správně vypsány i závislosti atd. atd.):

     0 Sat Feb 14 22:25:52 CET 2015 META-INF/maven/
     0 Sat Feb 14 22:25:52 CET 2015 META-INF/maven/clojure_test_2/
     0 Sat Feb 14 22:25:52 CET 2015 META-INF/maven/clojure_test_2/clojure_test_2/
  2003 Sat Feb 14 22:25:56 CET 2015 META-INF/maven/clojure_test_2/clojure_test_2/pom.xml
   113 Sat Feb 14 22:25:52 CET 2015 META-INF/maven/clojure_test_2/clojure_test_2/pom.properties

Leiningen samozřejmě celý projekt přeložil ze zdrojových kódů přímo do javovského bajtkódu (překlad je komplikovanější, protože se provádí po funcích):

     0 Sat Feb 14 22:25:56 CET 2015 clojure_test_2/
  1559 Sat Feb 14 22:25:56 CET 2015 clojure_test_2/core$_main.class
  2996 Sat Feb 14 22:25:56 CET 2015 clojure_test_2/core__init.class
  1859 Sat Feb 14 22:25:54 CET 2015 clojure_test_2/core$loading__4958__auto__.class
  1338 Sat Feb 14 22:25:56 CET 2015 clojure_test_2/core$fn__148.class
  1799 Sat Feb 14 22:25:54 CET 2015 clojure_test_2/core.class

A konečně můžeme v archivu nalézt i třídy knihovny clojure.data.json, která je naším testovacím projektem využívána:

     0 Sat Feb 14 22:25:54 CET 2015 clojure/
     0 Sat Feb 14 22:25:56 CET 2015 clojure/data/
  2021 Sat Feb 14 22:25:56 CET 2015 clojure/data/json$pprint_dispatch.class
  3219 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write_object.class
  2562 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$read_array.class
  3035 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$loading__4958__auto__.class
   722 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$fn__70$G__66__73.class
   957 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$fn__97$fn__98.class
  1168 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$pprint_object$fn__111.class
   787 Sat Feb 14 22:25:56 CET 2015 clojure/data/json$pprint_object$iter__114__118.class
  1294 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write_float.class
  1662 Sat Feb 14 22:25:56 CET 2015 clojure/data/json$pprint_generic.class
  1378 Sat Feb 14 22:25:56 CET 2015 clojure/data/json$pprint_object.class
   668 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write_plain.class
  1714 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$fn__70$G__65__77.class
  3591 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write.class
  1545 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$fn__97.class
  1365 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write_ratio.class
  1806 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$fn__23.class
  1047 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write_named.class
   767 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$read_integer$fn__44.class
     0 Sat Feb 14 22:25:54 CET 2015 clojure/data/json/
   138 Sat Feb 14 22:25:54 CET 2015 clojure/data/json/JSONWriter.class
  1164 Sat Feb 14 22:25:56 CET 2015 clojure/data/json$pprint_json.class
  2047 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$read_hex_char.class
  1255 Sat Feb 14 22:25:56 CET 2015 clojure/data/json$print_json.class
  1167 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$pprint_array$fn__105.class
   701 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write_null.class
   588 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$default_value_fn.class
  3661 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$codepoint_clause.class
  3061 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$read.class
  3574 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$read_object.class
   970 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$codepoint.class
  2866 Sat Feb 14 22:25:56 CET 2015 clojure/data/json$read_json.class
  2588 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$read_number$fn__49.class
  1039 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write_bignum.class
  1769 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$default_write_key_fn.class
  1298 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write_double.class
  1213 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$read_str.class
  2093 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$pprint_object$iter__114__118$fn__119$fn__120.class
  1898 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write_array.class
  1884 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$fn__67.class
  3002 Sat Feb 14 22:25:56 CET 2015 clojure/data/json$pprint.class
   868 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$fn__70.class
  1447 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$read_integer.class
  1249 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write_str.class
  2646 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$read_escaped_char.class
  1253 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$pprint_array.class
  1166 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$read_decimal.class
  2008 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$read_quoted_string.class
  3083 Sat Feb 14 22:25:56 CET 2015 clojure/data/json$pprint_object$iter__114__118$fn__119.class
  1290 Sat Feb 14 22:25:56 CET 2015 clojure/data/json$write_json.class
 29064 Sat Feb 14 22:25:56 CET 2015 clojure/data/json__init.class
  4993 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$_read.class
  5390 Sat Feb 14 22:25:56 CET 2015 clojure/data/json_compat_0_1__init.class
  2378 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$codepoint_case.class
  1164 Sat Feb 14 22:25:56 CET 2015 clojure/data/json$json_str.class
  1363 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$read_number.class
  1659 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write_generic.class
  1667 Sat Feb 14 22:25:56 CET 2015 clojure/data/json$pprint_generic$fn__133.class
 10739 Sat Feb 14 22:25:54 CET 2015 clojure/data/json$write_string.class

8. Spuštění projektu s využitím Java archivu

Java archiv vytvořený postupem popsaným v předchozí kapitole je možné použít pro spuštění aplikace. Musí se však zajistit, aby virtuální stroj jazyka Java našel i Java archiv se samotným runtime systémem Clojure. Pro jednoduchost předpokládejme, že soubor clojure-1.6.0.jar (popř. symbolický link na tento soubor) je umístěn v adresáři target. Spuštění je relativně snadné – musí se jen nastavit classpath a specifikovat jméno třídy s metodou main (což je v tomto případě shodné se jmenným prostorem našeho projektu):

cd target
java -classpath clojure-1.6.0.jar:clojure_test_2-0.1.0-SNAPSHOT.jar clojure_test_2.core

Aplikace se skutečně korektně spustí:

{"title":"What's Good About Clojure?",
 "url":"http:\/\/www.catalysoft.com\/articles\/goodAboutClojure.html",
 "last-checked":"Sat Feb 14 22:21:57 CET 2015"}

Lze použít i následující zkratku (viz též man java):

java -cp "*" clojure_test_2.core

Archiv clojure-1.6.0.jar se samozřejmě může nacházet i v jiném adresáři:

java -cp /home/tester/src/Clojure/clojure-1.6.0.jar:clojure_test_2-0.1.0-SNAPSHOT.jar clojure_test_2.core

Kupodivu je možné použít i starší verze Clojure (to ovšem neplatí za všech okolností):

java -cp /home/tester/src/Clojure/clojure-1.5.1.jar:clojure_test_2-0.1.0-SNAPSHOT.jar clojure_test_2.core

9. Vytvoření „megaarchivu“ s projektem i se samotným Clojure

V předchozí kapitole ukázaný způsob spouštění aplikace z javovského archivu je sice použitelný, ovšem v mnoha případech může být výhodnější, aby archiv obsahoval i samotný Clojure. Tím se zajistí téměř stoprocentní přenositelnost takto zabalené aplikace i na další počítače, které musí obsahovat pouze běhové prostředí Javy (ve verzi minimálně stejné, jaká byla použita pro vytvoření archivu, i když toto omezení je možné obejít). Poměrně zajímavé může být použití tímto způsobem zabalené aplikace v obrazu systému pro Docker. Nástroj Leiningen tvorbu podobných „megaarchivů“ podporuje: nazývá je uberjar-y. Pro jejich vytvoření se používá příkaz lein uberjar, ovšem před jeho provedením je rozumné vyčistit adresář s projektem příkazem lein clean:

lein clean
lein uberjar

Leiningen by po zadání těchto příkazů měl do adresáře s projektem vygenerovat další soubory, zejména adresář target/uberjar. Jednotlivé verze Leiningenu se ve svém chování nepatrně odlišují, nicméně získat byste měli přibližně následující strukturu. Soubor s megaarchivem, který nás nyní zajímá, je vyznačený tučným písmem:

.
├── doc
│   └── intro.md
├── LICENSE
├── project.clj
├── README.md
├── resources
├── src
│   └── clojure_test_2
│       └── core.clj
├── target
│   ├── uberjar
│   │   ├── clojure_test_2-0.1.0-SNAPSHOT-standalone.jar
│   │   └── stale
│   │       └── extract-native.dependencies
│   └── uberjar+uberjar
│       ├── classes
│       │   ├── clojure
│       │   │   └── data
│       │   │       ├── json
│       │   │       │   └── JSONWriter.class
│       │   │       ├── json$codepoint_case.class
│       │   │       ├── json$codepoint.class
│       │   │       ├── json$codepoint_clause.class
│       │   │       ├── json_compat_0_1__init.class
│       │   │       ├── json$default_value_fn.class
│       │   │       ├── json$default_write_key_fn.class
│       │   │       ├── json$fn__23.class
│       │   │       ├── json$fn__67.class
│       │   │       ├── json$fn__70.class
│       │   │       ├── json$fn__70$G__65__77.class
│       │   │       ├── json$fn__70$G__66__73.class
│       │   │       ├── json$fn__97.class
│       │   │       ├── json$fn__97$fn__98.class
│       │   │       ├── json__init.class
│       │   │       ├── json$json_str.class
│       │   │       ├── json$loading__4958__auto__.class
│       │   │       ├── json$pprint_array.class
│       │   │       ├── json$pprint_array$fn__105.class
│       │   │       ├── json$pprint.class
│       │   │       ├── json$pprint_dispatch.class
│       │   │       ├── json$pprint_generic.class
│       │   │       ├── json$pprint_generic$fn__133.class
│       │   │       ├── json$pprint_json.class
│       │   │       ├── json$pprint_object.class
│       │   │       ├── json$pprint_object$fn__111.class
│       │   │       ├── json$pprint_object$iter__114__118.class
│       │   │       ├── json$pprint_object$iter__114__118$fn__119.class
│       │   │       ├── json$pprint_object$iter__114__118$fn__119$fn__120.class
│       │   │       ├── json$print_json.class
│       │   │       ├── json$read_array.class
│       │   │       ├── json$_read.class
│       │   │       ├── json$read.class
│       │   │       ├── json$read_decimal.class
│       │   │       ├── json$read_escaped_char.class
│       │   │       ├── json$read_hex_char.class
│       │   │       ├── json$read_integer.class
│       │   │       ├── json$read_integer$fn__44.class
│       │   │       ├── json$read_json.class
│       │   │       ├── json$read_number.class
│       │   │       ├── json$read_number$fn__49.class
│       │   │       ├── json$read_object.class
│       │   │       ├── json$read_quoted_string.class
│       │   │       ├── json$read_str.class
│       │   │       ├── json$write_array.class
│       │   │       ├── json$write_bignum.class
│       │   │       ├── json$write.class
│       │   │       ├── json$write_double.class
│       │   │       ├── json$write_float.class
│       │   │       ├── json$write_generic.class
│       │   │       ├── json$write_json.class
│       │   │       ├── json$write_named.class
│       │   │       ├── json$write_null.class
│       │   │       ├── json$write_object.class
│       │   │       ├── json$write_plain.class
│       │   │       ├── json$write_ratio.class
│       │   │       ├── json$write_str.class
│       │   │       └── json$write_string.class
│       │   ├── clojure_test_2
│       │   │   ├── core.class
│       │   │   ├── core$fn__148.class
│       │   │   ├── core__init.class
│       │   │   ├── core$loading__4958__auto__.class
│       │   │   └── core$_main.class
│       │   └── META-INF
│       │       └── maven
│       │           └── clojure_test_2
│       │               └── clojure_test_2
│       │                   └── pom.properties
│       ├── clojure_test_2-0.1.0-SNAPSHOT.jar
│       └── stale
│           └── extract-native.dependencies
└── test
    └── clojure_test_2
        └── core_test.clj

Podívejme se na velikost tohoto souboru, například příkazem ls:

ls -l target/uberjar
total 3728
-rw-r--r-- 1 tester tester 3811198 Uno 14 22:23 clojure_test_2-0.1.0-SNAPSHOT-standalone.jar
drwxr-xr-x 2 tester tester    4096 Uno 14 22:23 stale

Vidíme, že se jedná o relativně velký archiv, ovšem není divu, protože obsahuje jak naši demonstrační aplikaci, tak i všechny potřebné knihovny (zde konkrétně clojure.data.json) i samotný Clojure. Pokud se chcete podívat na obsah tohoto archivu, lze opět použít nástroj jar:

jar tvf target/uberjar/clojure_test_2-0.1.0-SNAPSHOT-standalone.jar

10. Spuštění aplikace z „megaarchivu“

Ve chvíli, kdy je „megaarchiv“ vytvořen, je možné naši demonstrační aplikaci spustit velice snadno, a to následujícím způsobem:

cd target/uberjar
java -jar clojure_test_2-0.1.0-SNAPSHOT-standalone.jar

Povšimněte si, že není zapotřebí zadávat jméno třídy, v níž se má vyhledat statická metoda main. Dříve, přesněji řečeno při snaze o spuštění aplikace z Java archivu bez přidaného Clojure, bylo nutné uvádět jméno této třídy, zde konkrétně clojure_test2.core, ve skutečnosti se ovšem při použití java -jar najde jméno třídy v metadatech přidaných do javovského archivu nástrojem Leiningen.

widgety

Po spuštění by se na standardní výstup měly vypsat tyto tři řádky:

{"title":"What's Good About Clojure?",
 "url":"http:\/\/www.catalysoft.com\/articles\/goodAboutClojure.html",
 "last-checked":"Sat Feb 14 22:24:41 CET 2015"}

11. Obsah třetí části článku

Ve třetí části článku o nástroji Leiningen se budeme podrobněji zabývat dvěma oblastmi. Tou první je práce s jednotkovými testy, tj. vytváření a následné spouštění testů. Druhou – podle mého názoru dosti zajímavou – oblastí je využití Leiningenu při tvorbě a ladění webových aplikací. Seznámíme se s knihovnami Ring (https://github.com/ring-clojure/ring) a Hiccup (https://github.com/weave­jester/hiccup), které je možné použít pro velmi rychlou tvorbu kostry webových aplikací.

12. Odkazy na Internetu

  1. První část článku:
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/
  2. Leiningen: úvodní stránka
    http://leiningen.org/
  3. Leiningen: Git repository
    https://github.com/techno­mancy/leiningen
  4. leiningen-win-installer
    http://leiningen-win-installer.djpowell.net/
  5. Clojure 1: Úvod
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/
  6. Clojure 2: Symboly, kolekce atd.
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/
  7. Clojure 3: Funkcionální programování
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-3-cast-funkcionalni-programovani/
  8. Clojure 4: Kolekce, sekvence a lazy sekvence
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/
  9. Clojure 5: Sekvence, lazy sekvence a paralelní programy
    http://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/
  10. Clojure 6: Podpora pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/
  11. Clojure 7: Další funkce pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/
  12. Clojure 8: Identity, stavy, neměnné hodnoty a reference
    http://www.root.cz/clanky/programovaci-jazyk-clojure-8-identity-stavy-nemenne-hodnoty-a-referencni-typy/
  13. Clojure 9: Validátory, pozorovatelé a kooperace s Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-9-validatory-pozorovatele-a-kooperace-mezi-clojure-a-javou/
  14. Clojure 10: Kooperace mezi Clojure a Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/
  15. Clojure 11: Generátorová notace seznamu/list comprehension
    http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/
  16. Clojure 12: Překlad programů z Clojure do bajtkódu JVM I
    http://www.root.cz/clanky/programovaci-jazyk-clojure-12-preklad-programu-z-clojure-do-bajtkodu-jvm/
  17. Clojure 13: Překlad programů z Clojure do bajtkódu JVM II
    2) http://www.root.cz/clanky/programovaci-jazyk-clojure-13-preklad-programu-z-clojure-do-bajtkodu-jvm-pokracovani/
  18. Clojure 14: Základy práce se systémem maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/
  19. Clojure 15: Tvorba uživatelských maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/
  20. Clojure 16: Složitější uživatelská makra
    http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/
  21. Clojure 17: Využití standardních maker v praxi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/
  22. Clojure 18: Základní techniky optimalizace aplikací
    http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/
  23. Clojure 19: Vývojová prostředí pro Clojure
    http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/
  24. Clojure 20: Vývojová prostředí pro Clojure (Vimu s REPL)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-20-vyvojova-prostredi-pro-clojure-integrace-vimu-s-repl/
  25. Clojure 21: ClojureScript aneb překlad Clojure do JS
    http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/
Našli jste v článku chybu?
Lupa.cz: Další Češi si nechali vložit do těla čip

Další Češi si nechali vložit do těla čip

Lupa.cz: Jak udělat formulář, aby ho vyplnil i negramotný?

Jak udělat formulář, aby ho vyplnil i negramotný?

Vitalia.cz: Voda z Vltavy před a po úpravě na pitnou

Voda z Vltavy před a po úpravě na pitnou

DigiZone.cz: RRTV: frekvence pro Country Radio

RRTV: frekvence pro Country Radio

Podnikatel.cz: Rohlik.cz testoval roboty pro rozvážku

Rohlik.cz testoval roboty pro rozvážku

Vitalia.cz: Výživový poradce: Tyhle fešáky jedu celoročně

Výživový poradce: Tyhle fešáky jedu celoročně

Lupa.cz: Blíží se konec Wi-Fi sítí bez hesla?

Blíží se konec Wi-Fi sítí bez hesla?

DigiZone.cz: Wimbledon na Nova Sport až do 2019

Wimbledon na Nova Sport až do 2019

Měšec.cz: „Ukradli“ jsme peníze z bezkontaktních karet

„Ukradli“ jsme peníze z bezkontaktních karet

Vitalia.cz: dTest odhalil ten nejlepší kečup

dTest odhalil ten nejlepší kečup

Lupa.cz: Adblock Plus začal prodávat reklamy

Adblock Plus začal prodávat reklamy

Podnikatel.cz: ČSSZ posílá přehled o důchodovém kontě

ČSSZ posílá přehled o důchodovém kontě

Vitalia.cz: Tohle všechno se dá usušit

Tohle všechno se dá usušit

Lupa.cz: Jak se prodává firma za miliardu?

Jak se prodává firma za miliardu?

120na80.cz: Galerie: Čínští policisté testují českou minerálku

Galerie: Čínští policisté testují českou minerálku

Podnikatel.cz: Tyto pojmy k #EET byste měli znát

Tyto pojmy k #EET byste měli znát

DigiZone.cz: Regionální tele­vize CZ vysílá "Mapu úspěchu"

Regionální tele­vize CZ vysílá "Mapu úspěchu"

DigiZone.cz: Budoucnost TV vysílání ve Visegrádu

Budoucnost TV vysílání ve Visegrádu

DigiZone.cz: Nova opět stahuje „milionáře“

Nova opět stahuje „milionáře“

Vitalia.cz: Muž, který miluje příliš. Ženám neimponuje

Muž, který miluje příliš. Ženám neimponuje