Skip to content

Instantly share code, notes, and snippets.

@technomancy
Forked from sjl/lein.markdown
Created May 24, 2012 04:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save technomancy/2779389 to your computer and use it in GitHub Desktop.
Save technomancy/2779389 to your computer and use it in GitHub Desktop.

Random thoughts as I read through it.

comments inline

README

It'd be nice to have a curl https://raw.github.com/technomancy/leiningen/stable/bin/lein > ~/bin/lein in the README instead of a link that goes somewhere... to the script itself? or to a GitHub page for it? I don't know until I click it.

Some systems have wget in lieu of curl, and not everyone has ~/bin on their path, so I figure folks might as well just save it from their browser.

The README tells me to go to the tutorial but there's a bunch more important-looking information like Configuration, Profiles, etc. Do I need to read those or will they be covered later?

Yep, profiles is big enough to need its own page now. I think the configuration section is short enough to fit there as a summary once profiles are moved

The FAQ is pretty long and in-depth, might be better to move it out of the README.

Good call; spun this out.

The other sections are good, I'd keep those.

TUTORIAL

Creating a project part is good.

Packaging is next... I think it might be better to show the user how to actually do something useful. lein repl would be perfect to introduce here because they can actually see some Clojure code working and returning.

Definitely. There's a tension here between explaining how to write a > library and how to write an application. They overlap a lot, but there are a number of places they diverge, and it'd be best not to run into these things up front.

I'd also show them that lein repl sets up their classpath by having them do something like this in the repl:

(use 'my-stuff.core)
(some-fn-thats-defined-in-the-core-ns-in-the-default-project-skeleton)

Getting a "black triangle" on the screen for them as soon as possible so they can see their code in their files actually flowing through the system gives them a sense of "whew, okay, I can see roughly how this works".

Good call. Going to switch up the example to use clj-http since that's something immediately accessible to everyone. Though that means covering the repl after dependencies; is it better to have an example that ties everything together or an example that works earlier on? Hmm...

Save packaging for the end, if you even talk about it in the tutorial. I think you could split it into a separate document even -- it's not critical to your first 20 minutes with Leiningen.

Probably best to just cover it in the DEPLOY.md file

I'd also go ahead and introduce lein run here. It's very UNIXy and will feel familiar -- similar to Python/Ruby/etc's "run command to run your program". Tell them about the command, what code it runs, and how to change that. Show them an example.

Yep, though caveats about JVM boot time unfortunately apply.

The project.clj section is next. It's fine -- the description/url are trivial and easy to figure out.

The dependencies section is next. I think lein search here is bad. I know you mention that it takes "many minutes", but that doesn't adequately convery how painfully slow this is the first time you run it.

Yeah, I've pointed people to the web UI for now. We have someone working on partial updates for changes to the indices, but that doesn't help the first download. Need to look into support for mirrors.

I think the best solution is to fix lein search itself. First: it needs a progress meter. All I see when I run it is this:

$ time lein search math
Downloading index from central - http://repo1.maven.org/maven2 ... this may take a while.

No indication of how long it'll take, not even a file size so I can guess based on what I know my internet connection to be and go and get some coffee. There's not even any way to know it's not frozen without looking at my network throughput.

In Leiningen 2 it does show a progress meter, so that's a start.

Talking about the structure of the magic dependency vector is good -- it took me a while to figure that out.

The "Writing the Code" section is all about lein test -- call it "Running Tests" instead. Test selectors aren't important in the first 10 minutes -- move them to a separate "All about Lein Test" doc.

Yeah, "running code" should be about lein run, which didn't exist when the tutorial was written. Good place to mention the repl too.

Lein needs a lein autotest command that automatically reruns lein test whenever files change on disk. That would make all the silliness about REPL'ing run-tests or installing editor crap unnecessary.

The only reason I haven't implemented this is that the file watching APIs it would build upon are only available in JDK7, and so far everything except one feature works in 5.

Pull out the AOT compilation -- I've needed it once in the ~2 years I've been using Leiningen so it certainly doesn't need to be in the initial tutorial.

Zap. Moved some of it to the compile/javac docstrings.

I like the next section -- it's more of a high-level roadmap for users which is really nice.

The uberjar section looks good, same with server-side projects.

The publishing libraries section seems to skip the "Get an account on Clojars" step... is that no longer necessary? Also what happened to lein push instead of some crazy scp command?

Added a link to the Clojars registration page.

As soon as the uploads" branch is merged, scp and the custom push task will no longer be necessary; lein deploy clojars will work.

I think that's about it. Overall it looks pretty nice, the following are my main issues:

  • Introduce lein repl and lein run right away to give users their black triangle and make them feel like they're getting somewhere.
  • Fix lein search to not suck the first time it's run.
  • Skip the scary Javaish/Maveny stuff like lein install and AOT compilation in the basic tutorial.
  • Save profiles for a separate document.
  • Make a lein autotest command to make users' lives easier.

Thanks!

Profiles

In Leiningen 2.x you can change the configuration of your project by applying various profiles. For instance, you may want to have a few extra test data directories on the classpath during development without including them in the jar, or you may want to have Swank Clojure available in every project you hack on without modifying every single project.clj you use.

By default the :dev, :user, and :default profiles are activated for each task, but the settings they provide are not propagated downstream to projects that depend upon yours. Each profile is defined as a map which gets merged into your project map.

The example below adds a resources directory during development and a dependency upon "midje" that's only used for tests.

(defproject myproject "0.5.0-SNAPSHOT"
  :description "A project for doing things."
  :dependencies [[org.clojure/clojure "1.4.0"]]
  :profiles {:dev {:resources-path ["dummy-data"]
                   :dependencies [[midje "1.4.0"]]}})

You can place any arbitrary defproject entries into a given profile and they will be merged into the project map when that profile is active.

Declaring Profiles

In addition to project.clj, profiles specified in ~/.lein/profiles.clj will be available in all projects, though those from profiles.clj will be overridden by profiles of the same name in the project.clj file. This is why the :user profile is separate from :dev; the latter is intended to be specified in the project itself. In order to avoid collisions, the project should never define a :user profile, nor should profiles.clj define a :dev profile. Use the show-profiles task to see what's available.

If you want to access dependencies during development time for any project place them in your :user profile.

{:user {:plugins [[lein-swank "1.4.0"]
                  [lein-pprint "1.1.1"]]}}

Profiles are merged by taking each key and combining the value if it's a collection and replacing it if it's not. Profiles specified earlier take precedence when replacing. The dev profile takes precedence over user by default. Maps are merged recursively, sets are combined with clojure.set/union, and lists/vectors are concatenated. You can add hints via metadata that a given value should take precedence or be displaced if you want to override this logic:

{:profiles {:dev {:prep-tasks ^:replace ["clean" "compile"]
                  :aliases ^:displace {"launch" "run"}}}}

The exception to this merge logic is that plugins and dependencies have custom de-duplication logic since they must be specified as vectors even though they behave like maps (because it only makes sense to have a single version of a given dependency present at once).

Activating Profiles

Another use of profiles is to test against various sets of dependencies:

(defproject swank-clojure "1.5.0-SNAPSHOT"
  :description "Swank server connecting Clojure to Emacs SLIME"
  :dependencies [[org.clojure/clojure "1.2.1"]
                 [clj-stacktrace "0.2.4"]
                 [cdt "1.2.6.2"]]
  :profiles {:1.3 {:dependencies [[org.clojure/clojure "1.3.0"]]}
             :1.4 {:dependencies [[org.clojure/clojure "1.4.0-beta1"]]}})

To activate other profiles for a given run, use the with-profile higher-order task:

$ lein with-profile 1.3 test :database

Multiple profiles may be combined with commas:

$ lein with-profile qa,user test :database

Multiple profiles may be executed in series with colons:

$ lein with-profile 1.3:1.4 test :database

A single with-profile call does not apply across task comma-chained tasks.

Debugging

To see how a given profile affects your project map, use the lein-pprint plugin:

$ lein with-profile 1.4 pprint
{:compile-path "/home/phil/src/leiningen/lein-pprint/classes",
 :group "lein-pprint",
 :source-path ("/home/phil/src/leiningen/lein-pprint/src"),
 :dependencies
 ([org.clojure/tools.nrepl "0.0.5" :exclusions [org.clojure/clojure]]
  [clojure-complete "0.1.4" :exclusions [org.clojure/clojure]]
  [org.thnetos/cd-client "0.3.3" :exclusions [org.clojure/clojure]]),
 :target-path "/home/phil/src/leiningen/lein-pprint/target",
 :name "lein-pprint",
 [...]
 :description "Pretty-print a representation of the project map."}

In order to prevent profile settings from being propagated to other projects that depend upon yours, the default profiles are removed from your project when generating the pom and jar. Profiles activated through an explicit with-profile invocation will be preserved. The repl task uses its own profile in order to inject dependencies needed for the repl to function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment