nyet
nyet

Reputation: 596

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+

Upvotes: 12

Views: 3169

Answers (3)

madduci
madduci

Reputation: 2883

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)

Upvotes: 4

nyet
nyet

Reputation: 596

Creating a Windows MyApp.exe and MyApp-setup.exe on linux:

I don't need jpackage at all, only jlink, launch4j, and nsis:

  • Use jlink once natively to create the runtime and tar off the
    results for use on other machines.
  • launch4j can be instructed to use that runtime, and nsis can be
    told to copy the whole runtime on install.

Creating a MacOS MyApp.app on linux:

  • Use jlink to create a tarfile that can be reused to recreate Contents/runtime/Contents/Home (like for windows above)
  • Copy in the jpackage generated
    Contents/runtime/Contents/Info.plist and
    Contents/runtime/Contents/MacOS/libjli.dylib
  • Copy in the jpackage generated Contents/MacOS/MyApp stub and Contents/MacOS/libapplauncher.dynand hope they never have to change.
  • Create Contents/Info.plist and Contents/app/MyApp.cfg file from templates using the jpackage generated ones as a reference
  • Fill in Contents/app, Contents/Resources with my jar files and other resources

Creating a pkg on linux:

https://gist.github.com/msabramo/2a8e44eb6dcc3b89437d33649b0b1841

Creating a dmg on linux:

https://askubuntu.com/questions/1117461/how-do-i-create-a-dmg-file-on-linux-ubuntu-for-macos

Alternately, migrate from nsis to install4j

https://www.ej-technologies.com/products/install4j/overview.html

In theory once I get it all working on linux, I can port the effort to both Darwin and cygwin (WSL just doesn't work right for me atm, will get that working last)

Proof of concept is here (linux, MacOS, cygwin):

https://github.com/nyetwurk/ecuxplot

It is kind of ridiculous this multiplatform crosscompile tooling doesn't exist anywhere, considering the rise of CI/CD, and that the whole point of java is portability and architecture independence.

Upvotes: 4

Jason
Jason

Reputation: 12283

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

Upvotes: 5

Related Questions