Introduction à Leiningen pour Clojure

1. Introduction

Leiningen est un système de construction moderne pour nos projets Clojure. Il est également entièrement écrit et configuré dans Clojure.

Il fonctionne de la même manière que Maven, nous donnant une configuration déclarative qui décrit notre projet, sans avoir besoin de configurer les étapes exactes à exécuter.

Voyons comment démarrer avec Leiningen pour construire nos projets Clojure.

2. Installation de Leiningen

Leiningen est disponible en téléchargement autonome, ainsi qu'à partir d'un grand nombre de gestionnaires de packages pour différents systèmes.

Des téléchargements autonomes sont disponibles pour Windows ainsi que pour Linux et Mac. Dans tous les cas, téléchargez le fichier, rendez-le exécutable si nécessaire, puis il est prêt à être utilisé.

La première fois que le script est exécuté, il téléchargera le reste de l'application Leiningen, puis cela sera mis en cache à partir de ce moment:

$ ./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. Création d'un nouveau projet

Une fois Leiningen installé, nous pouvons l'utiliser pour créer un nouveau projet en invoquant lein new .

Cela crée un projet en utilisant un modèle particulier à partir d'un ensemble d'options:

  • app - Utilisé pour créer une application
  • default - Utilisé pour créer une structure de projet générale, généralement pour les bibliothèques
  • plugin - Utilisé pour créer un plugin Leiningen
  • template - Utilisé pour créer de nouveaux modèles Leiningen pour de futurs projets

Par exemple, pour créer une nouvelle application appelée «mon-projet», nous exécuterons:

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

Cela nous donne un projet contenant:

  • Une définition de construction - project.clj
  • Un répertoire source - src - comprenant un fichier source initial - src / mon_projet / core.clj
  • Un répertoire de test - test - comprenant un fichier de test initial - test / my_project / core_test.clj
  • Quelques fichiers de documentation supplémentaires - README.md, LICENSE, CHANGELOG.md et doc / intro.md

En regardant à l'intérieur de notre définition de build, nous verrons qu'elle nous dit quoi construire, mais pas comment le construire:

(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}})

Cela nous dit:

  • Les détails du projet comprenant le nom du projet, la version, la description, la page d'accueil et les détails de la licence.
  • L'espace de noms principal à utiliser lors de l'exécution de l'application
  • La liste des dépendances
  • Le chemin cible dans lequel créer la sortie
  • Un profil pour construire un uberjar

Notez que l'espace de noms source principal est my-project.core et se trouve dans le fichier my_project / core.clj. Il est déconseillé dans Clojure d'utiliser des espaces de noms à segment unique - l'équivalent des classes de niveau supérieur dans un projet Java.

De plus, les noms de fichiers sont générés avec des traits de soulignement au lieu de tirets car la machine virtuelle Java a des problèmes avec les tirets dans les noms de fichiers.

Le code généré est assez simple:

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

Notez également que Clojure n'est qu'une dépendance ici. Cela rend facile d'écrire des projets en utilisant n'importe quelle version des bibliothèques Clojure souhaitée , et en particulier d'avoir plusieurs versions différentes s'exécutant sur le même système.

Si nous modifions cette dépendance, nous obtiendrons la version alternative à la place.

4. Construction et fonctionnement

Notre projet ne vaut pas grand-chose si nous ne pouvons pas le construire, l'exécuter et le mettre en package pour la distribution, alors regardons cela ensuite.

4.1. Lancer une REPL

Une fois que nous avons un projet, nous pouvons lancer une REPL à l'intérieur de celui-ci en utilisant lein repl . Cela nous donnera une REPL qui a tout dans le projet déjà disponible sur le chemin de classe - y compris tous les fichiers du projet ainsi que toutes les dépendances.

Cela nous démarre également dans l'espace de noms principal défini pour notre projet:

$ 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

Cela exécute la fonction -main dans l'espace de noms actuel, que nous avons vu ci-dessus.

4.2. Exécution de l'application

Si nous travaillons sur un projet d'application - créé à l'aide de la nouvelle application lein - alors nous pouvons simplement exécuter l'application à partir de la ligne de commande. Ceci est fait en utilisant lein run :

$ lein run Hello, World!

Cela exécutera la fonction appelée -main dans l'espace de noms défini comme : main dans notre fichier project.clj .

4.3. Construire une bibliothèque

Si nous travaillons sur un projet de bibliothèque - créé en utilisant lein new default - alors nous pouvons construire la bibliothèque dans un fichier JAR pour l'inclure dans d'autres projets .

Nous pouvons y parvenir de deux manières: en utilisant lein jar ou lein install . La différence réside simplement dans l'emplacement du fichier JAR de sortie.

Si nous utilisons lein jar, il le placera dans le répertoire cible local :

$ 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.

Pourquoi ne pas l'essayer pour le prochain projet et voir à quel point cela peut fonctionner.