Introduzione a Leiningen per Clojure

1. Introduzione

Leiningen è un moderno sistema di build per i nostri progetti Clojure. È anche scritto e configurato interamente in Clojure.

Funziona in modo simile a Maven, fornendoci una configurazione dichiarativa che descrive il nostro progetto, senza la necessità di configurare i passaggi esatti da eseguire.

Facciamo un salto e vediamo come iniziare con Leiningen per costruire i nostri progetti Clojure.

2. Installazione di Leiningen

Leiningen è disponibile come download autonomo, nonché da un gran numero di gestori di pacchetti per diversi sistemi.

I download autonomi sono disponibili per Windows, Linux e Mac. In tutti i casi, scarica il file, rendilo eseguibile se necessario, quindi è pronto per l'uso.

La prima volta che lo script viene eseguito, scaricherà il resto dell'applicazione Leiningen, che verrà poi memorizzata nella cache da questo punto in avanti:

$ ./lein Downloading Leiningen to /Users/user/.lein/self-installs/leiningen-2.8.3-standalone.jar now... ..... Leiningen is a tool for working with Clojure projects. Several tasks are available: ..... Run `lein help $TASK` for details. .....

3. Creazione di un nuovo progetto

Una volta installato Leiningen, possiamo usarlo per creare un nuovo progetto invocando lein new .

Questo crea un progetto utilizzando un modello particolare da una serie di opzioni:

  • app : utilizzata per creare un'applicazione
  • default : utilizzato per creare una struttura generale del progetto, in genere per le librerie
  • plugin - Usato per creare un plugin Leiningen
  • template - Utilizzato per creare nuovi modelli Leiningen per progetti futuri

Ad esempio, per creare una nuova applicazione chiamata "my-project" dovremmo eseguire:

$ ./lein new app my-project Generating a project called my-project based on the 'app' template.

Questo ci dà un progetto contenente:

  • Una definizione di build - project.clj
  • Una directory di origine - src - che include un file di origine iniziale - src / my_project / core.clj
  • Una directory di test - test - che include un file di test iniziale - test / my_project / core_test.clj
  • Alcuni file di documentazione aggiuntivi: README.md, LICENSE, CHANGELOG.md e doc / intro.md

Guardando all'interno della nostra definizione di build, vedremo che ci dice cosa costruire, ma non come costruirlo:

(defproject my-project "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "//example.com/FIXME" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :url "//www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.9.0"]] :main ^:skip-aot my-project.core :target-path "target/%s" :profiles {:uberjar {:aot :all}})

Questo ci dice:

  • I dettagli del progetto costituiti da nome del progetto, versione, descrizione, homepage e dettagli della licenza.
  • Lo spazio dei nomi principale da utilizzare durante l'esecuzione dell'applicazione
  • L'elenco delle dipendenze
  • Il percorso di destinazione in cui creare l'output
  • Un profilo per costruire un uberjar

Si noti che lo spazio dei nomi della sorgente principale è my-project.core e si trova nel file my_project / core.clj. È sconsigliato in Clojure utilizzare spazi dei nomi a segmento singolo, l'equivalente delle classi di primo livello in un progetto Java.

Inoltre, i nomi dei file vengono generati con trattini bassi invece che trattini perché la JVM ha alcuni problemi con i trattini nei nomi dei file.

Il codice generato è piuttosto semplice:

(ns my-project.core (:gen-class)) (defn -main "I don't do a whole lot ... yet." [& args] (println "Hello, World!"))

Inoltre, nota che Clojure è solo una dipendenza qui. Ciò rende banale scrivere progetti utilizzando qualsiasi versione delle librerie Clojure si desideri , e soprattutto avere più versioni differenti in esecuzione sullo stesso sistema.

Se cambiamo questa dipendenza, avremo invece la versione alternativa.

4. Costruzione e funzionamento

Il nostro progetto non vale molto se non possiamo costruirlo, eseguirlo e impacchettarlo per la distribuzione, quindi diamo un'occhiata a quello successivo.

4.1. Avvio di un REPL

Una volta che abbiamo un progetto, possiamo lanciare un REPL al suo interno usando lein repl . Questo ci darà un REPL che ha tutto nel progetto già disponibile sul classpath, inclusi tutti i file di progetto e tutte le dipendenze.

Ci avvia anche nello spazio dei nomi principale definito per il nostro progetto:

$ lein repl nREPL server started on port 62856 on host 127.0.0.1 - nrepl://127.0.0.1:62856 []REPL-y 0.4.3, nREPL 0.5.3 Clojure 1.9.0 Java HotSpot(TM) 64-Bit Server VM 1.8.0_77-b03 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 my-project.core=> (-main) Hello, World! nil

Questo esegue la funzione -main nello spazio dei nomi corrente, che abbiamo visto sopra.

4.2. Esecuzione dell'applicazione

Se stiamo lavorando a un progetto applicativo - creato utilizzando lein new app - allora possiamo semplicemente eseguire l'applicazione dalla riga di comando. Questo viene fatto usando lein run :

$ lein run Hello, World!

Questo eseguirà la funzione chiamata -main nello spazio dei nomi definito come : main nel nostro file project.clj .

4.3. Costruire una biblioteca

Se stiamo lavorando su un progetto di libreria - creato utilizzando lein new default - allora possiamo costruire la libreria in un file JAR per l'inclusione in altri progetti .

Abbiamo due modi per ottenere questo risultato: usando lein jar o lein install . La differenza sta semplicemente nella posizione in cui viene posizionato il file JAR di output.

Se usiamo lein jar , lo posizionerà nella directory di destinazione locale :

$ lein jar Created /Users/user/source/me/my-library/target/my-library-0.1.0-SNAPSHOT.jar

If we use lein install, then it will build the JAR file, generate a pom.xml file and then place the two into the local Maven repository (typically under .m2/repository in the users home directory)

$ lein install Created /Users/user/source/me/my-library/target/my-library-0.1.0-SNAPSHOT.jar Wrote /Users/user/source/me/my-library/pom.xml Installed jar and pom into local repo.

4.4. Building an Uberjar

If we are working on an application project, Leiningen gives us the ability to build what is called an uberjar. This is a JAR file containing the project itself and all dependencies and set up to allow it to be run as-is.

$ lein uberjar Compiling my-project.core Created /Users/user/source/me/my-project/target/uberjar/my-project-0.1.0-SNAPSHOT.jar Created /Users/user/source/me/my-project/target/uberjar/my-project-0.1.0-SNAPSHOT-standalone.jar

The file my-project-0.1.0-SNAPSHOT.jar is a JAR file containing exactly the local project, and the file my-project-0.1.0-SNAPSHOT-standalone.jar contains everything needed to run the application.

$ java -jar target/uberjar/my-project-0.1.0-SNAPSHOT-standalone.jar Hello, World!

5. Dependencies

Whilst we can write everything needed for our project ourselves, it's generally significantly better to re-use the work that others have already done on our behalf. We can do this by having our project depend on these other libraries.

5.1. Adding Dependencies to Our Project

To add dependencies to our project, we need to add them correctly to our project.clj file.

Dependencies are represented as a vector consisting of the name and version of the dependency in question. We've already seen that Clojure itself is added as a dependency, written in the form [org.clojure/clojure “1.9.0”].

If we want to add other dependencies, we can do so by adding them to the vector next to the :dependencies keyword. For example, if we want to depend on clj-json we would update the file:

 :dependencies [[org.clojure/clojure "1.9.0"] [clj-json "0.5.3"]]

Once done, if we start our REPL – or any other way to build or run our project – then Leiningen will ensure that the dependencies are downloaded and available on the classpath:

$ lein repl Retrieving clj-json/clj-json/0.5.3/clj-json-0.5.3.pom from clojars Retrieving clj-json/clj-json/0.5.3/clj-json-0.5.3.jar from clojars nREPL server started on port 62146 on host 127.0.0.1 - nrepl://127.0.0.1:62146 REPL-y 0.4.3, nREPL 0.5.3 Clojure 1.9.0 Java HotSpot(TM) 64-Bit Server VM 1.8.0_77-b03 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 my-project.core=> (require '(clj-json [core :as json])) nil my-project.core=> (json/generate-string {"foo" "bar"}) "{\"foo\":\"bar\"}" my-project.core=>

We can also use them from inside our project. For example, we could update the generated src/my_project/core.clj file as follows:

(ns my-project.core (:gen-class)) (require '(clj-json [core :as json])) (defn -main "I don't do a whole lot ... yet." [& args] (println (json/generate-string {"foo" "bar"})))

And then running it will do exactly as expected:

$ lein run {"foo":"bar"}

5.2. Finding Dependencies

Often, it can be difficult to find the dependencies that we want to use in our project. Leiningen comes with a search functionality built in to make this easier. This is done using lein search.

For example, we can find our JSON libraries:

$ lein search json Searching central ... [com.jwebmp/json "0.63.0.60"] [com.ufoscout.coreutils/json "3.7.4"] [com.github.iarellano/json "20190129"] ..... Searching clojars ... [cheshire "5.8.1"] JSON and JSON SMILE encoding, fast. [json-html "0.4.4"] Provide JSON and get a DOM node with a human representation of that JSON [ring/ring-json "0.5.0-beta1"] Ring middleware for handling JSON [clj-json "0.5.3"] Fast JSON encoding and decoding for Clojure via the Jackson library. .....

This searches all of the repositories that our project is working with – in this case, Maven Central and Clojars. It then returns the exact string to put into our project.clj file and, if available, the description of the library.

6. Testing Our Project

Clojure has built-in support for unit testing our application, and Leiningen can harness this for our projects.

Our generated project contains test code in the test directory, alongside the source code in the src directory. It also includes a single, failing test by default – found in test/my_project/core-test.clj:

(ns my-project.core-test (:require [clojure.test :refer :all] [my-project.core :refer :all])) (deftest a-test (testing "FIXME, I fail." (is (= 0 1))))

This imports the my-project.core namespace from our project, and the clojure.test namespace from the core Clojure language. We then define a test with the deftest and testing calls.

We can immediately see the names of the test, and the fact that it's deliberately written to fail – it asserts that 0 == 1.

Let's run this using the lein test command, and immediately see the tests running and failing:

$ lein test lein test my-project.core-test lein test :only my-project.core-test/a-test FAIL in (a-test) (core_test.clj:7) FIXME, I fail. expected: (= 0 1) actual: (not (= 0 1)) Ran 1 tests containing 1 assertions. 1 failures, 0 errors. Tests failed.

If we instead fix the test, changing it to assert that 1 == 1 instead, then we'll get a passing message instead:

$ lein test lein test my-project.core-test Ran 1 tests containing 1 assertions. 0 failures, 0 errors.

This is a much more succinct output, only showing what we need to know. This means that when there are failures, they immediately stand out.

If we want to, we can also run a specific subset of the tests. The command line allows for a namespace to be provided, and only tests in that namespace are executed:

$ lein test my-project.core-test lein test my-project.core-test Ran 1 tests containing 1 assertions. 0 failures, 0 errors. $ lein test my-project.unknown lein test my-project.unknown Ran 0 tests containing 0 assertions. 0 failures, 0 errors.

7. Summary

This article has shown how to get started with the Leiningen build tool, and how to use it to manage our Clojure based projects – both executable applications and shared libraries.

Perché non provarlo per il prossimo progetto e vedere come può funzionare bene.