Skip to content

Манифест плагина ​

Каждое дополнение 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:

properties
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:

bash
./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, и Gradle version (внедряемый во время сборки) тоже остаётся 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.
  • [email protected]: Требует ровно версию 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 превращаются в â€".)

Две строки ниже различаются только значением — первая использует сырое длинное тире, вторая — его эскейп —:

properties
# 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:

kotlin
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 — переменная окружения, которую можно использовать вместо флага выше.

Проверьте свою работу ​

После того как вы отредактировали свои пять строк, подтвердите весь конвейер от начала до конца:

  1. Соберите: выполните ./gradlew build. Она должна завершиться без ошибок. (Если (Обязательное) свойство отсутствует, сборка останавливается на shadowJar и называет его.)
  2. Проверьте манифест внутри jar: выполните unzip -p build/libs/*.jar META-INF/MANIFEST.MF. Вы должны увидеть ваши id, name, main-class и другие строки из раздела Что генерируется выше.
  3. Установите его: поместите jar в папку plugins/ установки Pano.
  4. Подтвердите загрузку: запустите Pano и откройте Панель → Дополнения — теперь вы должны увидеть ваше дополнение в списке под его pluginName.