4. joulukuu 2010, 15:06

Leiningenin käyttöönotto

Ajattelin tuoda oman panokseni Leiningen—Clojure -pöytään, kun nyt on siihen aihetta. Eli nopea opas Leiningenin käyttöön Clojure-projektien hallinnassa! Kirjoitan nimiavaruuksista vähän, sillä ne voivat olla uusi tuttavuus joillekin (myös minulle) ei niin paljoa Javaa kirjoittaneille.

Ensimmäinen vaihe: asennetaan Leiningen

Leiningen tulee yhtenä sh-skriptinä, jonka voi ladata esimerkiksi täältä. Se on yleispätevä skripti, joten suosittelen sijoittamaan tiedoston johonkin yleisen polun varrelle. Vaikka /usr/bin/-hakemistoon, jos ei muualle keksi. Muista chmodata se suorituskykyiseksi!

Tämän jälkeen annetaan skriptin itse asentaa loput komentamalla lein self-install. Tämä on helppoa. Muuten, luulisin, että Leiningen tarvitsee Maven -hallintatyökalun, joten asennapa se distrosi pakettihallinnasta ensin. Varmuuden vuoksi. En ole satavarma tästä riippuvuudesta, joten jos Leiningen valittelee jotain asennuksensa aikana, asenna Maven.

Nyt Leiningen on asennettu, ja siihen pääsee käsiksi mistä vain komentamalla lein. Leiningen käyttää Mavenia tausta-apurina hakemaan netistä dependenssejä sun muuta. Mennäänpä kokeilemaan, miten teemme Helloworldit.

Uusi projekti Leiningenin avulla

Suosittelen itse luomaan uudet Leiningen-projektit ohjelmaa itseään käyttämällä. Siirrytään omaan koodipyhättöön, olkoon se vaikka ~/koodi, ja komennetaan lein new Hello. Tämä luo uuden, käyttökelpoisen rungon omaan hakemistoonsa ~/koodi/Hello, joten se on erityisen kätevä.

Projektin pääasiallinen runko on seuraavanlainen:

./
./src/Hello
./test/Hello/test

Nämä kolme hakemistoa ovat alustavasti merkittävässä asemassa. Juuressa on tärkeä Clojure-kielinen tiedosto project.clj, johon määritellään Leiningenille projektin riippuvuudet ja pääluokat.

Hakemistossa src on yllättäen projektin lähdekoodit. Sen alla on alihakemistoina kukin nimiavaruus (Leiningen luo meille projektin nimen mukaisen avaruuden Hello), joita kannattaa käyttää. Tällöin Leiningen tietää ajaa oikeita testejä oikeille tiedostoille, mikä on mukavaa koodailijan näkökulmasta. Vastaavasti hakemistossa test ovat siten kunkin lähdetiedoston testit. Kuten kaikki uudet dynaamiset kielet, Clojurekin tukee yksikkötestausta erittäin hyvällä tasolla, mikä on erinomainen juttu. Leiningen avustaa testailua mukavasti. Testaukselle on suunniteltu nimiavaruus Hello.test, jonka alle tulisivat kaikki samat yksiköt kuin Hello:n alle. Se on looginen rakenne; sen takia myös hakemistorakenteessa on yksi ylimääräinen alihakemisto.

Muita hakemistoja voi tulla sitä mukaa kun Leiningenin funktioita käytellään:

  • lib sisältää erilaiset jarrit, joita tarvitaan projektissa
  • classes sisältää käännetyt luokat

Leiningenin project.clj

Uuden projektin luotuamme Leiningen on jo tuonut käyttökelpoisen peruspohjan:

(defproject Hello "1.0.0-SNAPSHOT"
  :description "FIXME: write"
  :dependencies [[org.clojure/clojure "1.2.0"]
                 [org.clojure/clojure-contrib "1.2.0"]])

Oikein nätti ja helppotajuinen defproject-rakenne sisältää päänimiavaruuden, versionumeron, kuvauksen ja riippuvuudet. Mikäli haluaa, projektille voi asettaa pääluokan (eli luokan, joka ajetaan standalone-sovelluksena) käyttämällä listan perälle avainsanaa :main ja sen parametriksi vaikkapa Leiningenin tykkäämä Hello.core. Nyt Leiningen odottaa löytävänsä halutun pääluokan tiedostosta src/Hello/core.clj. Hello-esimerkkimme voisi olla vaikkapa seuraava:

(defproject Hello "1.0.0-SNAPSHOT"
  :description "Hello world"
  :dependencies [[org.clojure/clojure "1.2.0"]
                 [org.clojure/clojure-contrib "1.2.0"]]
  :main Hello.core)

Enempää emme välttämättä tarvitse tähän. Nyt voimme käskeä Leiningenin hakemaan listatut riippuvuudet komennolla lein deps. Nyt syntyy lib-hakemisto, jonka sisään pitäisi ilmestyä clojure ja clojure-contrib -jarrit. Siirtykäämme koodailemaan (l. leikkimään replissä).

Repl

Luultavasti käytät VimClojurea tai Emacsia, mutta varmuuden varalta esitellään komentoriviltä käyteltävä Repl. Leiningen käynnistää sinulle mukavan JLine-sisältöisen replin komennolla lein repl. Sen pitäisi kattaa kaikki projektin avaruudet.

Voit myös käyttää muita haluamiasi menetelmiä, tässä on apuna komento lein classpath, joka siis asettaa CLASSPATH-ympäristömuuttujan projektille sopivilla arvoilla. Voi olla joskus käyttöä, ehkäpä.

Tiedostoja

Tutkitaanpa Leiningenin luomaa pohjaa src/Hello/core.clj. Se on kovin pieni, vain nimiavaruus asetetaan tiedoston alussa. Tämä on fiksua pitää. Kukin Clojure-tiedosto voi napata oman nimiavaruuspalansa projektin päänimiavaruuden kupeesta. Se toimii ainakin pienien projektien kanssa.

Kokeillaanpa vähän, ja kirjoitetaan joku funktio.

(ns Hello.core)
(defn Hello
  "Say hello to person"
  [person]
  (str "Hello " person))

Okei, pieni ja typerä esimerkki. Mitenkäs varmistuisimme, että se toimii kunnolla?

Testataan!

Jotta voisimme olla varmoja Hello-funktiomme toimivuudesta, siirtykäämme testien pariin! En käy läpi Clojuren testausta sen kummemmin, viittaan vain yhteen tutoriaaliin: Unit Testing in Clojure, joka antaa joitain pohjatietoja. Opas on ehkä vähän vanhentunut, joten kun ymmärrät testien kirjoittamisen idean (deftest)-lohkoja käyttämällä, suosittelen lukemaan referenssistä muista rakenteista.

Loogisin paikka testata Hello.core (tiedostossa src/Hello/core.clj) on Hello.test.core (tiedostossa test/Hello/test/core.clj). Voit olla eri mieltä, en kiellä. Leiningen on itse asiassa jo tehnyt mainitun tiedoston, jossa on hyvä (ns)-lohko ja yksi raakiletesti. Nimiavaruuden määrittelevä ns menee näin:

(ns Hello.test.core
  (:use [Hello.core] :reload)
  (:use [clojure.test]))

Ja mitä se tekee, on ottaa oikea nimiavaruus käyttöön ja hakea testattava paketti käsillemme, ja vieläpä lisätyökalut siihen kaveriksi. Enempää emme äkkiseltään osaisi toivoakaan.

Testi luodaan näiden määritelmien kanssa leppoisasti. Käytän are-makroa tässä, sillä se on melkoisen rento ratkaisu hoidella muuten niin tympeä ja itseääntoistava kysely. Javan JUnit räjäyttäisi pään jo tässä vaiheessa!

(deftest test-hello
     (are [x y] (= (Hello x) y)
          "Jack" "Hello Jack"
          "world" "Hello world"
          "nurse" "Hello nurse"))

Testien sisältö ei ehkä vastaa mustalaatikkotestauksen tavoitteita, mutta tehkää itse sitten paremmin. Nyt sormet ristiin ja komennetaan lein test komentoriviltä. Ja läpihän se meni! Minähän en alunperin ole Javan omituisiin nimiavaruuskäytäntöihin tottunut, enkä aluksi kirjoitellut Clojureakaan noiden nimiavaruuksien kanssa. Kuitenkin kun havaitsin, miten helpoksi Leiningen tekee testauksen, jos nimiavaruudet ovat kunnossa, katsoin parhaaksi opiskella tämän kuntoon kerralla!

Pääohjelma

Projekti kaipaa vielä pääohjelmaa, jotta voimme katsella loput Leiningenin hienouksista. Mehän jo määrittelimme project.clj:hin “pääluokaksi” Hello.coren, mutta mitäpä siellä tapahtuisi? Ei vielä mitään: yksi funktiomäärittely ei vastaa main-metodia, eikä meillä ole luokkaakaan varsinaisesti. Käytäntönä on luoda -main -niminen funktio seuraavaan tapaan:

(defn -main [& args]
    (body))

Argumentit args tulevat arvatenkin komentoriviltä, joten CLI-ohjelmien tekoon ei paljoa vaadita. Meidän Hello World tietenkin tulostaa Hello Worldin:

(defn -main [& args]
  (println (Hello "world")))

Mutta emme ole vielä valmiita. Nämä funktiot pitää kääntää javaluokaksi, jotta se käynnistyisi standalonena. Lisätään (ns)-lohkoon yksi avainsana (:gen-class), eli käsketään generoimaan luokka. Nyt core.clj:n ensimmäinen lohko näyttää seuraavalta:

(ns Hello.core
  (:gen-class))

Käännös

Nyt riittää komentaa lein uberjar, ja meillä on isokokoinen purkki Hello-1.0.0-SNAPSHOT-standalone.jar projektimme juuressa. Ajamalla sen saamme odotetun tuloksen:

~/temp/Hello % java -jar Hello-1.0.0-SNAPSHOT-standalone.jar 
Hello world

Upeaa, eikö? Harmikseni en ole itse vielä ymmärtänyt, miten kevyemmällä lein jar -komennolla pärjättäisiin. Vaikka mitkä classpathit säätäisin käyttöön, ei se löydä pääluokkaamme. Uberjar pakkaa kaiken samaan, ja kokoa on megatavutolkulla. Tähän tutoriaaliin riittänee ensimmäinen toimiva ratkaisu.

Tageja:

---
---

---

Aiheen vierestä