mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-11 07:24:45 +08:00
Merge pull request #155 from Karlatemp/dependencies
Plugin dependencies loading.
This commit is contained in:
commit
75be7fec02
@ -23,7 +23,6 @@ import net.mamoe.mirai.console.plugin.loader.PluginLoadException
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
internal object BuiltInJvmPluginLoaderImpl :
|
||||
@ -52,8 +51,22 @@ internal object BuiltInJvmPluginLoaderImpl :
|
||||
override fun Sequence<File>.extractPlugins(): List<JvmPlugin> {
|
||||
ensureActive()
|
||||
|
||||
fun Sequence<Map.Entry<File, ClassLoader>>.findAllInstances(): Sequence<Map.Entry<File, JvmPlugin>> {
|
||||
return map { (f, pluginClassLoader) ->
|
||||
fun Sequence<Map.Entry<File, JvmPluginClassLoader>>.findAllInstances(): Sequence<Map.Entry<File, JvmPlugin>> {
|
||||
return onEach { (_, pluginClassLoader) ->
|
||||
val exportManagers = pluginClassLoader.findServices(
|
||||
ExportManager::class
|
||||
).loadAllServices()
|
||||
if (exportManagers.isEmpty()) {
|
||||
val rules = pluginClassLoader.getResourceAsStream("export-rules.txt")
|
||||
if (rules == null)
|
||||
pluginClassLoader.declaredFilter = StandardExportManagers.AllExported
|
||||
else rules.bufferedReader(Charsets.UTF_8).useLines {
|
||||
pluginClassLoader.declaredFilter = ExportManagerImpl.parse(it.iterator())
|
||||
}
|
||||
} else {
|
||||
pluginClassLoader.declaredFilter = exportManagers[0]
|
||||
}
|
||||
}.map { (f, pluginClassLoader) ->
|
||||
f to pluginClassLoader.findServices(
|
||||
JvmPlugin::class,
|
||||
KotlinPlugin::class,
|
||||
@ -61,6 +74,7 @@ internal object BuiltInJvmPluginLoaderImpl :
|
||||
JavaPlugin::class
|
||||
).loadAllServices()
|
||||
}.flatMap { (f, list) ->
|
||||
|
||||
list.associateBy { f }.asSequence()
|
||||
}
|
||||
}
|
||||
@ -68,7 +82,7 @@ internal object BuiltInJvmPluginLoaderImpl :
|
||||
val filePlugins = this.filterNot {
|
||||
pluginFileToInstanceMap.containsKey(it)
|
||||
}.associateWith {
|
||||
JvmPluginClassLoader(it, MiraiConsole::class.java.classLoader)
|
||||
JvmPluginClassLoader(it, MiraiConsole::class.java.classLoader, classLoaders)
|
||||
}.onEach { (_, classLoader) ->
|
||||
classLoaders.add(classLoader)
|
||||
}.asSequence().findAllInstances().onEach {
|
||||
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2020 Karlatemp. All rights reserved.
|
||||
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
|
||||
*
|
||||
* LuckPerms-Mirai/mirai-console.mirai-console.main/ExportManagerImpl.kt
|
||||
*
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/Karlatemp/LuckPerms-Mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.internal.plugin
|
||||
|
||||
import net.mamoe.mirai.console.plugin.jvm.ExportManager
|
||||
|
||||
internal class ExportManagerImpl(
|
||||
private val rules: List<(String) -> Boolean?>
|
||||
) : ExportManager {
|
||||
|
||||
override fun isExported(className: String): Boolean {
|
||||
rules.forEach {
|
||||
val result = it(className)
|
||||
if (result != null) return@isExported result
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun parse(lines: Iterator<String>): ExportManagerImpl {
|
||||
fun Boolean.without(value: Boolean) = if (this == value) null else this
|
||||
|
||||
val rules = ArrayList<(String) -> Boolean?>()
|
||||
lines.asSequence().map { it.trim() }.filter { it.isNotBlank() }.filterNot {
|
||||
it[0] == '#'
|
||||
}.forEach { line ->
|
||||
val command = line.substringBefore(' ')
|
||||
val argument = line.substringAfter(' ', missingDelimiterValue = "").trim()
|
||||
val argumentPackage = "$argument."
|
||||
|
||||
when (command) {
|
||||
"exports" -> rules.add {
|
||||
(it == argument || it.startsWith(argumentPackage)).without(false)
|
||||
}
|
||||
"protects" -> rules.add {
|
||||
if (it == argument || it.startsWith(argumentPackage))
|
||||
false
|
||||
else null
|
||||
}
|
||||
"export-all", "export-plugin", "export-system" -> rules.add { true }
|
||||
"protect-all", "protect-plugin", "protect-system" -> rules.add { false }
|
||||
}
|
||||
}
|
||||
return ExportManagerImpl(rules)
|
||||
}
|
||||
}
|
||||
}
|
@ -7,21 +7,120 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.console.internal.plugin
|
||||
|
||||
import net.mamoe.mirai.console.plugin.jvm.ExportManager
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
internal class JvmPluginClassLoader(
|
||||
file: File,
|
||||
val file: File,
|
||||
parent: ClassLoader?,
|
||||
val classLoaders: Collection<JvmPluginClassLoader>,
|
||||
) : URLClassLoader(arrayOf(file.toURI().toURL()), parent) {
|
||||
//// 只允许插件 getResource 时获取插件自身资源, #205
|
||||
override fun getResources(name: String?): Enumeration<URL> = findResources(name)
|
||||
override fun getResource(name: String?): URL? = findResource(name)
|
||||
// getResourceAsStream 在 URLClassLoader 中通过 getResource 确定资源
|
||||
// 因此无需 override getResourceAsStream
|
||||
|
||||
override fun toString(): String {
|
||||
return "JvmPluginClassLoader{source=$file}"
|
||||
}
|
||||
|
||||
private val cache = ConcurrentHashMap<String, Class<*>>()
|
||||
internal var declaredFilter: ExportManager? = null
|
||||
|
||||
companion object {
|
||||
val loadingLock = ConcurrentHashMap<String, Any>()
|
||||
|
||||
init {
|
||||
ClassLoader.registerAsParallelCapable()
|
||||
}
|
||||
}
|
||||
|
||||
override fun findClass(name: String): Class<*> {
|
||||
synchronized(kotlin.run {
|
||||
val lock = Any()
|
||||
loadingLock.putIfAbsent(name, lock) ?: lock
|
||||
}) {
|
||||
return findClass(name, false) ?: throw ClassNotFoundException(name)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun findClass(name: String, disableGlobal: Boolean): Class<*>? {
|
||||
// First. Try direct load in cache.
|
||||
val cachedClass = cache[name]
|
||||
if (cachedClass != null) {
|
||||
if (disableGlobal) {
|
||||
val filter = declaredFilter
|
||||
if (filter != null && !filter.isExported(name)) {
|
||||
throw LoadingDeniedException(name)
|
||||
}
|
||||
}
|
||||
return cachedClass
|
||||
}
|
||||
if (disableGlobal) {
|
||||
// ==== Process Loading Request From JvmPluginClassLoader ====
|
||||
//
|
||||
// If load from other classloader,
|
||||
// means no other loaders are cached.
|
||||
// direct load
|
||||
return kotlin.runCatching {
|
||||
super.findClass(name).also { cache[name] = it }
|
||||
}.getOrElse {
|
||||
if (it is ClassNotFoundException) null
|
||||
else throw it
|
||||
}?.also {
|
||||
// This request is from other classloader,
|
||||
// so we need to check the class is exported or not.
|
||||
val filter = declaredFilter
|
||||
if (filter != null && !filter.isExported(name)) {
|
||||
throw LoadingDeniedException(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==== Process Loading Request From JDK ClassLoading System ====
|
||||
|
||||
// First. scan other classLoaders's caches
|
||||
classLoaders.forEach { otherClassloader ->
|
||||
if (otherClassloader === this) return@forEach
|
||||
val filter = otherClassloader.declaredFilter
|
||||
if (otherClassloader.cache.containsKey(name)) {
|
||||
return if (filter == null || filter.isExported(name)) {
|
||||
otherClassloader.cache[name]
|
||||
} else throw LoadingDeniedException("$name was not exported by $otherClassloader")
|
||||
}
|
||||
}
|
||||
|
||||
// If no cache...
|
||||
return kotlin.runCatching {
|
||||
// Try load this class direct....
|
||||
super.findClass(name).also { cache[name] = it }
|
||||
}.getOrElse { exception ->
|
||||
if (exception is ClassNotFoundException) {
|
||||
// Cannot load the class from this, try others.
|
||||
classLoaders.forEach { otherClassloader ->
|
||||
if (otherClassloader === this) return@forEach
|
||||
val other = kotlin.runCatching {
|
||||
otherClassloader.findClass(name, true)
|
||||
}.onFailure { err ->
|
||||
if (err is LoadingDeniedException || err !is ClassNotFoundException)
|
||||
throw err
|
||||
}.getOrNull()
|
||||
if (other != null) return other
|
||||
}
|
||||
}
|
||||
// Great, nobody known what is the class.
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class LoadingDeniedException(name: String) : ClassNotFoundException(name)
|
||||
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.plugin.jvm
|
||||
|
||||
import net.mamoe.mirai.console.internal.plugin.ExportManagerImpl
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
|
||||
/**
|
||||
* 插件的类导出管理器
|
||||
*
|
||||
*
|
||||
* 允许插件将一些内部实现保护起来, 避免其他插件调用, 要启动这个特性,
|
||||
* 只需要创建名为 `export-rules.txt` 的规则文件,便可以控制插件的类的公开规则。
|
||||
*
|
||||
* 如果正在使用 `Gradle` 项目, 该规则文件一般位于 `src/main/resources` 下
|
||||
*
|
||||
* Example:
|
||||
* ```text
|
||||
*
|
||||
* # #开头的行全部识别为注释
|
||||
*
|
||||
* # exports, 允许其他插件直接使用某个类
|
||||
*
|
||||
* # 导出了一个internal包的一个类
|
||||
* #
|
||||
* exports org.example.miraiconsole.myplugin.internal.OpenInternal
|
||||
*
|
||||
* # 导出了整个 api 包
|
||||
* #
|
||||
* exports org.example.miraiconsole.myplugin.api
|
||||
*
|
||||
* # 保护 org.example.miraiconsole.myplugin.api2.Internal, 不允许其他插件直接使用
|
||||
* #
|
||||
* protects org.example.miraiconsole.myplugin.api2.Internal
|
||||
*
|
||||
* # 保护整个包
|
||||
* #
|
||||
* # 别名: protect-package
|
||||
* protects org.example.miraiconsole.myplugin.internal
|
||||
*
|
||||
* # 此规则不会生效, 因为在此条规则之前,
|
||||
* # org.example.miraiconsole.myplugin.internal 已经被加入到保护域中
|
||||
* exports org.example.miraiconsole.myplugin.internal.NotOpenInternal
|
||||
*
|
||||
*
|
||||
* # export-plugin, 允许其他插件使用除了已经被保护的全部类
|
||||
* # 使用此规则会同时让此规则后的所有规则全部失效
|
||||
* # 别名: export-all, export-system
|
||||
* # export-plugin
|
||||
*
|
||||
*
|
||||
* # 将整个插件放入保护域中
|
||||
* # 除了此规则之前显式 export 的类, 其他插件将不允许直接使用被保护的插件的任何类
|
||||
* # 别名: protect-all, protect-system
|
||||
* protect-plugin
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* 插件也可以通过 Service 来自定义导出控制
|
||||
*
|
||||
* Example:
|
||||
* ```kotlin
|
||||
* @AutoService(ExportManager::class)
|
||||
* object MyExportManager: ExportManager {
|
||||
* override fun isExported(className: String): Boolean {
|
||||
* println(" <== $className")
|
||||
* return true
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public interface ExportManager {
|
||||
/**
|
||||
* 如果 [className] 能够通过 [ExportManager] 的规则, 返回 true
|
||||
*
|
||||
* @param className [className] 是一个合法的满足 [ClassLoader] 的加载规则 的全限定名.
|
||||
* [className] 不应该是数组的全限定名或者JVM基本类型的名字.
|
||||
* See also: [ClassLoader.loadClass]
|
||||
* [ClassLoader#name](https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html#name)
|
||||
*/
|
||||
public fun isExported(className: String): Boolean
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public object StandardExportManagers {
|
||||
@ConsoleExperimentalApi
|
||||
public object AllExported : ExportManager {
|
||||
override fun isExported(className: String): Boolean = true
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public object AllDenied : ExportManager {
|
||||
override fun isExported(className: String): Boolean = false
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
@JvmStatic
|
||||
public fun parse(lines: Iterator<String>): ExportManager {
|
||||
return ExportManagerImpl.parse(lines)
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@
|
||||
[`PluginConfig`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt
|
||||
[`PluginDataStorage`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt
|
||||
|
||||
[`ExportManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/ExportManager.kt
|
||||
|
||||
[`MiraiConsole`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt
|
||||
[`MiraiConsoleImplementation`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt
|
||||
<!--[MiraiConsoleFrontEnd]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt-->
|
||||
@ -160,6 +162,68 @@ public final class JExample extends JavaPlugin {
|
||||
多个插件的加载是*顺序的*,意味着若一个插件的 `onLoad()` 等回调处理缓慢,后续插件的加载也会被延后,即使它们可能没有依赖关系。
|
||||
因此请尽量让 `onLoad()`,`onEnable()`,`onDisable()`快速返回。
|
||||
|
||||
### API 导出管理
|
||||
|
||||
允许插件将一些内部实现保护起来, 避免其他插件调用, 要启动这个特性,
|
||||
只需要创建名为 `export-rules.txt` 的规则文件,便可以控制插件的类的公开规则。
|
||||
|
||||
如果正在使用 `Gradle` 项目, 该规则文件一般位于 `src/main/resources` 下
|
||||
|
||||
Example:
|
||||
```text
|
||||
|
||||
# #开头的行全部识别为注释
|
||||
|
||||
# exports, 允许其他插件直接使用某个类
|
||||
|
||||
# 导出了一个internal包的一个类
|
||||
#
|
||||
exports org.example.miraiconsole.myplugin.internal.OpenInternal
|
||||
|
||||
# 导出了整个 api 包
|
||||
#
|
||||
exports org.example.miraiconsole.myplugin.api
|
||||
|
||||
# 保护 org.example.miraiconsole.myplugin.api2.Internal, 不允许其他插件直接使用
|
||||
#
|
||||
protects org.example.miraiconsole.myplugin.api2.Internal
|
||||
|
||||
# 保护整个包
|
||||
#
|
||||
# 别名: protect-package
|
||||
protects org.example.miraiconsole.myplugin.internal
|
||||
|
||||
# 此规则不会生效, 因为在此条规则之前,
|
||||
# org.example.miraiconsole.myplugin.internal 已经被加入到保护域中
|
||||
exports org.example.miraiconsole.myplugin.internal.NotOpenInternal
|
||||
|
||||
|
||||
# export-plugin, 允许其他插件使用除了已经被保护的全部类
|
||||
# 使用此规则会同时让此规则后的所有规则全部失效
|
||||
# 别名: export-all, export-system
|
||||
# export-plugin
|
||||
|
||||
|
||||
# 将整个插件放入保护域中
|
||||
# 除了此规则之前显式 export 的类, 其他插件将不允许直接使用被保护的插件的任何类
|
||||
# 别名: protect-all, protect-system
|
||||
protect-plugin
|
||||
|
||||
```
|
||||
|
||||
插件也可以通过 Service 来自定义导出控制
|
||||
|
||||
Example:
|
||||
```kotlin
|
||||
@AutoService(ExportManager::class)
|
||||
object MyExportManager: ExportManager {
|
||||
override fun isExported(className: String): Boolean {
|
||||
println(" <== $className")
|
||||
return true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 插件生命周期
|
||||
|
||||
Mirai Console 不提供热加载和热卸载功能,所有插件只能在服务器启动前加载,在服务器结束时卸载。([为什么不支持热加载和卸载插件?])
|
||||
|
Loading…
Reference in New Issue
Block a user