插件 Manifest 与打包

定义一个 manifest v2 插件 JAR,并将其作为发布产物分发。

每个 Turboism 插件 JAR 都恰好包含一个位于以下位置的描述符:

META-INF/turboism/plugin.json

Runtime 仅接受 schema version 2。不会加载或自动升级 version 1。

最小 Manifest

{
  "format": "turboism.plugin.meta",
  "schemaVersion": 2,
  "id": "dev.example.plugin.hello",
  "name": "Hello Plugin",
  "version": "0.1.0",
  "description": "Minimal Turboism plugin lifecycle example.",
  "entrypoints": [
    "dev.example.plugin.HelloPlugin"
  ],
  "turboismApi": "[0.1.0,0.2.0)",
  "authors": [
    { "name": "Example Author" }
  ],
  "license": "MIT",
  "website": "https://example.org/hello-plugin",
  "resources": [],
  "i18n": {
    "baseName": "META-INF/turboism/i18n/messages",
    "locales": []
  },
  "dependencies": [],
  "permissions": [],
  "capabilities": [],
  "environment": {
    "requiresCubism": false,
    "ui": "none"
  }
}

入口点规则

每个入口点必须:

  • 存在于同一个插件 JAR 中;
  • 为 public;
  • 实现 TurboismPlugin 或其子接口;
  • 提供一个 public 无参数构造函数。

多个入口点共享一个插件身份、描述符、ClassLoader、PluginContextDisposableScope、权限集、配置命名空间、存储命名空间、资源和本地化声明。

construct/init/enable: manifest order
disable/shutdown:       reverse manifest order

构造、初始化或启用期间发生的失败会回滚整个 JAR。

依赖

{
  "id": "dev.example.plugin.base",
  "version": "[1.0.0,2.0.0)",
  "type": "required",
  "ordering": "after",
  "reason": "Uses the base plugin contract."
}
  • typerequiredoptional
  • orderingnonebeforeafter
  • 依赖 ID 使用反向域名语法。
  • 版本范围使用当前 Turboism 版本范围语法。

依赖记录控制解析与排序。它不会将另一个插件的私有实现包变为公共 Java 依赖。

权限

每条权限记录都包含一个已知 ID、applicationuser scope,以及非空的原因。

{
  "id": "turboism.action.register",
  "scope": "application",
  "reason": "Registers the Hello action."
}

仅声明插件调用的 SDK 服务所需的权限。权限不保证 Provider 可用,也不会绕过操作验证。

资源与本地化

资源根目录是以 / 结尾的规范化相对前缀:

"resources": ["icons/"],
"i18n": {
  "baseName": "META-INF/turboism/i18n/messages",
  "locales": ["base", "en", "zh_Hans"]
}

随后 JAR 必须包含:

icons/hello.png
META-INF/turboism/i18n/messages.properties
META-INF/turboism/i18n/messages_en.properties
META-INF/turboism/i18n/messages_zh_Hans.properties

声明的根目录和目录必须存在。普通的非 class 资源必须属于已声明的根目录。路径遍历、绝对路径、反斜杠、空路径段和未声明资源都会被拒绝。

构建与验证

对于仓库内的插件模块:

./gradlew :plugins:hello:test :plugins:hello:jar validatePluginMeta \
  --no-daemon --console=plain

该仓库使用 Java 17,并在以下位置写入模块构建产物:

build/worktree/<worktreeId>/<module>/libs/

严格的 JAR 验证

JAR 本身即是安装产物。Runtime 将所选 JAR 视为不受信任的本地输入,并验证:

  • 常规文件、源链中没有符号链接,上限 16 MiB;
  • 恰好一个 META-INF/turboism/plugin.json
  • 描述符 ID、版本和 API 范围一致;
  • 没有复制的 SDK、Runtime、test、test-framework 或 Live2D 类;
  • 没有原生二进制或 installer payload;
  • 没有嵌套 JAR;
  • 声明的入口点、资源根目录和 i18n catalog 都存在;
  • 没有未声明的普通资源。

托管安装会暂存验证过的 JAR,并在 Cubism 重启后应用。不要通过重命名任意 ZIP 或 JAR 来创建插件。始终分发 Gradle 构建产出的 JAR。