Search This Blog

Netbeans Platform: Mac OSX App Bundle and Embedded JRE with Maven

One problem every Java Desktop developer has to deal with is that Java apps just don't act like native applications. Whether it's the JRE dependency, the swing look and feel, or just bad memories about slow performance in old versions of Java. On Mac OSX there is an additional hurdle where users expect their applications to be a .app bundle inside a DMG image with a shortcut to /Application allowing them to drag/drop the installation. The built in Netbeans Platform installer is nice, but it's a far cry from the native behavior.

With that in mind, I'm going to describe how you can really step up your Netbeans Platform Maven build to include an OSX deployment with a bundled JRE all packaged up as an APP inside a DMG image. What a mouth full.

Overview

1. Define properties - we'll expose some custom properties in our maven file in order to make it easier to understand and maintain.
2. Create and modify resource files.
3. Resource Filtering - some extra steps need to be taken to apply our properties to configuration files, so we'll have a filtering step utilizing the maven-resources-plugin.
4. Build the artifact - using the maven-antrun-plugin we'll build the Mac OSX bundle.
5. How to use it - What commands need to be run and from where.
6. Next steps - integration with CI, app signing, etc.

Define Properties

We start things off simple by defining a few properties. Here I've prefixed my app's properties with "ugs." to avoid any possible naming collisions and put them in my root POM.xml:

Create and Modify Resource Files

OSX applications use an information property list file named "Info.plist" to configure the application, we will need to create such a file using the above properties. There are a number of additional configurations which can be made in this file like associating file extensions with your app, but they won't be covered here. You can read more about that in the documentation.

Create this file in your application module at applicaiton/src/main/resource/Info.plist:

For convenience, you should also rename your launcher.conf file to your brandingToken and put it in the same directory as the Info.plist, for me this file is named ugsplatform.conf. The important part in this file is that it contains the commented out jdkhome property, it will be used later for bundling the JRE. Additional options can be configured with the "default_options" parameter:

# \${HOME} will be replaced by user home directory according to platformdefault_userdir="\${HOME}/.\${APPNAME}/${project.version}/dev"default_mac_userdir="\${HOME}/Library/Application Support/\${APPNAME}/${project.version}/dev"# options used by the launcher by default, can be overridden by explicit# command line switchesdefault_options="--branding ${brandingToken} -J-Xms64m -J-Xverify:none -J-Dsun.java2d.noddraw=true -J-Dsun.awt.noerasebackground=true -J-Dnetbeans.indexing.noFileRefresh=true"# for development purposes you may wish to append: -J-Dnetbeans.logger.console=true -J-ea# default location of JDK/JRE, can be overridden by using --jdkhome <dir> switch#jdkhome="/path/to/jdk"# clusters' paths separated by path.separator (semicolon on Windows, colon on Unices)#extra_clusters=

Finally, we also need to create a .icns icon file, it should have the same name as your brandingToken and be located in the application module at application/src/main/app-resources/

Resource Filtering

Now that we have our resource files, we need to substitute the properties. This is done using the maven-resources-plugin. There is nothing especially fancy here, we are setting filtering to true and copying the filtered resources into the target directory:

Build the Artifact

This is the big one, we download the JRE, create the package structure, sign the application and package it all up into a DMG image. By utilizing the maven-antrun-plugin we can do this in a procedural way.

Because we don't want to create the DMG image for every build, this is created as a profile which can be enabled independently of other features. I'll briefly describe the sections commented below to explain what is happening, but for further detail you'll need to carefully read the code.

Clean

In case the build fails, we need to delete any files which may have been left behind from a previous build. This will prevent accidentally releasing stale files.

Create folders

A special file structure needs to be created in order for the .app to be used, we start setting up that file structure by creating the folders.

Copy application

One important piece of the .app file structure is that the executable is put in a specific location. First we're copying the executable into the build directory so that we can do further modifications to it in a moment.

Copy logo and configuration files

Two more important files, copy the ${brandingToken}.icns and Info.plist files to their specified location.

Modify application script and copy to final location

We now move the executable into its final location in the .app file, which happens to be Contents/MacOS/. For OSX, the netbeans platform uses a shell script. We need to make a slight modification to this script because the relative location of the resources is different with this new file structure. To that end the replace command to update the working directory back to the bin directory so that everything works as it ought to.

Bundling the JRE

To bundle the JRE we first download it and untar the jre directory into .app/Contents/PlugIns/, some additional manipulation is needed to make sure everything works smoothly. Like resetting a quarantine bit and updating libjli.dylib

We also need to set the jdkhome variable in our .conf file, this is done with another substitution looking for #jdkhome="/path/to/jdk" and replacing it with the relative path to our bundled JRE.

Codesign JRE and App

This piece is something I haven't actually gotten to work yet. Presumably if you have your codesign identity configured properly on the build machine it should work as expected.

Create the DMG

Finally we need our DMG file complete with a symling to the /Application directory. You can take a look at the hdiutil and genisoimage commands which build the image on a OSX and Linux machines respectively.

How to use it

After all that, using it is a simple matter of running "mvn package -P create-dmg". The resulting file will look something like ugs-platform-app-2.0-SNAPSHOT.dmg and be located in your application/target directory.

Next Steps

The next steps for me are to create a windows installer with a bundled JRE, and setup TravisCI (which has OSX machines available) to build my artifacts.

Comments

Post a Comment

Popular posts from this blog

If you've ever worked with a vector tool to design something to be carved with a CNC routing machine, you may have needed to convert the resulting SVG file into a DXF. In my workflow I often use Inkscape to generate my SVG graphic, but my CAD software is CamBam and it requires a DXF file.

Many people have tried to make the perfect Inkscape plugin for exporting DXF, there is Better DXF Export, Better Better DXF Export and Big Blue Saw's DXF Export. I've had the most luck with the last one, but it was still finicky and often broke when updating Inkscape or OSX. Not only that, but at the end of the Big Blue Saw page, Simon mentions that he doesn't even use the plugin most of the time, instead relying on a command line tool called pstoedit.

With that in mind I made a small shell script which will convert an SVG file to DXF, you can save the script and run it directly or add the functions to your startup script.

I've been converting UGS to run on the Netbeans Platform over the past week, and in general its been great. The features and documentation I used while migrating UGS away from Swing were very well done. So aside from a couple hiccups, the Netbeans Platform has been great. Until a few days ago when I decided it was time to integrate my new changes with my Jenkins CI server.

It turns out that you can't build a Netbeans Platform application without connecting to the Netbeans IDE first. This has to do with the extra modules, platform components and build harness. Searching the subject online provides a lot of documentation, everything I've found is either out dated, contradictory, or I could only get it working with a new project and not my existing one.

The final solution was so simple, and minimally invasive that I wanted to create this blog post.

There are 4 interesting files for a Netbeans Platform build:build.xml - Some documentation suggests adding a download-netbeans-r…

Today we have a Sony TC-800B to take apart. This is a portable Reel-to-Reel recorder is from the late 60's or very early 70's and is the same device that was used to record the watergate tapes. Unlike the TEAC 360S I took apart last week, this machine was very easy to take apart.

The one I came across even had a carrying case, you might notice that the handle is crooked. This thing was somewhat busted up, if you look closely you can see through the window in the case that the plastic covering the reels is cracked too:

Will Winder is a software developer. In his four years of study at UNH he took variety of advanced Computer Science courses including Object Oriented Design, Computer Networks, Artificial Intelligence and Compiler Design. He has been working professionally using C, C++ and Java since graduating in 2006. In his free time he continues to expand his skills by involving himself in many projects, some of which can be seen on this blog.