Как стать автором
Обновить

О том, как я собирал на Linux'е application bundle для MacOS

Время на прочтение 6 мин
Количество просмотров 6.5K
Итак, довелось мне недавно обновить билд-скрипт одного приложения написанного на Java Swing. Приложение давно разрабатывается, и писалось ни одним поколением программистов, но дело свое делает хорошо и четко, поэтому собирается под основные платформы (Window's, Unix, MacOS). Вот и решили мы проапдейтить сборку нашего старого app bundle'а для MacOS, а заодно и положить в него встроенную jre версии 1.8. И тут начали выясняться интересные вещи: старый bundle был собран под Java 6 от Apple, и отныне не работал, формат Info.plist поменялся, ибо Oracle больше нравятся свои пропертя, старый JavaApplicationStub отныне вне закона, да здравствует JavaAppLauncher, ну и много другого интересного. Лично мне по душе больше Linux, да простят меня любители MacOS, и даже такая знакомая консоль терминала слабо согревала мою душу после долгих мытарств по просторам интернета в поисках опыта таких же отважных людей, победивших мою проблему. Это практически невероятно, но в конце концов, я обнаружил статью в блоге David Clunie, в которой он шаг за шагом описал практически все мои попытки найти пути решения поставленной задачи. Кого заинтересовал прошу подкат, для любителей оригиналов вот ссылка на заветную статью, заранее прошу прощения за качество моего перевода, т.к. он не будет полным и дословным.

Резюме:
Упаковка Java приложения в исполняемый Mac bundle процесс не сложный, но со временем он меняется. JavaApplicationStub заменен файлом JavaAppLauncher, собрать содержимое bundle'a и редактировать Info.plist вручную просто, но структура bundle'a и свойства в Info.plist были изменены.

Дэвид долгое время был фанатом Mac и Java, ему очень нравился кросс-платформенный подход в разработке, Так же он предпочетает горячо любимую мной консоль терминала, а Xcode использует только как тектсовый редактор, ему очень нравится make, а для сборки больших проектов он использует ant, он уверен, что ant это круто. Дэвид думает, что все это иногда сводит с ума людей читающих его код, но такова жизнь.

Все вышеупомянутое дает возможность его довольно устаревший подходу идти в ногу с изменениями в Java от Apple и Oracle. Разработка и развертывание Java приложения на Mac это не большой вызов. Дэвиду, как и мне, нужно чтобы его приложение работало под тремя основными платформами: Mac, Unix и Windows.

Когда-то был только один способ собрать приложение под Mac – использовать инструмент, поставляемый Apple, который назывался jarbundler. Jarbundler создавал правильную структуру файлов и папок для Mac приложения, упакованного в bundle. Каждое приложение под Mac на самом деле папка с именем «something.app», которая состоит из различных файлов с ресурсами, свойствами и т.д., включая бинарный выполняемый файл. В эпоху pre-Oracle, когда Apple поставляла собственную версию Java, необходимый бинарный файл был JavaApplicationStub, и jarbundler ожидает, что этот файл будет в нужном месте, когда приложение запускается. Вот ссылка на устаревшую документацию по этой теме.

Попробовав однажды использовать jarbundler, чтобы получить правильную структуру папок и файлов bundle'а, я прекратил его использовать, и просто вручную копировал файлы и папки каждого моего нового приложения в правильные места, редактировал файл Info.plist. Автоматизация обновления таких стандартных структур в make-файле была тривиальной. Так как я использовал очень мало вещей специфичных для Apple-JRE в моей работе, то, когда Apple прекратила поставлять JRE и это начала делать Oracle, это почти не сказалось на моем процессе разработки. Так что теперь у меня есть привычка использовать различные версии OpenJDK в зависимости от фазы луны, и все еще кажется, что все работает отлично.

Долгое время Дэвид собирал свои проекты под Java 1.5 просто потому, что возможно кто-то еще пользовался этой старой неподдерживаемой версией JRE, но в конце концов, стиснув зубы, он перешел на версию 1.7. Это казалось разумным, когда он узнал, что Java 9 (с которой он уже экспериментироал) больше не будет компилироваться для такого старого target'a. После непродолжительных экспериментов с соответствующими javac опциями (-target, -source и -bootclasspath) чтобы заставить замолчать различные (важные) warning'и, казалось, что все идет хорошо.

До тех пор пока я не скопировал собранный под Java 1.7 jar файл в мой Mac bundle и не подумал, а почему бы не увеличить значение свойства JVMVersion в Info.plist c “1.5+” до “1.7+”? После этого мое приложение больше не работало и выдавало предупреждение об ошибке «unsupported versions».

С этой точки зрения, я годами самодовольно игнорировал всякие сообщения в Mac Java mailing list про какой-то новый тул, именуемый appbundler, описанный Oracle'ом, и политика Apple стала таковой, что приложения больше не зависили от установленной JRE, вместо этого приложения должны были быть связана с их собственной полной копией приемлимой JRE.

Дальше было немного размышлений автора, итог таков, что он перешел на использование appbundler. Итак, оказалось, что использование appbundler'а зависит от ant, который автор обычно не использовал (а я использовал, так что мне повезло), и к сожалению конфигурации таска appbundler для ant не поддерживала опций JVM, которые так были нужны Дэвиду. Здесь описание поддерживаемых опций appbundler'a. На момент написания статьи в блог (октябрь 2014 года) appbundler выглядел уже немного устаревшим, и было ясно, что Oracle не разрабатывает его активно, что заставило немного поволноваться автора. Есть еще одна версия appbundler'a, которую активно мэйнтэйнят другие люди, в ней опций на много больше, и выглядит ее использование на много сложнее. Далее был найден пост от Michael Hall в Mac Java developers mailing list, в котором упоминался тул, написанный Майклом, а именно AppConverter, который ожидаемо конвертил старые приложения в новые (простите за туманный каламбур, но как есть, так есть, за Дэвида я ничего не дописываю). Звучало, как будто я нашел то, что мне было так нужно. К сожалению, когда я попробовал воспользоваться конвертером, выяснилось, что преобразованные приложения не отвечали на drag and drop application bundle'a, как было обещано.

В общем AppConverter был бесполезен для Дэвида, за одним единственным исключением, Дэвид посмотрел содержимое bundle'a, убедился, что это Java приложение, содержащее бинарный исполняемый файл JavaAppLauncher, который теперь используется вместо JavaApplicationStub, также пакет содержит Info.plist, который показал, что необходимо. В дополнение ко всему, оказалось, что все jar файлы теперь из папки «Contents/Resources/Java» переехали в «Contents/Java» (что подтвержали и посты в Mac Java developers mailing list, кстати лично на мой взгляд это плохое решение, мне было так хорошо, когда я мог лично сам, своими руками добавить все необходимые мне папки в classpath через Info.plist, ну там например разные пакеты с одинаковыми классами и jar файлами с одинаковыми именами, но в разных папках, в старых бандлах все это работало совершенно без проблем, а теперь, когда за меня все что добавленно в classpath копируется в одну единственную папку, про поддержку разных версий FOP можно просто забыть, придется самому пересобирать новые jar файлы с разными именами, например fop-0.20.jar; fop-1.0.jar; fop-2.0.jar, спасибо за то, что добавили работы).

Итак, Дэвид немного отредактировав вручну структуру Info.plist и скопировав JavaAppLauncher из bundle'a полученного после использования AppConverter, получил вполне рабочее приложение, что позволяло ему не думать о том, как же еще сконфигурировать appbundler, чтобы он все таки собирал рабочие bundle'ы.

В качестве примера, вот старая структура application bundle (по которой я так скучаю):

image

а вот новая, от которой теперь никуда не денешься:

image

а вот и старый Info.plist:

image

ну и для сравнения новый:

image

Еще раз обратите внимание на то, что теперь не нужно ничего добавлять в classpath, JavaAppLauncher сам автоматически добавит в classpath все, что будет в папке Contents/Java.

Теперь вместо того, чтобы иметь все java-проперти под общим тэгом Java, JavaAppLauncher использует свойство JVMMainClassName вместо Java/MainClass, и JVMOptions вместо Java/VMOptions.

Дальше Дэвид описывает свои дальнейшие эксперименты с application bundle'ом, версии ПО, которые он использовал, а так же негодование по поводу того, что же помешало авторам appbundler использовать старую структуру bundle'а и Info.plist. А вот и актуальная документация по этому поводу.

P.s. По началу Дэвид так и не добился того, чтобы встроить JRE в application bundle, но он не терял надежды, читал статьи с похожей тематикой, искал новые тулы, которые могли помочь, и были хорошо задокументированы и потом переименованы. Ну и наконец апофеоз сей притчи, есть таки универсальный лаунчер для Mac application bundle. Подводя итог, хочется сказать большое спасибо человеку по имени Dylan Myers, который оставил замечательный коммент, суть которого сводится к тому, что нужно переименовать universaleJavaApplicationStub в JavaApplicationStub, я пошел немного дальше и переименовал его в JavaAppLauncher. Что и стало для меня решением: собираем проект с помощью ant, в том числе и собираем bundle с помощью таска appbundle для ant (если вам нужна встроенная JRE, то собирать придется на Mac, т.к. будет скопирована JRE, которая установлена на ваш Mac, это только заготовка, я вручную доводил bundle до работоспособного состояния, но базовые свойства для Info.plist все же можно указать в таске appbundle), заменяем стандартный JavaAppLauncher на universaleJavaApplicationStub и переименовываем его в JavaAppLauncher. После всех проделанных манипуляций, я положил готовый application bundle на свой билд сервер под Linux, и Jenkins совершенно без проблем сливает новую версию приложения из репозитория, запускает ant таски, копирует нужные jar файлы в bundle, и самое главное, это работает. Спасибо за внимание.
Теги:
Хабы:
+19
Комментарии 3
Комментарии Комментарии 3

Публикации

Истории

Работа

Java разработчик
359 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн