Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to build installers for a java application for multiple targets on the same platform?

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+

like image 545
nyet Avatar asked Nov 02 '19 22:11

nyet


2 Answers

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)

like image 191
madduci Avatar answered Sep 26 '22 07:09

madduci


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.

  1. Create a runable program, something as simple as
    package com.example;

    public class Greeter {
        public static void main(String[] args) {
            System.out.println("Hi, I'm the greeter. Welcome.");
        }
    }
  1. compile the program and place in a jar (call it greeter.jar for this example and place in the build directory, called target for this example)
  2. run 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
  1. run 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
  1. run the application with .\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

like image 23
Jason Avatar answered Sep 25 '22 07:09

Jason