Манифест плагина
Каждое дополнение Pano поставляется с небольшим файлом метаданных — манифестом — который сообщает Pano, как называется ваше дополнение, кто его сделал, какой класс запустить первым и от чего оно зависит. Эта страница показывает горстку значений, которые вы задаёте, где вы их задаёте и как убедиться, что они попали в собранное дополнение.
Пришли из JavaScript?
Манифест — это эквивалент package.json из мира Java: небольшой файл, описывающий ваш проект. Точка входа просто означает класс, который Pano запускает первым при загрузке вашего дополнения.
Несколько терминов, которые вы увидите на этой странице:
- PF4J (Plugin Framework for Java) — библиотека, которая находит и загружает jar-файлы плагинов, пока Pano работает. Вы никогда не вызываете её сами; ей просто нужно, чтобы существовали определённые метаданные.
- JAR — упакованный вывод Java, который на самом деле просто zip-файл. При сборке дополнения вы получаете один
.jar. MANIFEST.MF— обычный текстовый файл внутри этого jar (подMETA-INF/), содержащий метаданные, которые читает PF4J.
Вы не редактируете MANIFEST.MF вручную. Вместо этого вы задаёте всё в properties-файле, а сборка копирует ваши значения в манифест за вас.
Эта страница предполагает, что вы создали проект из boilerplate
Эти инструкции применимы к проекту плагина, созданному из Pano boilerplate. Если вы ещё этого не сделали, начните с Начала работы. Boilerplate уже поставляет gradle.properties со всеми заполненными ключами ниже.
Когда вы реально это трогаете? На практике вы меняете только пять строк перед первой сборкой — pluginId, pluginName, pluginDescription, pluginClass и pluginDeveloper. Всё остальное может остаться ровно таким, каким его поставляет boilerplate.
Настройка gradle.properties
Откройте gradle.properties в корневой папке вашего проекта плагина — boilerplate уже содержит его, предзаполненный всеми ключами ниже. Во время сборки Gradle читает эти значения и внедряет их в манифест итогового JAR за вас.
Пример
Вот полный gradle.properties из дополнения Announcements:
pluginId=pano-plugin-announcement
pluginName=Announcements
pluginDescription=Create, edit and manage your Minecraft server announcements!
pluginPanoVersion=local-build
pluginClass=com.panomc.plugins.announcement.AnnouncementPlugin
pluginDeveloper=Pano
pluginLicense=MIT
pluginSourceUrl=https://github.com/panomc/pano-plugin-announcement
pluginDependencies=
pluginRequires=Держите эти значения в простом ASCII (буквы без диакритики, цифры, базовая пунктуация). Если вам нужно длинное тире, диакритика или эмодзи, сначала прочтите заметку о кодировке под разделом Подводные камни ниже.
Контрольная точка. Соберите один раз и загляните внутрь jar:
./gradlew build
unzip -p build/libs/*.jar META-INF/MANIFEST.MFСреди напечатанных строк вы должны увидеть ваши id, name и main-class.
Что генерируется
При заданном gradle.properties выше сборка записывает в META-INF/MANIFEST.MF строки вроде этих (имена атрибутов приходят от PF4J; значения — прямо из ваших properties):
id: pano-plugin-announcement
name: Announcements
description: Create, edit and manage your Minecraft server announcements!
pano-version: local-build
main-class: com.panomc.plugins.announcement.AnnouncementPlugin
version: local-build
developer: Pano
license: MIT
source-url: https://github.com/panomc/pano-plugin-announcementВ этом весь смысл файла: gradle.properties на входе, MANIFEST.MF на выходе. (Точный набор строк зависит от того, какие необязательные properties вы заполнили.)
Ключевые свойства
Теперь значения, строка за строкой. (Обязательно) означает, что сборка Pano падает без него — если обязательное свойство отсутствует, ./gradlew build останавливается на шаге shadowJar с ошибкой, называющей свойство. Это не означает, что оно нужно PF4J. (Необязательно) свойства можно оставить пустыми или удалить.
pluginId: (Обязательно) Уникальный id вашего дополнения. Используйте только строчные буквы, цифры и дефисы — без пробелов. Соглашение —pano-plugin-<name>(например,pano-plugin-announcement); префиксpano-plugin-— соглашение, а не жёсткое требование, но придерживайтесь его. Выберите его один раз и никогда не меняйте (смотрите подсказку ниже).pluginName: (Обязательно) Читаемое человеком имя плагина (например,Announcements).pluginDescription: (Необязательно) Краткое описание того, что делает ваш плагин.pluginPanoVersion: (Обязательно) Версия Pano, под которую собран этот плагин. Оставьте это какlocal-buildво время разработки — смотрите предупреждение о версиях ниже.pluginClass: (Обязательно) Полный путь к главному классу вашего дополнения — имя пакета плюс имя класса, напримерcom.example.myplugin.MyPlugin. Это класс вsrc/main/kotlin/..., чьё объявление содержит: PanoPlugin(— класс, который Pano запускает первым при загрузке вашего дополнения. (Разработчики называют это полностью квалифицированным именем.)pluginDeveloper: (Обязательно) Автор или организация, разрабатывающая плагин.pluginLicense: (Необязательно) Лицензия плагина (например,MIT,Apache-2.0).pluginSourceUrl: (Необязательно) URL исходного кода плагина.pluginDependencies: (Необязательно) Другие плагины Pano, которые нужны вашему дополнению, разделённые запятыми — напримерpluginDependencies=other-plugin, some-plugin?. Полный синтаксис смотрите в разделе Зависимости ниже.pluginRequires: (Необязательно) На каких версиях Pano вашему дополнению разрешено работать, записывается как диапазон версий вроде>=1.0.0. Пусто (по умолчанию) означает любую версию. Задавайте это, только если ваше дополнение полагается на возможность, добавленную в конкретном релизе Pano, напримерpluginRequires=>=1.2.0. (Это соответствует атрибуту манифестаrequires.)
pluginPanoVersion против pluginRequires
Эти два звучат похоже, но делают разную работу:
pluginPanoVersionпросто записывает, под какую версию Pano вы собирали. Это информационно.pluginRequiresпринуждается: если вы задаёте диапазон, Pano отказывается загружать ваше дополнение на любой версии Pano вне его.
Ваш pluginId используется повсюду — выбирайте его тщательно
Pano переиспользует эту одну строку по всей системе, так что она также именует:
- папку данных вашего дополнения (
plugins/<pluginId>/), - то, как Pano отслеживает версию схемы базы данных вашего дополнения (на какой миграции находятся ваши таблицы),
- сегмент URL для UI вашего дополнения,
- каждое разрешение, которое определяет ваше дополнение (каждое имеет префикс
pano.plugin.<pluginId>.…), и - id вашего листинга в маркетплейсе (
resourceId) при публикации — смотрите Сборка и публикация.
Выберите его один раз и никогда не меняйте после того, как ваше дополнение стало активным. Переименование заставляет Pano считать дополнение совершенно новым: ваши старые таблицы базы данных, файлы и выданные разрешения игнорируются и фактически теряются.
Вы не задаёте номера версий вручную
Номера версий вычисляются за вас, и ошибка здесь ломает конвейер релизов — так что просто не трогайте их:
- Вы никогда не печатаете номер версии. Boilerplate не просит вас задавать Gradle
versionвообще, так что не добавляйте его. - Локальные сборки всегда
local-build. ДержитеpluginPanoVersion=local-build, и Gradleversion(внедряемый во время сборки) тоже остаётсяlocal-build. - При релизе конвейер заполняет его. Когда вы делаете push, CI — автоматическая сборка, которая выполняется на GitHub — использует инструмент под названием semantic-release для вычисления реальной
versionиз ваших сообщений коммитов. Ручное редактирование ломает это.
Версия Pano, показанная в маркетплейсе, — ещё одно, отдельное значение, настраиваемое при публикации. Смотрите Сборка и публикация, как версии выводятся из ваших коммитов.
Зависимости
Вы объявляете, какие другие плагины Pano нужны вашему дополнению, в gradle.properties; сборка записывает их в манифест за вас.
Не то же самое, что зависимость-библиотека
Это не зависимость-библиотека build.gradle.kts (строки implementation(...) — эквивалент npm install в Pano). pluginDependencies означает другие плагины Pano, которые должны быть установлены на сервере во время выполнения, чтобы ваше дополнение работало.
Зависимости плагинов (pluginDependencies)
Перечислите другие дополнения, которые нужны вашему, разделённые запятыми.
- Синтаксис:
pluginIdилиpluginId@version - Необязательная зависимость: добавьте
?к ID плагина.
Часть после @ — это диапазон версий. Поддерживаются стандартные операторы сравнения вроде >=, <=, > и <; полную грамматику смотрите в документации PF4J.
Примеры:
pluginDependencies=other-plugin: Требует любую версиюother-plugin.pluginDependencies=other-plugin@1.2.0: Требует ровно версию1.2.0.pluginDependencies=other-plugin@>=1.2.0: Требует версию1.2.0или выше.pluginDependencies=other-plugin@<2.0.0: Требует любую версию ниже2.0.0.pluginDependencies=other-plugin?: Необязательная зависимость. Если присутствует, загружается перед вашим плагином; если нет, ваш всё равно загружается.pluginDependencies=other-plugin, some-plugin?: Две зависимости, разделённые запятыми — одна обязательная, одна необязательная.
Что если обязательная зависимость отсутствует?
Если обязательная зависимость не установлена на сервере, Pano отказывается загружать ваше дополнение и логирует ошибку при запуске — проверьте консоль/лог платформы, чтобы увидеть, какой из них не хватает.
Подводные камни
gradle.properties читается как ISO-8859-1
Используйте простой ASCII в значениях gradle.properties
Используйте только символы простого ASCII — английские буквы без диакритики, цифры и базовую пунктуацию — в этих значениях. Для чего-либо ещё (длинное тире, буква с диакритикой или эмодзи) пишите Unicode-эскейп \uXXXX вместо сырого символа, иначе он будет искажён в манифесте.
Почему: Gradle парсит файлы .properties в кодировке ISO-8859-1 (Latin-1), а не UTF-8. (Для любопытных: буквальное длинное тире — — это байты UTF-8 0xE2 0x80 0x94, которые при чтении как Latin-1 превращаются в â€".)
Две строки ниже различаются только значением — первая использует сырое длинное тире, вторая — его эскейп —:
# Wrong — the literal em dash is mangled to â€"
pluginDescription=Manage your server — fast and simple.
# Right - \u2014 is the escape for an em dash
pluginDescription=Manage your server \u2014 fast and simple.Чтобы найти эскейп для любого символа, найдите его на Unicode-сайте (ищите, например, «unicode code point for é») и запишите как \uXXXX — например, é это \u00E9.
Продвинутое: ручная настройка
Файл gradle.properties — это просто слой удобства. Если вы предпочитаете настраивать манифест сами или вам нужны динамические значения, вы можете отредактировать задачу shadowJar в build.gradle.kts. (shadowJar — это шаг сборки, который упаковывает ваше дополнение плюс его библиотеки в единый .jar, который загружает Pano.)
Вот как Pano Boilerplate сопоставляет свойства с манифестом. Каждая строка val … by project вытягивает соответствующее значение из gradle.properties; блок manifest { } затем записывает его в MANIFEST.MF под именем атрибута, которого ожидает PF4J:
shadowJar {
val pluginId: String by project
val pluginName: String by project
val pluginDescription: String? by project
val pluginPanoVersion: String by project
val pluginClass: String by project
val pluginDeveloper: String by project
val pluginLicense: String? by project
val pluginSourceUrl: String? by project
val pluginDependencies: String? by project
val pluginRequires: String? by project
manifest {
attributes["id"] = pluginId
attributes["name"] = pluginName
pluginDescription?.let { attributes["description"] = it }
attributes["pano-version"] = pluginPanoVersion
attributes["main-class"] = pluginClass
attributes["version"] = version
attributes["developer"] = pluginDeveloper
pluginLicense?.let { attributes["license"] = it }
pluginSourceUrl?.let { attributes["source-url"] = it }
pluginDependencies?.let { attributes["dependencies"] = it }
pluginRequires?.let { attributes["requires"] = it }
}
}Не переименовывайте ключи атрибутов
Вы можете свободно добавлять атрибуты, но не переименовывайте существующие ключи (id, name, main-class, pano-version и так далее). PF4J ищет их по этим точным именам, так что переименованный ключ незаметно перестаёт загружать ваше дополнение. Для справки: сам PF4J требует только id, main-class и version; всё остальное необязательно и записывается в манифест только когда вы это задаёте.
Pano использует PF4J в фоне для обработки всей загрузки плагинов и управления ими. Вы никогда не взаимодействуете с ним напрямую в стандартной разработке, но если хотите более глубокие технические детали, можете обратиться к документации PF4J.
Свойства премиум-сборки
Отгружаете премиум (платное) дополнение? Сборка встраивает публичный ключ лицензии — небольшой ключ, который Pano использует для проверки, что покупатель действительно заплатил — в ваш jar во время сборки. Это задаётся флагами сборки, а не свойствами манифеста, и без любого из них ваше дополнение собирается как бесплатный (нелицензированный) jar. Полный процесс живёт на странице Премиум-аддоны; используемые там флаги:
-PlicenseServer=dev|prod|<url>— на какой сервер лицензий указывает сборка.-PpanoLicensePublicKey=<base64>— сам публичный ключ, переданный как строка base64.PANO_LICENSE_PUBLIC_KEY— переменная окружения, которую можно использовать вместо флага выше.
Проверьте свою работу
После того как вы отредактировали свои пять строк, подтвердите весь конвейер от начала до конца:
- Соберите: выполните
./gradlew build. Она должна завершиться без ошибок. (Если (Обязательное) свойство отсутствует, сборка останавливается наshadowJarи называет его.) - Проверьте манифест внутри jar: выполните
unzip -p build/libs/*.jar META-INF/MANIFEST.MF. Вы должны увидеть вашиid,name,main-classи другие строки из раздела Что генерируется выше. - Установите его: поместите jar в папку
plugins/установки Pano. - Подтвердите загрузку: запустите Pano и откройте Панель → Дополнения — теперь вы должны увидеть ваше дополнение в списке под его
pluginName.