Создание дистрибутивов для разных операционных систем в Java 9 и 10

    В статье рассказывается о построении полноценных дистрибутивов для Windows, macOS и Linux стандартными средствами Java 9 и 10.

    Дополняет ранее опубликованную статью об уменьшении размера дистрибутива, делая акцент не на модульности, а на особенностях создания дистрибутива для разных операционных систем.

    Перечисляются произошедшие с Java 9 изменения. Описывается последовательность шагов, выполняемых скриптом сборки, с указанием мест, где возможны изменение поведения и настройка. Приводится история со счастливым финалом преодоления особенностей и ошибок, появившихся в Java 9.



    Пролог


    Настольные (desktop) приложения, написанные на языке програмирования Java, по-прежнему имеют право на существование. Самым ярким примером является IntelliJ IDEA, дистрибутивы для установки которой существуют для разных операционных систем.

    В JDK включена утилита командной строки Java Packager, служащая для компиляции, создания цифровой подписи и сборки дистрибутивов Java-приложений. Утилита впервые появилась в JDK 7 Update 6. Кроме использования в командной строке её функции доступны в виде задач (tasks) для Ant. В документации они именуются JavaFX Ant Tasks.

    При написании статьи были использованы:


    Проект может быть найден на GitHub. Компиляция и сборка дистрибутива осуществляется при помощи Maven. Проект примера включает три части (модуля в терминологии Maven):

    • multiplatform-distribution-client с кодом приложения;
    • multiplatform-distribution-distrib со вспомогательными файлами дистрибутива;
    • multiplatform-distribution-resources с ресурсами для инсталляторов.

    Сборка дистрибутива


    В таблице перечислены в порядке выполнения этапы формирования дистрибутивов. Специально выделены жирным те этапы, на которых может возникнуть необходимость или желание изменить поведение, внешний вид или состав дистрибутивов.
    № п/п Модуль Maven Плагин Maven Фаза жизненного цикла Maven Этап
    1 multiplatform-distribution-resources maven-resources-plugin process-resources Подстановка строковых значений в файлах .iss (для Windows) и .plist (для macOS)
    2 multiplatform-distribution-client maven-dependency-plugin generate-sources Формирование списка зависимостей для манифеста
    3 build-helper-maven-plugin generate-sources Замена разделителя в списке зависимостей для манифеста
    4 maven-compiler-plugin compile Компиляция кода
    5 maven-jar-plugin package Создание файла .jar
    6 maven-dependency-plugin package Копирование зависимостей для дистрибутива
    7 maven-resources-plugin package Копирование дополнительных файлов для дистрибутива
    8 maven-antrun-plugin package
    1. Использование графических и конфигурационных файлов для сборки дистрибутива
    2. Создание образа JRE
    3. Формирование дистрибутива для конкретной операционной системы

    9 maven-assembly-plugin package Формирование файла дистрибутива .tar.gz для Linux (только при запуске на Linux)
    10 multiplatform-distribution-distrib maven-assembly-plugin package
    1. Подстановка строковых значений в файлах .bat (для Windows), .sh (для macOS и Linux) и лицензии
    2. Формирование универсального дистрибутива .zip (без JRE)


    Чтобы создать дистрибутивы в macOS и Linux, нужны только JDK и Maven. В операционной системе Windows предварительно необходимо установить Inno Setup или WiX Toolset. Далее подразумевается, что используется Inno Setup.

    Запуск компиляции и сборки дистрибутива в Windows и macOS:

    mvn clean package -P native-deploy

    Запуск компиляции и сборки дистрибутива в Linux (дополнительно создаётся файл архива tar.gz):

    mvn clean package -P native-deploy,tar-gz

    Файлы созданного дистрибутива с расширением exe (для Windows), dmg (для macOS) располагаются в каталоге multiplatform-distribution-client/target/deploy/native, с расширением tar.gz (для Linux) — в каталоге multiplatform-distribution-client/target.

    Универсальный дистрибутив с расширением zip, пригодный для любой операционной системы и не содержащий в себе JRE, создаётся в каталоге multiplatform-distribution-distrib/target.

    Особенности Java 9 и 10


    Долгожданный выход Java 9 деструктивно повлиял на скрипт сборки дистрибутива, до этого успешно работавший в предыдущей версии Java.

    Во-первых, задача <fx:deploy>, выполняющая формирование образа JRE и создание дистрибутива, немного изменила структуру используемых ею каталогов. Скрипт сборки был изменён, чтобы учесть это.

    Во-вторых, при создании инсталляторов дистрибутивов перестали загружаться текстовые и графические ресурсы, см. JDK-8186683. Невозможность загрузки ресурсов из classpath при сборке дистрибутива в Java 9 компенсирована добавлением нового аргумента dropinResourcesRoot. В качестве значения аргумента рекомендуется указать путь к каталогу с ресурсами. В описании задачи в файле pom.xml это будет выглядеть так:



    В-третьих, из-за допущенных ошибок в реализации в Java 9 пропала возможность включать в дистрибутив подкаталоги с файлами, например, подкаталог lib с библиотеками-зависимостями программы. Ошибка проявлялась только в Windows и macOS. Преодоление этой проблемы превратилось в целую детективную историю и вынужденно растянулось на долгое время, отсрочив публикацию статьи.

    Хроника событий:

    1. Клонирован репозиторий OpenJFX и найдена ошибка.
    2. Обнаружен уже существующий JDK-8179033 с описанием тех же симптомов.
    3. По совету lany (спасибо ему большое) написаны письма раз и два в группу openjfx-dev, с предложением изменений, требуемых для исправления ошибки, и советом, как их правильность проверить.
    4. Переписка с исполнителями JDK-8179033 — благодарю Виктора Дроздова за доброжелательное отношение и терпение.
    5. Ошибка исправлена и исправление попало в очередную сборку предварительной версии Java 10jdk-10-ea+36.
    6. Сборка дистрибутива, запущенная из командной строки, стала выполняться успешно.
    7. При попытке в IntelliJ IDEA добавить jdk-10-ea+36 в список SDK — ошибка (в отличие от предыдущих сборок), создан IDEA-183920.
    8. Комментатором IDEA-183920 указана причина ошибки добавления JDK — исчезнование именно в этой сборке JDK утилиты javah, в том числе наличие которой требовалось для успешной идентификации.
    9. Выход IntelliJ IDEA 2017.3.2, в которой ошибка идентификации JDK 10 исправлена.
    10. Сборка дистрибутива стала возможна из среды разработки тоже.

    Адаптация примера для собственного использования


    1. Переименовать модули Maven проекта, заменив префикс multiplatform-distribution в наименовании на что-то другое.
    2. Переименовать файлы multiplatform-distribution.bat и multiplatform-distribution.sh, находящиеся в модуле multiplatform-distribution-distrib.
    3. Изменить в файлах pom.xml:

      • имена переименованных модулей Maven;
      • имена каталогов, соответствующих переименованным модулям Maven.
    4. Изменить в файлах assembly.xml:
      • имена переименованных модулей Maven;
      • упоминания изменённых имён файлов multiplatform-distribution.bat и multiplatform-distribution.sh.
    5. Изменить в файле корневого pom.xml значения свойств с именами <app.*>, содержащие:

      • полное наименование приложения;
      • краткое наименование приложения;
      • год (-a) copyright;
      • код приложения в именах файлах;
      • пакет приложения по умолчанию;
      • наименование класса для запуска приложения.
    6. Добавить в модуль multiplatform-distribution-client собственный код.
    7. Изменить содержимое файлов license.txt и readme.txt в модуле multiplatform-distribution-client.
    8. Изменить содержимое графических файлов и их имена в модуле multiplatform-distribution-resources.

    Выводы


    • стандартными средствами JDK 9 и 10 можно построить дистрибутивы для различных операционных систем;
    • кастомизация состава, поведения и вида дистрибутивов относительно проста;
    • в Java 9 появились некоторые особенности, которые были учтены при написании данной статьи.

    Недавно появился JEP 311: Java Packager API & CLI для создания нового API и CLI (интерфейса командной строки) для Java Packager. JEP в настоящее время отклонён, ранее изменения планировалось произвести в JDK 10 и JDK 11. При возобновлении активности данная статья может получить продолжение в ближайшем будущем.

    На предстоящих 4 марта (JBreak 2018 в Новосибирске) и 6-7 апреля (JPoint 2018 в Москве) конференциях имеется возможность посетить доклады на близкие темы по Java 9 и будущим версиям Java:

    JUG.ru Group 1 326,45
    Конференции для взрослых. Java, .NET, JS и др. 18+
    Поделиться публикацией
    Комментарии 12
    • +1

      Правильно ли я понял, что под линукс создание rpm/deb/etc. не рассматривалось? Без них не так интересно

      • +1
        При сборке дистрибутива под Linux сейчас создаются все возможные виды дистрибутивов при текущих значениях параметров (см. nativeBundles="all" у элемента <jfxdeploy> в файле pom.xml для модуля multiplatform-distribution-client).

        Для задачи <fx:deploy> все возможные значения параметра nativeBundles приведены в документации по указанной ссылке. Среди форматов есть и deb, и rpm. Про all сказано, что:
        Value all produces all applicable self-contained application packages for the platform on which the Ant tasks are run.
      • +1
        А как создать desktop дистрибутив для 32bit windows?
        • 0
          Начиная с версии 9, Oracle Java только 64-битная. Для предыдущих версий (7 и 8) можно создать 32-битный дистрибутив.

          Требуемые действия:
          1. Скачать и установить, например, 32-битную Java 8.
          2. Изменить требуемые переменные окружения (JAVA_HOME, Path) операционной системы.
          3. В файле pom.xml (главном) заменить значение переменной на <project.build.javaVersion>1.8</project.build.javaVersion>.
          4. В файле pom.xml (модуля multiplatform-distribution-client) заменить значение системного пути к файлу ant-javafx.jar на <systemPath>${java.home}/../lib/ant-javafx.jar</systemPath>.
          • +1
            Грусть, печаль владельцам 32bit десктопов или ну её нафиг java9+?
            • 0
              В предварительных сборках (Early-Access Builds) для Java 9 поддержка 32-битности ещё была, но в окончательном выпуске — уже нет. Достаточно подробное изложение истории в хронологическом порядке в ответе на Stack Overflow.

              При наличии 32-разрядных операционных систем тогда уж пользоваться Java 8, при 64-разрядной — самой последней версией, какая есть.
              • 0
                Планируется переход на частый выпуск новых версий, oracle забивает на 32bit платформы. Sun WORE потихоньку становится Oracle whore (что бы всё работало нужно больше золота).
                Кстати java 10 не будет будут 18.3, 18.9,… Adobe уже довела до совершенства flash. Чем Oracle хуже. Вобщем продолжаем наблюдения, но пака не поздно переходим обратно на C++.

                ps: Несмотря на все улучшения и другие навороты все программы написанные на java досих пор пользуются славой как самые прожорливые и не поворотливые, имеющее внутри эпическое количество абстракций. При этом 64бит java как и раньше имеет 16 битые индексы на поля, константы и методы.

                pps: Изображенный на картинке Дюк, кидающий windows, macos и linux как бы намекает будет вам еще java-core
                • 0
                  Кстати java 10 не будет будут 18.3, 18.9,…
                  Уже ранее успели передумать, следующая версия будет как раз Java 10: письмо Марка Рейнхольда, новость о том же в еженедельном дайджесте JUG.ru, упоминание об этом в недавнем обзоре на InfoWorld и т.д.

                  Предварительная версия именно JDK 10 упоминается и в данном хабрапосте.
                  • 0
                    При этом 64бит java как и раньше имеет 16 битые индексы на поля, константы и методы.

                    Что вы этим хотели сказать?
                    С тем же успехом можно было бы сокрушаться, что процессоры уже давно 64-битные, а байт как был восьмибитным, так и остался.

                    • 0
                      Я хочу сказать что если уж они улучшают то что мешает сделать нормальные выравненные структуры данных и убрать ограничение на 64K методов.
                      • +4
                        что мешает сделать нормальные выравненные структуры данных

                        О каком выравнивании речь?
                        В *.class-файле никакое выравнивание не нужно, это будет бессмысленным разбазариванием места. А как выравнивать данные во время исполнения решает VM/JIT и class-файл тут опять не при делах.


                        и убрать ограничение на 64K методов

                        Классы принято делать небольшими, так их удобнее разрабатывать и сопровождать.
                        Если вдруг возникает проблема с 64К чего-либо в рамках одного класса, то это


                        1. либо что-то автогенерированное и в этом случае это проблема недоделанного генератора, а не формата class-файлов.
                        2. либо что-то написанное руками и тогда это лютый говнокод.
                          И, соответственно, снова не проблема формата class-файлов.

                        Ни один из вариантов на уважительную причину для поломки совместимости совершенно не тянет.


                        У вас есть контрпримеры?

                        • –1
                          О каком выравнивании речь? В *.class-файле никакое выравнивание не нужно, это будет бессмысленным разбазариванием места.

                          Об обычном выравнивании для уменьшения пенальти за не выравненные обращения к памяти. Java и не разбазаривание места — хорошая шутка.
                          А как выравнивать данные во время исполнения решает VM/JIT и class-файл тут опять не при делах.

                          Вот он бы JITил быстрее выравненные данные, даже просто мог использовать эту структуру напрямую не преобразовывая.
                          Классы принято делать небольшими, так их удобнее разрабатывать и сопровождать.

                          Классы нынче принято не разрабатывать, а потреблять уже готовые. Обильно обмызвая эти шедевры еще несколькими слоями абстракций и костылей.
                          Если вдруг возникает проблема с 64К чего-либо в рамках одного класса, то это…

                          Это ж спарта энтерпрайз тут мировой центр говнокодерства лучших практик. Взглянем на SAP или не к ночи помянутый android — ещё тот образец для подражания.
                          Ни один из вариантов на уважительную причину для поломки совместимости совершенно не тянет.

                          Старые виртуальные машины не запускают новые class файлы. Требуется новая jvm. О какой совместимости идёт речь?
                          Да и какая уважительная причина забить на все 32бит платформы?

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое