Einführung in Leiningen für Clojure

1. Einleitung

Leiningen ist ein modernes Build-System für unsere Clojure-Projekte. Es ist auch vollständig in Clojure geschrieben und konfiguriert.

Es funktioniert ähnlich wie Maven und gibt uns eine deklarative Konfiguration, die unser Projekt beschreibt, ohne dass genaue Schritte für die Ausführung konfiguriert werden müssen.

Lassen Sie uns einsteigen und sehen, wie Sie mit Leiningen beginnen, um unsere Clojure-Projekte zu erstellen.

2. Leiningen installieren

Leiningen ist als eigenständiger Download sowie bei einer Vielzahl von Paketmanagern für verschiedene Systeme erhältlich.

Standalone-Downloads sind sowohl für Windows als auch für Linux und Mac verfügbar. Laden Sie in jedem Fall die Datei herunter, machen Sie sie bei Bedarf ausführbar und sie kann dann verwendet werden.

Wenn das Skript zum ersten Mal ausgeführt wird, wird der Rest der Leiningen-Anwendung heruntergeladen und von diesem Punkt an zwischengespeichert:

$ ./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. Ein neues Projekt erstellen

Sobald Leiningen installiert ist, können wir damit ein neues Projekt erstellen, indem wir lein new aufrufen .

Dadurch wird ein Projekt unter Verwendung einer bestimmten Vorlage aus einer Reihe von Optionen erstellt:

  • App - Wird zum Erstellen einer Anwendung verwendet
  • Standard - Wird verwendet, um eine allgemeine Projektstruktur zu erstellen, normalerweise für Bibliotheken
  • Plugin - Wird zum Erstellen eines Leiningen Plugins verwendet
  • Vorlage - Dient zum Erstellen neuer Leiningen-Vorlagen für zukünftige Projekte

Um beispielsweise eine neue Anwendung mit dem Namen "my-project" zu erstellen, würden wir Folgendes ausführen:

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

Dies gibt uns ein Projekt mit:

  • Eine Build-Definition - project.clj
  • Ein Quellverzeichnis - src - einschließlich einer anfänglichen Quelldatei - src / my_project / core.clj
  • Ein Testverzeichnis - Test - einschließlich einer ersten Testdatei - test / my_project / core_test.clj
  • Einige zusätzliche Dokumentationsdateien - README.md, LICENSE, CHANGELOG.md und doc / intro.md

Wenn wir uns unsere Build-Definition ansehen, werden wir sehen, dass sie uns sagt, was wir bauen sollen, aber nicht, wie wir es bauen sollen:

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

Dies sagt uns:

  • Die Details des Projekts bestehen aus dem Projektnamen, der Version, der Beschreibung, der Homepage und den Lizenzdetails.
  • Der Hauptnamespace, der beim Ausführen der Anwendung verwendet werden soll
  • Die Liste der Abhängigkeiten
  • Der Zielpfad, in den die Ausgabe eingebaut werden soll
  • Ein Profil für den Bau eines Uberjars

Beachten Sie, dass der Hauptquellennamensraum my-project.core ist und sich in der Datei my_project / core.clj befindet. In Clojure wird davon abgeraten, Namespaces mit einem Segment zu verwenden - das entspricht Klassen der obersten Ebene in einem Java-Projekt.

Darüber hinaus werden die Dateinamen mit Unterstrichen anstelle von Bindestrichen generiert, da die JVM einige Probleme mit Bindestrichen in Dateinamen hat.

Der generierte Code ist ziemlich einfach:

(ns my-project.core (:gen-class)) (defn -main "I don't do a whole lot ... yet." [& args] (println "Hello, World!"))

Beachten Sie auch, dass Clojure hier nur eine Abhängigkeit ist. Dies macht es trivial, Projekte mit jeder gewünschten Version der Clojure-Bibliotheken zu schreiben und insbesondere mehrere verschiedene Versionen auf demselben System auszuführen.

Wenn wir diese Abhängigkeit ändern, erhalten wir stattdessen die alternative Version.

4. Bauen und Laufen

Unser Projekt ist nicht viel wert, wenn wir es nicht erstellen, ausführen und für die Verteilung verpacken können. Schauen wir uns das als nächstes an.

4.1. REPL starten

Sobald wir ein Projekt haben, können wir mit lein repl eine REPL darin starten . Dadurch erhalten wir eine REPL, die alles im Projekt bereits im Klassenpfad verfügbar hat - einschließlich aller Projektdateien sowie aller Abhängigkeiten.

Es startet uns auch im definierten Hauptnamespace für unser Projekt:

$ 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

Dies führt die Funktion -main im aktuellen Namespace aus, den wir oben gesehen haben.

4.2. Ausführen der Anwendung

Wenn wir auf einem Anwendungs Projekt arbeiten - erstellt mit lein neuen App - dann können wir einfach die Anwendung von der Befehlszeile ausführen. Dies geschieht mit lein run :

$ lein run Hello, World!

Dadurch wird die Funktion -main in dem Namespace ausgeführt, der in unserer Datei project.clj als : main definiert ist .

4.3. Eine Bibliothek bauen

Wenn wir auf einem Bibliotheksprojekt arbeiten - erstellt mit lein neuen Standard - dann können wir die Bibliothek in eine JAR - Datei für die Aufnahme in anderen Projekten bauen .

Wir haben zwei Möglichkeiten, dies zu erreichen - mit lein jar oder lein install . Der Unterschied besteht einfach darin, wo sich die Ausgabe-JAR-Datei befindet.

Wenn wir verwenden lein jar dann wird es sie in dem lokalen platzieren Zielverzeichnis :

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

Wenn wir lein install verwenden , wird die JAR-Datei erstellt, eine pom.xml- Datei generiert und die beiden dann im lokalen Maven-Repository abgelegt (normalerweise unter .m2 / repository im Home-Verzeichnis des Benutzers).

$ 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. Einen Uberjar bauen

Wenn wir an einem Anwendungsprojekt arbeiten, können wir mit Leiningen ein sogenanntes Uberjar erstellen . Dies ist eine JAR-Datei, die das Projekt selbst und alle Abhängigkeiten enthält und so eingerichtet ist, dass es unverändert ausgeführt werden kann.

$ 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

Die Datei my-project-0.1.0-SNAPSHOT.jar ist eine JAR-Datei, die genau das lokale Projekt enthält, und die Datei my-project-0.1.0-SNAPSHOT-standalone.jar enthält alles, was zum Ausführen der Anwendung erforderlich ist.

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

5. Abhängigkeiten

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.

Probieren Sie es beim nächsten Projekt aus und sehen Sie, wie gut es funktionieren kann.