Creating a Distribution Package of Your Java Application
Say, you have written a Java application and now it is time to create windows installer package to upload it to your site so that anybody could download it, run and enjoy your proggy. Of course, one can distribute a plain JAR file, but not all users will be able to start it successfully.
It is better to give them a normal plain installer package that they know how to run through the wizard. You may also want to bundle a JRE with your application so your program will run on every machine with no JRE installed. I would also advise obfuscate your code, thus, no evil hacker could get your code too easily. It also makes sense to wrap your Java application in a regular windows executable (EXE) file.
You will need the following tools and libraries that are distributed free of charge:
1. Ant tool.
2. NSIS: a scriptable win32 installer/uninstaller system.
3. ProGuard: java shrinker, optimizer, and obfuscator.
4. Launch4j: Cross-platform Java executable wrapper for creating lightweight Windows native EXEs.
5. NSIS Ant task: to compile NSIS scripts.
The goal is to create a single build.xml file that will compile (JAVAC), obfuscate, shrink, optimize (ProGuard) our code, create an EXE file (Launch4j) and pack it all including other files (like license text file) to an installer package (NSIS).
The ant build file consists of a number of targets that can depend upon other targets in the same build file. That means before the target does something useful it will run the targets it depends upon:
Initialization Target
First, let’s define a number of properties:
XML:
-
<property name="src" location="d:\myproggy\src" />
-
<property name="lib" location="lib" />
-
<property name="images" location="images" />
-
<property name="nsis" location="nsis" />
-
<property name="dist" location="dist" />
-
<property name="html" location="html" />
-
<property name="build" location="build" />
-
<property name="launch4j.dir" location="../.." />
-
<property name="original.dir" location="d:\myproggy" />
You may want to justify the paths according to your system.
The target init will depend upon target ‘clean’ – we want to clean everything before the build starts:
XML:
-
<target name="clean" description="clean up">
-
<delete dir="${build}"/>
-
<delete dir="${html}"/>
-
<delete dir="${images}"/>
-
<delete dir="${lib}"/>
-
<delete dir="${dist}"/>
-
</target>
After it is over, the target ‘init’ will run.
XML:
-
<target name="init" depends="clean">
-
<tstamp />
-
<mkdir dir="${build}" />
-
<mkdir dir="${html}" />
-
<mkdir dir="${images}" />
-
<mkdir dir="${lib}" />
-
<mkdir dir="${dist}" />
-
-
<copy todir="${html}">
-
<fileset dir="${original.dir}/html/"/>
-
</copy>
-
-
<copy todir="${images}">
-
<fileset dir="${original.dir}/images/"/>
-
</copy>
-
-
<copy todir="${lib}">
-
<fileset dir="${original.dir}/lib"/>
-
</copy>
-
-
</target>
We create a number of folders, copy source code, libraries, html, and image files.
Compile Target
XML:
-
<target name="compile" depends="init" description="compile the source">
-
<javac srcdir="${src}" destdir="${build}" classpathref="dist.classpath" source="1.5" debug="on" />
-
-
<copy todir="${build}">
-
<fileset dir="${src}">
-
<include name="*.vm"/>
-
<include name="*.properties"/>
-
</fileset>
-
</copy>
-
-
</target>
Compile the classes and copy the required files, such as Velocity templates and property files to the folder with the freshly compiled classes.
JAR Target
Next step is to create a JAR file and feed it to ProGuard that will “obfuscate, shrink, optimize” our code. ProGuard task has a number of limitations and requirements, so while my example is pretty clear you may want to read the ProGuard documentation. In a few words you name the libraries that are used by your application, specify you jar file name, and the name of the jar that will be the result of ProGuard’s work. ProGuard modifies the names of classes, so you need to keep the name of your Main-Class untouched. Anyway, consult the docs or you may run into some issues here.
XML:
-
<target name="jar" depends="compile" description="create the jar">
-
-
<fileset dir="${lib}" id="lib.dist.fileset">
-
<include name="**/*.jar" />
-
</fileset>
-
-
<pathconvert pathsep=" " property="dist.classpath" refid="lib.dist.fileset">
-
<map from="${lib}" to=".\lib" />
-
</pathconvert>
-
-
<!-- Put everything in ${build} into a jar file -->
-
<jar jarfile="${ant.project.name}.jar">
-
<fileset dir="${build}" includes="**/*" />
-
<manifest>
-
<!-- SET YOUR MAIN CLASS HERE -->
-
<attribute name="Main-Class" value="com.javazing.MyProggyLauncher" />
-
<attribute name="Class-Path" value=". ${dist.classpath}" />
-
</manifest>
-
</jar>
-
-
<!-- obfuscate and optimize by ProGuard -->
-
<taskdef resource="proguard/ant/task.properties" classpath="${launch4j.dir}/lib/proguard.jar" />
-
<proguard>
-
-
-libraryjars jre/lib/rt.jar
-
-libraryjars lib/commons-collections-3.1.jar
-
-libraryjars lib/commons-io-1.2.jar
-
-libraryjars lib/commons-lang-2.1.jar
-
-libraryjars lib/commons-logging.jar
-
-libraryjars lib/hsqldb.jar
-
-libraryjars lib/jdom-1.0.jar
-
-libraryjars lib/log4j.jar
-
-libraryjars lib/lucene-core-1.9.1.jar
-
-libraryjars lib/org.eclipse.core.commands_3.2.0.I20060511-0800a.jar
-
-libraryjars lib/org.eclipse.equinox.common_3.2.0.v20060512.jar
-
-libraryjars lib/org.eclipse.jface.text_3.2.0.v20060518-0800.jar
-
-libraryjars lib/org.eclipse.jface_3.2.0.I20060511-0800a.jar
-
-libraryjars lib/org.eclipse.osgi_3.2.0.v20060510.jar
-
-libraryjars lib/org.eclipse.swt.win32.win32.x86_3.2.0.v3224.jar
-
-libraryjars lib/org.eclipse.text_3.2.0.v20060518-0800.jar
-
-libraryjars lib/rome-0.8.jar
-
-libraryjars lib/velocity-1.4.jar
-
-
-injars ${ant.project.name}.jar
-
-outjars obfuscated_${ant.project.name}.jar
-
-
-keepclasseswithmembers public class * {
-
public static void main(java.lang.String[]);
-
}
-
-
-dontskipnonpubliclibraryclasses
-
-
-keep public class com.javazing.MyProggyLauncher
-
-
-keep class * implements java.sql.Driver
-
-
-
</proguard>
-
-
</target>
EXE Target
After the JAR target, we have a jar file that needs to be wrapped to become a native Windows executable file. We use Launch4j tool here. Launch4j needs an xml file with the settings:
proggy.xml file:
XML:
-
<launch4jConfig>
-
<dontWrapJar>false</dontWrapJar>
-
<headerType>0</headerType>
-
<jar>..\obfuscated_my_cool_proggy.jar</jar>
-
<outfile>..\my_cool_proggy.exe</outfile>
-
<errTitle>MyProggy</errTitle>
-
<jarArgs></jarArgs>
-
<chdir>.</chdir>
-
<customProcName>false</customProcName>
-
<stayAlive>false</stayAlive>
-
<icon>proggy.ico</icon>
-
<jre>
-
<path>jre</path>
-
<minVersion>1.5.0</minVersion>
-
<maxVersion></maxVersion>
-
<initialHeapSize>0</initialHeapSize>
-
<maxHeapSize>0</maxHeapSize>
-
<args></args>
-
</jre>
-
<splash>
-
<file>proggy.bmp</file>
-
<waitForWindow>true</waitForWindow>
-
<timeout>60</timeout>
-
<timeoutErr>true</timeoutErr>
-
</splash>
-
</launch4jConfig>
You may want to get an ICO & BMP images for your application. The icon file is the image to be rendered at the left top corner of every Windows application. The BMP file will be displayed as a splash file when the proggy starts. Java 1.6 has built-in splash functionality, so you may want to use it along with some nice PNG image with shadows and other alpha tricks, and not the Launch4J’s ugly bmp file.
XML:
-
<target name="exe" depends="jar">
-
<taskdef name="launch4j" classname="net.sf.launch4j.ant.Launch4jTask" classpath="${launch4j.dir}/launch4j.jar
-
:${launch4j.dir}/lib/xstream.jar" />
-
<launch4j configFile="./l4j/proggy.xml" />
-
-
</target>
Distibution Target
NSIS comes with a number of templates, samples, some nice graphics and GUI application. Therefore, you will definitely want to see its samples. my_proggy.nsi is the name of the script that will guide you installation and deinstallation processes: create folders, copy files, create menu group, create desktop icons etc.
Sample NSIS script:
NSIS:
-
!include "MUI.nsh"
-
-
!define MUI_ICON "proggy.ico"
-
!define MUI_UNICON "proggy.ico"
-
-
Function .onInit
-
# the plugins dir is automatically deleted when the installer exits
-
InitPluginsDir
-
File /oname=$PLUGINSDIR\splash.bmp "D:\myproggy\splash.bmp"
-
#optional
-
#File /oname=$PLUGINSDIR\splash.wav "C:\myprog\sound.wav"
-
-
splash::show 1000 $PLUGINSDIR\splash
-
-
Pop $0 ; $0 has '1' if the user closed the splash screen early,
-
; '0' if everything closed normally, and '-1' if some error occurred.
-
FunctionEnd
-
-
-
;--------------------------------
-
;General
-
-
;Name and file
-
Name "${PRODUCT_NAME}"
-
OutFile "${EXE_NAME}"
-
-
;Default installation folder
-
InstallDir "$PROGRAMFILES\${PRODUCT_NAME}"
-
-
;Get installation folder from registry if available
-
InstallDirRegKey HKCU "Software\${PRODUCT_NAME}" ""
-
-
;--------------------------------
-
;Interface Configuration
-
-
!define MUI_HEADERIMAGE
-
!define MUI_HEADERIMAGE_BITMAP "proggy_header.bmp" ; optional
-
-
;--------------------------------
-
;Variables
-
-
Var MUI_TEMP
-
Var STARTMENU_FOLDER
-
-
;--------------------------------
-
;Interface Settings
-
-
!define MUI_ABORTWARNING
-
-
;--------------------------------
-
;Pages
-
-
!insertmacro MUI_PAGE_LICENSE "license.rtf"
-
!insertmacro MUI_PAGE_DIRECTORY
-
-
;Start Menu Folder Page Configuration
-
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU"
-
!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\${PRODUCT_NAME}"
-
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
-
!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
-
!insertmacro MUI_PAGE_INSTFILES
-
!insertmacro MUI_UNPAGE_CONFIRM
-
!insertmacro MUI_UNPAGE_INSTFILES
-
-
;--------------------------------
-
;Languages
-
-
!insertmacro MUI_LANGUAGE "English"
-
-
;--------------------------------
-
;Installer Sections
-
Section "install"
-
-
;Add files
-
SetOutPath "$INSTDIR"
-
-
File "${FOLDER}\${EXE_NAME}"
-
File "${FOLDER}\settings.ini"
-
-
SetOutPath "$INSTDIR"
-
file /r ${FOLDER}\images
-
file /r ${FOLDER}\html
-
file /r ${FOLDER}\jre
-
file /r ${FOLDER}\lib
-
file ${FOLDER}\evaluation_license.txt
-
file ${FOLDER}\license.rtf
-
-
;create desktop shortcut
-
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\${EXE_NAME}" ""
-
-
;create start-menu items
-
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
-
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
-
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\${EXE_NAME}" "" "$INSTDIR\${EXE_NAME}" 0
-
-
;write uninstall information to the registry
-
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${SHORT_NAME}" "DisplayName" "${SHORT_NAME} (remove only)"
-
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${SHORT_NAME}" "UninstallString" "$INSTDIR\Uninstall.exe"
-
-
WriteUninstaller "$INSTDIR\Uninstall.exe"
-
-
SectionEnd
-
;--------------------------------
-
;Uninstaller Section
-
-
Section "Uninstall"
-
-
;ADD YOUR OWN FILES HERE...
-
;Delete Files
-
RMDir /r "$INSTDIR\*.*"
-
-
;Remove the installation directory
-
RMDir "$INSTDIR"
-
-
;Delete Start Menu Shortcuts
-
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
-
Delete "$SMPROGRAMS\${PRODUCT_NAME}\*.*"
-
RmDir "$SMPROGRAMS\${PRODUCT_NAME}"
-
-
;Delete Uninstaller And Unistall Registry Entries
-
DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\${SHORT_NAME}"
-
DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${SHORT_NAME}"
-
-
Delete "$INSTDIR\Uninstall.exe"
-
-
RMDir "$INSTDIR"
-
-
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
-
-
Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
-
-
;Delete empty start menu parent diretories
-
StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
-
-
startMenuDeleteLoop:
-
ClearErrors
-
RMDir $MUI_TEMP
-
GetFullPathName $MUI_TEMP "$MUI_TEMP\.."
-
-
IfErrors startMenuDeleteLoopDone
-
-
StrCmp $MUI_TEMP $SMPROGRAMS startMenuDeleteLoopDone startMenuDeleteLoop
-
startMenuDeleteLoopDone:
-
-
DeleteRegKey /ifempty HKCU "Software\${PRODUCT_NAME}"
-
-
SectionEnd
-
-
;Function that calls a messagebox when installation finished correctly
-
Function .onInstSuccess
-
MessageBox MB_OK "You have successfully installed ${PRODUCT_NAME}. Use the desktop icon to start the program."
-
FunctionEnd
-
-
Function un.onUninstSuccess
-
MessageBox MB_OK "You have successfully uninstalled ${PRODUCT_NAME}."
-
FunctionEnd
And the appropriate target:
XML:
-
<target name="dist" depends="exe" description="make installation package for distribution">
-
-
<taskdef name="nsis" classname="net.sf.nsisant.Task">
-
<classpath location="nsisant-1.2.jar"/>
-
</taskdef>
-
-
<nsis script="nsis/my_proggy.nsi" verbosity="4" out="nsis_build.log" noconfig="yes">
-
<define name="VERSION" value="1.0"/>
-
<define name="PRODUCT_NAME" value="My Proggy"/>
-
<define name="EXE_NAME" value="${ant.project.name}.exe"/>
-
<define name="SHORT_NAME" value="MyCoolProggy"/>
-
<define name="FOLDER" value="D:\launch4j\MyProggy"/>
-
</nsis>
-
-
<copy file="${nsis}/${ant.project.name}.exe" tofile="${dist}/${ant.project.name}.exe"/>
-
-
<delete file="${nsis}/${ant.project.name}.exe"/>
-
-
<delete dir="${build}"/>
-
<delete dir="${html}"/>
-
<delete dir="${images}"/>
-
<delete dir="${lib}"/>
-
-
<delete file="${ant.project.name}.jar"/>
-
<delete file="${ant.project.name}.exe"/>
-
<delete file="obfuscated_${ant.project.name}.jar" />
-
-
-
</target>
It is really convenient. After you make some changes in the code, you run a single file and have an installer package ready.
By the way, it also makes sense to check for the required system paths before the build process. Create a .BAT file:
DOS:
Good luck!
Download build.xml file.
April 5th, 2007 at 4:02 am
Even if nothing new, this is a very nice "compilation", thank you.
The only problem is that there's no such solution for java web applications. A webapplication would be distributed as a WAR file, but the problem is:
#1 there's no generic lightweigh web based installer.
#2 there's no obfuscator that can work in a webapplication and be aware of JSPs (java server pages) or other wide used web tehnologies.
#3 and finally there's no tutorial like your's that summaries everything in one page, so that "it just works" :).
Thank you,
Demetrios.
April 5th, 2007 at 5:34 am
[...] http://www.javazing.com/2007/04/05/creating-a-distribution-package-of-your-java-application/ [...]
December 23rd, 2007 at 4:38 pm
[...] Stuff Javazing blog has an article on how to do that. Ultimately before you’ll be able to follow the tutorial you going to have to download the [...]
March 22nd, 2008 at 3:00 am
Hello
I have an application i created in java, But i find it hard to create an icon that will run the application !!!!!!!.
May 16th, 2008 at 12:46 am
Sergey,
Thank you for pointing me in this direction. I have been looking for it for long. I have made some simple programs to be distributed along with a book I am writing. Purchasing commercial utility programs is at this point for me expensive. This can solve my problem. Hope is not too complicated because of the use of the 4 tools and libraries mentioned above. I am also sure that many other programmers are in the same boat as me. I will try, and will be in contact with you in the future. At any rate, I do thank you very much.
Paco De la Puerta
August 21st, 2008 at 12:54 am
Hi..
Thank you so much. I had some difficulty connecting all these 4 tools in producing an installer for my project. But I managed to get it right after 3,4 days of effort.
September 24th, 2008 at 3:35 pm
This is a great setup, thanks for writing about it. I've been tasked to come up with the deployment/install strategy for a new product here at work and came up with the same idea... sort of... I'm glad to see that this idea has worked for someone else.
Btw, I saw that NSIS has a few examples on how to create a native launcher via NSIS so you could eliminate the launch4J step.
October 8th, 2008 at 10:23 pm
Hi...
Thank you. your blog has helped me to an extent, but still I am facing some difficulties. I am glad if u can help me in this issue. I have to create a installation file which copies files to a specified folder and then inserts an icon into existing software application and clicking that icon should call the main class of that .jar file.
February 22nd, 2009 at 10:31 am
Thank u sir.. it is more use full...It teach nice thing & who expert in java & wish to improve the performance of java application using native code..
May 25th, 2009 at 2:24 pm
Sergey, you do great job, many thanks. A bit pity, what have not explanations how to integrate this solutions in netbeans. But I see that you use eclipse IDE.
July 9th, 2009 at 11:04 am
Hi Sergey
Its a clear and neat expected explanation for distribution of Java application ,its much appreciated.
For me,it may take another two or more readings to understand and imply it completely,none the less,your step by step explanation is awesome.
As of now,my Java application development is simple so I haven't used any IDE environment for developing Java applications.I may use IDE environment soon,most likely "Netbeans".
If possible,throwing lights on integrating these in "Netbeans" environment will be much appreciated.
Well, I also thank Grigory for mentioning about IDE platform in his comments in this post.
By
Venky
July 9th, 2009 at 12:01 pm
Hi Sergey,
Its clear and neat expected explanation for distribution of Java applications,its much appreciated.
For me it may take two or more readings to use it effectively.
If possible,please throw lights on integrating in "Netbeans" environment,it will be much appreciated.
With Thanks
Venky
September 12th, 2009 at 8:30 am
How can i create a java program that installs the startup image of a phone. thanks