XUtils

JavaPackager

Maven and Gradle plugin which provides an easy way to package Java applications in native Windows, macOS or GNU/Linux executables, and generate installers for them.


History

It was born while teaching to my students how to build and distribute their Java apps, and after seeing that a chain of several plugins was needed to achieve this task, I decided to develop a plugin :ring: to rule them all.

Apps packaged with JavaPackager

How to use this plugin

Package your app with Maven

Add the following plugin tag to your pom.xml:

<plugin>
    <groupId>io.github.fvarrui</groupId>
    <artifactId>javapackager</artifactId>
    <version>{latest.version}</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>package</goal>
            </goals>
            <configuration>
                <!-- mandatory -->
                <mainClass>path.to.your.mainClass</mainClass>
                <!-- optional -->
                <bundleJre>true|false</bundleJre>
                <generateInstaller>true|false</generateInstaller>
                <administratorRequired>true|false</administratorRequired>
                <platform>auto|linux|mac|windows</platform>
                <additionalResources>
                    <additionalResource>file path</additionalResource>
                    <additionalResource>folder path</additionalResource>
                    <additionalResource>...</additionalResource>
                </additionalResources>
                <linuxConfig>...</linuxConfig>
                <macConfig>...</macConfig>
                <winConfig>...</winConfig>
                [...]
            </configuration>
        </execution>
    </executions>
</plugin>

See Maven plugin configuration samples to know more.

And execute the next command in project’s root folder:

mvn package

Package your app with Gradle

Apply JavaPackager plugin in build.gradle using legacy mode (because at the moment it’s only available in Maven Central repository):

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'io.github.fvarrui:javapackager:{latest.version}'
    }
}

apply plugin: 'io.github.fvarrui.javapackager.plugin'

Create your packaging task:

task packageMyApp(type: io.github.fvarrui.javapackager.gradle.PackageTask, dependsOn: build) {
    // mandatory
    mainClass = 'path.to.your.mainClass'
    // optional
    bundleJre = true|false
    generateInstaller = true|false
    administratorRequired = true|false
    platform = "auto"|"linux"|"mac"|"windows"
    additionalResources = [ file('file path'), file('folder path'), ... ]
    linuxConfig {
        ...
    }
    macConfig {
        ...
    }
    winConfig {
        ...
    }
    ...
}

See Gradle plugin configuration samples to know more.

And execute the next command in project’s root folder:

gradle packageMyApp

Plugin assets

Any asset used by JavaPackager, such as application icons or templates, can be replaced just by placing a file with the same name in ${assetsDir} folder organized by platform.

${assetsDir}/
├── linux/
├── mac/
└── windows/

Icons

If icons are located in ${assetsDir} folder, it would not be necessary to use icon properties:

${assetsDir}/
├── linux/
│   └── ${name}.png     # on GNU/Linux it has to be a PNG file
├── mac/
│   └── ${name}.icns    # on MacOS it has to be a ICNS file
└── windows/
    └── ${name}.ico     # on Windows it has to be a ICO file

:warning: If icon is not specified , it will use an icon by default for all platforms.

Templates

Velocity templates (.vtl files) are used to generate some artifacts which have to be bundled with the app or needed to generate other artifacts.

It is possible to use your own customized templates. You just have to put one of the following templates in the ${assetsDir} folder organized by platform, and the plugin will use these templates instead of default ones:

${assetsDir}/
├── linux/
|   ├── assembly.xml.vtl               # maven-assembly-plugin template to generate ZIP/TGZ bundles for GNU/Linux
|   ├── control.vtl                    # DEB control template
|   ├── desktop.vtl                    # Desktop template
|   ├── desktop-appimage.vtl           # AppImage format Desktop template
|   ├── mime.xml.vtl                   # MIME.XML template
│   └── startup.sh.vtl                 # Startup script template
├── mac/
|   ├── assembly.xml.vtl               # maven-assembly-plugin template to generate ZIP/TGZ bundles for MacOS
|   ├── customize-dmg.applescript.vtl  # DMG customization Applescript template
|   ├── Info.plist.vtl                 # Info.plist template
│   └── startup.vtl                    # Startup script template
└── windows/
    ├── assembly.xml.vtl               # maven-assembly-plugin template to generate ZIP/TGZ bundles for Windows
    ├── exe.manifest.vtl               # exe.manifest template
    ├── ini.vtl                        # WinRun4J INI template
    ├── iss.vtl                        # Inno Setup Script template
    ├── msm.wxs.vtl                    # WiX Toolset WXS template to generate Merge Module
    ├── startup.vbs.vtl                # Startup script template (VB Script)
    ├── why-ini.vtl                    # WHY INI template
    └── wxs.vtl                        # WiX Toolset WXS template to generate MSI

An object called info of type PackagerSettings is passed to all templates with all plugin properties.

You can use default templates as examples to create your own templates, and use the extra map property to add your own properties in the plugin settings to use in your custom templates (e.g. ${info.extra["myProperty"]}).

Additional JVM options at runtime

When you build your app, all configuration details are hardcoded into the executable and cannot be changed without recreating or hacking it with a resource editor. JavaPackager introduces a feature that allows to pass additional JVM options at runtime from an .l4j.ini file (like Launch4j does, but available for all platforms in the same way). So, you can specify these options in the packager’s configuration (packaging time), in INI file (runtime) or in both.

The INI file’s name must correspond to ${name}.l4j.ini and it has to be located next to the executable on Windows and GNU/Linux, and in Resources folder on MacOS.

The options should be separated with spaces or new lines:

# Additional JVM options
-Dswing.aatext=true
-Dsomevar="%SOMEVAR%"
-Xms16m

An VM argument per line.

And then bundle this file with your app:

<additionalResources>
    <additionalResource>${name}.l4j.ini</additionalResource>
</additionalResources>

Last property copies ${name}.l4j.ini file next to the EXE/binary on Windows/Linux, and in Resources folder on MacOS.

How to use SNAPSHOT versions

Here you can find the uploaded JavaPackager SNAPSHOT versions.

Maven

Add the plugin repository to your pom.xml:

<pluginRepositories>
    <pluginRepository>
        <id>nexus</id>
        <name>nexus-snapshot-repository</name>
        <url>https://oss.sonatype.org/content/repositories/snapshots</url>
        <snapshots>
            <enabled>true</enabled>
            <updatePolicy>always</updatePolicy>
        </snapshots>
        <releases>
            <enabled>false</enabled>
        </releases>
    </pluginRepository>
</pluginRepositories>

And then you can use the latest SNAPSHOT version:

<plugin>    
    <groupId>io.github.fvarrui</groupId>
    <artifactId>javapackager</artifactId>
    <version>{javapackager.version}-SNAPSHOT</version>
    [...]
</plugin>

Or a specific SNAPSHOT version (specifying its timestamp and index):

<plugin>
    <groupId>io.github.fvarrui</groupId>
    <artifactId>javapackager</artifactId>
    <version>{javapackager.version}-{timestamp}-{index}</version>
    [...]
</plugin>

SNAPSHOT version example: 1.7.2-20230505.095442-5.

Gradle

Add the plugin repository to your build.gradle and use the latest SNAPSHOT version:

buildscript {
    repositories {
        maven {
            url "https://oss.sonatype.org/content/repositories/snapshots"
        }
    }
    dependencies {
        classpath 'io.github.fvarrui:javapackager:{javapackager.version}-SNAPSHOT'
    }
}

Or set a specific SNAPSHOT version specifying its timestamp and index:

buildscript {
    [...]
    dependencies {
        classpath 'io.github.fvarrui:javapackager:{javapackager.version}-{timestamp}-{index}'
    }
}

SNAPSHOT version example: 1.7.2-20230505.095442-5.

How to build and install the plugin in your local repo

Execute next commands in BASH (GNU/Linux or macOS) or CMD (Windows):

  1. Download source code and change to the project directory:
git clone https://github.com/fvarrui/JavaPackager.git [--branch devel]
cd JavaPackager
  1. Compile, package and install the plugin in your local repository (ommit ./ on Windows):
./gradlew publishToMavenLocal

How to release the plugin to Maven Central

Run next command (ommit ./ on Windows):

./gradlew publish closeAndReleaseRepository

Articles

  • coming soon...