From 316a40e4bc4a4450ee5bc05d675916e79aecf88b Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 28 Aug 2020 20:07:02 +0800 Subject: [PATCH] Support PluginLoader with ServiceLoader --- backend/mirai-console/build.gradle.kts | 6 + .../net/mamoe/mirai/console/MiraiConsole.kt | 2 +- .../internal/data/SemverAsStringSerializer.kt | 31 ---- .../internal/data/builtin/ConsoleDataScope.kt | 1 + .../internal/plugin/JarPluginLoaderImpl.kt | 103 +++++------- .../internal/plugin/JvmPluginInternal.kt | 26 ++- .../internal/plugin/PluginManagerImpl.kt | 41 +++-- .../console/internal/plugin/PluginsLoader.kt | 159 ------------------ .../mirai/console/plugin/PluginLoader.kt | 18 +- .../mirai/console/plugin/PluginManager.kt | 8 + .../plugin/description/PluginDependency.kt | 51 +----- .../plugin/description/PluginDescription.kt | 3 +- .../mirai/console/plugin/jvm/JvmPlugin.kt | 5 +- .../plugin/jvm/JvmPluginDescription.kt | 112 +++--------- .../mirai/console/plugin/jvm/KotlinPlugin.kt | 21 +-- .../mamoe/mirai/console/util/BotManager.kt | 2 +- .../mamoe/mirai/console/data/SettingTest.kt | 7 +- buildSrc/src/main/kotlin/Versions.kt | 2 +- frontend/mirai-console-pure/build.gradle.kts | 7 + 19 files changed, 147 insertions(+), 458 deletions(-) delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/SemverAsStringSerializer.kt delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginsLoader.kt diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index a33be3dc8..61b4b23d0 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -9,6 +9,7 @@ import java.util.TimeZone plugins { kotlin("jvm") kotlin("plugin.serialization") + kotlin("kapt") id("java") `maven-publish` id("com.jfrog.bintray") @@ -82,6 +83,11 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:5.2.0") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0") + + + val autoService = "1.0-rc7" + kapt("com.google.auto.service", "auto-service", autoService) + compileOnly("com.google.auto.service", "auto-service-annotations", autoService) } ext.apply { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt index acfc72ad0..28ff81c47 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt @@ -19,13 +19,13 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole.INSTANCE import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge -import net.mamoe.mirai.console.internal.data.builtin.childScopeContext import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.center.PluginCenter import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI +import net.mamoe.mirai.console.util.childScopeContext import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.MiraiLogger import java.io.File diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/SemverAsStringSerializer.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/SemverAsStringSerializer.kt deleted file mode 100644 index 9f9a58f94..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/SemverAsStringSerializer.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.internal.data - -import com.vdurmont.semver4j.Semver -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializer -import kotlinx.serialization.builtins.serializer - -@Serializer(forClass = Semver::class) -internal object SemverAsStringSerializerLoose : KSerializer by String.serializer().map( - serializer = { it.toString() }, - deserializer = { - Semver(it.removePrefix("v").removePrefix("V"), Semver.SemverType.LOOSE) - } -) - -@Serializer(forClass = Semver::class) -internal object SemverAsStringSerializerIvy : KSerializer by String.serializer().map( - serializer = { it.toString() }, - deserializer = { - Semver(it.removePrefix("v").removePrefix("V"), Semver.SemverType.IVY) - } -) \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtin/ConsoleDataScope.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtin/ConsoleDataScope.kt index ee6ea5af0..eeb677304 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtin/ConsoleDataScope.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtin/ConsoleDataScope.kt @@ -16,6 +16,7 @@ import net.mamoe.mirai.console.data.PluginConfig import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge +import net.mamoe.mirai.console.util.childScope import net.mamoe.mirai.utils.minutesToMillis diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt index 022ec0dce..fdb73ccb1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt @@ -11,23 +11,23 @@ package net.mamoe.mirai.console.internal.plugin import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job import kotlinx.coroutines.ensureActive import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge -import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip -import net.mamoe.mirai.console.internal.data.createInstanceOrNull import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader import net.mamoe.mirai.console.plugin.PluginLoadException -import net.mamoe.mirai.console.plugin.jvm.* +import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader +import net.mamoe.mirai.console.plugin.jvm.JvmPlugin +import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.childScopeContext import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.yamlkt.Yaml import java.io.File -import java.net.URI +import java.net.URLClassLoader +import java.util.* import kotlin.coroutines.CoroutineContext +import kotlin.streams.asSequence internal object JarPluginLoaderImpl : AbstractFilePluginLoader(".jar"), @@ -48,71 +48,53 @@ internal object JarPluginLoaderImpl : logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable) }) - internal val classLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader) - - init { // delayed - coroutineContext[Job]!!.invokeOnCompletion { - classLoader.clear() - } - } + internal val classLoaders: MutableList = mutableListOf() @Suppress("EXTENSION_SHADOWED_BY_MEMBER") // doesn't matter override val JvmPlugin.description: JvmPluginDescription get() = this.description - override fun Sequence.mapToDescription(): List { - return this.associateWith { URI("jar:file:${it.absolutePath.replace('\\', '/')}!/plugin.yml").toURL() } - .mapNotNull { (file, url) -> + override fun Sequence.extractPlugins(): List { + ensureActive() + + fun ServiceLoader.loadAll(file: File?): Sequence { + return stream().asSequence().mapNotNull { kotlin.runCatching { - url.readText() - }.fold( - onSuccess = { yaml -> - Yaml.nonStrict.decodeFromString(JvmPluginDescriptionImpl.serializer(), yaml) - }, - onFailure = { - logger.error("Cannot load plugin file ${file.name}", it) - null - } - )?.also { it._file = file } + it.type().kotlin.objectInstance ?: it.get() + }.onFailure { + logger.error("Cannot load plugin ${file ?: ""}", it) + }.getOrNull() } + } + + val inMemoryPlugins = + ServiceLoader.load( + JvmPlugin::class.java, + generateSequence(MiraiConsole::class.java.classLoader) { it.parent }.last() + ).loadAll(null) + + val filePlugins = this.associateWith { + URLClassLoader(arrayOf(it.toURI().toURL()), MiraiConsole::class.java.classLoader) + }.onEach { (_, classLoader) -> + classLoaders.add(classLoader) + }.mapValues { + ServiceLoader.load(JvmPlugin::class.java, it.value) + }.flatMap { (file, loader) -> + loader.loadAll(file) + } + + return (inMemoryPlugins + filePlugins).toSet().toList() } @Throws(PluginLoadException::class) - override fun load(description: JvmPluginDescription): JvmPlugin { - val main = when (description) { - is JvmMemoryPluginDescription -> { - description.instance - } - is JvmPluginDescriptionImpl -> with(description) { - classLoader.loadPluginMainClassByJarFile( - pluginName = name, - mainClass = mainClassName, - jarFile = file - ).kotlin.run { - objectInstance - ?: createInstanceOrNull() - ?: (java.constructors + java.declaredConstructors) - .firstOrNull { it.parameterCount == 0 } - ?.apply { kotlin.runCatching { isAccessible = true } } - ?.newInstance() - } ?: error("No Kotlin object or public no-arg constructor found for $mainClassName") - } - else -> error("Illegal description: ${description::class.qualifiedName}") - } - - description.runCatching { - ensureActive() - - check(main is JvmPlugin) { "Main class ${main::class.qualifiedNameOrTip} from plugin ${description.name} does not extend JvmPlugin." } - - if (main is JvmPluginInternal) { - main._description = description - main.internalOnLoad() - } else main.onLoad() - - return main + override fun load(plugin: JvmPlugin) { + ensureActive() + runCatching { + if (plugin is JvmPluginInternal) { + plugin.internalOnLoad() + } else plugin.onLoad() }.getOrElse { - throw PluginLoadException("Exception while loading ${description.name}", it) + throw PluginLoadException("Exception while loading ${plugin.description.name}", it) } } @@ -126,6 +108,7 @@ internal object JarPluginLoaderImpl : override fun disable(plugin: JvmPlugin) { if (!plugin.isEnabled) return + ensureActive() if (plugin is JvmPluginInternal) { plugin.internalOnDisable() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt index 7841c04b0..0e7c6dae9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt @@ -19,7 +19,6 @@ import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer import net.mamoe.mirai.console.plugin.jvm.JvmPlugin -import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.utils.MiraiLogger import java.io.File import java.io.InputStream @@ -35,8 +34,7 @@ internal val T.job: Job where T : CoroutineScope, T : Plugin get() = this.co @PublishedApi internal abstract class JvmPluginInternal( parentCoroutineContext: CoroutineContext -) : JvmPlugin, - CoroutineScope { +) : JvmPlugin, CoroutineScope { final override var isEnabled: Boolean = false @@ -44,17 +42,9 @@ internal abstract class JvmPluginInternal( override fun getResourceAsStream(path: String): InputStream? = resourceContainerDelegate.getResourceAsStream(path) // region JvmPlugin - /** - * Initialized immediately after construction of [JvmPluginInternal] instance - */ - @Suppress("PropertyName") - internal open lateinit var _description: JvmPluginDescription - - final override val description: JvmPluginDescription get() = _description - final override val logger: MiraiLogger by lazy { MiraiConsole.newLogger( - "Plugin ${this._description.name}" + "Plugin ${this.description.name}" ) } @@ -121,11 +111,13 @@ internal abstract class JvmPluginInternal( internal val _intrinsicCoroutineContext: CoroutineContext by lazy { CoroutineName("Plugin $name") } - @JvmField internal val coroutineContextInitializer = { - CoroutineExceptionHandler { _, throwable -> - if (throwable !is CancellationException) logger.error(throwable) + CoroutineExceptionHandler { context, throwable -> + if (throwable.rootCauseOrSelf !is CancellationException) logger.error( + "Exception in coroutine ${context[CoroutineName]?.name ?: ""} of ${description.name}", + throwable + ) } .plus(parentCoroutineContext) .plus( @@ -183,4 +175,6 @@ internal inline fun AtomicLong.updateWhen(condition: (Long) -> Boolean, update: } return false } -} \ No newline at end of file +} + +internal val Throwable.rootCauseOrSelf: Throwable get() = generateSequence(this) { it.cause }.lastOrNull() ?: this \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt index 2702a3db9..3b0bb81ea 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt @@ -15,7 +15,6 @@ import kotlinx.atomicfu.locks.withLock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip import net.mamoe.mirai.console.internal.data.cast import net.mamoe.mirai.console.internal.data.mkdir import net.mamoe.mirai.console.plugin.* @@ -84,16 +83,16 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol // region LOADING - private fun

PluginLoader.loadPluginNoEnable(description: D): P { - return kotlin.runCatching { - this.load(description).also { resolvedPlugins.add(it) } + private fun

PluginLoader.loadPluginNoEnable(plugin: P) { + kotlin.runCatching { + this.load(plugin) + resolvedPlugins.add(plugin) }.fold( onSuccess = { - logger.info { "Successfully loaded plugin ${description.name}" } - it + logger.info { "Successfully loaded plugin ${plugin.description.name}" } }, onFailure = { - logger.info { "Cannot load plugin ${description.name}" } + logger.info { "Cannot load plugin ${plugin.description.name}" } throw it } ) @@ -138,33 +137,30 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol private fun loadPluginLoaderProvidedByPlugins() { loadersLock.withLock { - JarPluginLoaderImpl.classLoader.pluginLoaders.asSequence() - .flatMap { (name, pluginClassLoader) -> + JarPluginLoaderImpl.classLoaders.asSequence() + .flatMap { pluginClassLoader -> ServiceLoader.load(PluginLoader::class.java, pluginClassLoader) .stream().asSequence() - .associateBy { name } .asSequence() } - .forEach { (name, provider) -> + .forEach { provider -> val pluginLoader = kotlin.runCatching { provider.get() }.getOrElse { logger.error( - { "Could not load PluginLoader ${it::class.qualifiedNameOrTip} from plugin $name" }, + { "Could not load PluginLoader ${provider.type().canonicalName}." }, it ) return@forEach } _pluginLoaders.add(pluginLoader) - logger.info { "Successfully loaded PluginLoader ${pluginLoader::class.qualifiedNameOrTip} from plugin $name" } + logger.info { "Successfully loaded PluginLoader ${provider.type().canonicalName}." } } } } private fun List.loadAndEnableAllInOrder() { - return this.map { (loader, desc) -> - loader to loader.loadPluginNoEnable(desc) - }.forEach { (loader, plugin) -> + return this.forEach { (loader, _, plugin) -> loader.enablePlugin(plugin) } } @@ -189,7 +185,9 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol } private fun List>.listAllPlugins(): List, List>> { - return associateWith { loader -> loader.listPlugins().map { desc -> desc.wrapWith(loader) } }.toList() + return associateWith { loader -> + loader.listPlugins().map { plugin -> plugin.description.wrapWith(loader, plugin) } + }.toList() } @Throws(PluginMissingDependencyException::class) @@ -231,8 +229,9 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol } internal data class PluginDescriptionWithLoader( - @JvmField val loader: PluginLoader<*, PluginDescription>, // easier type - @JvmField val delegate: PluginDescription + @JvmField val loader: PluginLoader, // easier type + @JvmField val delegate: PluginDescription, + @JvmField val plugin: Plugin ) : PluginDescription by delegate @Suppress("UNCHECKED_CAST") @@ -240,9 +239,9 @@ internal fun PluginDescription.unwrap(): D = if (this is PluginDescriptionWithLoader) this.delegate as D else this as D @Suppress("UNCHECKED_CAST") -internal fun PluginDescription.wrapWith(loader: PluginLoader<*, *>): PluginDescriptionWithLoader = +internal fun PluginDescription.wrapWith(loader: PluginLoader<*, *>, plugin: Plugin): PluginDescriptionWithLoader = PluginDescriptionWithLoader( - loader as PluginLoader<*, PluginDescription>, this + loader as PluginLoader, this, plugin ) internal operator fun List.contains(dependency: PluginDependency): Boolean = diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginsLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginsLoader.kt deleted file mode 100644 index bbda38f37..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginsLoader.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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.internal.plugin - -import net.mamoe.mirai.console.MiraiConsole -import java.io.File -import java.net.URLClassLoader - -internal class PluginsLoader(private val parentClassLoader: ClassLoader) { - private val loggerName = "PluginsLoader" - internal val pluginLoaders = linkedMapOf() - private val classesCache = mutableMapOf>() - private val logger = MiraiConsole.newLogger(loggerName) - - /** - * 清除所有插件加载器 - */ - fun clear() { - val iterator = pluginLoaders.iterator() - while (iterator.hasNext()) { - val plugin = iterator.next() - var cl = "" - try { - cl = plugin.value.toString() - plugin.value.close() - iterator.remove() - } catch (e: Throwable) { - logger.error("Plugin(${plugin.key}) can't not close its ClassLoader(${cl})", e) - } - } - classesCache.clear() - } - - /** - * 移除单个插件加载器 - */ - fun remove(pluginName: String): Boolean { - pluginLoaders[pluginName]?.close() ?: return false - pluginLoaders.remove(pluginName) - return true - } - - fun loadPluginMainClassByJarFile(pluginName: String, mainClass: String, jarFile: File): Class<*> { - try { - if (!pluginLoaders.containsKey(pluginName)) { - pluginLoaders[pluginName] = - PluginClassLoader( - jarFile, - this, - parentClassLoader - ) - } - return pluginLoaders[pluginName]!!.loadClass(mainClass) - } catch (e: ClassNotFoundException) { - throw ClassNotFoundException( - "PluginsClassLoader(${pluginName}) can't load this pluginMainClass:${mainClass}", - e - ) - } catch (e: Throwable) { - throw Throwable("init or load class error", e) - } - } - - /** - * 尝试加载插件的依赖,无则返回null - */ - fun findClassByName(name: String): Class<*>? { - return classesCache[name] ?: pluginLoaders.values.asSequence().mapNotNull { - kotlin.runCatching { - it.findClass(name, false) - }.getOrNull() - }.firstOrNull() - } - - fun addClassCache(name: String, clz: Class<*>) { - synchronized(classesCache) { - if (!classesCache.containsKey(name)) { - classesCache[name] = clz - } - } - } -} - - -/** - * A Adapted URL Class Loader that supports Android and JVM for single URL(File) Class Load - */ - -internal open class AdaptiveURLClassLoader(file: File, parent: ClassLoader) : ClassLoader() { - - private val internalClassLoader: ClassLoader by lazy { - kotlin.runCatching { - val loaderClass = Class.forName("dalvik.system.PathClassLoader") - loaderClass.getConstructor(String::class.java, ClassLoader::class.java) - .newInstance(file.absolutePath, parent) as ClassLoader - }.getOrElse { - URLClassLoader(arrayOf((file.toURI().toURL())), parent) - } - } - - override fun loadClass(name: String?): Class<*> { - return internalClassLoader.loadClass(name) - } - - - private val internalClassCache = mutableMapOf>() - - internal val classesCache: Map> - get() = internalClassCache - - internal fun addClassCache(string: String, clazz: Class<*>) { - synchronized(internalClassCache) { - internalClassCache[string] = clazz - } - } - - - fun close() { - if (internalClassLoader is URLClassLoader) { - (internalClassLoader as URLClassLoader).close() - } - internalClassCache.clear() - } - -} - -internal class PluginClassLoader( - file: File, - private val pluginsLoader: PluginsLoader, - parent: ClassLoader -) : AdaptiveURLClassLoader(file, parent) { - - override fun findClass(name: String): Class<*> { - return findClass(name, true) - } - - fun findClass(name: String, global: Boolean = true): Class<*> { - return classesCache[name] ?: kotlin.run { - var clazz: Class<*>? = null - if (global) { - clazz = pluginsLoader.findClassByName(name) - } - if (clazz == null) { - clazz = loadClass(name)//这里应该是find, 如果不行就要改 - } - pluginsLoader.addClassCache(name, clazz) - this.addClassCache(name, clazz) - @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") - clazz!! // compiler bug - } - } -} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt index 5a383cb08..f83a5f12e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt @@ -42,13 +42,15 @@ import java.util.* */ public interface PluginLoader

{ /** - * 扫描并返回可以被加载的插件的 [描述][PluginDescription] 列表. + * 扫描并返回可以被加载的插件的列表. + * + * 这些插件都应处于还未被加载的状态. * * 在 console 启动时, [PluginManager] 会获取所有 [PluginDescription], 分析依赖关系, 确认插件加载顺序. * * **实现细节:** 此函数*只应该*在 console 启动时被调用一次. 但取决于前端实现不同, 或由于被一些插件需要, 此函数也可能会被多次调用. */ - public fun listPlugins(): List + public fun listPlugins(): List

/** * 获取此插件的描述. @@ -75,7 +77,7 @@ public interface PluginLoader

{ * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等). */ @Throws(PluginLoadException::class) - public fun load(description: D): P + public fun load(plugin: P) /** * 启用这个插件. @@ -165,11 +167,11 @@ public abstract class AbstractFilePluginLoader

.mapToDescription(): List + protected abstract fun Sequence.extractPlugins(): List

- public final override fun listPlugins(): List = pluginsFilesSequence().mapToDescription() + public final override fun listPlugins(): List

= pluginsFilesSequence().extractPlugins() } @@ -179,9 +181,9 @@ internal class DeferredPluginLoader

( ) : PluginLoader { private val instance by lazy(initializer) - override fun listPlugins(): List = instance.listPlugins() + override fun listPlugins(): List

= instance.run { listPlugins() } override val P.description: D get() = instance.run { description } - override fun load(description: D): P = instance.load(description) + override fun load(plugin: P) = instance.load(plugin) override fun enable(plugin: P) = instance.enable(plugin) override fun disable(plugin: P) = instance.disable(plugin) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt index 94ef436af..9f7820129 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt @@ -131,6 +131,13 @@ public interface PluginManager { */ public fun Plugin.disable(): Unit = safeLoader.disable(this) + /** + * 加载这个插件 + * + * @see PluginLoader.load + */ + public fun Plugin.load(): Unit = safeLoader.load(this) + /** * 启用这个插件 * @@ -155,6 +162,7 @@ public interface PluginManager { public override fun PluginLoader<*, *>.unregister(): Boolean = PluginManagerImpl.run { unregister() } public override fun Plugin.disable(): Unit = PluginManagerImpl.run { disable() } public override fun Plugin.enable(): Unit = PluginManagerImpl.run { enable() } + public override fun Plugin.load(): Unit = PluginManagerImpl.run { load() } public override val

P.safeLoader: PluginLoader get() = PluginManagerImpl.run { safeLoader } } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt index 148e943fc..0e4dc7802 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt @@ -7,22 +7,17 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("unused") + package net.mamoe.mirai.console.plugin.description import com.vdurmont.semver4j.Semver -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import net.mamoe.mirai.console.internal.data.map -import net.mamoe.yamlkt.Yaml -import net.mamoe.yamlkt.YamlDynamicSerializer /** * 插件的一个依赖的信息. * * @see PluginDescription.dependencies */ -@Serializable(with = PluginDependency.SmartSerializer::class) public data class PluginDependency( /** 依赖插件名 */ public val name: String, @@ -33,47 +28,15 @@ public data class PluginDependency( * * 允许 [Apache Ivy 风格版本号表示](http://ant.apache.org/ivy/history/latest-milestone/settings/version-matchers.html) */ - public val version: @Serializable(net.mamoe.mirai.console.internal.data.SemverAsStringSerializerIvy::class) Semver? = null, + public val version: Semver? = null, /** * 若为 `false`, 插件在找不到此依赖时也能正常加载. */ public val isOptional: Boolean = false ) { - public override fun toString(): String { - return "$name v$version${if (isOptional) "?" else ""}" - } - - - /** - * 可支持解析 [String] 作为 [PluginDependency.version] 或单个 [PluginDependency] - */ - public object SmartSerializer : KSerializer by YamlDynamicSerializer.map( - serializer = { it }, - deserializer = { any -> - when (any) { - is Map<*, *> -> Yaml.nonStrict.decodeFromString( - serializer(), - Yaml.nonStrict.encodeToString>(any) - ) - else -> { - var value = any.toString() - val isOptional = value.endsWith('?') - if (isOptional) { - value = value.removeSuffix("?") - } - - val components = value.split(':') - when (components.size) { - 1 -> PluginDependency(value, isOptional = isOptional) - 2 -> PluginDependency( - components[0], - Semver(components[1], Semver.SemverType.IVY), - isOptional = isOptional - ) - else -> error("Illegal plugin dependency statement: $value") - } - } - } - } + public constructor(name: String, version: String, isOptional: Boolean) : this( + name, + Semver(version, Semver.SemverType.IVY), + isOptional ) } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt index 3fdf4937f..a5f259502 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt @@ -10,7 +10,6 @@ package net.mamoe.mirai.console.plugin.description import com.vdurmont.semver4j.Semver -import kotlinx.serialization.Serializable import net.mamoe.mirai.console.plugin.Plugin @@ -56,6 +55,6 @@ public interface PluginDescription { * * @see PluginDependency */ - public val dependencies: List<@Serializable(with = PluginDependency.SmartSerializer::class) PluginDependency> + public val dependencies: List } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt index 3764496a8..e6bb091d8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt @@ -31,9 +31,12 @@ import net.mamoe.mirai.utils.MiraiLogger /** * Java, Kotlin 或其他 JVM 平台插件 * - * ### ResourceContainer + * ## ResourceContainer * 实现为 [ClassLoader.getResourceAsStream] * + * ## 实现 [JvmPlugin] + * j + * * @see AbstractJvmPlugin 默认实现 * * @see JavaPlugin Java 插件 diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index fd78da67f..66e554c6a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -7,119 +7,45 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("unused") + package net.mamoe.mirai.console.plugin.jvm import com.vdurmont.semver4j.Semver -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient -import net.mamoe.mirai.console.internal.data.SemverAsStringSerializerLoose import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.description.PluginKind -import net.mamoe.mirai.console.util.ConsoleExperimentalAPI -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import java.io.File - -/** - * @see KotlinMemoryPlugin 不需要 "plugin.yml", 不需要相关资源的在内存中加载的插件. - */ -@ConsoleExperimentalAPI -public data class JvmMemoryPluginDescription( - public override val kind: PluginKind, - public override val name: String, - public override val author: String, - public override val version: Semver, - public override val info: String, - public override val dependencies: List, - val instance: JvmPlugin -) : JvmPluginDescription { - init { - require(!name.contains(':')) { "':' is forbidden in plugin name" } - } -} /** * JVM 插件的描述. 通常作为 `plugin.yml` - * - * - * ```yaml - * # 必须. 插件名称, 允许空格, 允许中文, 不允许 ':' - * name: "MyTestPlugin" - * - * # 必须. 插件主类, 即继承 KotlinPlugin 或 JavaPlugin 的类 - * main: org.example.MyPluginMain - * - * # 必须. 插件版本. 遵循《语义化版本 2.0.0》规范 - * version: 0.1.0 - * - * # 可选. 插件种类. - * # 'NORMAL': 表示普通插件 - * # 'LOADER': 表示提供扩展插件加载器的插件 - * kind: NORMAL - * - * # 可选. 插件描述 - * info: "这是一个测试插件" - * - * # 可选. 插件作者 - * author: "Mirai Example" - * - * # 可选. 插件依赖列表. 两种指定方式均可. - * dependencies: - * - name: "the" # 依赖的插件名 - * version: null # 依赖的版本号, 支持 Apache Ivy 格式. 为 null 或不指定时不限制版本 - * isOptional: true # `true` 表示插件在找不到此依赖时也能正常加载 - * - "SamplePlugin" # 名称为 SamplePlugin 的插件, 不限制版本, isOptional=false - * - "TestPlugin:1.0.0+" # 名称为 ExamplePlugin 的插件, 版本至少为 1.0.0, isOptional=false - * - "ExamplePlugin:1.5.0+?" # 名称为 ExamplePlugin 的插件, 版本至少为 1.5.0, 末尾 `?` 表示 isOptional=true - * - "Another test plugin:[1.0.0, 2.0.0)" # 名称为 Another test plugin 的插件, 版本要求大于等于 1.0.0, 小于 2.0.0, isOptional=false - * ``` + * @see SimpleJvmPluginDescription */ public interface JvmPluginDescription : PluginDescription /** - * @see JvmPluginDescriptionImpl + * @see JvmPluginDescription */ -@MiraiExperimentalAPI -@Serializable -public class JvmPluginDescriptionImpl internal constructor( - public override val kind: PluginKind = PluginKind.NORMAL, +public data class SimpleJvmPluginDescription +@JvmOverloads public constructor( public override val name: String, - @SerialName("main") - public val mainClassName: String, + public override val version: Semver, public override val author: String = "", - public override val version: @Serializable(with = SemverAsStringSerializerLoose::class) Semver, public override val info: String = "", - @SerialName("depends") - public override val dependencies: List<@Serializable(with = PluginDependency.SmartSerializer::class) PluginDependency> = listOf() + public override val dependencies: List = listOf(), + public override val kind: PluginKind = PluginKind.NORMAL, ) : JvmPluginDescription { + @JvmOverloads + public constructor( + name: String, + version: String, + author: String = "", + info: String = "", + dependencies: List = listOf(), + kind: PluginKind = PluginKind.NORMAL, + ) : this(name, Semver(version, Semver.SemverType.LOOSE), author, info, dependencies, kind) + init { require(!name.contains(':')) { "':' is forbidden in plugin name" } } - - /** - * 在手动实现时使用这个构造器. - */ - @Suppress("unused") - public constructor( - kind: PluginKind, name: String, mainClassName: String, author: String, - version: Semver, info: String, depends: List, - file: File - ) : this(kind, name, mainClassName, author, version, info, depends) { - this._file = file - } - - public val file: File - get() = _file ?: error("Internal error: JvmPluginDescription(name=$name)._file == null") - - - @Suppress("PropertyName") - @Transient - @JvmField - internal var _file: File? = null - - public override fun toString(): String { - return "JvmPluginDescription(kind=$kind, name='$name', mainClassName='$mainClassName', author='$author', version='$version', info='$info', dependencies=$dependencies, _file=$_file)" - } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/KotlinPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/KotlinPlugin.kt index 6b418b552..4fbf19f88 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/KotlinPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/KotlinPlugin.kt @@ -20,27 +20,10 @@ import kotlin.coroutines.EmptyCoroutineContext * 必须通过 "plugin.yml" 指定主类并由 [JarPluginLoader] 加载. */ public abstract class KotlinPlugin @JvmOverloads constructor( - parentCoroutineContext: CoroutineContext = EmptyCoroutineContext + public final override val description: JvmPluginDescription, + parentCoroutineContext: CoroutineContext = EmptyCoroutineContext, ) : JvmPlugin, AbstractJvmPlugin(parentCoroutineContext) -/** - * 在内存动态加载的插件. 此为预览版本 API. - */ -public abstract class KotlinMemoryPlugin @JvmOverloads constructor( - description: JvmPluginDescription, - parentCoroutineContext: CoroutineContext = EmptyCoroutineContext -) : JvmPlugin, AbstractJvmPlugin(parentCoroutineContext) { - internal final override var _description: JvmPluginDescription - get() = super._description - set(value) { - super._description = value - } - - init { - _description = description - } -} - /* public object MyPlugin : KotlinPlugin() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt index 9666cd2a9..7336579d0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt @@ -14,7 +14,7 @@ package net.mamoe.mirai.console.util import net.mamoe.mirai.Bot -import net.mamoe.mirai.console.internal.util.BotManagerImpl +import net.mamoe.mirai.console.internal.data.builtin.BotManagerImpl import net.mamoe.mirai.contact.User public interface BotManager { diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt index bdfaaf869..d5c01c6f9 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt @@ -11,6 +11,7 @@ package net.mamoe.mirai.console.data import kotlinx.serialization.json.Json import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin +import net.mamoe.mirai.console.plugin.jvm.SimpleJvmPluginDescription import net.mamoe.mirai.console.util.ConsoleInternalAPI import org.junit.jupiter.api.Test import kotlin.test.assertEquals @@ -19,7 +20,11 @@ import kotlin.test.assertSame @OptIn(ConsoleInternalAPI::class) internal class PluginDataTest { - object MyPlugin : KotlinPlugin() + object MyPlugin : KotlinPlugin( + SimpleJvmPluginDescription( + "1", "2" + ) + ) class MyPluginData : AutoSavePluginData() { var int by value(1) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 750014c5a..069d9124e 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -18,7 +18,7 @@ object Versions { const val core = "1.2.2" - const val console = "1.0-M3-1" + const val console = "1.0-RC-dev-1" const val consoleGraphical = "0.0.7" const val consoleTerminal = "0.1.0" const val consolePure = console diff --git a/frontend/mirai-console-pure/build.gradle.kts b/frontend/mirai-console-pure/build.gradle.kts index 5886499c8..aa7c2b6bc 100644 --- a/frontend/mirai-console-pure/build.gradle.kts +++ b/frontend/mirai-console-pure/build.gradle.kts @@ -1,6 +1,7 @@ plugins { kotlin("jvm") kotlin("plugin.serialization") + kotlin("kapt") id("java") `maven-publish` id("com.jfrog.bintray") @@ -43,6 +44,12 @@ dependencies { runtimeOnly("net.mamoe:mirai-core-qqandroid:${Versions.core}") testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}") testApi(project(":mirai-console")) + + + val autoService = "1.0-rc7" + kapt("com.google.auto.service", "auto-service", autoService) + compileOnly("com.google.auto.service", "auto-service-annotations", autoService) + testCompileOnly("com.google.auto.service", "auto-service-annotations", autoService) } ext.apply {