Browse Source

Support PluginLoader with ServiceLoader

Him188 5 năm trước cách đây
mục cha
commit
316a40e4bc
19 tập tin đã thay đổi với 146 bổ sung457 xóa
  1. 6 0
      backend/mirai-console/build.gradle.kts
  2. 1 1
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt
  3. 0 31
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/SemverAsStringSerializer.kt
  4. 1 0
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtin/ConsoleDataScope.kt
  5. 42 59
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt
  6. 10 16
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt
  7. 20 21
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt
  8. 0 159
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginsLoader.kt
  9. 10 8
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
  10. 8 0
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt
  11. 7 44
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt
  12. 1 2
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt
  13. 4 1
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
  14. 19 93
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt
  15. 2 19
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/KotlinPlugin.kt
  16. 1 1
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt
  17. 6 1
      backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt
  18. 1 1
      buildSrc/src/main/kotlin/Versions.kt
  19. 7 0
      frontend/mirai-console-pure/build.gradle.kts

+ 6 - 0
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 {

+ 1 - 1
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

+ 0 - 31
backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/SemverAsStringSerializer.kt

@@ -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<Semver> 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<Semver> by String.serializer().map(
-    serializer = { it.toString() },
-    deserializer = {
-        Semver(it.removePrefix("v").removePrefix("V"), Semver.SemverType.IVY)
-    }
-)

+ 1 - 0
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
 
 

+ 42 - 59
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<JvmPlugin, JvmPluginDescription>(".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<ClassLoader> = mutableListOf()
 
     @Suppress("EXTENSION_SHADOWED_BY_MEMBER") // doesn't matter
     override val JvmPlugin.description: JvmPluginDescription
         get() = this.description
 
-    override fun Sequence<File>.mapToDescription(): List<JvmPluginDescriptionImpl> {
-        return this.associateWith { URI("jar:file:${it.absolutePath.replace('\\', '/')}!/plugin.yml").toURL() }
-            .mapNotNull { (file, url) ->
-                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 }
-            }
-    }
+    override fun Sequence<File>.extractPlugins(): List<JvmPlugin> {
+        ensureActive()
 
-    @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")
+        fun <T> ServiceLoader<T>.loadAll(file: File?): Sequence<T> {
+            return stream().asSequence().mapNotNull {
+                kotlin.runCatching {
+                    it.type().kotlin.objectInstance ?: it.get()
+                }.onFailure {
+                    logger.error("Cannot load plugin ${file ?: "<no-file>"}", it)
+                }.getOrNull()
             }
-            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." }
+        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)
+        }
 
-            if (main is JvmPluginInternal) {
-                main._description = description
-                main.internalOnLoad()
-            } else main.onLoad()
+        return (inMemoryPlugins + filePlugins).toSet().toList()
+    }
 
-            return main
+    @Throws(PluginLoadException::class)
+    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()

+ 10 - 16
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> 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 ?: "<unnamed>"} of ${description.name}",
+                throwable
+            )
         }
             .plus(parentCoroutineContext)
             .plus(
@@ -183,4 +175,6 @@ internal inline fun AtomicLong.updateWhen(condition: (Long) -> Boolean, update:
         }
         return false
     }
-}
+}
+
+internal val Throwable.rootCauseOrSelf: Throwable get() = generateSequence(this) { it.cause }.lastOrNull() ?: this

+ 20 - 21
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 <P : Plugin, D : PluginDescription> PluginLoader<P, D>.loadPluginNoEnable(description: D): P {
-        return kotlin.runCatching {
-            this.load(description).also { resolvedPlugins.add(it) }
+    private fun <P : Plugin, D : PluginDescription> PluginLoader<P, D>.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<PluginDescriptionWithLoader>.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<PluginLoader<*, *>>.listAllPlugins(): List<Pair<PluginLoader<*, *>, List<PluginDescriptionWithLoader>>> {
-        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<Plugin, PluginDescription>, // easier type
+    @JvmField val delegate: PluginDescription,
+    @JvmField val plugin: Plugin
 ) : PluginDescription by delegate
 
 @Suppress("UNCHECKED_CAST")
@@ -240,9 +239,9 @@ internal fun <D : PluginDescription> 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<Plugin, PluginDescription>, this, plugin
     )
 
 internal operator fun List<PluginDescription>.contains(dependency: PluginDependency): Boolean =

+ 0 - 159
backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginsLoader.kt

@@ -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<String, PluginClassLoader>()
-    private val classesCache = mutableMapOf<String, Class<*>>()
-    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<String, Class<*>>()
-
-    internal val classesCache: Map<String, Class<*>>
-        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
-        }
-    }
-}

+ 10 - 8
backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt

@@ -42,13 +42,15 @@ import java.util.*
  */
 public interface PluginLoader<P : Plugin, D : PluginDescription> {
     /**
-     * 扫描并返回可以被加载的插件的 [描述][PluginDescription] 列表.
+     * 扫描并返回可以被加载的插件的列表.
+     *
+     * 这些插件都应处于还未被加载的状态.
      *
      * 在 console 启动时, [PluginManager] 会获取所有 [PluginDescription], 分析依赖关系, 确认插件加载顺序.
      *
      * **实现细节:** 此函数*只应该*在 console 启动时被调用一次. 但取决于前端实现不同, 或由于被一些插件需要, 此函数也可能会被多次调用.
      */
-    public fun listPlugins(): List<D>
+    public fun listPlugins(): List<P>
 
     /**
      * 获取此插件的描述.
@@ -75,7 +77,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
      * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
      */
     @Throws(PluginLoadException::class)
-    public fun load(description: D): P
+    public fun load(plugin: P)
 
     /**
      * 启用这个插件.
@@ -165,11 +167,11 @@ public abstract class AbstractFilePluginLoader<P : Plugin, D : PluginDescription
             .filter { it.isFile && it.name.endsWith(fileSuffix, ignoreCase = true) }
 
     /**
-     * 读取扫描到的后缀与 [fileSuffix] 相同的文件中的 [PluginDescription]
+     * 读取扫描到的后缀与 [fileSuffix] 相同的文件中的插件实例, 但不 [加载][PluginLoader.load]
      */
-    protected abstract fun Sequence<File>.mapToDescription(): List<D>
+    protected abstract fun Sequence<File>.extractPlugins(): List<P>
 
-    public final override fun listPlugins(): List<D> = pluginsFilesSequence().mapToDescription()
+    public final override fun listPlugins(): List<P> = pluginsFilesSequence().extractPlugins()
 }
 
 
@@ -179,9 +181,9 @@ internal class DeferredPluginLoader<P : Plugin, D : PluginDescription>(
 ) : PluginLoader<P, D> {
     private val instance by lazy(initializer)
 
-    override fun listPlugins(): List<D> = instance.listPlugins()
+    override fun listPlugins(): List<P> = 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)
 }

+ 8 - 0
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 : Plugin> P.safeLoader: PluginLoader<P, PluginDescription> get() = PluginManagerImpl.run { safeLoader }
     }
 }

+ 7 - 44
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<PluginDependency> by YamlDynamicSerializer.map(
-        serializer = { it },
-        deserializer = { any ->
-            when (any) {
-                is Map<*, *> -> Yaml.nonStrict.decodeFromString(
-                    serializer(),
-                    Yaml.nonStrict.encodeToString<Map<*, *>>(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
     )
 }

+ 1 - 2
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<PluginDependency>
 }
 

+ 4 - 1
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 插件

+ 19 - 93
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<PluginDependency>,
-    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<PluginDependency> = listOf(),
+    public override val kind: PluginKind = PluginKind.NORMAL,
 ) : JvmPluginDescription {
 
-    init {
-        require(!name.contains(':')) { "':' is forbidden in plugin name" }
-    }
-
-    /**
-     * 在手动实现时使用这个构造器.
-     */
-    @Suppress("unused")
+    @JvmOverloads
     public constructor(
-        kind: PluginKind, name: String, mainClassName: String, author: String,
-        version: Semver, info: String, depends: List<PluginDependency>,
-        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")
-
+        name: String,
+        version: String,
+        author: String = "",
+        info: String = "",
+        dependencies: List<PluginDependency> = listOf(),
+        kind: PluginKind = PluginKind.NORMAL,
+    ) : this(name, Semver(version, Semver.SemverType.LOOSE), author, info, dependencies, kind)
 
-    @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)"
+    init {
+        require(!name.contains(':')) { "':' is forbidden in plugin name" }
     }
 }

+ 2 - 19
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()

+ 1 - 1
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 {

+ 6 - 1
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)

+ 1 - 1
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

+ 7 - 0
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 {