JPackage в gradle для Java17
JPackage в gradle для Java17

Привет, Хабр.

Недавно в нашей компании возникла потребность в небольшой утилите для Windows пользователей.

Вся логика была уже реализована на Java библиотеках, так что оставалось только UI оболочка. Для этого была выбрана JavaFX. Приложение получилось красивое и удобное. На следующем шаге мы решили сделать исполняемое приложение из нашего JavaFX application, и для этого мы использовали инструмент jpackage.

Давайте рассмотрим, как это работает. Чтобы создать исполняемый файл для проекта JavaFX, вы можете воспользоваться несколькими подходами. Один из самых распространенных способов — использование инструмента jpackage, который входит в состав JDK начиная с версии 14. Этот инструмент позволяет упаковать ваш JavaFX проект в исполняемый файл для различных операционных систем.

Сборка исполняемого файла через gradle

Создание исполняемого файла происходит через специальный плагин: 'org.beryx.jlink' . Для Java17 подходит версия 2.24.1 (а для Java21 версия 3.1.1).

Документация здесь

Процесс делится на несколько этапов:

  • Сборка самого проекта в jar файл.

  • Подготовка образа Java.

  • Создание исполняемого файла для конкретной операционной системы.

  • Создание дистрибутива установочного файла для конкретной операционной системы.

Мы рассмотрим вариант для ОС Windows.

Для этого шага нам понадобится дополнительно установить WiX Toolset.

Его последнюю версию можно скачать тут и добавить в переменную среды PATH.

Шаги:

У нас есть JavaFx приложение, которое собирается и запускается.

1. Подключить плагин в build.gradle

id 'org.beryx.jlink' version '2.24.1' 

2. Добавить task в build.gradle

jlink {
    imageZip = project.file("${buildDir}/distributions/app-${javafx.platform.classifier}.zip")
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
    launcher {
        name = 'WinApplication'
    }

    jpackage() {
        if (org.gradle.internal.os.OperatingSystem.current().windows) {
            imageOptions += ['--icon', 'icon.ico']
        }
        imageOptions += ['--vendor', 'MyCompany'
        ]
        installerOptions += [
                '--vendor', 'MyCompany',
                '--app-version', version
        ]
    }
}

jlink создаст для нас образ java со всеми нашими зависимостями.

  launcher {

        name = 'WinApplication'   - Здесь указывается наименование нашего приложения, будет соответственно 'WinApplication.exe'.

  }

Jpackage отвечает за создание самого исполняемого файла и установочного файла.

imageOptions += ['--icon', 'icon.ico'] определяет иконку для приложения, если не указывать эту опцию, то будет иконка по умолчанию.

иконка по умолчанию
иконка по умолчанию

 '--vendor', 'MyCompany' если мы хотим указать производителя.

'--app-version', version определяет версию (важно не использовать при указании версии «SNAPSHOT» (например, “1.0.0-SNAPSHOT” будет ошибка.)

3. Запускаем создание образа для инсталлятора

gradlew jpackageImage

получаем сам исполняемый файл.

По сути, WinApplication.exe уже можно запускать.

4. Запускаем создание дистрибутива

gradlew jpackage

Создались два файла для установки:WinApplication-1.0.0.exe и WinApplication-1.0.0.msi

Пример. build.gradle

plugins {
    id 'java'
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.10'
    id 'org.beryx.jlink' version '2.24.1'
}

group 'com.example'
version '1.0.0'

repositories {
    mavenCentral()
}

ext {
    junitVersion = '5.8.1'
}

sourceCompatibility = '17'
targetCompatibility = '17'

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

application {
    mainModule = 'com.example.winapplication'
    mainClass = 'com.example.winapplication.WinApplication'
}


javafx {
    version = '17.0.1'
    modules = ['javafx.controls', 'javafx.fxml']
}

dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
}

test {
    useJUnitPlatform()
}

jlink {
    imageZip = project.file("${buildDir}/distributions/app-${javafx.platform.classifier}.zip")
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
    launcher {
        name = 'WinApplication'
    }

    jpackage() {
        if (org.gradle.internal.os.OperatingSystem.current().windows) {
            imageOptions += ['--icon', 'icon.ico']
        }
        imageOptions += ['--vendor', 'MyCompany'
        ]
        installerOptions += [
                '--vendor', 'MyCompany',
                '--app-version', version
        ]
    }
}

jlinkZip {
    group = 'distribution'
}

Пример всего проекта можно посмотреть тут

P.S. Для Linux данный плагин работает без изменений.

linuxApplication наше приложение

linuxapplication-1.0.7-1.x86_64.rpm и linuxapplication-1.0.7-1_amd64.deb установочные пакеты.

 Приложение запускается!

Комментарии (4)


  1. volodin_igor
    14.02.2025 09:11

    Лайк


  1. SserjIrk
    14.02.2025 09:11

    Если GUI на JavaFX почему не использовали компиляцию в натив через GraalVM? плагин gluonfx (есть mvn есть gradle версии) дают вроде элементарный способ: mvn gluonfx:build и на выходе получается полностью самодостаточный нативный исполняемый файл.

    Вроде даже позволяет мобильные apk и что-то там для яблок собирать. Но это я не пробовал. А вот для win и linux собирает очень шустрые образы. Ресурсов потребляют заметно меньше чем то-же самое запущенное в виде jar-а.


    1. EvgenVasilev Автор
      14.02.2025 09:11

      Самое главное, что jpackage - это инструмент из "коробки" JDK и мы умеем с ним работать. Нам показалось, что создание нативного образа с помощью GraalVM требует значительных усилий по настройке. Может понадобиться явно указывать, какие классы и ресурсы должны быть включены в образ, что может быть трудоемким процессом.
      Так же у нас использовались библиотеки, которые пока не полностью поддерживаются в GraalVM.
      А образ, который мы в итоге собрали через плагин GluonFX, получился несколько больше по размеру чем у jpackage.


  1. TastaBlud
    14.02.2025 09:11

    Спасибо, затронули больную тему: сама собираю приложение (и микрофреймворк для него подобный брошенному TornadoFX) для javafx. И всё бы хорошо, но плагин javafx для gradle сломали в версии 1 и потому приходится пользоваться 0.14 (нельзя выставить зависимости api вместо implementation - для монолитного приложения это ничего, но для фреймворка это важно)

    А сам jlink плагин badass работает на ура: собирает и запускаемое приложение и инсталлятор, как положено, со всеми зависимостями, параметрами командной строки и прочим.

    У меня не было проблем и нареканий.