A while ago I got into
a fight with jpackage.
Long story short, I concluded that it wasn't possible to use jpackage
to
produce an application that runs in "module path" mode but that also has
one or more automatic modules.
It turns out that it is possible, but it's not obvious from the documentation at all and requires some extra steps. The example project demonstrates this.
Assume I've got an application containing modules com.io7m.demo.m1
,
com.io7m.demo.m2
, and com.io7m.demo.m3
. The com.io7m.demo.m3
module
is the module that contains the main class (at com.io7m.demo.m3/com.io7m.demo.m3.M3
).
In this example, assume that com.io7m.demo.m2
is actually an automatic module
and therefore would cause jlink
to fail if it tried to process it.
I first grab a JDK from Foojay and unpack it:
$ wget -O jdk.tar.gz -c 'https://api.foojay.io/disco/v3.0/ids/9604be3e0c32fe96e73a67a132a64890/redirect' $ mkdir -p jdk $ tar -x -v --strip-components=1 -f jdk.tar.gz --directory jdk
Then I grab a JRE from Foojay and unpack it:
$ wget -O jre.tar.gz -c 'https://api.foojay.io/disco/v3.0/ids/3981936b6f6b297afee4f3950c85c559/redirect' $ mkdir -p jre $ tar -x -v --strip-components=1 -f jre.tar.gz --directory jre
I could reuse the same JDK from the first step, but the JRE is smaller and
thus it makes the application distribution smaller as we won't be using jlink
to strip out any unused modules.
I then build the application, and this produces a set of platform-independent modular jar files:
$ mvn clean package
I copy the jars into a jars
directory:
$ cp ./m1/target/m1-20231111.jar jars $ cp ./m2/target/m2-20231111.jar jars $ cp ./m3/target/m3-20231111.jar jars
Then I call jpackage
:
$ jpackage \ --runtime-image jre \ -t app-image \ --module com.io7m.demo.m3 \ --module-path jars --name jpackagetest
The key argument that makes this work is the --runtime-image
option. It
effectively means "don't try to produce a reduced jlink
runtime".
This produces an application that works correctly:
$ file jpackagetest/bin/jpackagetest jpackagetest/bin/jpackagetest: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, not stripped $ ./jpackagetest/bin/jpackagetest M1: Module module com.io7m.demo.m1 M2: Module module m2 JRT: java.base JRT: java.compiler JRT: java.datatransfer JRT: java.desktop ...
We can see from the first two lines of output that both com.io7m.demo.m1
and (the badly-named) m2
are on the module path and have not been placed
on the class path. This means that any services declared in the module
descriptors will actually work properly.
We can take a look at the internal configuration:
$ cat jpackagetest/lib/app/jpackagetest.cfg [Application] app.mainmodule=com.io7m.demo.m3/com.io7m.demo.m3.M3 [JavaOptions] java-options=-Djpackage.app-version=20231111 java-options=--module-path java-options=$APPDIR/mods
We can see that the internal configuration uses an (undocumented) $APPDIR
variable that expands to the full path to a mods
directory inside the
application distribution. The mods
directory contains the unmodified
application jars:
$ ls jpackagetest/lib/app/mods/ m1-20231111.jar m2-20231111.jar m3-20231111.jar $ sha256sum jars/* f8de3acf245428576dcf2ea47f5eb46cf64bb1a5daf43281e9fc39179cb3154f jars/m1-20231111.jar 6ad0f7357cf03dcc654a3f9b8fa8ce658826fc996436dc848165f6f92973bb90 jars/m2-20231111.jar b5c4d7d858dad6f819d224dd056b9b54009896a02b0cd5c357cf463de0d9fdd2 jars/m3-20231111.jar $ sha256sum jpackagetest/lib/app/mods/* f8de3acf245428576dcf2ea47f5eb46cf64bb1a5daf43281e9fc39179cb3154f jpackagetest/lib/app/mods/m1-20231111.jar 6ad0f7357cf03dcc654a3f9b8fa8ce658826fc996436dc848165f6f92973bb90 jpackagetest/lib/app/mods/m2-20231111.jar b5c4d7d858dad6f819d224dd056b9b54009896a02b0cd5c357cf463de0d9fdd2 jpackagetest/lib/app/mods/m3-20231111.jar
Now to try to get this working on Windows with the elderly wix tools...