How Clojure Babies are Made: What Leiningen Is

07 April 2013

"What the hell is Leiningen?" is a question you've probably overheard
many times in your day-to-day life. You've probably even asked it
yourself. Up until now we've described specific capabilities that
Leiningen has and how those capabilities are implemented. It can
build and run your app,
it has a trampoline, and so forth.

But it's time to take a step back and get a high-level understanding
of what Leiningen is. It's time to stare deeply into Leiningen's eyes
and say "I see you," like Jake Sully in that Avatar documentary. We'll
do this by giving an overview of the non-coding related tasks you need
to accomplish when building software. Next, we'll describe how
Leiningen helps you accomplish these tasks and compare it to similar
tools in Ruby.

This post isn't as nitty-gritty as the previous posts in the Clojure
Babies series, but it will help lay the groundwork for an upcoming
post on packaging. Additionally, I hope it will clarify what a
programming language artifact ecosystem is. This concept is often
overlooked when teaching a programming language, and when it is
covered it's not covered in a systematic way. Together, noble-chinned
reader, we will remedy that situation. For our generation and all
generations to come.

Programming Language Artifact Ecosystems

In order to become proficient at a language, you need to know much
more than just its syntax and semantics. You need to familiarize
yourself with the entire programming language ecosystem, which is
comprised of everything you need in order to build working software in
that language. It can be broken down into at least the following
sub-ecosystems:

The documentation ecosystem

The developer community

The development environment ecosystem (editor support)

The artifact ecosystem

Right now we only care about the artifact ecosystem. For our purposes,
a programming artifact is a library or executable. Ruby gems, shell
scripts, Java jars, shared libraries, and "HAL9000.exe" are all
programming artifacts.

An artifact ecosystem is the set of tools and services that allow
you to do the following with regard to artifacts:

Retrieve them from repositories

Incorporate them in your own project, (possibly) resolving conflicts

Build them

Publish them to repositories

Run them

Tools are often layered on top of each other, one tool smoothing out
the warts of the tools it wraps. For example, the following tools (and
more) are part of the Ruby artifact ecosystem:

Ruby Gems provides a package specification, the means to
incorporate gems in your project, and the means to build and publish
gems

Bundler provides a layer on top of Ruby Gems, providing dependency
resolution and gem retrieval

Jeweler is one of many tools for easing the process of creating
gemspecs and building gems.

Other languages have their own tools. Java has Maven, PHP has Pear or
whatever. Artifact management is a common need across languages.

In previous Clojure Baby posts, we've seen that we can use Leiningen
to build and run Clojure programs. It turns out that Leiningen also
handles the remaining tasks - retrieving packages, incorporating them
in your project, and publishing them. It's truly the Swiss Army
Bazooka (I'm going to keep repeating that phrase until it catches on)
of the Clojure artifact ecosystem.

But why is it that in Ruby you need an entire constellation of tools,
while in Clojure you only need one?

Leiningen Is a Task Runner with Clojure Tasks Built In

Leiningen is able to handle so many responsibilities because it is, at
heart, a task runner. It just happens to come with an
excellent set of built-in tasks
for handling Clojure artifacts. (Incidentally, this is probably where
Leiningen's name came from. "Leiningen Versus the Ants" is a short
story where the protagonist fights ants. Ant is a Java build tool that
evidently is unpleasant to use for Clojure builds.) By comparison,
Ruby's Rake is also a task runner used by many of Ruby's artifact
tools, but Rake provides no built-in tasks for working with Ruby
artifacts.

"Task runner" is a little bit ambiguous, so let's break it down.
Ultimately, all Leiningen tasks are just Clojure functions. However,
in previous posts
we've seen how fun it is to try and run Clojure functions from the
command line. In case you need a short refresher: it's not fun at all!

Leiningen allows the Clojure party to remain fun by serving as an
adapter between the CLI and Clojure. It takes care of the plumbing
required for you to run a Clojure function. Whether the function is
provided by your project, by Leiningen's built-in tasks, or by a
Leiningen plugin,
Leiningen does everything necessary to get the function to run. In a
way, Leiningen's like an attentive butler who quietly and competently
takes care of all your chores so that you can focus on your true
passions, like knitting sweaters for gerbils or whatever.

This manner of executing code was foreign to me when I first came to
Clojure. At that time I had mostly coded in Ruby and JavaScript, and I
had a decent amount of experience in Objective C. Those languages
employ two different paradigms of code execution.

Ruby and Javascript, being scripting languages, don't require
compilation and execute statements as they're encountered. Objective C
requires compilation and always starts by executing a main method.
With Leiningen, Clojure has achieved an amalgamation of the two
paradigms by allowing you to easily run arbitrary functions with a
compiled language.

The End

Hopefully, this article has given you a firmer grasp of what Leiningen
is. The idea that Leiningen is a task runner with a powerful set of
built-in tasks designed to aid Clojure artifact management should help
you organize your disparate chunks of Leiningen knowledge.

In the next article, we'll add another chunk of Leiningen knowledge
by examining the way Leiningen retrieves artifacts from repositories
and incorporates them in your project.

Goodbye for now!

Shout Outs

Thanks to Pat Shaughnessy and
technomancy for reviewing this article.
technomancy provided the line "Leiningen is an adapter between the CLI
and Clojure", which really helped!