Introdução a Leiningen para Clojure
1. Introdução
Leiningen is a modern build system for our Clojure projects. Também foi escrito e configurado inteiramente em Clojure.
Funciona de maneira semelhante ao Maven, fornecendo uma configuração declarativa que descreve nosso projeto, sem a necessidade de configurar as etapas exatas a serem executadas.
Vamos pular e ver como começar com Leiningen para construir nossos projetos Clojure.
2. Instalando Leiningen
Leiningen is available as a standalone download,, bem como de um grande número depackage managers para sistemas diferentes.
Downloads independentes estão disponíveis paraWindows, bem como paraLinux and Mac. Em todos os casos, baixe o arquivo, torne-o executável se necessário e, então, ele estará pronto para uso.
Na primeira vez em que o script for executado, ele fará o download do restante do aplicativo Leiningen e, em seguida, ele será armazenado em cache a partir deste ponto:
$ ./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. Criando um novo projeto
Assim que o Leiningen estiver instalado, podemos usá-lo para criar um novo projeto invocandolein new.
Isso cria um projeto usando um modelo específico de um conjunto de opções:
-
app - usado para criar um aplicativo
-
default - usado para criar uma estrutura geral do projeto, normalmente para bibliotecas
-
plugin - usado para criar um plug-in Leiningen
-
template - usado para criar novos modelos Leiningen para projetos futuros
Por exemplo, para criar um novo aplicativo chamado "my-project", executaríamos:
$ ./lein new app my-project
Generating a project called my-project based on the 'app' template.
Isso nos dá um projeto que contém:
-
Uma definição de compilação -project.clj
-
Um diretório de origem -src – incluindo um arquivo de origem inicial -src/my_project/core.clj
-
Um diretório de teste -test - incluindo um arquivo de teste inicial -test/my_project/core_test.clj
-
Alguns arquivos de documentação adicionais -README.md, LICENSE, CHANGELOG.md edoc/intro.md
Olhando dentro de nossa definição de construção, veremos que ela nos diz o que construir, mas não como construir:
(defproject my-project "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://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}})
Isso nos diz:
-
Os detalhes do projeto que consistem no nome do projeto, versão, descrição, página inicial e detalhes da licença.
-
O principal espaço para nome a ser usado ao executar o aplicativo
-
A lista de dependências
-
O caminho de destino para construir a saída
-
Um perfil para construir um uberjar
Observe que o namespace de origem principal émy-project.core, e é encontrado no arquivomy_project/core.clj. . Não é recomendado no Clojure usar namespaces de segmento único - o equivalente a classes de nível superior em um projeto Java.
Além disso, os nomes de arquivos são gerados com sublinhados em vez de hífens porque a JVM tem alguns problemas com hífens nos nomes de arquivos.
O código gerado é bem simples:
(ns my-project.core
(:gen-class))
(defn -main
"I don't do a whole lot ... yet."
[& args]
(println "Hello, World!"))
Also, notice that Clojure is just a dependency here. This makes it trivial to write projects using whatever version of the Clojure libraries are desired, e especialmente para ter várias versões diferentes em execução no mesmo sistema.
Se mudarmos essa dependência, obteremos a versão alternativa.
4. Construindo e funcionando
Nosso projeto não vale muito se não pudermos construí-lo, executá-lo e empacotá-lo para distribuição, então vamos dar uma olhada nisso a seguir.
4.1. Lançando um REPL
Once we have a project, we can launch a REPL inside of it using lein repl. Isso nos dará um REPL que possui tudo no projeto já disponível no caminho de classe - incluindo todos os arquivos do projeto e todas as dependências.
Também nos inicia no namespace principal definido para o nosso projeto:
$ 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
Isso executa a função-main no namespace atual, que vimos acima.
4.2. Executando o aplicativo
Se estivermos trabalhando em um projeto de aplicativo - criado usandolein new app - entãowe can simply run the application from the command line. This is done using lein run:
$ lein run
Hello, World!
Isso executará a função chamada-main no namespace definido como:main em nosso arquivoproject.clj.
4.3. Construindo uma Biblioteca
Se estivermos trabalhando em um projeto de biblioteca - criado usandolein new default - entãowe can build the library into a JAR file for inclusion in other projects.
Temos duas maneiras de fazer isso - usandolein jar oulein install. A diferença é simplesmente onde o arquivo JAR de saída é colocado.
If we use lein jar then it will place it in the local target directory:
$ 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 (normalmente em.m2/repository no diretório inicial dos usuários)
$ 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. Construindo um Uberjar
Se estivermos trabalhando em um projeto de aplicativo,Leiningen gives us the ability to build what is called an uberjar. Este é um arquivo JAR que contém o próprio projeto e todas as dependências e configurado para permitir que ele seja executado como está.
$ 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
O arquivomy-project-0.1.0-SNAPSHOT.jar é um arquivo JAR contendo exatamente o projeto local, e o arquivomy-project-0.1.0-SNAPSHOT-standalone.jar contém tudo o que é necessário para executar o aplicativo.
$ java -jar target/uberjar/my-project-0.1.0-SNAPSHOT-standalone.jar
Hello, World!
5. Dependências
Embora possamos escrever tudo o que é necessário para o nosso projeto, geralmente é significativamente melhor reutilizar o trabalho que outros já fizeram em nosso nome. Podemos fazer isso fazendo com que nosso projeto dependa dessas outras bibliotecas.
5.1. Adicionando Dependências ao nosso Projeto
Para adicionar dependências ao nosso projeto, precisamos adicioná-las corretamente ao nosso arquivoproject.clj.
As dependências são representadas como um vetor que consiste no nome e na versão da dependência em questão. Já vimos que o próprio Clojure é adicionado como uma dependência, escrito na forma[org.clojure/clojure “1.9.0”].
Se quisermos adicionar outras dependências, podemos fazê-lo adicionando-as ao vetor próximo à palavra-chave:dependencies. Por exemplo, se quisermos depender declj-json, devemos atualizar o arquivo:
: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=>
Também podemos usá-los de dentro do nosso projeto. Por exemplo, poderíamos atualizar o arquivosrc/my_project/core.clj gerado da seguinte maneira:
(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"})))
E, em seguida, executá-lo fará exatamente o esperado:
$ lein run
{"foo":"bar"}
5.2. Encontrando Dependências
Muitas vezes, pode ser difícil encontrar as dependências que queremos usar em nosso projeto. Leiningen comes with a search functionality built in para tornar isso mais fácil. Isso é feito usandolein search.
Por exemplo, podemos encontrar nossas bibliotecas JSON:
$ 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 - neste caso, Maven Central eClojars. Em seguida, ele retorna a string exata para colocar em nosso arquivoproject.clj e, se disponível, a descrição da biblioteca.
6. Testando nosso projeto
Clojure tem suporte embutido para teste de unidade de nosso aplicativo e Leiningen pode aproveitar isso para nossos projetos.
Nosso projeto gerado contém o código de teste no diretóriotest, junto com o código-fonte no diretóriosrc. Também inclui um único teste com falha por padrão - encontrado emtest/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))))
Isso importa o namespacemy-project.core do nosso projeto e o namespaceclojure.test da linguagem Clojure principal. Em seguida, definimos um teste com as escalasdeftestetesting .
Podemos ver imediatamente os nomes do teste e o fato de que ele foi deliberadamente escrito para falhar - ele afirma que0 == 1.
Let’s run this using the lein test command, e ver imediatamente os testes em execução e com falha:
$ 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.
Se, em vez disso, corrigirmos o teste, alterando-o para afirmar que1 == 1, então receberemos uma mensagem de passagem:
$ lein test
lein test my-project.core-test
Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
Este é um resultado muito mais sucinto, mostrando apenas o que precisamos saber. Isso significa quewhen there are failures, they immediately stand out.
Se quisermos, também podemos executar um subconjunto específico dos testes. A linha de comando permite que um espaço para nome seja fornecido e apenas os testes nesse espaço para nome são executados:
$ 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. Sumário
Este artigo mostrou como começar com a ferramenta de construção Leiningen e como usá-la para gerenciar nossos projetos baseados em Clojure - aplicativos executáveis e bibliotecas compartilhadas.
Por que não experimentá-lo no próximo projeto e ver como ele pode funcionar?