Перейти к основному содержимому

Введение в Leiningen для Clojure

· 9 мин. чтения

1. Введение

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

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

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

2. Установка Лейнингена

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 .

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

  • app — используется для создания приложения
  • по умолчанию — используется для создания общей структуры проекта, обычно для библиотек.
  • плагин — используется для создания плагина 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}})

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

  • Подробная информация о проекте, состоящая из названия проекта, версии, описания, домашней страницы и сведений о лицензии.
  • Основное пространство имен, используемое при выполнении приложения
  • Список зависимостей
  • Целевой путь для создания вывода в
  • Профиль для сборки уберджара

Обратите внимание, что основным исходным пространством имен является my-project.core , и оно находится в файле my_project/core.clj. В Clojure не рекомендуется использовать односегментные пространства имен — эквивалент классов верхнего уровня в проекте Java.

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

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

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

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

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

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

4. Строительство и запуск

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

4.1. Запуск REPL

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

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

$ 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

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

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

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

$ lein run
Hello, World!

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

4.3. Создание библиотеки

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

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

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

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

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

$ 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. Создание уберджара

Если мы работаем над проектом приложения, Leiningen дает нам возможность построить то, что называется uberjar . Это 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

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

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

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

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

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

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

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

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

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

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

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

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

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

И тогда запуск будет работать точно так, как ожидалось:

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

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

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

Например, мы можем найти наши библиотеки 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.
.....

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

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

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

Наш сгенерированный проект содержит тестовый код в каталоге test , а также исходный код в каталоге src . Он также включает один неудачный тест по умолчанию, который находится в 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))))

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

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

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

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

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

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

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

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

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

$ 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. Резюме

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

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