Skip to content

Latest commit

 

History

History
198 lines (160 loc) · 6.7 KB

8-02_creating-executable-jar.asciidoc

File metadata and controls

198 lines (160 loc) · 6.7 KB

Packaging a Project into a JAR File

by Alan Busby

Problem

You want to package a project into an executable JAR.

Solution

Use the Leiningen build tool to package your application as an uberjar, a JAR file that includes an application and all of its dependencies.

To follow along with this recipe, create a new Leiningen project:

$ lein new foo

Configure the project to be executable by adding :main and :aot parameters to the project’s project.clj file:

(defproject foo "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.5.1"]]
  :main foo.core
  :aot :all)

To finish making the project executable, add a -main function and :gen-class declaration to src/foo/core.clj. Remove the existing foo function:

(ns foo.core
  (:gen-class))

(defn -main  [& args]
  (->> args
       (interpose " ")
       (apply str)
       (println "Executed with the following args: ")))

Run the application using the lein run command to verify it is functioning correctly:

$ lein run 1 2 3

To package the application with all of its dependencies included, invoke lein uberjar:

$ lein uberjar
Created /tmp/foo/target/uberjar/foo-0.1.0-SNAPSHOT.jar
Created /tmp/foo/target/foo-0.1.0-SNAPSHOT-standalone.jar

Execute the generated target/foo-0.1.0-SNAPSHOT-standalone.jar file by passing it as the -jar option to the java executable:

$ java -jar target/foo-1.0.0-standalone.jar 1 2 3
Executed with the following args:  1 2 3

Discussion

Executable JAR files provide an excellent method to package a program so it can be provided to users, called by cron jobs, combined with other Unix tools, or used in any other scenario where command-line invocation is useful.

Under the hood, an executable JAR is like any other JAR file in that it contains a collection of program resources such as class files, Clojure source files, and classpath resources. Additionally, an executable JAR contains metadata indicating which class contains the main method as a Main-Class tag in its internal manifest file.

A Leiningen uberjar is a JAR file that contains not only your program, but all the dependencies bundled in as well. When Leiningen builds an uberjar, it can detect from the :main entry in project.clj that your program supplies a -main function and writes an appropriate manifest that will ensure that the emitted JAR file is executable.

The :gen-class in your namespace and the :aot Leiningen option are required to precompile your Clojure source file into a JVM class file, since the "Main-Class" manifest entry doesn’t know how to reference or compile Clojure source files.

Packaging JARs Without Their Dependencies

Not only does Leiningen make it possible to package a project with its dependencies, it also makes it possible to package it without its dependencies.

The jar command packages a project’s code without any of its upstream dependencies. Not even Clojure itself is included in the JAR file—​you’ll need to BYOC.[1]

By invoking the command lein jar in the foo project, you’ll generate target/foo-0.1.0-SNAPSHOT.jar:

$ lein jar
Created /tmp/foo/target/jar/target/foo-0.1.0-SNAPSHOT.jar

Listing the contents of the JAR file using the unzip command,[2] you can see that very little is packaged—​just a Maven .pom file, generated JVM class files, and the project’s miscellany:

$ unzip -l target/foo-0.1.0-SNAPSHOT.jar
Archive:  target/foo-0.1.0-SNAPSHOT.jar
  Length     Date   Time    Name
 --------    ----   ----    ----
      113  12-06-13 10:26   META-INF/MANIFEST.MF
     2595  12-06-13 10:26   META-INF/maven/foo/foo/pom.xml
       91  12-06-13 10:26   META-INF/maven/foo/foo/pom.properties
      292  12-06-13 10:26   META-INF/leiningen/foo/foo/project.clj
      292  12-06-13 10:26   project.clj
      229  12-06-13 10:26   META-INF/leiningen/foo/foo/README.md
    11220  12-06-13 10:26   META-INF/leiningen/foo/foo/LICENSE
        0  12-06-13 10:26   foo/
     1210  12-06-13 10:26   foo/core$_main.class
     1304  12-06-13 10:26   foo/core$fn__16.class
     1492  12-06-13 10:26   foo/core$loading__4910__auto__.class
     1755  12-06-13 10:26   foo/core.class
     2814  12-06-13 10:26   foo/core__init.class
      162  12-04-13 14:54   foo/core.clj
 --------                   -------
    23569                   14 files

The target/foo-0.1.0-SNAPSHOT-standalone.jar listing, on the other hand, includes over 3,000 files.[3]

Since the packaged pom.xml file includes a listing of the project’s dependencies, build tools like Leiningen or Maven can resolve these dependencies on their own. This allows for efficient packaging of libraries. Can you imagine if each and every Clojure library included the entirety of its dependencies? It would be a bandwidth nightmare.

Because of this property, lean JAR files such as this are what is deployed to remote repositories when you use the lein deploy command.[4]

Without its dependencies included—​namely, Clojure—​you’ll need to do a bit more work to run the foo application. First, download Clojure 1.5.1. Then invoke foo.core via the java command, including clojure-1.5.1.jar and foo-0.1.0-SNAPSHOT.jar on the classpath (via the -cp option):

# Download Clojure
$ wget \
  http://repo1.maven.org/maven2/org/clojure/clojure/1.5.1/clojure-1.5.1.zip
$ unzip clojure-1.5.1.zip

# Execute the application
$ java -cp target/foo-0.1.0-SNAPSHOT.jar:clojure-1.5.1/clojure-1.5.1.jar \
       foo.core \
       1 2 3
Executed with the following args:  1 2 3

See Also


1. Bring your own Clojure!
2. Available on most Unix-based systems.
3. All of which we won’t be committing to print. Take a look for yourself with the command lein uberjar && unzip -l target/foo-0.1.0-SNAPSHOT-standalone.jar.
4. See [sec_deploy_clojars], for more information on releasing libraries.