Last month Apple released OS X Mavericks (10.9) and again removed Java 1.6 from your system during the upgrade. Since Oracle stopped support for Java 1.6 I think this is a good thing, and I want to avoid installing Java 1.6 for any application. At first CrashPlan seemed to work with Java 1.7 just fine, Crashplan’s menubar icon told me it was backing up and I didn’t get any alerts from Crashplan about my backups either. However today I noticed that CrashPlan will not start the client. When I start the CrashPlan client I’m greeted by a dialog that tells me that I need Java 1.6. When I looked at the CrashPlan support site it told me to install Java 1.6, what a shame! CrashPlan does offer an alternative, but it comes with a warning that it might cause problems with other applications and involves changing the application settings for Java 1.7. Since these settings are stored in a Info.plist file within the Java installation directory I would have to remember to set this each time I install a Java update. All in all not a good proposition, so armed with the knowledge that the application should work with Java 1.7 I started to look for a way to start it.
Piecing it together
The first clues for figuring out how to start the application come from the file /Applications/CrashPlan.app/Contents/Info.plist. This file holds information that is used by the system to determine how to start an application. For a java application like CrashPlan it tells about specific Java options and arguments to the Java Virtual Machine. The part that was of particular interest was the following:
<key>Java</key> <dict> <key>ClassPath</key> <array> <string>$JAVAROOT/lib/com.backup42.desktop.jar</string> <string>$JAVAROOT/skin</string> <string>$JAVAROOT/lang</string> </array> <key>JVMVersion</key> <string>1.5*</string> <key>JVMArches</key> <array> <string>i386</string> <string>ppc</string> </array> <key>MainClass</key> <string>com.backup42.desktop.CPDesktopWrapper</string> <key>StartOnMainThread</key> <true/> <key>VMOptions</key> <array> <string>-Dfile.encoding=UTF-8</string> <string>-DappBaseName=CrashPlan</string> <string>-Dsun.net.inetaddr.ttl=300</string> <string>-Dnetworkaddress.cache.ttl=300</string> <string>-Dsun.net.inetaddr.negative.ttl=0</string> <string>-Dnetworkaddress.cache.negative.ttl=0</string> <string>-Djava.net.preferIPv4Stack=true</string> </array> <key>WorkingDirectory</key> <string>$APP_PACKAGE/Contents/Resources/Java</string> </dict>
In this block some crucial information is set. The name of the main class that has to be invoked (MainClass key), the class path used by the application (ClassPath key), options to the JVM (VMOptions key) and the working directory of the application (WorkingDirectory key).
Exceptions…
With this information I created a first version of a shell script to start the application. However it quickly failed with an Exception:
[11.06.13 22:12:15.057 ERROR main root ] Failed to launch CPDesktop; java.lang.NoClassDefFoundError: org/eclipse/swt/widgets/Display Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/swt/widgets/Display at com.backup42.desktop.CPDesktop.<init>(CPDesktop.java:266) at com.backup42.desktop.CPDesktop.main(CPDesktop.java:200) at com.backup42.desktop.CPDesktopWrapper.main(CPDesktopWrapper.java:23) Caused by: java.lang.ClassNotFoundException: org.eclipse.swt.widgets.Display at java.net.URLClassLoader$1.run(URLClassLoader.java:366) at java.net.URLClassLoader$1.run(URLClassLoader.java:355) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:354) at java.lang.ClassLoader.loadClass(ClassLoader.java:425) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) at java.lang.ClassLoader.loadClass(ClassLoader.java:358) ... 3 more
So the application was not finding all the classes. An ls on the lib directory gave a list of additional jar files.
darwin:Java enckevort$ ls -l lib/ total 40568 -rw-r--r-- 1 root staff 1290808 18 apr 2013 c42_protolib.jar -rw-r--r-- 1 root staff 5716574 18 apr 2013 com.backup42.desktop.jar -rw-r--r-- 1 root staff 215243 18 apr 2013 com.jniwrapper.jniwrap.jar -rw-r--r-- 1 root staff 1285054 18 apr 2013 com.jniwrapper.macpack.jar -rw-r--r-- 1 root staff 706581 18 apr 2013 com.jniwrapper.winpack.jar -rw-rw-r-- 1 enckevort staff 268794 16 mrt 2011 commons-jxpath-1.1.jar -rw-r--r-- 1 enckevort staff 946973 16 mrt 2011 jna-3.2.5.jar -rw-r--r-- 1 enckevort staff 41829 16 mrt 2011 json-20070829.jar -rw-r--r-- 1 root staff 159123 27 mrt 2012 json-lib-2.4.jar -rw-rw-r-- 1 enckevort staff 41291 16 mrt 2011 jtux.jar -rw-r--r-- 1 root staff 481534 27 mrt 2012 log4j-1.2.16.jar -rw-rw-r-- 1 enckevort staff 69028 16 mrt 2011 miglayout15-swt.jar -rw-r--r-- 1 root staff 108731 18 apr 2013 org.eclipse.core.commands_3.6.1.v20120814-150512.jar -rw-r--r-- 1 root staff 106763 18 apr 2013 org.eclipse.equinox.common_3.6.100.v20120522-1841.jar -rw-r--r-- 1 root staff 1090959 18 apr 2013 org.eclipse.jface_3.8.101.v20120817-083647.jar -rw-r--r-- 1 root staff 1396459 18 apr 2013 org.eclipse.osgi_3.8.1.v20120830-144521.jar -rw-r--r-- 1 root staff 450279 27 mrt 2012 protobuf-java-2.4.1.jar -rw-rw-r-- 1 enckevort staff 81591 16 mrt 2011 sbbi-upnplib-1.0.4.jar -rw-r--r-- 1 root staff 25496 18 apr 2013 slf4j-api-1.6.1.jar -rw-r--r-- 1 root staff 9753 18 apr 2013 slf4j-log4j12-1.6.1.jar -rw-r--r-- 1 root staff 1664400 18 apr 2013 swt-64.jar -rw-r--r-- 1 root staff 1760744 18 apr 2013 swt.jar -rw-r--r-- 1 root staff 2522902 27 mrt 2012 trove-3.0.2.jar -rw-r--r-- 1 root staff 284059 18 apr 2013 twitter4j.jar
So my second attempt was to just add all those jar files to the classpath. Unfortunately, and slightly surprisingly that didn’t work! I got a new exception:
[11.06.13 22:15:31.797 ERROR main com.backup42.desktop.CPDesktop ] Failed to launch CPDesktop; java.lang.UnsatisfiedLinkError: Cannot load 32-bit SWT libraries on 64-bit JVM, java.lang.UnsatisfiedLinkError: Cannot load 32-bit SWT libraries on 64-bit JVM java.lang.UnsatisfiedLinkError: Cannot load 32-bit SWT libraries on 64-bit JVM at org.eclipse.swt.internal.Library.loadLibrary(Unknown Source) at org.eclipse.swt.internal.Library.loadLibrary(Unknown Source) at org.eclipse.swt.internal.C.<clinit>(Unknown Source) at org.eclipse.swt.widgets.Display.<clinit>(Unknown Source) at com.backup42.desktop.CPDesktop.<init>(CPDesktop.java:266) at com.backup42.desktop.CPDesktop.main(CPDesktop.java:200) at com.backup42.desktop.CPDesktopWrapper.main(CPDesktopWrapper.java:23)
Okay, so it is trying to load the wrong libraries… what’s going on?
I decided to check if the application had a manifest file. This is a file that can be present in a jar file and describes the additional jar files to add to the classpath when trying to load a class. The file has a fixed location and name: META-INF/MANIFEST.MF. So I checked for the presence with the following command:
unzip -l lib/com.backup42.desktop.jar META-INF/MANIFEST.MF
I was in luck, the jar file did indeed come with a manifest file. I extracted it and got the following file:
Manifest-Version: 1.0 Ant-Version: Apache Ant 1.8.2 Created-By: 1.6.0_31-b04-415-11M3646 (Apple Inc.) Built-By: Code 42 Software Class-Path: c42_protolib.jar com.jniwrapper.jniwrap.jar com.jniwrapper .macpack.jar com.jniwrapper.winpack.jar comfyj-2.10.jar commons-colle ctions-3.2.1-mini.jar commons-jxpath-1.1.jar jna-3.2.5.jar json-20070 829.jar json-lib-2.4.jar jtux.jar log4j-1.2.16.jar miglayout15-swt.ja r org.eclipse.core.commands_3.6.1.v20120814-150512.jar org.eclipse.eq uinox.common_3.6.100.v20120522-1841.jar org.eclipse.jface_3.8.101.v20 120817-083647.jar org.eclipse.osgi_3.8.1.v20120830-144521.jar protobu f-java-2.4.1.jar rhino-1.7r3.jar sbbi-upnplib-1.0.4.jar slf4j-api-1.6 .1.jar slf4j-log4j12-1.6.1.jar trove-3.0.2.jar twitter4j.jar
When I created the classpath based on this list I got again one step further. However I still hadn’t solved the puzzle, because I got another Exception:
***WARNING: Display must be created on main thread due to Cocoa restrictions. [11.06.13 22:27:46.058 ERROR main root ] Failed to launch CPDesktop; org.eclipse.swt.SWTException: Invalid thread access Exception in thread "main" org.eclipse.swt.SWTException: Invalid thread access at org.eclipse.swt.SWT.error(Unknown Source) at org.eclipse.swt.SWT.error(Unknown Source) at org.eclipse.swt.SWT.error(Unknown Source) at org.eclipse.swt.widgets.Display.error(Unknown Source) at org.eclipse.swt.widgets.Display.createDisplay(Unknown Source) at org.eclipse.swt.widgets.Display.create(Unknown Source) at org.eclipse.swt.graphics.Device.<init>(Unknown Source) at org.eclipse.swt.widgets.Display.<init>(Unknown Source) at org.eclipse.swt.widgets.Display.<init>(Unknown Source) at org.eclipse.swt.widgets.Display.getDefault(Unknown Source) at com.backup42.desktop.CPDesktop.<init>(CPDesktop.java:354) at com.backup42.desktop.CPDesktop.main(CPDesktop.java:200) at com.backup42.desktop.CPDesktopWrapper.main(CPDesktopWrapper.java:23)
I was pretty convinced that I had the options right, and the warning message told me I was looking at a OS X specific issue, so I did a quick internet search for the message “***WARNING: Display must be created on main thread due to Cocoa restrictions.”. Quickly I found out that I was missing one OS X specific setting: -XstartOnFirstThread. When I added it my last exception was gone and I could start the CrashPlan client!
The script
The final version of the shell script lives in my /usr/local/bin and looks like this:
#!/bin/bash -e APP_PACKAGE='/Applications/CrashPlan.app' JAVAROOT="${APP_PACKAGE}/Contents/Resources/Java" cd "${JAVAROOT}" CLASSPATH="${JAVAROOT}/skin:${JAVAROOT}/lang" JARS="com.backup42.desktop.jar c42_protolib.jar com.jniwrapper.jniwrap.jar com.jniwrapper.macpack.jar com.jniwrapper.winpack.jar comfyj-2.10.jar commons-collections-3.2.1-mini.jar commons-jxpath-1.1.jar jna-3.2.5.jar json-20070829.jar json-lib-2.4.jar jtux.jar log4j-1.2.16.jar miglayout15-swt.jar org.eclipse.core.commands_3.6.1.v20120814-150512.jar org.eclipse.equinox.common_3.6.100.v20120522-1841.jar org.eclipse.jface_3.8.101.v20120817-083647.jar org.eclipse.osgi_3.8.1.v20120830-144521.jar protobuf-java-2.4.1.jar rhino-1.7r3.jar sbbi-upnplib-1.0.4.jar slf4j-api-1.6.1.jar slf4j-log4j12-1.6.1.jar trove-3.0.2.jar twitter4j.jar" for JAR in ${JARS} do CLASSPATH="${JAVAROOT}/lib/${JAR}:${CLASSPATH}" done java -XstartOnFirstThread -Dfile.encoding=UTF-8 -DappBaseName=CrashPlan -Dsun.net.inetaddr.ttl=300 -Dnetworkaddress.cache.ttl=300 -Dsun.net.inetaddr.negative.ttl=0 -Dnetworkaddress.cache.negative.ttl=0 -Djava.net.preferIPv4Stack=true -cp "${CLASSPATH}" com.backup42.desktop.CPDesktopWrapper
Conclusion
I can now start the CrashPlan client with the command crashplan in Terminal. Trying to start CrashPlan from the menubar icon or from /Applications still gives the same message about needing Java 1.6. This script shows however that there is no reason why CrashPlan shouldn’t support Java 1.7 and I hope it will help convince Code 42 to resolve this issue. It is already three years ago that Apple announced that it would phase out support for its Java Virtual Machine, so it is really a shame that Code 42 hash’t made an effort to support Java 1.7.