Using the jpackager

Rachel Greenham rachel at merus.eu
Fri Nov 9 15:20:32 UTC 2018


FWIW what I'm doing to build a windows app for jpackager, in terms of 
gradle tasks, that isn't modular. I hope this cleans up over time, but 
this is the final result of having just got the damn thing to work! :-)

I think *eventually* we'll have a single call to jpackager do the whole 
lot. But I think we're not there yet, so it's split out into separate 
steps. You *will* need to make changes for your own context:

Build the JRE needed using JLink, supplying the needed modules. The 
JLink task referenced is actually written in Java and wraps 
ToolProvider, but it's pretty trivial and could almost-more-easily be 
done with an Exec. NB: The JLink task as written puts it in a "java" 
subdirectory of the given destinationDir.

     task buildAdminJre(type: JLink) {
         description 'Build the Client JRE for ' + nativeOsName
         destinationDir 
rootProject.file("deploy/bindist/"+requiredJava.merusNativeAdminJreName)
         modules = [
             'java.base',
             'java.desktop',
             'java.xml',
             'java.logging'
         ]
         bindServices false
         modulePath = 
[System.properties.getProperty('java.home')+File.separatorChar+'jmods']
         noHeaderFiles true
         noManPages true
         stripDebug true
     }

Have the jar task build the application as normal. Here's mine as an 
example. The important part is, you don't need to build a single fat 
jar, but you can include the dependent jars with the Class-Path line 
below. Mine isn't a JavaFX app, so I don't know what it does now wrt 
pulling in the openjfx jars here, or whether you add them as modules to 
the jlink task above. I expect the former would be preferable.

     /*
     Basic non-fat jar.
     */
     jar {
         manifest {
             attributes(
                 'Product-Name': applicationName,
                 'Main-Class': mainClassName,
                 'Package-Title': project.group,
                 'Package-Vendor': vendor,
                 'Package-Version': adminVersion,
                 'Permissions': "all-permissions",
                 'Class-Path': 
configurations.runtimeClasspath.files.collect { it.getName() }.join(' ')
             )
         }
         archiveName = 'adm.jar'
     }


Build the application image. This should be mostly platform independent. 
(I think the only thing stopping it being is probably the .ico file for 
the icon.) Note I'm supplying as --input the lib dir from the target 
built by the standard gradle installDist task, so it contains the 
dependencies as well. adm.jar's manifest lists these as dependencies in 
Class-Path as per above. The JRE is supplied in the --runtime-image 
parameter.

     task createImage(type: Exec) {
         description 'Build the App Image for this platform using jpackager'
         dependsOn installDist
         dependsOn buildAdminJre
         def imageDir = "$buildDir/image"
         outputs.dir new File(imageDir,applicationName)
         commandLine 'jpackager', 'create-image',
             '--verbose',
             '--version',adminVersion,
             '--input', new 
File(installDist.outputs.files.singleFile,"lib"),
             '--output',imageDir,
             '--name',applicationName,
             '--description','<DESCRIPTION>',
             '--main-jar','adm.jar',
             '--class',mainClassName,
             '--icon','src/main/files/app-icon.ico',
             '--runtime-image',new 
File(buildAdminJre.outputs.files.singleFile,"java"),
             '--vendor',vendor
     }

You'll now have the application image suitable for your platform. I've 
only tried this on Windows so far.

Note: My app has a space in the applicationName value. This breaks at 
the moment. The following is a workaround task, split out for easy 
removal later when I'm sure it won't be necessary any more:

     task fixWindowsImage(type: Copy) {
         /*
         This task creates a copy of the image created by createImage task
         and fixes it up for using as the source of a Windows Installer.
         As of first writing, what this means is renaming a couple of files,
         because create-image crushes out spaces in the application name but
         we want them in, we have to rename the generated .exe and .cfg 
files
         to have that space again.
         Then msiInstaller will work to actually create the shortcut and
         start menu items.
         */
         dependsOn createImage
         from createImage.outputs.files.singleFile
         into "$buildDir/fixedImage/$applicationName"
         rename ("<NAME-WITHOUT-SPACES>.exe", applicationName+".exe")
         rename ("<NAME-WITHOUT-SPACES>.cfg", applicationName+".cfg")
     }

Then, as a separate stage, run the bit that puts it into an installer. 
So here the input supplied in --app-image is the complete application 
image as already created and fixed by the above tasks.

     task msiInstaller(type: Exec) {
         /*
         see fixWindowsImage for a necessary fix to the created application
         image to allow shortcut and startmenu items to work.
         */
         description 'Build the Windows 64-bit MSI installer using 
jpackager'
         doFirst {
             /*
             For no reason I can discern, this task stopped working
             (did nothing, as if not called) until I added this 
doFirst{} block
             */
             println description
         }
         dependsOn fixWindowsImage
         def msiDir = "$buildDir/msi"
         def msiName = applicationName+'-'+adminVersion+'.msi'
         outputs.file new File(msiDir, msiName)
         commandLine 'jpackager', 'create-installer', 'msi',
             '--verbose',
             '--version',adminVersion,
             '--app-image',fixWindowsImage.outputs.files.singleFile,
             '--output', msiDir,
             '--name',applicationName,
             '--description','<DESCRIPTION>',
             '--icon','src/main/files/app-icon.ico',
             '--win-per-user-install',
             '--win-shortcut',
             '--win-menu',
             '--win-menu-group','<SUBMENU-NAME>',
             '--win-upgrade-uuid','<UUID>'
     }

Left for your own amusement, code-signing! (Off-Topic here although 
arguably jpackager could include that too.)

-- 
Rachel

On 09/11/2018 14:04, Lennart Börjeson wrote:
> I've been trying to understand how to use the jpackager, but I'm stumped.
>
> I have for a long been using the now defunct gradle-javafx plugin, so I've never really used the old javapackager either, only indirectly through gradle.
>
> So I'm maybe asking terribly noob questions, but here goes:
>
> Let's say I have a non-modular application packaged as an executable jar, i.e. a GUI I can launch with the "java -jar" command: How can I package this as a native application/dmg with jpackager?
>
> On a more general note: What's the required layout/contents of the "input directory"?
>
>
> Best regards,
>
> /Lennart Börjeson



More information about the openjfx-dev mailing list