Pengenalan Leiningen untuk Clojure

1. Pengenalan

Leiningen adalah sistem binaan moden untuk projek Clojure kami. Ia juga ditulis dan dikonfigurasi sepenuhnya dalam Clojure.

Ini berfungsi sama dengan Maven, memberikan konfigurasi deklaratif yang menggambarkan projek kami, tanpa perlu mengkonfigurasi langkah-langkah yang tepat untuk dilaksanakan.

Mari masuk dan lihat bagaimana untuk memulakan Leiningen untuk membina projek Clojure kami.

2. Memasang Leiningen

Leiningen tersedia sebagai muat turun mandiri, serta dari sebilangan besar pengurus pakej untuk sistem yang berbeza.

Muat turun mandiri tersedia untuk Windows dan juga Linux dan Mac. Dalam semua keadaan, muat turun fail, buat boleh dilaksanakan jika perlu, dan kemudian ia siap digunakan.

Kali pertama skrip dijalankan, ia akan memuat turun aplikasi Leiningen yang selebihnya, dan kemudian ini akan dicache dari masa ini:

$ ./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. Membuat Projek Baru

Setelah Leiningen dipasang, kita dapat menggunakannya untuk membuat projek baru dengan menggunakan lein baru .

Ini membuat projek menggunakan templat tertentu dari sekumpulan pilihan:

  • aplikasi - Digunakan untuk membuat aplikasi
  • lalai - Digunakan untuk membuat struktur projek umum, biasanya untuk perpustakaan
  • plugin - Digunakan untuk membuat Plugin Leiningen
  • template - Digunakan untuk membuat templat Leiningen baru untuk projek masa depan

Sebagai contoh, untuk membuat aplikasi baru yang disebut "my-project" kami akan melaksanakan:

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

Ini memberi kita projek yang mengandungi:

  • Definisi binaan - project.clj
  • Direktori sumber - src - termasuk fail sumber awal - src / my_project / core.clj
  • Direktori ujian - ujian - termasuk fail ujian awal - test / my_project / core_test.clj
  • Beberapa fail dokumentasi tambahan - README.md, LICENSE, CHANGELOG.md dan doc / intro.md

Melihat ke dalam definisi binaan kami, kami akan melihat bahawa ia memberitahu kami apa yang harus dibina, tetapi bukan bagaimana membuatnya:

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

Ini memberitahu kita:

  • Perincian projek yang terdiri daripada nama projek, versi, keterangan, halaman utama dan perincian lesen.
  • Ruang nama utama untuk digunakan semasa melaksanakan aplikasi
  • Senarai kebergantungan
  • Laluan sasaran untuk membina output ke
  • Profil untuk membina uberjar

Perhatikan bahawa ruang nama sumber utama adalah my-project.core , dan terdapat dalam fail my_project / core.clj. Tidak digalakkan di Clojure untuk menggunakan ruang nama segmen tunggal - kelas setara dengan kelas atasan dalam projek Java.

Selain itu, nama fail dihasilkan dengan garis bawah dan bukan tanda hubung kerana JVM mempunyai beberapa masalah dengan tanda hubung dalam nama fail.

Kod yang dihasilkan cukup mudah:

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

Juga, perhatikan bahawa Clojure hanyalah pergantungan di sini. Ini menjadikannya sepele untuk menulis projek menggunakan versi apa pun dari perpustakaan Clojure yang diinginkan , dan terutamanya untuk mempunyai beberapa versi yang berbeza berjalan pada sistem yang sama.

Sekiranya kita mengubah kebergantungan ini, maka kita akan mendapat versi alternatif.

4. Membina dan Berlari

Projek kami tidak bernilai jika kami tidak dapat membinanya, menjalankannya dan membungkusnya untuk diedarkan, jadi mari kita lihat seterusnya.

4.1. Melancarkan REPL

Sebaik sahaja kita mempunyai projek, kita dapat melancarkan REPL di dalamnya menggunakan lein repl . Ini akan memberi kita REPL yang mempunyai semua perkara dalam projek yang sudah ada di classpath - termasuk semua fail projek dan juga semua pergantungan.

Ini juga memulakan kita di ruang nama utama yang ditentukan untuk projek kami:

$ 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

Ini melaksanakan fungsi -main dalam ruang nama semasa, yang kita lihat di atas.

4.2. Menjalankan Aplikasi

Sekiranya kita mengusahakan projek aplikasi - dibuat menggunakan aplikasi baru lein - maka kita hanya dapat menjalankan aplikasi dari baris perintah. Ini dilakukan dengan menggunakan lein run :

$ lein run Hello, World!

Ini akan melaksanakan fungsi yang disebut -main di ruang nama yang ditakrifkan sebagai : utama dalam fail project.clj kami .

4.3. Membina Perpustakaan

Sekiranya kita mengusahakan projek perpustakaan - dibuat menggunakan lein new default - maka kita dapat membina perpustakaan menjadi fail JAR untuk dimasukkan ke dalam projek lain .

Kami mempunyai dua cara untuk mencapainya - menggunakan lein jar atau lein install . Perbezaannya hanyalah di mana fail JAR output diletakkan.

Sekiranya kita menggunakan balang lein maka ia akan meletakkannya di direktori sasaran tempatan :

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

Mengapa tidak mencubanya pada projek seterusnya dan lihat sejauh mana ia boleh berfungsi.