Maven
From APIDesign
Apache's Maven is an advanced, high level concept build system. Compared to Ant or make, it feel like a modern programming language, compared to assembly. While the other build systems describe step by step, what shall be done and how, the Maven is build around automatic management of dependencies and configuration of various plugins. It is kind of module system, in fact.
Contents |
Is Maven ready for Desktop?
In work, while working on NetBeans, I rely mostly on Ant. However for my Saturday night project, I decided to try Maven. It's management of dependencies is really awesome. I do not need to take care about locations of actual libraries, I can just spell their name and version and the Maven downloads them automatically during the build. Perfect, I love this part of the system.
In Maven, compared to Ant, I have huge problems to configure the system to do exactly what I want. For example, I want to package my application (two my own JAR files, plus all the dependency JARs) into a single ZIP file that everyone could download. This is not easy at all. The only tool that does something similar seems to be shade plugin. But its functionality is still far from optimal.
It seems to me that development of simple Java desktop applications is not really well supported by current Maven plugins. Packaging to a compound ZIP seemed unsupported, execution of a main method is supported, but not by official Apache's plugin. It seems to me that Java desktop is second class citizen in Maven.
On the other hand one can find tons of tools for server side. Packaging to WAR file is possible (this is almost the ZIP file requested above, and I even wanted to create WAR instead of ZIP, but it does not work, it requires some web.xml file, which I obviously don't have). Execution on glashfish, grizzly, etc. is easy. All these application servers have their own plugins and thus are easy to start with.
All this leads to me following question: is Maven really ready for development of Java desktop applications? And a follow up: Why it is not?
It is Ready! It is Just Different.
Thanks to all your overwhelming responses I managed to find solution of the packaging. I can confirm that Maven can be used to package Java desktop applications. Just give up on shade and use assembly plugin. This is the configuration in my pom.xml file:
<plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.2-beta-2</version> <executions> <execution> <id>create-executable-jar</id> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <descriptors> <descriptor>all-zip.xml</descriptor> </descriptors> <finalName>myapp-${version}</finalName> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>myapp.Main</mainClass> </manifest> </archive> </configuration> </plugin>
One also needs to create additional assembly file. In my case it is located next to the pom.xml and named all-zip.xml:
<?xml version="1.0" encoding="UTF-8"?> <assembly> <id>all</id> <formats> <format>zip</format> </formats> <dependencySets> <dependencySet> <useProjectArtifact>false</useProjectArtifact> <outputDirectory>lib</outputDirectory> </dependencySet> </dependencySets> <files> <file> <source>target/myapp-${version}.jar<source> <outputDirectory>/</outputDirectory> </file> </files> </assembly>
And that is all. I have to admit that at the end this is much simpler than using plain Ant. In Ant it would be an enormous amount of XML code to generate the Class-Path attribute tons of <pathconvert> magic. In Maven this is for almost free (as soon as you know which plugin is the right one to use). My application now has executable main JAR and all the libraries in subfolder lib. It can be executed as java -jar myapp-1.8/myapp-1.8.jar. Perfect!
myapp-1.8/myapp-1.8.jar myapp-1.8/lib/jersey-core-1.1.0-ea.jar myapp-1.8/lib/jsr311-api-1.1.jar myapp-1.8/lib/jersey-server-1.1.0-ea.jar myapp-1.8/lib/asm-3.1.jar myapp-1.8/lib/jersey-json-1.1.0-ea.jar myapp-1.8/lib/jettison-1.1.jar myapp-1.8/lib/jaxb-impl-2.1.10.jar myapp-1.8/lib/jackson-asl-0.9.4.jar myapp-1.8/lib/org-netbeans-libs-freemarker-RELEASE67.jar myapp-1.8/lib/org-netbeans-modules-queries-RELEASE67.jar myapp-1.8/lib/org-openide-filesystems-RELEASE67.jar myapp-1.8/lib/org-openide-util-RELEASE67.jar myapp-1.8/lib/jersey-client-1.1.0-ea.jar myapp-1.8/lib/freemarker-2.3.8.jar
I am surprised this is not one of the standard archetypes. As I wrote in Chapter 9, one of the best APIs is wizard. Good wizard can turn any API, regardless how bad, complicated and hidden, into perfectly shining, beautiful star. Good tools make everything easier. Having Java Application Archetype would save me a lot of hours of googling and blogging (if I could find the archetype, of course).
Compare to Ant
Laszlo Kishalmi donated the same code in Ant:
<copy todir="${dist.dir}/lib" flatten="true"> <fileset dir="${basedir}/lib" includes="**/*.jar"/> </copy> <pathconvert property="main.jar.class-path" dirsep="/" pathsep=" "> <path><fileset dir="${dist.dir}" includes="lib/*.jar"/></path> <map from="${dist.dir}/" to=""/> </pathconvert> <jar update="true" jarfile="${dist.dir}/main.jar"> <manifest> <attribute name="Class-Path" value="${main.jar.class-path}"/> </manifest> </jar>
Can you feel the difference? Especially the pathconvert is very close to black magic and voodoo. I know it exists, but whenever I need to use pathconvert I usually need two or three hours to remind myself again about its capabilities. Obviously as soon as I am done I want to cluelessly forget about such nightmare as soon as possible.
Lessons Learned
Time to wrap this episode up. Maven seems to have alive community which is really helpful answering questions.
The best way to learn Maven is not to read its manual! There is online version of Maven Complete Reference. Ten minutes of reading its chapter 12 helped me more than hours of browsing in Maven manual. This is good, yet surprising. The Ant manual is perfect and almost definitive source of information. I never needed any book to learn something about Ant. The Maven manual on the other hand seems incomplete (e.g. not describing all the options of each plugin) and low level (e.g. describing only how to do something, not what a plugin can be useful for). As a result I have incorrectly chosen the shade plugin, while I should have point all my attention to assembly one. Last, but not least. The Maven Complete Reference does not seem to be indexed by google. At least it showed up in none of my queries. That is unfortunate. There shall be some link from the (incomplete) manual to such a good on-line definitive tutorial.
Declarative Programming offers us higher level abstractions. Such abstractions usually allow API users to specify what shall be done without the need to describe how to do it. Nice difference between these two approaches can be seen when packaging an application in Maven or in Ant (the build.xml and pom.xml are APITypes). While in Ant I would need to copy all the library JARs into appropriate location and then generate main module manifest Class-Path attribute manually and ZIP the whole bundle, in Maven I can just choose the right plugin, tell it that my libraries shall are in lib directory and that I want Class-Path attribute and the rest is done automatically. This is perfect, but it has its own hidden issues.
It seems to me that the documentation for declarative APIs cannot be the same as for procedural ones. The higher level abstractions may be too abstract for consumption by regular API novice. As shown in my Maven adventures, I had a vision of what I wanted to do, but I could not select the appropriate abstractions (e.g. the Maven plugin) to help me in achieving my goal.
In case of procedural Ant, this is much easier. I know that I need to copy library JARs. Guess what is the appropriate tool? Yes, it is the copy task. I need to create a ZIP file. Guess what one shall use? Of course, the zip task. Finding the right mapping is easy, as Ant is naturally mapping the (simple) concepts user has in his head to the right tools. Of course, only simple concepts are mapped. As soon as one starts to think about generating the Class-Path attribute, one will get lost. This is just too complicated to be automated in Ant (as far as I am aware one of the right tools is pathconvert task, but the whole order of steps is quite complex).
Getting High
The trouble with high level concepts is that there needs to be some entry point that maps the actual API user need to the right tool. Treating the Maven plugins as such entry points is not sufficient. Every plugin has huge set of additional configuration options and can be tweaked to do almost everything. So when looking at assembly plugin documentation I have seen that this is useful for creation of uber JAR and I immediately have lost my interest. If I investigated it more, I would have found out it is useful for solving many other goals as well. But API shall be easy to use.
The entry point for an API should be a usecase. A description of an (expected) user need. This is true for any API, but clearly for Declarative Programming this is more important than ever. In this case the usecase is to package ZIP file with multiple JAR files. This would immediately trigger my (or google's, when I was querying for keywords like ZIP maven JAR Class-Path) attention and I could then follow up to the actual example describing how to realize my usecase via pom.xml configuration.
Abstractions Need Usecases
The more abstract concept you expose, the more important good usecase is. For example NetBeans is using Lookup concept, which is quite abstract and used for various, at first sight unrelated, goals. It is almost impossible to explain newcomers the Lookup API itself. It always needs to be introduced by real problem, real usecase. For example: if you want to MVC in NetBeans, you need Lookup. Use it this way!. Then the usecase is clear, and the actual (originally confusing) API is just a tool to realize that usecase.
Abstractions are soul of API design. They create the high level concepts that simplify our life (when one knows how to use it, the Maven is really more friendly than Ant). Yet, abstractions are not the primary goal. Humans need a guide to use the abstractions. Human need good usecases that document declarative APIs.
What Do API Users Say?
Too many developers are focused on the mechanics of how a task will be performed and the steps required to perform them. Typical documentation of software explains what command line switches are available, but not why you might want to use them. I have been frustrated by this for years. I always skip to the "Examples" section of any doc, as I usually can infer use cases from the examples far better than from studying method signatures in the abstract. A classic example would be the Java API docs. Why would I want to use a LinkedHashMap? Maybe I slept through that lecture in CS class. It would be neat to make a pivoted set of docs for the JDK in terms of patterns, like LRU Cache => LinkedHashMap. The only way I've learned Maven is to study other people's POMs, guess-and-check, and then discuss.
-- Basscakes 15:55, Sep 15, 2009
<comments/>