Over a million developers have joined DZone.

Installing NetBeans Platform Applications on Mac OS X with Drag-n-Drop

DZone 's Guide to

Installing NetBeans Platform Applications on Mac OS X with Drag-n-Drop

· Java Zone ·
Free Resource

Recent versions of the NetBeans IDE come with a "Build installers" menu which makes available to your own platform application the same installers used by the IDE. It's a very nice feature, even though in Mac OS X you might want to create an application that can be simply installed by dragging-n-dropping it into the Applications folder. Mac OS X allows this feature by means of "application bundles".

Yesterday I finally ported some code from the old blueMarine Ant version and Mavenized it, so it can be easily used everywhere. First, there's an OS X Application Bundle Maven Plugin, but it seems abandoned since three years and it needs some specific Mac OS X tools from the Developer package. Not good - for instance, I want everything, including the Mac OS X installer, prepared by my Hudson running on Linux. So I prefer to use the excellent JarBundler that works in 100% Java.

Unfortunately, it seems that there's no Maven plugin for JarBundler, only an Ant Task. The maven-antrun-plugin comes to the rescue and this is the working configuration:

                    <taskdef name="jarbundler"
                             classpath="src/main/app-resources/jarbundler-${tft.jarbundler.version}.jar" />

                    <mkdir dir="${project.build.directory}/appbundle"/>
                    <unjar src="${project.build.directory}/${project.build.finalName}.zip"

                    <jarbundler dir="${project.build.directory}"
                        <javafileset dir="src/main/app-resources">
                            <include name="**/*.icns" />
                        <javafileset dir="${project.build.directory}/appbundle">
                            <include name="**/*" />

                    <chmod file="${project.build.directory}/${tft.appbundle.name}.app/Contents/Resources/Java/${netbeans.cluster}/bin/${netbeans.cluster}"

                    <exec dir="${project.build.directory}" os="Mac OS X" executable="hdiutil">
                        <arg value="create"/>
                        <arg value="-noanyowners"/>
                        <arg value="-imagekey"/>
                        <arg value="zlib-level=1"/>
                        <arg value="-srcfolder"/>
                        <arg value="${tft.appbundle.name}.app"/>
                        <arg value="${tft.appbundle.name}.dmg"/>

                    <exec dir="${project.build.directory}" os="Linux" executable="mkisofs">
                        <arg value="-V"/>
                        <arg value="${tft.appbundle.name}"/>
                        <arg value="-U"/>
                        <arg value="-f"/>
                        <arg value="-D"/>
                        <arg value="-l"/>
                        <arg value="-L"/>
                        <arg value="-allow-multidot"/>
                        <arg value="-max-iso9660-filenames"/>
                        <arg value="-relaxed-filenames"/>
                        <arg value="-no-iso-translate"/>
                        <arg value="-r"/>
                        <arg value="-o"/>
                        <arg value="${tft.appbundle.name}.dmg"/>
                        <arg value="-root"/>
                        <arg value="${tft.appbundle.name}.app"/>
                        <arg value="${tft.appbundle.name}.app"/>

                    <gzip src="${project.build.directory}/${tft.appbundle.name}.dmg"


In a Maven NetBeans Platform application you have to configure this section in the "Application" module (the one with type nbm-application). It's bound to the pre-integration-test phase since it's the first one available right after the package phase: in fact, it relies on the .zip with all the distribution files to have been already prepared. It first extracts them to a temporary directory, then calls the JarBundler task which lays out them in the proper way for a Mac OS X application bundle. 

There are a couple of tricks. JarBundler by default prepares a Java launcher with a full classpath and the main class; on the other hand, the start of a NetBeans Platform application is more complex, as a shell script (which is auto-generated by the build) first inspects the installation, searching for NetBeans Platform clusters, then prepares the proper command line for Java. So we have to do the following things:

  1. Set a dummy parameter for 'mainclass' (JarBundler wants it)
  2. Set a dummy parameter for 'jar' (JarBundler wants it); since JarBundler checks that a file exists, the 'boot.jar' from the NetBeans Platform distribution is given.
  3. Set a fake stubfile (the stubfile usually is a small Mac OS X binary executable which parses the configuration of the application bundle, reads the JVM configuration, such as the minimum Java version, then finds a proper JVM installed in Mac OS X). My fake stubfile is just a shell script that finds and executes the launcher auto-generated by the build. For instance, for an application called 'solidblue' it's something such as:

    dir=`dirname "$0"`
    exec $dir/../Resources/Java/solidblue/bin/solidblue

A second problem is how to pack the generated application bundle. It could be zipped, but Mac OS X users expect a .dmg file, which contains a disk image. On Mac OS X the hdiutil command can generate it, but there's no hdiutil outside Mac OS X. Fortunately, on Linux systems there's a mkisofs that can do the job. It has been originally designed for creating ISO 9660 (CD-ROM) images to be burned, but it can serve our scope. It just needs a bunch of arguments that make sure that the resulting artifact is mountable by Mac OS X and does not truncate long file names. hdiutil and mkisofs are conditionally executed by the Ant task, so the proper tool is picked in function of the current operating system.

A final trick is needed because, unfortunately, JarBundler is not available as a Maven artifact on a public repository. The solution I picked is to commit the jar file with sources and have it referenced by path by <taskdef>. Fortunately is a very small .jar file. So, the extra sources file that you need are:

[Mistral-MacOSX:SolidBlue/solidblue-src/application] fritz% ls -al src/main/app-resources/
total 1248
drwxrwx---  5 fritz  staff     170 Jan  4 01:49 .
drwxrwx---  3 fritz  staff     102 Oct 31 10:21 ..
-rw-r--r--@ 1 fritz  staff   18509 Dec 27  2010 jarbundler-2.2.0.jar
-rwxrwx--x  1 fritz  staff      84 Jan  4 01:49 solidblue
-rw-r--r--@ 1 fritz  staff  306657 Jan  4 01:57 solidblue.icns

As you can guess, if you put a .icns file (the Mac OS X standard for containing application icons), it will be picked and used, both for the Finder and for the application icon seen when you press cmd-tab to browse the currently running applications). Let's prepare NetBeans Platform applications that are first-class citizen in Mac OS X and don't scream immediately "Hey, I've been made with Java and I miss some o.s. integration stuff"!

As a final word, this is some stuff that needs to be reused. Early this year I published a trick that allows to mimic composition with Maven POMs, that is you can put a lot of stuff into your super pom and then activate it on demand (the various sections are wrapped in separate profiles that can be activated by the presence of a well known file). It's what I've done for the chunk of POM configuration shown above, that is part of my own superpom and can be activated by putting an empty file named src/config/activate-netbeans-platform-appbundle-profile in your module. You can see two working examples of this setup in my projects SolidBlue (hg clone http://bitbucket.org/tidalwave/solidblue-src, tag 1.0-ALPHA-4) and blueArgyle (hg clone http://bitbucket.org/tidalwave/blueargyle-src, tag 1.0-ALPHA-3).



Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}