in which we coin a term which is the opposite of deprecate

«

»

Earlier this month I published the results of a survey I had posted for Leiningen users. I got a lot of great data, but I especially appreciated the free-form "other comments" section that let people just ramble. I was happy to see that many of the suggestions have already been implemented in the ongoing work on Leiningen 2.

I still think lein-multi should be rolled into core so people can be encouraged to test cross-version.

Profiles

This brings me to what is probably the biggest feature in Leiningen 2.0: profiles. In Leiningen 1 we had some special cases to segregate out "dev" mode from the rest so you would only have certain dependencies or directories available during development. This is useful to have, but the implementation was pretty ad-hoc and riddled with special cases. During some discussion with the Cake developers we talked about whether that could be generalized, which turned into the idea of profiles.

So now rather than a handful of project configuration keys that are only active during development time, we have a :dev profile that's active by default where you can keep your additional test-only :dependencies and :resource-paths. You can also keep all your :plugins that you want active across all projects in the :user profile rather than using lein plugin install. That way there is only ever one plugin list active at a time, meaning it can be de-duplicated, avoiding messy conflicts.

But you can also create profiles for other situations:

(defproject clj-http "0.3.3-SNAPSHOT"
  :description "A Clojure HTTP library"
  :url "https://github.com/dakrone/clj-http/"
  :min-lein-version "2.0.0"
  :dependencies [[org.clojure/clojure "1.3.0"] ; elided below...
                 [org.apache.httpcomponents/httpclient "4.1.2"]
                 [cheshire "2.2.0"]]
  :profiles {:dev {:dependencies [[ring/ring-jetty-adapter "1.0.2"]
                                  [ring/ring-devel "1.0.2"]]}
             :1.2 {:dependencies [[org.clojure/clojure "1.2.1"]]}
             :1.4 {:dependencies [[org.clojure/clojure "1.4.0-beta1"]]}}
  :aliases {"all" ["with-profile" "dev,1.2:dev:dev,1.4"]})

Here you can see the :dev profile with some handy dependencies used for testing, but there are also profiles for :1.2 and :1.4 that can be used like lein multi. The with-profile task is used to apply alternate profiles to a given task run:

$ lein with-profile dev,1.2 test
Performing task 'test' with profile(s): 'dev,1.2'

Testing clj-http.test.client

Testing clj-http.test.cookies

Testing clj-http.test.core

Ran 47 tests containing 175 assertions.
0 failures, 0 errors.

You can see that commas allow multiple profiles to be specified at once. You can also use colons to chain together profile sets sequentially:

$ lein with-profile dev:dev,1.2:dev,1.4 test
Performing task 'test' with profile(s): 'dev'
[...]
Performing task 'test' with profile(s): 'dev,1.2'
[...]
Performing task 'test' with profile(s): 'dev,1.4'
[...]

Of course, since with-profile is a higher-order task, it can accept any other task as an argument, not just test. So you could use it for deploying to different environments or any place where you'd want an alternate set of project configuration values.

Aliases

You'll also notice that the :aliases entry in the defproject above maps a string to a vector. This is a new feature about which I am unreasonably pleased. You've always been able to add aliases for Leiningen tasks; this is how lein halp has worked. But now you can actually alias a string to a partial application of a task:

:aliases {"all" ["with-profile" "dev,1.2:dev:dev,1.4"]}

This means that all test translates into calling with-profile dev,1.2:dev:dev,1.4 test. But partially-applied aliases have other uses as well:

:aliases {"reflect" ["assoc" ":warn-on-reflection" "true" "compile"]}

This allows you to invoke lein reflect to get a list of all your reflection warnings. Note that in this case assoc refers to the task that comes from the lein-assoc plugin, not the clojure.core/assoc function. Each string in the alias vector is interpreted the same way as if it were a direct command-line argument to the lein script, which is why strings must be used. In this case reading it into keywords and booleans happens inside the assoc task.

And that's one of the neat things about having tasks as functions.

Repl

The repl task has been rewritten from the ground-up by Colin Jones. The new version supports a JVM-native readline implementation as well as thorough completion and nREPL support.

Preview

By this point I'm sure you're thinking to yourself, "Gosh, that sounds super; I wish I could use it now!" In fact, Leiningen 2 is already pretty usable and stable if you don't mind running from git. You need a copy of Leiningen 1.x around to bootstrap it, but running lein install inside the leiningen-core directory should get you to the point where you can symlink bin/lein to somewhere on your path and have it work. I recommend linking it as lein2 for the time being since you'll probably still need an installation of 1.x easily accessible.

Of course, being a major version increment it's got some backwards-incompatibilities. Fresh from witnessing the very bumpy transition to Clojure 1.3, I'd rather avoid that for Leiningen 2, so taking a cue from golang's gofix tool, I've released the lein-precate plugin.

Precate, obviously, is the opposite of deprecate. The idea is that you could run it on your project and have it spit out a new project.clj which would be compatible with Leiningen 2. It's not perfect, but it should provide you with a starting point for your transition:

$ lein plugin install lein-precate 0.3.0
$ cat project.clj # the original 1.x-compatible version:
(defproject clojure-http-client "1.1.1-SNAPSHOT"
  :description "An HTTP client for Clojure."
  :source-path "src/clj"
  :extra-classpath-dirs ["dumb-stuff"]
  :dev-dependencies [[swank-clojure "1.3.4"]]
  :dependencies [[org.clojure/clojure "1.2.1"]
                 [org.clojure/clojure-contrib "1.2.0"]])

$ lein precate # let's see how that would look for Leiningen 2
(defproject clojure-http-client "1.1.1-SNAPSHOT"
  :description "An HTTP client for Clojure."
  :source-paths ["src/clj"]
  :dependencies {org.clojure/clojure "1.2.1", 
                 org.clojure/clojure-contrib "1.2.0"}
  :profiles {:dev
              {:resource-paths ["dumb-stuff"],
               :dependencies {swank-clojure "1.3.4"}}}
  :min-lein-version "2.0.0")

Unfortunately that output had to be manually edited a bit for cosmetic reasons; Clojure's pretty-printer doesn't really know what to do with defproject forms. But it should cover most of the changes necessary to take your project into the exciting new world of Leiningen 2. The biggest changes will come from the move from :dev-dependencies to :dependencies in the :dev profile. But this is not a foolproof translation since in Leiningen 2 :dependencies only run in the context of the project itself, while in Leiningen 1 :dev-dependencies ran in both Leiningen and the project. In retrospect this was a design mistake, but there are a number of plugins out there that take advantage of this fact and will need to be split into separate artifacts for the parts that run in Leiningen vs the parts that run in the project. I've taken some time to adapt some of the most commonly-used plugins for this change, but I'm sure I missed some of the more obscure ones.

The plan from here is to polish off a few more features and cut a preview release. The preview will still be missing a handful of the more obscure features from Leiningen 1 like shell wrappers and selective transitive class file cleaning, but for the vast majority of projects it should be usable for everyday work. See the "post-preview" section of the todo file for details on what's remaining. The hope is to have it ready at latest by the Clojure/West conference. Enjoy!

Update: Leiningen 2.0.0 has been released! See the upgrade guide.

« older | 2012-02-29T00:57:50Z | newer »