diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt index e90654632..433733de8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt @@ -46,6 +46,8 @@ import java.nio.file.Path import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext /** @@ -84,59 +86,109 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI override fun createLogger(identity: String?): MiraiLogger = instance.createLogger(identity) @OptIn(ConsoleExperimentalAPI::class, ExperimentalPermission::class) + @Suppress("RemoveRedundantBackticks") internal fun doStart() { - val buildDateFormatted = - buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) - mainLogger.info { "Starting mirai-console..." } - mainLogger.info { "Backend: version $version, built on $buildDateFormatted." } - mainLogger.info { frontEndDescription.render() } + phase `greeting`@{ + val buildDateFormatted = + buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) - if (coroutineContext[Job] == null) { - throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.") - } - if (coroutineContext[CoroutineExceptionHandler] == null) { - throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a CoroutineExceptionHandler in it.") + mainLogger.info { "Starting mirai-console..." } + mainLogger.info { "Backend: version $version, built on $buildDateFormatted." } + mainLogger.info { frontEndDescription.render() } } - MiraiConsole.job.invokeOnCompletion { - Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) } - } + phase `check coroutineContext`@{ + if (coroutineContext[Job] == null) { + throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.") + } + if (coroutineContext[CoroutineExceptionHandler] == null) { + throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a CoroutineExceptionHandler in it.") + } - mainLogger.verbose { "Loading configurations..." } - ConsoleDataScope.reloadAll() - - mainLogger.verbose { "Loading PermissionService..." } - PermissionService.INSTANCE.let { ps -> - if (ps is StorablePermissionService<*>) { - ConsoleDataScope.addAndReloadConfig(ps.config) - mainLogger.verbose { "Reloaded PermissionService settings." } + MiraiConsole.job.invokeOnCompletion { + Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) } } } - mainLogger.verbose { "Loading built-in commands..." } - BuiltInCommands.registerAll() - mainLogger.verbose { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" } - CommandManager - CommandManagerImpl.commandListener // start + // start - mainLogger.verbose { "Loading plugins..." } - PluginManager - PluginManagerImpl.loadEnablePlugins() - mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." } - mainLogger.info { "mirai-console started successfully." } + phase `load configurations`@{ + mainLogger.verbose { "Loading configurations..." } + ConsoleDataScope.reloadAll() + } - runBlocking { - for ((id, password) in AutoLoginConfig.plainPasswords) { - mainLogger.info { "Auto-login $id" } - MiraiConsole.addBot(id, password).alsoLogin() + val pluginLoadSession: PluginManagerImpl.PluginLoadSession + + phase `load plugins`@{ + PluginManager // init + + mainLogger.verbose { "Loading PluginLoader provider plugins..." } + PluginManagerImpl.loadEnablePluginProviderPlugins() + mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." } + + mainLogger.verbose { "Scanning high-priority extension and normal plugins..." } + pluginLoadSession = PluginManagerImpl.scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider() + mainLogger.verbose { "${pluginLoadSession.allKindsOfPlugins.size} plugin(s) found." } + + mainLogger.verbose { "Loading Extension provider plugins..." } + PluginManagerImpl.loadEnableHighPriorityExtensionPlugins(pluginLoadSession) + mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." } + } + + phase `load PermissionService`@{ + mainLogger.verbose { "Loading PermissionService..." } + PermissionService.INSTANCE.let { ps -> + if (ps is StorablePermissionService<*>) { + ConsoleDataScope.addAndReloadConfig(ps.config) + mainLogger.verbose { "Reloaded PermissionService settings." } + } } + } - for ((id, password) in AutoLoginConfig.md5Passwords) { - mainLogger.info { "Auto-login $id" } - MiraiConsole.addBot(id, password).alsoLogin() + phase `prepare commands`@{ + mainLogger.verbose { "Loading built-in commands..." } + BuiltInCommands.registerAll() + mainLogger.verbose { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" } + CommandManager + CommandManagerImpl.commandListener // start + } + + phase `load normal plugins`@{ + mainLogger.verbose { "Loading normal plugins..." } + val count = PluginManagerImpl.loadEnableNormalPlugins(pluginLoadSession) + mainLogger.verbose { "$count normal plugin(s) loaded." } + } + + mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) loaded." } + + phase `auto-login bots`@{ + runBlocking { + for ((id, password) in AutoLoginConfig.plainPasswords) { + mainLogger.info { "Auto-login $id" } + MiraiConsole.addBot(id, password).alsoLogin() + } + + for ((id, password) in AutoLoginConfig.md5Passwords) { + mainLogger.info { "Auto-login $id" } + MiraiConsole.addBot(id, password).alsoLogin() + } } } PostStartupExtension.useExtensions { it() } + + mainLogger.info { "mirai-console started successfully." } + } + + @Retention(AnnotationRetention.SOURCE) + @DslMarker + private annotation class MiraiIsCool + + @MiraiIsCool + private inline fun phase(block: () -> Unit) { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + block() } } \ No newline at end of file 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 5683356c0..fbeb7d048 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 @@ -22,7 +22,6 @@ import net.mamoe.mirai.console.plugin.PluginLoadException import net.mamoe.mirai.console.plugin.jvm.* import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.info import java.io.File import java.net.URLClassLoader import java.util.concurrent.ConcurrentHashMap @@ -73,8 +72,8 @@ internal object JarPluginLoaderImpl : URLClassLoader(arrayOf(it.toURI().toURL()), MiraiConsole::class.java.classLoader) }.onEach { (_, classLoader) -> classLoaders.add(classLoader) - }.asSequence().findAllInstances().onEach { loaded -> - logger.info { "Successfully initialized JvmPlugin ${loaded}." } + }.asSequence().findAllInstances().onEach { + //logger.verbose { "Successfully initialized JvmPlugin ${loaded}." } }.onEach { (file, plugin) -> pluginFileToInstanceMap[file] = plugin } + pluginFileToInstanceMap.asSequence() @@ -97,9 +96,13 @@ internal object JarPluginLoaderImpl : override fun enable(plugin: JvmPlugin) { if (plugin.isEnabled) return ensureActive() - if (plugin is JvmPluginInternal) { - plugin.internalOnEnable() - } else plugin.onEnable() + runCatching { + if (plugin is JvmPluginInternal) { + plugin.internalOnEnable() + } else plugin.onEnable() + }.getOrElse { + throw PluginLoadException("Exception while loading ${plugin.description.name}", it) + } } override fun disable(plugin: JvmPlugin) { 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 9f0f1c7b9..5805b3d01 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 @@ -113,48 +113,58 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol ) } - /** - * STEPS: - * 1. 遍历插件列表, 使用 [builtInLoaders] 加载 [PluginKind.LOADER] 类型的插件 - * 2. [启动][PluginLoader.enable] 所有 [PluginKind.LOADER] 的插件 - * 3. 使用内建和所有插件提供的 [PluginLoader] 加载全部除 [PluginKind.LOADER] 外的插件列表. - * 4. 解决依赖并排序 - * 5. 依次 [PluginLoader.load] - * 但不 [PluginLoader.enable] - * - * @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable] - */ - @Suppress("UNCHECKED_CAST") - @Throws(PluginMissingDependencyException::class) - internal fun loadEnablePlugins() { - loadAndEnableLoaderProviders() - loadPluginLoaderProvidedByPlugins() + internal class PluginLoadSession( + val allKindsOfPlugins: List, List>> + ) + + // Phase #2 + internal fun scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider(): PluginLoadSession { + return PluginLoadSession(loadersLock.withLock { _pluginLoaders.listAllPlugins() }) + } + + // Phase #0 + internal fun loadEnablePluginProviderPlugins() { + loadAndEnableLoaderProvidersUsingBuiltInLoaders() + } + + // Phase #3 + internal fun loadEnableHighPriorityExtensionPlugins(session: PluginLoadSession): Int { loadersLock.withLock { - _pluginLoaders.listAllPlugins().flatMap { it.second } - .also { - logger.debug("All plugins: ${it.joinToString { (_, desc, _) -> desc.name }}") - } + session.allKindsOfPlugins.flatMap { it.second } + .filter { it.kind == PluginKind.HIGH_PRIORITY_EXTENSIONS } .sortByDependencies() - .also { - logger.debug("Sorted plugins: ${it.joinToString { (_, desc, _) -> desc.name }}") - } - .loadAndEnableAllInOrder() + .also { it.loadAndEnableAllInOrder() } + .let { return it.size } } } - private fun loadPluginLoaderProvidedByPlugins() { + // Phase #4 + internal fun loadEnableNormalPlugins(session: PluginLoadSession): Int { loadersLock.withLock { - PluginLoaderProvider.useExtensions { - logger.info { "Loaded PluginLoader ${it.instance} from $" } - _pluginLoaders.add(it.instance) + session.allKindsOfPlugins.flatMap { it.second } + .filter { it.kind == PluginKind.NORMAL } + .sortByDependencies() + .also { it.loadAndEnableAllInOrder() } + .let { return it.size } + } + } + + // Phase #1 + internal fun loadPluginLoaderProvidedByPlugins() { + loadersLock.withLock { + PluginLoaderProvider.useExtensions { ext, plugin -> + logger.info { "Loaded PluginLoader ${ext.instance} from ${plugin.name}" } + _pluginLoaders.add(ext.instance) } } } private fun List.loadAndEnableAllInOrder() { - return this.forEach { (loader, _, plugin) -> + this.forEach { (loader, _, plugin) -> loader.loadPluginNoEnable(plugin) + } + this.forEach { (loader, _, plugin) -> loader.enablePlugin(plugin) } } @@ -171,7 +181,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol */ @Suppress("UNCHECKED_CAST") @Throws(PluginMissingDependencyException::class) - private fun loadAndEnableLoaderProviders(): List { + private fun loadAndEnableLoaderProvidersUsingBuiltInLoaders(): List { val allDescriptions = builtInLoaders.listAllPlugins() .asSequence() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/BuiltInPermissionServices.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/BuiltInPermissionServices.kt index aa4a36d70..2018036cc 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/BuiltInPermissionServices.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/BuiltInPermissionServices.kt @@ -79,7 +79,10 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService

get() = PermissionImpl::class override val permissions: MutableMap get() = config.permissions - override val grantedPermissionsMap: MutableMap> get() = config.grantedPermissionMap + + @Suppress("UNCHECKED_CAST") + override val grantedPermissionsMap: MutableMap> + get() = config.grantedPermissionMap as MutableMap> override fun createPermission(id: PermissionId, description: String, base: PermissionId?): PermissionImpl = PermissionImpl(id, description, base) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/StorablePermissionService.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/StorablePermissionService.kt index f0b37efba..ca41cebfa 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/StorablePermissionService.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/StorablePermissionService.kt @@ -36,8 +36,8 @@ public interface StorablePermissionService

: PermissionService

> - by value>>(ConcurrentHashMap()) + public val grantedPermissionMap: MutableMap> + by value>>(ConcurrentHashMap()) .withDefault { CopyOnWriteArrayList() } public companion object { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginKind.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginKind.kt index a2fc7e6b7..e7f6b0003 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginKind.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginKind.kt @@ -12,17 +12,34 @@ package net.mamoe.mirai.console.plugin.description import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer +import net.mamoe.mirai.console.extension.Extension +import net.mamoe.mirai.console.extensions.BotConfigurationAlterer +import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.plugin.PluginLoader +import net.mamoe.mirai.console.plugin.description.PluginKind.* /** - * 插件类型 + * 插件类型. + * + * 插件类型将影响加载顺序: [LOADER] -> [HIGH_PRIORITY_EXTENSIONS] -> [NORMAL]. + * + * 依赖解决过程与插件类型有很大关联. 在一个较早的阶段, 只会解决在此阶段加载的插件. 意味着 [LOADER] 不允许依赖一个 [NORMAL] 类型的插件. */ @Serializable(with = PluginKind.AsStringSerializer::class) public enum class PluginKind { - /** 表示此插件提供一个 [PluginLoader], 应在加载其他 [NORMAL] 类型插件前加载 */ + /** 表示此插件提供一个 [PluginLoader], 也可以同时提供其他 [Extension] 应最早被加载 */ LOADER, + /** + * 表示此插件提供一些高优先级的 [Extension], 应在加载其他 [NORMAL] 类型插件前加载 + * + * 高优先级的 [Extension] 通常是覆盖 Console 内置的部分服务的扩展. 如 [PermissionServiceProvider]. + * + * 一些普通的 [Extension], 如 [BotConfigurationAlterer], 也可以使用 [NORMAL] 类型插件注册. + */ + HIGH_PRIORITY_EXTENSIONS, + /** 表示此插件为一个通常的插件, 按照正常的依赖关系加载. */ NORMAL;