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.
  • 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 превращаются в â€".)

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

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.