How to Create Platform-Specific Installers for Your Java Applications from GitHub Actions
Published: 2020-11-18 00:05:54

Java 14 and above ship with the [Packaging Tool, `jpackage`](https://docs.oracle.com/en/java/javase/14/jpackage) for packaging self-contained Java applications. This new tool is surprisingly easy to use, and bundles together all the jars for your application, and dependencies into an operating system specific installer. You can create `.deb` files on Linux, `.MSI` files on Windows, and `dmg` on macOS. The Packaging Tool leverages the underlying operating system tools to build the packagers, and you need to have those tools installed. The good news is that GitHub Actions runners come with these tools preinstalled, making it a breeze to use GitHub Actions to generate and publish installers for your application. You do not even have to create launcher shell scripts - this is done on your behalf.

So roughly, here are the steps to get to this level of automation going - first, make sure your build generates a Java jar file from your code, and collects dependencies in one place. Then create GitHub Actions workflows that install Java 14 or better, to checkout your code, build it, run the packager, and publish the installer. Let us get into some details. To follow along, you can take a look at the sample GitHub project, [sualeh/build-jpackage](https://github.com/sualeh/build-jpackage) which has everything in place.

If you use Gradle, you can collect dependencies pretty easily - just add something like the following to extend your build task to collect dependencies in the `build/libs` folder.

```
task copyToLib(type: Copy) {
    into "${buildDir}/libs"
    from configurations.runtimeClasspath
}

build.dependsOn(copyToLib)
```

The next time you run `gradle build`, you will see a lot of jars in `build/libs`. If you use Apache Maven or Apache Ant, you can do something similar to collect all your dependencies in one place.

Next, set up a GitHub Actions workflow for your project. You need to create a YAML file in `.github/workflows`, and check out code, and install Java 14 or better. Checkout and compile your project there, and make sure that it succeeds. The details of how to do this are readily available, so I am not going through them - and in any case you can look at the workflows in the [example project](https://github.com/sualeh/build-jpackage/tree/master/.github/workflows). (Please star the project if you find it useful!) 

After you study the [`jpackage` tool](https://docs.oracle.com/en/java/javase/14/jpackage) command-line options, you can put the options down in a command-line file, such as [jpackage.cfg](https://github.com/sualeh/build-jpackage/blob/master/jpackage.cfg), and create platform specific ones too, such as [jpackage-linux.cfg](https://github.com/sualeh/build-jpackage/blob/master/jpackage-linux.cfg). You will need these in your workflow. If you have `jpackage` installed locally, try them out locally too.

After you have made sure that `jpackage` runs, add a call to it right after your build.

```yaml
    - id: build
      name: Build distribution
      run: |
        gradle build
        jpackage --verbose "@jpackage.cfg" "@jpackage-linux.cfg"
```

And finally, you are ready to publish. You have a few options here - you can publish it as a build artifact using `actions/upload-artifact`, or as a release using `actions/create-release` and `actions/upload-release-asset`. If you want to publish somewhere else, you can do that too.

Take a look at the [complete example workflow](https://github.com/sualeh/build-jpackage/tree/master/.github/workflows), and see how easy it all is, really. If you have been following along, and have this down, then here is a challenge for you. It is always nice to have your application's logo on the installer. However, `jpackage` expects you to create this in a platform specific format, for example as an `.ico` file on Windows and a `.png` file on Linux. As a challenge, create your logo in SVG format, and use Image Magick in GitHub Actions to convert it into an appropriate format for your installer to use. I am still working on this, so if you get there first, please put a comment on this article.

And be sure to star [sualeh/build-jpackage](https://github.com/sualeh/build-jpackage)!