1. 简介

Leiningen 是一个现代化的 Clojure 项目构建工具,它完全用 Clojure 编写和配置。

它的工作方式与 Maven 类似,提供了一种声明式的配置方式来描述项目结构,而不需要手动指定每一步执行细节。

接下来我们将快速上手 Leiningen,看看如何用它来构建 Clojure 项目。

2. 安装 Leiningen

Leiningen 提供了独立可执行版本,同时也支持多种操作系统的包管理器安装方式

你可以从以下链接下载适用于不同平台的独立脚本:

✅ 下载完成后赋予执行权限,然后就可以使用了。

首次运行脚本时,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. 创建新项目

安装完成后,可以通过 lein new 命令创建新项目。该命令基于模板生成项目结构:

  • app – 用于创建应用程序
  • default – 用于创建通用项目结构,通常用于库开发
  • plugin – 用于创建 Leiningen 插件
  • template – 用于创建新的 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

构建定义文件 project.clj 内容如下:

(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 版本,甚至在同一个系统中运行多个不同版本的 Clojure 项目。

4. 构建与运行

项目创建后,我们当然要能构建、运行、打包分发,下面来看具体操作。

4.1. 启动 REPL

进入项目目录后,使用 lein repl 启动 REPL 环境。此时所有项目源码和依赖都会自动加入 classpath,并默认进入主命名空间:

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

📌 它会自动执行 project.clj 中定义的 :main 命名空间下的 -main 函数。

4.3. 构建库(Library)

对于使用 lein new default 创建的库项目,可以使用以下命令构建 JAR 文件:

  • lein jar:将 JAR 文件输出到本地 target 目录
  • lein install:构建 JAR 并生成 pom.xml,然后安装到本地 Maven 仓库
$ lein jar
Created /Users/user/source/me/my-library/target/my-library-0.1.0-SNAPSHOT.jar
$ 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. 构建 Uberjar

对于应用程序项目,可以使用 lein uberjar 构建一个包含所有依赖的可执行 JAR(即 Uberjar):

$ 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

📌 其中 -standalone.jar 包含了所有依赖,可以直接运行:

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

5. 依赖管理

虽然我们可以自己实现所有功能,但复用社区已有库更高效。Leiningen 提供了强大的依赖管理能力。

5.1. 添加依赖

project.clj 文件中添加依赖项即可。依赖以向量形式表示,包含库名和版本:

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

📌 添加后,Leiningen 会自动下载依赖并加入 classpath。

启动 REPL 后即可使用:

$ 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
...
my-project.core=> (require '(clj-json [core :as json]))
nil
my-project.core=> (json/generate-string {"foo" "bar"})
"{\"foo\":\"bar\"}"

也可以在项目源码中使用:

(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. 查找依赖

使用 lein search 可以搜索可用的依赖库:

$ lein search json
Searching central ...
[com.jwebmp/json "0.63.0.60"]
[com.ufoscout.coreutils/json "3.7.4"]
...
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/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))))

📌 该测试故意失败(0 ≠ 1)。

运行测试:

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

修改为正确断言后:

(deftest a-test
  (testing "This should pass."
    (is (= 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
Ran 1 tests containing 1 assertions.
0 failures, 0 errors.

$ lein test my-project.unknown
Ran 0 tests containing 0 assertions.
0 failures, 0 errors.

7. 总结

本文介绍了 Leiningen 的基本使用方式,包括项目创建、依赖管理、构建、运行和测试。它适用于 Clojure 应用程序和库的开发。

不妨在下一个 Clojure 项目中试试 Leiningen,体验它的便捷与高效。


原始标题:Introduction to Leiningen for Clojure | Baeldung