I would like to build a .msi, .deb, and .pkg from the same source tree and on the same machine.
Distributable runtimes for Java9+ are no longer downloadable, so perfectly sane solutions like launch4j+nsis no longer work.
javapackager has been abandoned by Oracle.
OpenJDK's jpackager can't (and will never) cross compile for different build projects, and it isn't even a real product yet.
Is there a way to build installers for win/linux/macos from the same machine?
Is the promise of "compile once, run everywhere" is truly dead and buried?
I have a legacy java application that is now in limbo, since MacOS java8 doesn't support java.awt.desktop, which requires java9+
I use since Java 9 and the inception of jlink
and jpackage
a cross platform setup made of different docker images and Virtual Machines, where I can build the runtimes and the installers (MSI, DEB/RPM and DMG/PKG) on the target platforms within' my host system.
For Mac, you can use a KVM image, if you don't have Apple Hardware, where you can issue commands over ssh.
For Windows, a Linux docker container is used, packed with wine, the OpenJDK for Windows, the Visual Studio build tools, WIX and CMake to perform the build of the runtime image and a customised MSI installer (since the javapackage
version is too simple)
The answer to your question is not short. But I'll try to be brief and point to all relevant information.
The short answer is: you can do this.
The longer answer is: you still have to build a runtime for each target environment from within that target environment, but you only have to do this once. You can then save that runtime and reuse it to automatically build installers with your latest Java jars/code in a single environment. For example, use jlink
to build the runtime image and jpackage
to build the app image for Windows, Linux, and macOS (on those respective systems) then copy those app images to macOS and build an nsis
installer (or installer builder of your choice) for each platform from within macOS.
When you update your code and recompile, you can just copy the new jars into the pre-built app image. You'll have to copy in all your dependencies, too, but that would be necessary for any installer. There is a config file in the runtime built by jpackage
that has options, classpath, etc., which you can change without a need to rebuild the runtime.
package com.example;
public class Greeter {
public static void main(String[] args) {
System.out.println("Hi, I'm the greeter. Welcome.");
}
}
greeter.jar
for this example and place in the build directory, called target
for this example)jilnk
to build a runtime. The following command uses jlink
from JDK11 and puts the result in a directory called runtime
. This example includes all modules on the module path, but you can use jdeps
to get just the modules you need. I suggest including all modules if you do not want to ever have to rebuid this runtime when your project evolves and depends on more of the Java runtime. Not to mention transitive dependencies on the JRE. > set JLINK=C:\Program Files\Java\jdk-11.0.6\bin\jlink.exe
> "%JLINK%" --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules ALL-MODULE-PATH --output runtime
jpackage
to build an app image suitable for packaging in an installer. This uses jpackage from JDK14 early access (the only version of the JDK that has jpackage
at the time of this writing). The command line option —win-console
is only for Windows and is only necessary if the program does something with stdin/stdout (the console). Our example writes to the console, so we need this. This argument will likely sometimes open a console window when running your application, so remove it if you have a pure windows based (gui) application. > set JPKG=C:\Program Files\Java\jdk-14-ea\bin\jpackage.exe
> "%JPKG%" --type app-image -i target —win-console -n Greeter --main-class com.example.Greeter --main-jar greeter.jar --runtime-image runtime
.\Greeter\Greeter.exe
The resulting app image (in the app-image
directory) can be used to build an installer with your favorite install builder (I use NSIS). You can do this on any platform. Furthermore, when you update you program you only have to copy your new jars into the app image. There is no need to rebuild the app image or the runtime. This copy of the jars can take place on any platform, and there is no need for Windows to be run in order to build a new installer for a new version of your application.
If your application has jar dependencies (say from Maven central), you’ll need to copy those jars to the Greeter/app
directory and update app.classpath
in the Greeter/app/Greeter.cfg
file. Again, all this can be done on any platform, no need to start up the target platform (Windows in my case).
Also, jpackage
is an officially supported tool but only available in EA JDK 14 (it's Feb 2020 as I write). JDK 14 may be downloaded and jpackage
can be used with other versions of JDK (like JDK 11 LTS).
See https://blogs.oracle.com/jtc/a-brief-example-using-the-early-access-jpackage-utility
The JEP for jpackage
has been marked "Closed/Delivered" suggesting the tool is mature and just waiting for JDK 14 to be released: https://openjdk.java.net/jeps/343
There is an example project on GitHub that has a lot of useful command line examples on how to run jlink
and jpackage
: https://github.com/jtconnors/SocketClientFX
Though this project uses outdated command options. You can run jpackage --help
to get the new options.
Useful Links:
JDK 14 (early access until March 17th, 2020): http://jdk.java.net/14/
Explains non-modular usage of jlink: https://medium.com/azulsystems/using-jlink-to-build-java-runtimes-for-non-modular-applications-9568c5e70ef4
jlink manual: https://docs.oracle.com/javase/9/tools/jlink.htm#JSWOR-GUID-CECAC52B-CFEE-46CB-8166-F17A8E9280E9
jpackage - run with the -help
option to get good reference information
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With