…or how to compile, pack and use a *.java for a clojure project. A naive post, yeah.

TL’DR Difficulties make us learn and make things. Just use java-src

Using java interop from clojure is easy. Easy until what you have to import is present in some repository. Being relative new to clojure I stuck into this problem twice already.

First I tried to use a dbus-java not from a *.deb package but from a Maven repo, and didn’t find any *.pom on any public repo, which looked kind a sucks, because I find at least 2 repos on Github, which did attempts to mavenize the old project from freedesktop.org, but unfortunately I didn’t see any easy way to provide Github repo as a repository, as easy as I used to specify with Bundler

# Some Gemfile...
# Ruby ecosystem has something to learn from :)
gem "lurker", git: "git://github.com/razum2um/lurker.git"

# since bundler 1.6
gem "lurker", github: "razum2um/lurker"

You can also specify :branch and :ref which is highly useful, if you remember go-lang packet management - you’ll sigh.

So there was a great standardization, probably from lacking any other ways, since Rubygems and Bundler are must-use-tools de-facto. I end up trying to setup my own nexus repository. Luckily it’s seemed really simple, but kinda overkill to distribute a jar dependency.

Later I discovered a local-repo possibility in the project.clj, but this kind of distribution of dependencies makes me think about entropy increase :)

The second time I run into the problem was need in a simple *.java, which has to be used from -javaagent

import java.lang.instrument.Instrumentation;

public class Foo {
    private static Instrumentation instrumentation;

    public static void premain(String args, Instrumentation inst) {
        instrumentation = inst;
    }

    public static long getObjectSize(Object o) {
        return instrumentation.getObjectSize(o);
    }
}

I had absolutely no idea about how to achieve this from clojure. Probably there is no way. So we need to pack it and use from the project. In order to do this we need:

  • compile the file
  • optionally edit a manifest file
  • pack to a jar
  • prepare a repo dir
  • install the jar into the directory with checksums

Like this:

$ javac Foo.java

$ cat MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8.0_05 (Oracle Corporation)
Premain-Class: Foo

$ jar cfm foo.jar MANIFEST.MF Foo.class

$ mkdir repo

$ mvn install:install-file -DcreateChecksum=true -Dpackaging=jar -Dfile=foo.jar \
      -Dversion=0.0.1 -DgroupId=local -DartifactId=foo-java -DlocalRepositoryPath=repo

This must end up with this structure under project directory:

repo
└── local
    └── foo-java
        ├── 0.0.1
        │   ├── foo-java-0.0.1.jar
        │   ├── foo-java-0.0.1.jar.md5
        │   ├── foo-java-0.0.1.jar.sha1
        │   ├── foo-java-0.0.1.pom
        │   ├── foo-java-0.0.1.pom.md5
        │   └── foo-java-0.0.1.pom.sha1
        ├── maven-metadata-local.xml
        ├── maven-metadata-local.xml.md5
        └── maven-metadata-local.xml.sha1

After that we can add it to the project.clj:

:dependencies [...
              [local/foo-java "0.0.1"]]
:repositories {"local" "file:repo"}
:java-agents [[local/foo-java "0.0.1"]]

I did it but looked kinda tedious for me. This could be a bash script, but why not some clojure functions? I did this way.

The devil is hidden in details: you DO NOT have to use it as a *.jar. No, really. Don’t use command line until you type lein new or lein repl. ;) Add this to your ~/.lein/project.clj:

:dependencies [...
              [java-src "RELEASE"]
              ...]

Use it from REPL just as pointed out in the README. MANIFEST.MF file is located nearby the .java file and is automatically picked up:

(require '[java-src.core :refer [source-java]])
(source-java "path/to/Foo.java")
;; ...add to project.clj and sucking restart REPL here
(import Foo)

Teardown: you have to restart REPL to add brand-new dependencies. JVM is such a JVM :\