RSS feed
<< Better webapp config: Spring integration and an example | Home | Making your Java app shine on OS X >>

Packaging Java apps for OS X with Maven

Anyone attending a Java conference the past few years must have noticed the steady increase of Java developers working on a Mac. The good looking hardware sure helps, but the main reason people are flocking to the Mac is OS X.

Apple's operating system is a great platform for Java developers since it combines the power of Unix with a UI that is actually possible to use. Installing an application is as easy as downloading it and dragging it to the Applications folder.

But that's until you try to install Java software. Some projects are helpful and create double-clickable jars, others just reuse the scripts from their Linux distribution and force you to start Terminal just to lauch their app.

The reason for this is probably that hand-crafting Java application bundles for OS X is quite tedious. A while back I decided to do something about this sad state and make the Java world a better place to be for Mac people.

What I did was to create a Maven plugin that will take any Maven project  and make it into a first class OS X application bundle.

But first, a little motivational example to illustrate why you should care about this:

My friend Sigurd has made this cool little Java app called "Hva" that helps you keep track of hours. Every now and then it will pop up and ask you what you've done  since last time.



(For those of you who's Norwegian is a little rusty: "Hva" translates to "What" and this dialog asks what you've been up to since 7:36 PM)

Because Sigurd is one of these helpful guys, he distributes his app as a double-clickable jar file.

Like any other application, I dragged the jar file to the Applications folder on my Mac and this is what it looks like:



This really isn't too bad, but there are some subtle things that tells me this is not a properly bundled OS X application. First "Hva?" is the only application that has a file name suffix (".jar") in it's name.  Second, the logo of the application is the generic and dull Java Application logo.

So what can Sigurd do to give the users of Hva a better experience on the Mac?

Well, if he's using Maven (and he should!) there is no longer any excuse for giving his Mac users a bellow par experience of his application:

Yesterday I released the OS X Application Bundle Maven plugin which is a part of the Mojo project over at Codehaus.

All you need to give this plugin is  a main method to launch and it creates a beautiful OS X application bundle ready for clicking by your users.

In order to use this plugin for Hva, Sigurd could add hva.jar to his Maven repository and create a Maven project with this pom.xml:

<project>
<modelVersion>4.0.0</modelVersion>
<groupId>hva</groupId>
<artifactId>hva-maven</artifactId>
<version>1.0-SNAPSHOT</version>
<name>hva</name>
<dependencies>
<dependency>
<groupId>hva</groupId>
<artifactId>hva</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>

Then he needs to add the following plugin configuration:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>osxappbundle-maven-plugin</artifactId>
<version>1.0-alpha-1</version>
<configuration>
<mainClass>Hva</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>bundle</goal>
</goals>
</execution>
</executions>
</plugin>

Running mvn:install should now create an OS X application bundle in the project's  target directory:


As you can see, the plugin has created an application bundle, plus packaged the application in both a zip and a disk image file ready for distribution.

This is nice, but what if Sigurd wants to add a custom icon to Hva? First he needs to open the Icon Composer utility located in /Developer/Application/Utilities/Icon Composer:


 

Here, I've created an icon in various sizes from the photo in my collection that looked most like the letter "H". I've stored this file as "hva.icns" in src/main/app-resources and used the iconFile property to tell the plugin where it can find the icon, like this:

<plugin>
<groupid>org.codehaus.mojo</groupid>
<artifactid>osxappbundle-maven-plugin</artifactid>
<version>1.0-alpha-1</version>
<configuration>
<mainclass>Hva</mainclass>
<iconFile>${basedir}/src/main/app-resources/hva.icns</iconFile>
</configuration>
<executions>
<execution>
<goals>
<goal>bundle</goal>
</goals>
</execution>
</executions>
</plugin>

After running mvn:install, our target/ directory now looks like this:



Let's rename it and move it to the Applications folder:


Notice the neat icon on the application and how much smoother it looks in the Dock now:


 

See the plugin's site for full documentation. The Maven example project I created for Hva can be found in the Simplericty Subversion repository, here:

http://simplericity.org/svn/simplericity/projects/hva-maven/


 




Re: Packaging Java apps for OS X with Maven

Excellent post! Thank you, I always wanted that.

Re: Packaging Java apps for OS X with Maven

I get an error when I do what you suggested: Ariel:~/Projects/Eclipse/GenieTownKbEdit fgm$ mvn package osxappbundle:bundle [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'osxappbundle'. [INFO] ---------------------------------------------------------------------------- [INFO] Building GenieTownKbEdit [INFO] task-segment: [package, osxappbundle:bundle] [INFO] ---------------------------------------------------------------------------- [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Nothing to compile - all classes are up to date [INFO] [resources:testResources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:testCompile] [INFO] Nothing to compile - all classes are up to date [INFO] [surefire:test] [INFO] ------------------------------------------------------------------------ [ERROR] FATAL ERROR [INFO] ------------------------------------------------------------------------ [INFO] null [INFO] ------------------------------------------------------------------------ [INFO] Trace java.lang.NullPointerException at org.apache.maven.plugin.surefire.SurefirePlugin.constructSurefireBooter(SurefirePlugin.java:594) at org.apache.maven.plugin.surefire.SurefirePlugin.execute(SurefirePlugin.java:391) at org.apache.maven.plugin.DefaultPluginManager.executeMojo(DefaultPluginManager.java:443) at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoals(DefaultLifecycleExecutor.java:539) at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalWithLifecycle(DefaultLifecycleExecutor.java:480) at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoal(DefaultLifecycleExecutor.java:459) at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalAndHandleFailures(DefaultLifecycleExecutor.java:311) at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeTaskSegments(DefaultLifecycleExecutor.java:278) at org.apache.maven.lifecycle.DefaultLifecycleExecutor.execute(DefaultLifecycleExecutor.java:143) at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:334) at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:125) at org.apache.maven.cli.MavenCli.main(MavenCli.java:280) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:585) at org.codehaus.classworlds.Launcher.launchEnhanced(Launcher.java:315) at org.codehaus.classworlds.Launcher.launch(Launcher.java:255) at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430) at org.codehaus.classworlds.Launcher.main(Launcher.java:375) [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2 seconds [INFO] Finished at: Mon Oct 29 15:12:52 PDT 2007 [INFO] Final Memory: 5M/9M [INFO] ------------------------------------------------------------------------ Ariel:~/Projects/Eclipse/GenieTownKbEdit fgm$

Re: Packaging Java apps for OS X with Maven

Hi Frank,

I have a feeling your problem might be related to a bug in Surefire:

http://jira.codehaus.org/browse/SUREFIRE-300?

The workaround seems to be adding junit as a tet dependency.

Good luck!

Re: Packaging Java apps for OS X with Maven

Great. That worked great. Thanks. I had been using the Eclipse exporter but I need to customize the info.plist file, and I prefer to set it up once ...

Re: Packaging Java apps for OS X with Maven

Eirik: I got another problem... I specified: <configuration> <mainClass>com.genietown.kbedit.KbEditor</mainClass> <iconFile>${basedir}/src/main/resources/KbEdit.icns</iconFile> <jvmVersion>1.5*</jvmVersion> <dictionaryFile> ${basedir}/src/main/resources/Info.plist.template </dictionaryFile> </configuration> in my pom.xml, I get the error: Setting property: resource.loader => 'file,classpath'. [INFO] [osxappbundle:bundle {execution: default}] [ERROR] ResourceManager : unable to find resource '/Users/fgm/Projects/Eclipse/GenieTownKbEdit/src/main/resources/Info.plist.template' in any resource loader. [INFO] ------------------------------------------------------------------------ when I try to build the file. But, ... if I do an ls, I can verify the file is there... ls /Users/fgm/Projects/Eclipse/GenieTownKbEdit/src/main/resources/Info.plist.template /Users/fgm/Projects/Eclipse/GenieTownKbEdit/src/main/resources/Info.plist.template (I tried various other combinations, nothing seems to work.) Hoping that you can help Frank

Re: Packaging Java apps for OS X with Maven

Frank,

I think you've found a bug in the plugin.

It seems like absolute references to the dictionary won't work.

I'll fix this in the plugin for the next release, but for now you can change the reference to "src/main/resources/Info.plist.template" and see if that works.

Eirik.

Re: Packaging Java apps for OS X with Maven

hello, great plugin! Is it possible to have the plugin generate a .app file (and .dmg and .zip) with a different name than the artifactId of the pom.xml? I'm using the bundleName to change the name once the app loads, but I'd also like to change the name of the file itself. The reason for this is that right now one source tree (and pom.xml) generates multiple artifacts which contain separate apps. The overall project is called 'xyz', but the .app generated should be called 'foo'. Also, is it possible to use mulitple configurations of the same plugin to generate apps which call different main()'s?

Re: Packaging Java apps for OS X with Maven

Hi Josh,

The best way of doing that would probably be to add multiple executions of the plugin with separate configuration.

However, as you point out, there is no configuration for the bundle file name. I think you could override the buildDirectory, diskImageFile and zipFile configuration fields, although I haven't really tested this.

Eirik.

Re: Packaging Java apps for OS X with Maven

wow. great. I was honestly surprised to see it work the first time I tried. I hope that'll the example other will follow :) If I want my the dmg artifacts to be installed/deployed to repositories, is there a better alternative than using the build-helper:attach-artifact mojo ? Shouldn't your mojo do it automatically ?

Re: Packaging Java apps for OS X with Maven

Greg,

That's a great idea! I've implemented it and deployed a new snapshot of alpha-2.

Thanks,
Eirik.

Re: Packaging Java apps for OS X with Maven

Brilliant, thanks ! It would be nice if your jira was up-to-date, so we could follow what's going where with what releases. (or maybe you use another issue tracking that mojo's at codehaus ?) ... also, I had another request (which can also be implemented using a combination of other plugins I guess) : is there a way to bundle dependencies in the app/dmg ?

Re: Packaging Java apps for OS X with Maven

Hi Greg,

I try to keep Jira up to date, but I haven't added all minor issues and fixes there. I'll try to improve on that :-)

You can find the issues for the plugin here: http://jira.codehaus.org/browse/MOJO/component/12964

Regarding the inclusion of dependencies in the bundle, I'm not sure I understand your question. The dependencies from the pom are included today aren't they?

Re: Packaging Java apps for OS X with Maven

Oh, I had the impression they weren't, but I'm probably wrong .. had other build issues in the meantime, so we're using a jar for now. (strangely enough, I have two projects, and only one of them works with your plugin + buildhelper; the 2nd one refuses to attach the dmg, although it's actually there...)

Add a comment Send a TrackBack