1. Introducción
Leiningen es un sistema de construcción moderno para nuestros proyectos de Clojure. También está escrito y configurado completamente en Clojure.
Funciona de forma similar a Maven, dándonos una configuración declarativa que describe nuestro proyecto, sin necesidad de configurar los pasos exactos a ejecutar.
Comencemos y veamos cómo comenzar con Leiningen para construir nuestros proyectos de Clojure.
2. Instalación de Leiningen
Leiningen está disponible como descarga independiente, así como en una gran cantidad de administradores de paquetes para diferentes sistemas.
Las descargas independientes están disponibles para Windows, así como para Linux y Mac. En todos los casos, descargue el archivo, hágalo ejecutable si es necesario y estará listo para usar.
La primera vez que se ejecuta el script, se descargará el resto de la aplicación Leiningen, y luego se almacenará en caché a partir de este punto:
$ ./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. Creación de un proyecto nuevo
Una vez que Leiningen está instalado, podemos usarlo para crear un nuevo proyecto invocando lein new .
Esto crea un proyecto usando una plantilla particular de un conjunto de opciones:
- aplicación : se utiliza para crear una aplicación
- predeterminado : se utiliza para crear una estructura de proyecto general, generalmente para bibliotecas
- complemento : se utiliza para crear un complemento de Leiningen
- plantilla : se utiliza para crear nuevas plantillas de Leiningen para proyectos futuros
Por ejemplo, para crear una nueva aplicación llamada "mi-proyecto" ejecutaríamos:
$ ./lein new app my-project Generating a project called my-project based on the 'app' template.
Esto nos da un proyecto que contiene:
- Una definición de construcción - project.clj
- Un directorio de origen, src , que incluye un archivo de origen inicial, src / my_project / core.clj
- Un directorio de prueba, prueba , que incluye un archivo de prueba inicial, prueba / my_project / core_test.clj
- Algunos archivos de documentación adicionales: README.md, LICENSE, CHANGELOG.md y doc / intro.md
Mirando dentro de nuestra definición de compilación, veremos que nos dice qué compilar, pero no cómo hacerlo:
(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}})
Esto nos dice:
- Los detalles del proyecto que consisten en el nombre del proyecto, la versión, la descripción, la página de inicio y los detalles de la licencia.
- El espacio de nombres principal que se utilizará al ejecutar la aplicación.
- La lista de dependencias
- La ruta de destino para construir la salida en
- Un perfil para construir un uberjar
Tenga en cuenta que el espacio de nombres de la fuente principal es my-project.core y se encuentra en el archivo my_project / core.clj. En Clojure se desaconseja utilizar espacios de nombres de un solo segmento, el equivalente a las clases de nivel superior en un proyecto Java.
Además, los nombres de archivo se generan con guiones bajos en lugar de guiones porque la JVM tiene algunos problemas con los guiones en los nombres de archivo.
El código generado es bastante simple:
(ns my-project.core (:gen-class)) (defn -main "I don't do a whole lot ... yet." [& args] (println "Hello, World!"))
Además, observe que Clojure es solo una dependencia aquí. Esto hace que sea trivial escribir proyectos usando cualquier versión de las bibliotecas de Clojure que se desee , y especialmente tener varias versiones diferentes ejecutándose en el mismo sistema.
Si cambiamos esta dependencia, obtendremos la versión alternativa.
4. Construir y ejecutar
Nuestro proyecto no vale mucho si no podemos construirlo, ejecutarlo y empaquetarlo para su distribución, así que veamos eso a continuación.
4.1. Lanzar un REPL
Una vez que tenemos un proyecto, podemos lanzar un REPL dentro de él usando lein repl . Esto nos dará un REPL que tiene todo el proyecto ya disponible en la ruta de clase, incluidos todos los archivos del proyecto y todas las dependencias.
También nos inicia en el espacio de nombres principal definido para nuestro proyecto:
$ 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
Esto ejecuta la función -main en el espacio de nombres actual, que vimos anteriormente.
4.2. Ejecutando la Aplicación
Si estamos trabajando en un proyecto de aplicación, creado con lein new app , simplemente podemos ejecutar la aplicación desde la línea de comandos. Esto se hace usando lein run :
$ lein run Hello, World!
Esto ejecutará la función llamada -main en el espacio de nombres definido como : main en nuestro archivo project.clj .
4.3. Construyendo una biblioteca
Si estamos trabajando en un proyecto de biblioteca, creado usando lein new default , podemos construir la biblioteca en un archivo JAR para incluirlo en otros proyectos .
Tenemos dos formas de lograr esto: usando lein jar o lein install . La diferencia está simplemente en dónde se coloca el archivo JAR de salida.
Si usamos lein jar , lo colocará en el directorio de destino local :
$ lein jar Created /Users/user/source/me/my-library/target/my-library-0.1.0-SNAPSHOT.jar
Si usamos lein install , compilará el archivo JAR, generará un archivo pom.xml y luego colocará los dos en el repositorio local de Maven (normalmente en .m2 / repository en el directorio de inicio de los usuarios)
$ 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. Construyendo un Uberjar
Si estamos trabajando en un proyecto de aplicación, Leiningen nos da la posibilidad de construir lo que se llama un uberjar . Este es un archivo JAR que contiene el proyecto en sí y todas las dependencias y está configurado para permitir que se ejecute tal cual.
$ 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
El archivo my-project-0.1.0-SNAPSHOT.jar es un archivo JAR que contiene exactamente el proyecto local, y el archivo my-project-0.1.0-SNAPSHOT-standalone.jar contiene todo lo necesario para ejecutar la aplicación.
$ java -jar target/uberjar/my-project-0.1.0-SNAPSHOT-standalone.jar Hello, World!
5. Dependencias
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.
¿Por qué no probarlo en el próximo proyecto y ver qué tan bien puede funcionar?