Leiningen教程中文版

来自Dennis的知识库
2012年12月9日 (日) 14:48Dennis zhuang讨论 | 贡献的版本

跳转到: 导航搜索

目录

Leiningen教程

声明

  • 本Wiki上的任何文字信息均在GNU自由文档许可证1.3或更高版本下发布,如果用于任何商业用途都需经本人同意。任何转载都请注明出处。
  • 本Wiki上的内容来自本人的学习笔记,来源可能包括原创、书籍、网页、链接等,如果侵犯了您的知识产权,请与本人联系,我将及时删除。
  • 我的联系方式 killme2008@gmail.com

Leiningen是什么

Leiningen是一个用于自动化(构建)clojure项目的工具,使你免于心急火燎的窘境。

它处理各种各样项目相关的任务,可以做到:

  • 创建新项目
  • 管理你的项目的依赖关系
  • 运行测试
  • 运行一个REPL(你不需要再关心如何将依赖加入classpath)
  • 编译java源码(如果有的话)
  • 运行项目(如果项目是一个app的话)
  • 为项目产生一个maven风格的pom文件
  • 为部署编译和打包项目
  • 发行类库到maven仓库,例如Clojars
  • 运行clojure编写的自定义的自动化任务(称为leiningen插件)

如果你来自Java世界,Leiningen就是Maven和Ant的无痛结合。如果你是Ruby或者Python世界的人们,则Leiningen 就是那个组合了RubyGems/Bundler/Rake和pip/Fabric等功能的一个单一工具。

本教程覆盖范围

本教程主要覆盖了项目结构、依赖管理、运行测试、REPL以及跟部署相关的话题。

对于那些从来没有接触过Ant或者Maven的JVM初哥,(忠告是):不要惊慌失措。Leiningen也是为了你们而设计。这个教程 会帮助你快速开始,并解释Leiningen如何处理项目自动化以及JVM的依赖管理。

获得帮助

另外,记住Leiningen发行附带了相当全面的帮助;lein help可以获得一个任务列表,同时lein help task可以获取任务的详细信息。 更多的文档,例如readme,示范配置以及本教程也同时提供给您。

Leiningen项目

Leiningen是跟项目打交道。一个项目就是一个包含了一组Clojure(可能也有Java)源码文件的目录,同时附带一些元信息。 元信息存在根目录的一个称为project.clj(默认名称)的文件。project.clj就是你用来告诉Leiningen 项目是什么样的,包括:

  • 项目名称
  • 项目描述
  • 你的项目依赖哪些类库
  • 使用的Clojure版本是什么
  • 哪里找到(项目)源码
  • App的main namespace是什么

等等。

大多数Leiningen的任务仅在项目的上下文里才有意义(译者注:也就是大多数任务不能脱离某个项目使用)。但是有些任务如 lein repl也可以从任何目录全局地工作。

接下来,让我们看看项目是如何创建的。

创建一个项目

我们假设你已经按照README安装了Leiningen。 创建一个项目很简单:

   $ lein new my-stuff

基于'default'模板,生成了一个名为my-stuff的项目:

   $ cd my-stuff
   $ tree
   .
   |-- project.clj
   |-- README.md
   |-- src
   |   `-- my_stuff
   |       `-- core.clj
   `-- test
       `-- my_stuff
           `-- core_test.clj

目录布局

到这一步,我们得到了项目的README,一个包含了代码的src/目录,一个 test/目录,以及一个向Leiningen描述项目的project.clj文件。 src/my_stuff/core.clj文件对应my-stuff.core命名空间。

尽管大多数纯Clojure项目都不需要定制目录布局,但是Leiningen允许你这样做。

文件名-到-命名空间的映射惯例

注意到我们使用my-stuff.core作为命名空间,而不只是my-sutff, 这是因为Clojure不鼓励使用只有一段(single-segment,译者注:意思是没有"."连接的的单一名称)的命名空间。 另外我们也注意到含有波折号(也就是减号)的命名空间,会被映射到波折号被下划线替换的文件,这是因为 JVM加载含有波折号的文件会遇到问题。命名空间的错综复杂是新手感到困扰的一个常见来源,虽然我们已经偏离本 教程的范围,但是你可以在别处阅读到它们

project.clj

一个默认的project.clj文件初始看起来类似这样:

   (defproject my-stuff "0.1.0-SNAPSHOT"
     :description "FIXME: write description"
     :url "http://example.com/FIXME"
     :license {:name "Eclipse Public License"
               :url "http://www.eclipse.org/legal/epl-v10.html"}
     :dependencies org.clojure/clojure "1.4.0")

如果你没有为description填写一个简短的(描述)句子,你的项目 会难在搜索结果里找到,所以请填写一下。也请填写下:url(译注:项目地址URL)。 有时候,你也需要充实下README文件,但是现在让我们略过(这一步)去设置下:dependencies。 注意到,不像其他语言那样,Clojure在这里也只是另一个依赖罢了,你可以很容易地切换Clojure版本。

依赖

概览

Clojure是一门寄宿语言(译注:寄宿于JVM之上),并且Clojure的类库也像其他JVM语言那样作为JAR发布。

JAR(jar文件)基本上仅仅是是一些附加了JVM特有元信息的.zip文件。 它们通常包含有.class文件(JVM字节码),和.clj源码文件, 但是它们也可以包含其他一些东西,例如配置文件、JavaScript文件或者含有静态数据的文本文件。


已发布的JVM类库拥有identifiers(artifact group, artifact id)和versions

Artifact IDs, Groups和Versions

你可以使用web接口搜索Clojars,搜索clj-http的结果页显示为:

   [clj-http "0.5.5"]

有两种不同的途径来设置依赖clj-http类库的最新版本,一种是类似于上面所展示 的Leiningen格式,另一种是Maven格式。我们现在略过Maven的方式,但是你需要为使用) 来自Maven central的Java类库学习一下(maven方式)。你可以直接拷贝Leiningen版本 到project.clj里的:dependencies vector。

在vector里,"clj-http"被称为"artifact id"(译者注:有maven经验的童鞋会很熟悉)。"0.5.5"就是版本号。 一些类库还会有"group id",显示成这样:

   [com.cedarsoft.utils.legacy/hibernate "1.3.4"]

group-id就是斜杠之前的部分。特别是对于Java类库来说,它通常是倒序的域名。而Clojure类库 通常使用相同的group-id和artifact-id(例如clj-http),这种情况下你可以忽略group-id。 如果某个类库从属于一个很大的分组(例如ring-jetty-adapterring项目的一部分), 那么通常所有子项目的group-id保持一样。

Snapshot版本

有时候版本号会以 "-SNAPSHOT"结尾。这意味着这不是一个正式的release版本,而是一个开发阶段构建(版本)。 不鼓励依赖snapshot版本,但是有时候这也是必须的,例如你需要bug fix等,但还没有将它们发布。尽管如此, snapshot版本不被保证停留,所以很重要的一点是非开发阶段的release绝不依赖你无法控制的snapshot版本。 添加一个snapshot依赖到你的项目里,会导致Leiningen每天主动查找该依赖的最新版本(而正常的release版本 会缓存在本地仓库),所以如果你使用了很多snapshot版本,可能会拖慢Leiningen一些。

请注意,有一些类库会让它们的group-id和artifact-id对应到它们jar里提供的命名空间,但是这一点仅是惯例。 并不保证总是能匹配起来,因此,在你写下:require:import语句之前请翻阅 下类库的文档。

仓库

依赖库存储在maven仓库(正式的名称是maven artifact仓库,允许一点模棱两可的话也可以简称为仓库)。

如果你熟悉Perl的CPAN,Python的Cheeseshop(也就是PyPi),Ruby的rubygems.org或者Node.js的NPM,它们其实是一类东西。 Leiningen重用已有的JVM仓库设施。已经有一些很流行的开源仓库。Leiningen默认使用它们中的两个: clojars.orgMaven Central

Clojars是Clojure社区的中心Maven仓库,而Central是更广泛的JVM社区的中心Maven仓库。

你可以添加第三方仓库,通过设置project.clj里的:repositories关键字。查看sample.project.clj示范。

checkout依赖

有时候需要并行地开发两个项目,但是为了让变更生效,总是要运行lein install并重启你的REPL, 这样做非常不方便。Leiningen提供了一种称为checkout依赖(简称checkouts)的解决办法。为了使用它,你需要 在项目根目录创建一个名为checkouts的目录,类似:

   .
   |-- project.clj
   |-- README.md
   |-- checkouts
   |-- src
   |   `-- my_stuff
   |       `-- core.clj
   `-- test
       `-- my_stuff
           `-- core_test.clj

然后,在checkouts目录里,创建你需要的项目的符号链接:

   .
   |-- project.clj
   |-- README.md
   |-- checkouts
   |   `-- superlib2 [link to ~/code/oss/superlib2]
   |   `-- superlib3 [link to ~/code/megacorp/superlib3]
   |-- src
   |   `-- my_stuff
   |       `-- core.clj
   `-- test
       `-- my_stuff
           `-- core_test.clj

checkouts目录里的类库比从仓库拉取的类库优先级更高,但是这不是用来替代 罗列在项目project.clj里的:dependencies,而只是为了方便使用的一种补充。

checkouts特性是非传递性的(译注:A依赖B,B依赖C,那么A将依赖C,这称为依赖传递):换句话说, Leiningen不会查找一个checkout依赖库的checkout依赖。

运行代码

配置够了,让我们看看代码运行。启动一个REPL(读取-求值-打印循环:read-eval-print loop):

   $ lein repl
   nREPL server started on port 40612
   Welcome to REPL-y!
   Clojure 1.4.0
       Exit: Control+D or (exit) or (quit)
   Commands: (user/help)
       Docs: (doc function-name-here)
             (find-doc "part-of-name-here")
     Source: (source function-name-here)
             (user/sourcery function-name-here)
    Javadoc: (javadoc java-object-or-class-here)
   Examples from clojuredocs.org: [clojuredocs or cdoc]
             (user/clojuredocs name-here)
             (user/clojuredocs "ns-here" "name-here")
   user=>

REPL是一种你可以输入任意代码运行在项目上下文里的交互式提示。因为我们已经添加了clj-http依赖到:dependencies里,我们就可以从项目的src/目录的my-stuff.core命名空间里加载它:


   user=> (require 'my-stuff.core)
   nil
   user=> (my-stuff.core/-main)
   Hello, World!
   nil
   user=> (require '[clj-http.client :as http])
   nil
   user=> (def response (http/get "http://leiningen.org"))
   #'user/response
   user=> (keys response)
   (:trace-redirects :status :headers :body)

调用-main显示一起打印了输出("Hello, World!")和返回值nil。

通过doc可以使用内置文档,并且clojuredocs提供更全面的例子,来自ClojureDocs站点:

   user=> (doc reduce)
   -------------------------
   clojure.core/reduce
   ([f coll] [f val coll])
     f should be a function of 2 arguments. If val is not supplied,
     returns the result of applying f to the first 2 items in coll, then
     applying f to that result and the 3rd item, etc. If coll contains no
     items, f must accept no arguments as well, and reduce returns the
     result of calling f with no arguments.  If coll has only 1 item, it
     is returned and f is not called.  If val is supplied, returns the
     result of applying f to val and the first item in coll, then
     applying f to that result and the 2nd item, etc. If coll contains no
     items, returns val and f is not called.
   user=> (user/clojuredocs pprint)
   Loading clojuredocs-client...
   ========== vvv Examples ================
     user=> (def *map* (zipmap
                         [:a :b :c :d :e]
                         (repeat
                           (zipmap [:a :b :c :d :e]
                             (take 5 (range))))))
     #'user/*map*
     user=> *map*
     {:e {:e 4, :d 3, :c 2, :b 1, :a 0}, :d {:e 4, :d 3, :c 2, :b 1, :a 0}, :c {:e 4, :d 3, :c 2, :b 1, :a 0}, :b {:e 4, :d 3, :c 2, :b 1, :a 0}, :a {:e 4, :d 3, :c 2, :b 1, :a 0}}
     user=> (clojure.pprint/pprint *map*)
     {:e {:e 4, :d 3, :c 2, :b 1, :a 0},
      :d {:e 4, :d 3, :c 2, :b 1, :a 0},
      :c {:e 4, :d 3, :c 2, :b 1, :a 0},
      :b {:e 4, :d 3, :c 2, :b 1, :a 0},
      :a {:e 4, :d 3, :c 2, :b 1, :a 0}}
     nil
   ========== ^^^ Examples ================
   1 example found for clojure.pprint/pprint

你甚至能查看函数的源码:

   user=> (source my-stuff.core/-main)
   (defn -main
     "I don't do a whole lot."
     [& args]
     (println "Hello, World!"))
   user=> ; use control+d to exit

如果已经编写了-main函数的代码等待执行,你并不需要交互式地键入代码,执行run任务更简单:

   $ lein run -m my-stuff.core
   Hello, World!

提供替代的-m参数来告诉Leiningen在另一个命名空间里查找-main函数。设置project.clj里默认的:main关键字,可以让你省略-m参数。

对于长时间运行的lein run进程,可能你希望利用高阶的trampoline任务来节省内存,它允许Leiningen的JVM进程在启动项目的JVM之前退出。(译注,可以通过lein help trampoline来查看任务的详细说明):

   $ lein trampoline run -m my-stuff.server 5000

测试

We haven't written any tests yet, but we can run the failing tests included from the project template:

   $ lein test
   lein test my.test.stuff
   FAIL in (a-test) (stuff.clj:7)
   FIXME, I fail.
   expected: (= 0 1)
     actual: (not (= 0 1))
   Ran 1 tests containing 1 assertions.
   1 failures, 0 errors.

Once we fill it in the test suite will become more useful. Sometimes if you've got a large test suite you'll want to run just one or two namespaces at a time; `lein test my.test.stuff` will do that. You also might want to break up your tests using test selectors; see `lein help test` for more details.

Running `lein test` from the command-line is suitable for regression testing, but the slow startup time of the JVM makes it a poor fit for testing styles that require tighter feedback loops. In these cases, either keep a repl open for running the appropriate call to [clojure.test/run-tests](http://clojuredocs.org/clojure_core/1.3.0/clojure.test/run-tests) or look into editor integration such as [clojure-test-mode](https://github.com/technomancy/clojure-mode).

Keep in mind that while keeping a running process around is convenient, it's easy for that process to get into a state that doesn't reflect the files on disk—functions that are loaded and then deleted from the file will remain in memory, making it easy to miss problems arising from missing functions (often referred to as "getting slimed"). Because of this it's advised to do a `lein test` run with a fresh instance periodically in any case, perhaps before you commit.

使用项目

Generally speaking, there are three different goals that are typical of Leiningen projects:

  • An application you can distribute to end-users
  • A server-side application
  • A library for other Clojure projects to consume

For the first, you typically build an uberjar. For libraries, you will want to have them published to a repository like Clojars or a private repository. For server-side applications it varies as described below. Generating a project with `lein new app myapp` will start you out with a few extra defaults suitable for non-library projects.

Uberjar

The simplest thing to do is to distribute an uberjar. This is a single standalone executable jar file most suitable for giving to nontechnical users. For this to work you'll need to specify a namespace as your `:main` in `project.clj`. By this point our `project.clj` file should look like this:

```clj (defproject my-stuff "0.1.0-SNAPSHOT"

 :description "FIXME: write description"
 :url "http://example.com/FIXME"
 :license {:name "Eclipse Public License"
           :url "http://www.eclipse.org/legal/epl-v10.html"}
 :dependencies [[org.clojure/clojure "1.3.0"]
                [org.apache.lucene/lucene-core "3.0.2"]
                [clj-http "0.4.1"]]
 :profiles {:dev {:dependencies midje "1.3.1"}}
 :test-selectors {:default (complement :integration)
                 :integration :integration
                 :all (fn [_] true)}
 :main my.stuff)

```

The namespace you specify will need to contain a `-main` function that will get called when your standalone jar is run. This namespace should have a `(:gen-class)` declaration in the `ns` form at the top. The `-main` function will get passed the command-line arguments. Let's try something simple in `src/my/stuff.clj`:

```clj (ns my.stuff

 (:gen-class))

(defn -main [& args]

 (println "Welcome to my project! These are your args:" args))

```

Now we're ready to generate your uberjar:

   $ lein uberjar
   Compiling my.stuff
   Compilation succeeded.
   Created /home/phil/src/leiningen/my-stuff/target/my-stuff-0.1.0-SNAPSHOT.jar
   Including my-stuff-0.1.0-SNAPSHOT.jar
   Including clj-http-0.4.1.jar
   Including clojure-1.3.0.jar
   Including lucene-core-3.0.2.jar
   Created /home/phil/src/leiningen/my-stuff/target/my-stuff-0.1.0-SNAPSHOT-standalone.jar

This creates a single jar file that contains the contents of all your dependencies. Users can run it with a simple `java` invocation, or on some systems just by double-clicking the jar file.

   $ java -jar my-stuff-0.1.0-standalone.jar Hello world.
   Welcome to my project! These are your args: (Hello world.)

You can run a regular (non-uber) jar with the `java` command-line tool, but that requires constructing the classpath yourself, so it's not a good solution for end-users.

Of course if your users already have Leiningen installed, you can instruct them to use `lein run` as described above.

框架 (Uber)jars

Many Java frameworks expect deployment of a jar file or derived archive sub-format containing a subset of the application's necessary dependencies. The framework expects to provide the missing dependencies itself at run-time. Dependencies which are provided by a framework in this fashion may be specified in the `:provided` profile. Such dependencies will be available during compilation, testing, etc., but won't be included by default by the `uberjar` task or plugin tasks intended to produce stable deployment artifacts.

For example, Hadoop job jars may be just regular (uber)jar files containing all dependencies except the Hadoop libraries themselves:

```clj (project example.hadoop "0.1.0"

 ...
 :profiles {:provided
            {:dependencies
             org.apache.hadoop/hadoop-core "0.20.2-dev"}}
 :main example.hadoop)

```

   $ lein uberjar
   Compiling example.hadoop
   Created /home/xmpl/src/example.hadoop/example.hadoop-0.1.0.jar
   Including example.hadoop-0.1.0.jar
   Including clojure-1.4.0.jar
   Created /home/xmpl/src/example.hadoop/example.hadoop-0.1.0-standalone.jar
   $ hadoop jar example.hadoop-0.1.0-standalone.jar
   12/08/24 08:28:30 INFO util.Util: resolving application jar from found main method on: example.hadoop
   12/08/24 08:28:30 INFO flow.MultiMapReducePlanner: using application jar: /home/xmpl/src/example.hadoop/./example.hadoop-0.1.0-standalone.jar
   ...

Plugins are required to generate framework deployment jar derivatives (such as WAR files) which include additional metadata, but the `:provided` profile provides a general mechanism for handling the framework dependencies.

服务端项目

There are many ways to get your project deployed as a server-side application. Aside from the obvious uberjar approach, simple programs can be packaged up as tarballs with accompanied shell scripts using the [lein-tar plugin](https://github.com/technomancy/lein-tar) and then deployed using [pallet](http://hugoduncan.github.com/pallet/), [chef](http://opscode.com/chef/), or other mechanisms. Web applications may be deployed as uberjars using embedded Jetty with `ring-jetty-adapter` or as .war (web application archive) files created by the [lein-ring plugin](https://github.com/weavejester/lein-ring). For things beyond uberjars, server-side deployments are so varied that they are better-handled using plugins rather than tasks that are built-in to Leiningen itself.

If you do end up involving Leiningen in production via something like `lein trampoline run`, it's very important to ensure you take steps to freeze all the dependencies before deploying, otherwise it could be easy to end up with [unrepeatable deployments](https://github.com/technomancy/leiningen/wiki/Repeatability). Consider including `~/.m2/repository` in your unit of deployment along with your project code. It's recommended to use Leiningen to create a deployable artifact in a continuous integration setting. For example, you could have a [Jenkins](http://jenkins-ci.org) CI server run your project's full test suite, and if it passes, upload a tarball to S3. Then deployment is just a matter of pulling down and extracting the known-good tarball on your production servers.

Also remember that the `run` task defaults to including the `user`, `dev`, and `default` profiles, which are not suitable for production. Using `lein trampoline with-profile production run -m myapp.main` is recommended. By default the production profile is empty, but if your deployment includes the `~/.m2/repository` directory from the CI run that generated the tarball, then you should add its path as `:local-repo` along with `:offline? true` to the `:production` profile. Staying offline prevents the deployed project from diverging at all from the version that was tested in the CI environment.

发行类库

If your project is a library and you would like others to be able to use it as a dependency in their projects, you will need to get it into a public repository. While it's possible to [maintain your own private repository](https://github.com/technomancy/leiningen/blob/rc/doc/DEPLOY.md) or get it into [Central](http://search.maven.org), the easiest way is to publish it at [Clojars](http://clojars.org). Once you have [created an account](https://clojars.org/register) there, publishing is easy:

   $ lein deploy clojars
   Created ~/src/my-stuff/target/my-stuff-0.1.0-SNAPSHOT.jar
   Wrote ~/src/my-stuff/pom.xml
   No credentials found for clojars
   See `lein help deploying` for how to configure credentials.
   Username: me
   Password: 
   Retrieving my-stuff/my-stuff/0.1.0-SNAPSHOT/maven-metadata.xml (1k)
       from https://clojars.org/repo/
   Sending my-stuff/my-stuff/0.1.0-SNAPSHOT/my-stuff-0.1.0-20120531.032047-14.jar (5k)
       to https://clojars.org/repo/
   Sending my-stuff/my-stuff/0.1.0-SNAPSHOT/my-stuff-0.1.0-20120531.032047-14.pom (3k)
       to https://clojars.org/repo/
   Retrieving my-stuff/my-stuff/maven-metadata.xml (1k)
       from https://clojars.org/repo/
   Sending my-stuff/my-stuff/0.1.0-SNAPSHOT/maven-metadata.xml (1k)
       to https://clojars.org/repo/
   Sending my-stuff/my-stuff/maven-metadata.xml (1k)
       to https://clojars.org/repo/

Once that succeeds it will be available as a package on which other projects may depend. For instructions on storing your credentials so they don't have to be re-entered every time, see `lein help deploying`. When deploying a release that's not a snapshot, Leiningen will attempt to sign it using [GPG](http://gnupg.org) to prove your authorship of the release. See the [deploy guide](https://github.com/technomancy/leiningen/blob/rc/doc/DEPLOY.md). for details of how to set that up. The deploy guide includes instructions for deploying to other repositories as well.

== That's It!

Now go start coding your next project! 现在可以开始编码你的下一个项目了!

个人工具
名字空间

变换
操作
导航
工具箱