Discussion:
Using the jpackager
Lennart Börjeson
2018-11-09 14:04:08 UTC
Permalink
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
Nir Lisker
2018-11-09 14:17:36 UTC
Permalink
There are some instructions in the JEP [1]. They show how to create a dmg.

[1]
https://bugs.openjdk.java.net/browse/JDK-8200758?focusedCommentId=14217780&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-14217780
Post by Lennart Börjeson
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.
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
Lennart Börjeson
2018-11-09 15:08:40 UTC
Permalink
You're thinking of the "HelloWorld.exe" example? That's not really informative, since it doesn't specify how to create the contents of the directory, nor the layout of the levels below app/ and runtime/.

I have a runnable jar, let's call it APP.jar.

If I try the naive: jpackager create-image -o out -j APP.jar

Error messages:

Bundler Mac Application Image skipped because of a configuration problem: The configured main jar does not exist APP.jar
Advice to fix: The main jar must be specified relative to the app resources (not an absolute path), and must exist within those resources.



OK, let's do that. Put the jar in an "input dir": jpackager create-image -o out -i in -j APP.jar

Error messages:

Creating app bundle: /Users/lennartb/App/out/App.app
"Adding modules: [] to runtime image."
Exception: jdk.tools.jlink.plugin.PluginException: java.io.IOException: jdk.tools.jlink.plugin.PluginException: ModuleTarget attribute is missing for java.base module
Error: Bundler "Mac Application Image" (mac.app) failed to produce a bundle.



I have of course tried a lot of other things, but I'd really like some pointers here...

Best regards,

/Lennart
Post by Nir Lisker
There are some instructions in the JEP [1]. They show how to create a dmg.
[1] https://bugs.openjdk.java.net/browse/JDK-8200758?focusedCommentId=14217780&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-14217780
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.
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
Rachel Greenham
2018-11-09 15:20:32 UTC
Permalink
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
Post by Lennart Börjeson
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.
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
Rachel Greenham
2018-11-09 15:41:11 UTC
Permalink
Post by Rachel Greenham
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.
Correction, the *latter* would be preferable, of course.
--
Rachel
Sverre Moe
2018-11-09 21:34:30 UTC
Permalink
Post by Rachel Greenham
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
}
Which gradle plugin are you using that gives you type JLink?
Rachel Greenham
2018-11-09 23:59:27 UTC
Permalink
None, that's just my own class task in buildSrc. It's a fairly trivial
wrapper around a ToolProvider invocation of jlink, so I didn't think it
was relevant enough to paste the source for that here too, when you
could also do it trivially with an Exec task similar to the jpackager
ones later in my first reply.

It's literally public class JLink extends DefaultTask with a bunch of
getters and setters for the options and a run method that turns them
into arguments and invokes via ToolProvider. The same task type is also
used elsewhere in the root project to build the server JRE for the
server component of this project, which was enough to make it worthwhile
writing the task. (I also wanted to learn how to write java gradle
tasks.) The point of the example I did paste was just about showing what
options to set to jlink. jlink in any detail is offtopic here, this bit
was just an illustration of using that to build the JRE first with the
required modules, then use the result in jpackager to build a
non-modular app.

I would expect to write a similar simple task for jpackager too at some
point, I just haven't yet. They may be the basis of a useful packager
plugin, but probably not before jpackager itself is more mature.
--
Rachel
Post by Rachel Greenham
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
     }
Which gradle plugin are you using that gives you type JLink?
Loading...