
Про те, як я збирав на Linux'e application bundle для MacOS
Отже, довелося мені недавно оновити білд-скрипт одного додатка написаного на Java Swing. Додаток давно розробляється, і писався жодним поколінням програмістів, але справа своє робить добре і чітко, тому збирається під основні платформи (Window's, Unix, MacOS). Ось і вирішили ми проапдейтити збірку нашого старого app bundle'a для 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'a, я припинив його використовувати, і просто вручну копіював файли і теки кожної моєї нової програми в правильні місця, редагував файл 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'a залежить від 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 (за якою я так сумую):
а ось нова, від якої тепер нікуди не дінешся:
а ось і старий Info.plist:
ну і для порівняння новий:
Ще раз зверніть увагу на те, що тепер не потрібно нічого додавати в classpath, JavaAppLauncher сам автоматично додасть до classpath все, що буде в теці Contents/Java.
Тепер замість того, щоб мати всі java-проперті під загальним тегом Java, JavaAppLauncher використовує властивість JVMMainClassName замість Java/MainClass, і JVMOptions замість Java/VMOptions.
Далі Девід описує свої подальші експерименти з application bundle'ом, версії ПЗ, які він використовував, а так само обурення з приводу того, що ж завадило авторам appbundler використовувати стару структуру bundle'a і Info.plist. А ось і актуальна документація з цього приводу.
P.s. Спочатку Девід так і не домігся того, щоб вбудувати JRE в application bundle, але він не втрачав надії, читав статті зі схожою тематикою, шукав нові тули, які могли допомогти, і були добре задокументовані і потім перейменовані. Ну і нарешті апофеоз цієї притчі, є таки універсальний лаунчер для Mac application bundle. Підбиваючи підсумок, хочеться сказати велике спасибі людині на ім'я Dylan Myers, який залишив чудовий коммент, суть якого зводиться до того, що потрібно перейменувати universaleJavaApplicationStub, я пішов трохи далі і перейменував його в JavaAppLauncher. Що і стало для мене рішенням: збираємо проект за допомогою ant, в тому числі і збираємо bundle за допомогою таска appbundle для ant (якщо вам потрібна вбудована JRE, то збирати доведеться на Mac, оскільки буде скопійована JRE, яка встановлена на ваш Mac, це тільки заготовка, я вручну доводив bundle до працездатного стану, але базові властивості для Info.plist все ж можна вказати в тасці appbundle), замінюємо стандартний JavaAppLauncher на universaleJavaApplicationStub і перейменовуємо його в JavaAppLauncher. Після всіх виконаних маніпуляцій, я поклав готовий application bundle на свій білд сервер під Linux, і Jenkins абсолютно без проблем зливає нову версію програми з репозитарію, запускає ant таски, копіює потрібні jar файли в bundle, і найголовніше, це працює. Дякую за увагу.