«1. Введение

Leiningen — это современная система сборки для наших проектов Clojure. Он также полностью написан и настроен на Clojure.

Он работает аналогично Maven, предоставляя нам декларативную конфигурацию, описывающую наш проект, без необходимости настраивать точные шаги для выполнения.

Давайте начнем и посмотрим, как начать работу с Leiningen для создания наших проектов Clojure.

2. Установка Leiningen

Leiningen доступен для отдельной загрузки, а также из большого количества менеджеров пакетов для различных систем.

Отдельные загрузки доступны для Windows, а также для Linux и Mac. В любом случае загрузите файл, при необходимости сделайте его исполняемым, после чего он будет готов к использованию.

При первом запуске сценария он загрузит остальную часть приложения Leiningen, а затем с этого момента будет кэшироваться:

$ ./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. Создание нового проекта

После установки Leiningen , мы можем использовать его для создания нового проекта, вызвав lein new.

Это создает проект с использованием определенного шаблона из набора опций:

    приложение — используется для создания приложения по умолчанию — используется для создания общей структуры проекта, обычно для плагина библиотек — используется для создать шаблон плагина Leiningen — используется для создания новых шаблонов Leiningen для будущих проектов

Например, чтобы создать новое приложение с именем «my-project», мы должны выполнить:

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

Это дает нам проект содержащий:

    Определение сборки — project.clj Исходный каталог — src — включая начальный исходный файл — src/my_project/core.clj Тестовый каталог — test — включая начальный тестовый файл — test/my_project/core_test.clj Некоторые дополнительные файлы документации — README.md, LICENSE, CHANGELOG.md и doc/intro.md

Заглянув внутрь нашего определения сборки, мы увидим, что он говорит нам, что строить, но не как это строить:

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

Это говорит нам:

    Подробная информация о проекте, состоящая из названия проекта, версии, описания, домашней страницы и лицензии смысловые детали. Основное пространство имен, используемое при выполнении приложения. Список зависимостей. Целевой путь для создания выходных данных в профиль для создания uberjar. /core.clj. В Clojure не рекомендуется использовать односегментные пространства имен — эквивалент классов верхнего уровня в проекте Java.

Кроме того, имена файлов генерируются с символами подчеркивания вместо дефисов, потому что JVM имеет некоторые проблемы с дефисами в именах файлов.

Сгенерированный код довольно прост:

Также обратите внимание, что Clojure здесь просто зависимость. Это упрощает написание проектов с использованием любой желаемой версии библиотек Clojure, особенно если в одной системе работает несколько разных версий.

(ns my-project.core
  (:gen-class))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (println "Hello, World!"))

Если мы изменим эту зависимость, вместо нее мы получим альтернативную версию.

4. Сборка и запуск

Наш проект ничего не стоит, если мы не можем его собрать, запустить и упаковать для распространения, так что давайте посмотрим на это дальше.

4.1. Запуск REPL

Когда у нас есть проект, мы можем запустить внутри него REPL, используя lein repl. Это даст нам REPL, в котором все в проекте уже доступно в пути к классам, включая все файлы проекта, а также все зависимости.

Это также запускает нас в определенном основном пространстве имен для нашего проекта:

Это выполняет функцию -main в текущем пространстве имен, которое мы видели выше.

$ 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

4.2. Запуск приложения

Если мы работаем над проектом приложения, созданным с помощью lein new app, то мы можем просто запустить приложение из командной строки. Это делается с помощью lein run:

Это запустит функцию с именем -main в пространстве имен, определенном как :main в нашем файле project.clj.

$ lein run
Hello, World!

4.3. Построение библиотеки

Если мы работаем над проектом библиотеки, созданным с использованием lein new default, то мы можем собрать библиотеку в файл JAR для включения в другие проекты.

«У нас есть два способа добиться этого — с помощью lein jar или lein install. Разница заключается просто в том, где находится выходной JAR-файл.

Если мы используем lein jar, он поместит его в локальный целевой каталог:

Если мы используем lein install, то он создаст файл JAR, сгенерирует файл pom.xml, а затем поместит два в локальный репозиторий Maven (обычно в .m2/repository в домашнем каталоге пользователя)

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

4.4. Создание Uberjar

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

Если мы работаем над проектом приложения, Leiningen дает нам возможность построить то, что называется uberjar. Это JAR-файл, содержащий сам проект и все зависимости, и настроенный так, чтобы его можно было запускать как есть.

Файл my-project-0.1.0-SNAPSHOT.jar представляет собой JAR-файл, содержащий именно локальный проект, а файл my-project-0.1.0-SNAPSHOT-standalone.jar содержит все необходимое для запуска приложение.

$ 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

5. Зависимости

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

Хотя мы можем сами написать все необходимое для нашего проекта, гораздо лучше повторно использовать работу, которую другие уже сделали от нашего имени. Мы можем сделать это, заставив наш проект зависеть от этих других библиотек.

5.1. Добавление зависимостей в наш проект

Чтобы добавить зависимости в наш проект, нам нужно правильно добавить их в наш файл project.clj.

Зависимости представлены в виде вектора, состоящего из имени и версии рассматриваемой зависимости. Мы уже видели, что сам Clojure добавляется как зависимость, записанная в виде [org.clojure/clojure «1.9.0»].

Если мы хотим добавить другие зависимости, мы можем сделать это, добавив их в вектор рядом с ключевым словом :dependencies. Например, если мы хотим зависеть от clj-json, мы должны обновить файл:

После этого, если мы запустим наш REPL — или любой другой способ сборки или запуска нашего проекта — тогда Leiningen гарантирует, что зависимости будут загружены и доступны в пути к классам:

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

Мы также можем использовать их внутри нашего проекта. Например, мы могли бы обновить сгенерированный файл src/my_project/core.clj следующим образом:

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

И тогда он запустится так, как ожидалось:

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

5.2. Поиск зависимостей

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

Часто бывает сложно найти зависимости, которые мы хотим использовать в нашем проекте. Leiningen поставляется со встроенной функцией поиска, чтобы упростить эту задачу. Это делается с помощью лейн-поиска.

Например, мы можем найти наши библиотеки JSON:

Это ищет все репозитории, с которыми работает наш проект — в данном случае Maven Central и Clojars. Затем он возвращает точную строку для помещения в наш файл project.clj и, если доступно, описание библиотеки.

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

6. Тестирование нашего проекта

Clojure имеет встроенную поддержку модульного тестирования нашего приложения, и Leiningen может использовать это для наших проектов.

Наш сгенерированный проект содержит тестовый код в каталоге test, а также исходный код в каталоге src. Он также включает один неудачный тест по умолчанию, который находится в test/my_project/core-test.clj:

Это импортирует пространство имен my-project.core из нашего проекта и пространство имен clojure.test. из основного языка Clojure. Затем мы определяем тест с вызовами deftest и testing.

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

Мы сразу видим названия тестов и тот факт, что они намеренно написаны как провальные — в них утверждается, что 0 == 1.

Давайте запустим это с помощью команды lein test и сразу увидим тесты работает и терпит неудачу:

Если вместо этого мы исправим тест, изменив его, чтобы утверждать, что 1 == 1 вместо этого, то вместо этого мы получим проходящее сообщение:

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

Это гораздо более краткое вывод, показывая только то, что нам нужно знать. Это означает, что когда есть неудачи, они сразу выделяются.

$ lein test
lein test my-project.core-test

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.

Если мы хотим, мы также можем запустить определенное подмножество тестов. Командная строка позволяет указать пространство имен, и выполняются только тесты в этом пространстве имен:

7. Резюме

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

«В этой статье показано, как начать работу с инструментом сборки Leiningen и как использовать его для управления нашими проектами на основе Clojure — как исполняемыми приложениями, так и общими библиотеками.

Почему бы не попробовать это в следующем проекте и посмотреть, насколько хорошо это может работать.

«