Вступ до Лейнінгена для Clojure

1. Вступ

Leiningen - це сучасна система побудови наших проектів Clojure. Він також повністю написаний та налаштований на мові Clojure.

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

Давайте заглянемо і побачимо, як розпочати роботу з Лейнінгеном для розробки наших проектів 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
  • шаблон - Використовується для створення нових шаблонів Лейнінгена для майбутніх проектів

Наприклад, для створення нового додатка під назвою «мій проект» ми б виконали:

$ ./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 / 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 "//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}})

Це говорить нам:

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

Зверніть увагу, що основним простором імен джерела є 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 - тоді ми можемо просто запустити програму з командного рядка. Це робиться за допомогою 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

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.

Чому б не спробувати це на наступному проекті і не подивитися, наскільки це може працювати.