Pengantar Leiningen untuk Clojure

1. Perkenalan

Leiningen adalah sistem pembangunan modern untuk proyek Clojure kami. Itu juga ditulis dan dikonfigurasi seluruhnya di Clojure.

Ia bekerja mirip dengan Maven, memberi kita konfigurasi deklaratif yang mendeskripsikan proyek kita, tanpa perlu mengkonfigurasi langkah yang tepat untuk dieksekusi.

Mari masuk dan lihat bagaimana memulai dengan Leiningen untuk membangun proyek Clojure kita.

2. Menginstal Leiningen

Leiningen tersedia sebagai unduhan mandiri, serta dari sejumlah besar pengelola paket untuk sistem yang berbeda.

Unduhan mandiri tersedia untuk Windows serta untuk Linux dan Mac. Dalam semua kasus, unduh file, buat dapat dieksekusi jika perlu, dan kemudian siap digunakan.

Pertama kali dijalankan, skrip akan mengunduh aplikasi Leiningen lainnya, dan selanjutnya akan di-cache mulai dari sini:

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

Setelah Leiningen diinstal, kita dapat menggunakannya untuk membuat proyek baru dengan memanggil lein new .

Ini membuat proyek menggunakan templat tertentu dari serangkaian opsi:

  • app - Digunakan untuk membuat aplikasi
  • default - Digunakan untuk membuat struktur proyek umum, biasanya untuk perpustakaan
  • plugin - Digunakan untuk membuat Plugin Leiningen
  • template - Digunakan untuk membuat template Leiningen baru untuk proyek mendatang

Misalnya, untuk membuat aplikasi baru bernama "proyek-saya", kita akan menjalankan:

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

Ini memberi kami proyek yang berisi:

  • Definisi build - project.clj
  • Direktori sumber - src - termasuk file sumber awal - src / my_project / core.clj
  • Sebuah direktori uji - uji - termasuk file uji awal - test / my_project / core_test.clj
  • Beberapa file dokumentasi tambahan - README.md, LICENSE, CHANGELOG.md dan doc / intro.md

Melihat ke dalam definisi build kita, kita akan melihat bahwa definisi tersebut memberi tahu kita apa yang harus dibangun, tetapi tidak bagaimana cara membangunnya:

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

  • Rincian proyek terdiri dari nama proyek, versi, deskripsi, beranda dan rincian lisensi.
  • Namespace utama yang akan digunakan saat menjalankan aplikasi
  • Daftar ketergantungan
  • Jalur target tempat membangun keluaran
  • Profil untuk membangun uberjar

Perhatikan bahwa namespace sumber utama adalah my-project.core , dan ditemukan di file my_project / core.clj. Di Clojure tidak disarankan untuk menggunakan ruang nama segmen tunggal - yang setara dengan kelas tingkat atas dalam proyek Java.

Selain itu, nama file dibuat dengan garis bawah dan bukan tanda hubung karena JVM memiliki beberapa masalah dengan tanda hubung dalam nama file.

Kode yang dihasilkan cukup sederhana:

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

Juga, perhatikan bahwa Clojure hanyalah ketergantungan di sini. Ini membuatnya mudah untuk menulis proyek menggunakan versi apa pun dari pustaka Clojure yang diinginkan , dan terutama memiliki beberapa versi berbeda yang berjalan pada sistem yang sama.

Jika kita mengubah ketergantungan ini, kita akan mendapatkan versi alternatifnya.

4. Membangun dan Menjalankan

Proyek kita tidak terlalu berharga jika kita tidak dapat membangunnya, menjalankannya dan mengemasnya untuk distribusi, jadi mari kita lihat selanjutnya.

4.1. Meluncurkan REPL

Setelah kami memiliki proyek, kami dapat meluncurkan REPL di dalamnya menggunakan lein repl . Ini akan memberi kita REPL yang memiliki semua yang ada di proyek sudah tersedia di classpath - termasuk semua file proyek serta semua dependensi.

Ini juga memulai kita di namespace utama yang ditentukan untuk proyek kita:

$ 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 mengeksekusi fungsi -main di namespace saat ini, yang kita lihat di atas.

4.2. Menjalankan Aplikasi

Jika kita mengerjakan proyek aplikasi - dibuat menggunakan lein new app - maka kita cukup menjalankan aplikasi dari baris perintah. Ini dilakukan dengan menggunakan lein run :

$ lein run Hello, World!

Ini akan menjalankan fungsi yang disebut -main di namespace yang didefinisikan sebagai : main di file project.clj kita .

4.3. Membangun Perpustakaan

Jika kita sedang mengerjakan proyek perpustakaan - dibuat menggunakan lein new default - maka kita dapat membangun perpustakaan menjadi file JAR untuk dimasukkan dalam proyek lain .

Kami memiliki dua cara untuk mencapai hal ini - menggunakan lein jar atau lein install . Perbedaannya hanya di tempat file JAR keluaran ditempatkan.

Jika kita menggunakan lein jar maka itu akan menempatkannya di direktori target lokal :

$ 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 mencobanya di proyek berikutnya dan lihat seberapa baik itu bisa berhasil.