Fun with lein, Money with maven
While doing Clojure projects, it is the second time I faced a problem with a customer’s “build team” that knows what Java is, loves Maven, but does not believe in Mr. Leiningen, hence all of the lein niceties (plugins, once liners, tasks, etc..) need to now be converted to “pom.xml”s.
A good start is “lein pom”. While it only scratches the surface, it does generate a “pom.xml” with most of the dependencies. But in most cases it needs to be “massaged” well in order to fit а real maven build process.
Usual Suspects
Besides the most common “lein repl”, here is what I usually need lein to do:
* Compile Clojure code
* Some files need to be AOT compiled
* Run Clojure tests
* Compile ClojureScript
(not Clojure specific, but I’ll include it anyway)
* Compile protobuf
* Create a JAR for most projects
* Create a self executing “uberjar” for others
When Clojure is “Ahead Of Time”
Compiling, AOTing and running tests can be done with Clojure Maven Plugin:
<plugin> <groupId>com.theoryinpractise</groupId> <artifactId>clojure-maven-plugin</artifactId> <version>1.3.20</version> <extensions>true</extensions> <executions> <execution> <id>compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> </executions> <configuration> <namespaces> <namespace>whatsapp.core</namespace> </namespaces> <compileDeclaredNamespaceOnly>true</compileDeclaredNamespaceOnly> <sourceDirectories> <sourceDirectory>src</sourceDirectory> </sourceDirectories> <testSourceDirectories> <testSourceDirectory>test</testSourceDirectory> </testSourceDirectories> </configuration> </plugin> |
notice “namespaces” and “compileDeclaredNamespaceOnly”, this is how AOT is done for selected namespaces.
For AOT it’s good to remember that “a side effect of compiling Clojure code is loading the namespaces in order to make macros and functions they use available”, here are AOT compilation gotchas to keep in mind.
Compiling ClojureScript
This one is a bit trickier. If it is possible to convince a build team to install lein as a library that is used for the build process (e.g. similar to “protoc” to compile protobufs), then to compile ClojureScript, a lein cljsbuild can be added to the profile:
vi ~/.lein/profiles.clj |
{:user {:plugins [[lein-cljsbuild "1.0.0"]]}} |
and an exec maven plugin can be used to relay the execution to “lein”:
<plugin> <artifactId>exec-maven-plugin</artifactId> <groupId>org.codehaus.mojo</groupId> <executions> <execution> <id>compiling ClojureScript</id> <phase>generate-sources</phase> <goals> <goal>exec</goal> </goals> <configuration> <executable>lein</executable> <arguments> <argument>cljsbuild</argument> <argument>once</argument> </arguments> </configuration> </execution> </executions> </plugin> |
In fact, if “lein” is installed, it can be used via “exec-maven-plugin” to do everything else as well, but it all depends on build teams’ restrictions. For example, financial customers may have extremely strict “policies”/”rules”/”opinions”.
A couple more options to explore for building ClojureScript would be lein maven plugin and zi-cljs. Here is a related discussion on a ClojureScript google group.
Making Shippables
“lein uberjar” with some config in “project.clj” is all that is needed in “lein” world. In maven universe maven shade plugin will do just that:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.3</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>org.gitpod.WhatsApp</mainClass> </transformer> </transformers> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> </execution> </executions> </plugin> |
above will create a self executing JAR with all dependencies included and with an entry point (-main) in “org.gitpod.WhatsApp”.
Google Protocol Buffers
With lein it is as simple as pluging in lein protobuf. In maven, it is not as simple, but also not terribly difficult and solved via maven-protoc-plugin:
<plugin> <groupId>com.google.protobuf.tools</groupId> <artifactId>maven-protoc-plugin</artifactId> <version>0.3.2</version> <extensions>true</extensions> <executions> <execution> <goals> <goal>compile</goal> </goals> <phase>generate-sources</phase> </execution> </executions> <configuration> <protocExecutable>${PROTOBUF_HOME}/src/protoc</protocExecutable> <protoSourceRoot>resources/proto</protoSourceRoot> <outputDirectory>target/classes</outputDirectory> <!--<additionalProtopathElements>--> <!-- <param>${PROTOBUF_HOME}/src/google/protobuf</param>--> <!--</additionalProtopathElements>--> </configuration> </plugin> |
here is a repository it currently lives at:
<pluginRepositories> <pluginRepository> <id>protoc-plugin</id> <url>http://sergei-ivanov.github.com/maven-protoc-plugin/repo/releases/</url> </pluginRepository> </pluginRepositories> |
notice “additionalProtopathElements”. In case clojure-protobuf is used with extensions, a path to “descriptor.proto” can be specified in “additionalProtopathElements”.