mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-05 07:29:16 +08:00
Support PluginLoader with ServiceLoader
This commit is contained in:
parent
1bd1b5a4fd
commit
316a40e4bc
@ -9,6 +9,7 @@ import java.util.TimeZone
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("plugin.serialization")
|
kotlin("plugin.serialization")
|
||||||
|
kotlin("kapt")
|
||||||
id("java")
|
id("java")
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
id("com.jfrog.bintray")
|
id("com.jfrog.bintray")
|
||||||
@ -82,6 +83,11 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.2.0")
|
testImplementation("org.junit.jupiter:junit-jupiter-api:5.2.0")
|
||||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine: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 {
|
ext.apply {
|
||||||
|
@ -19,13 +19,13 @@ import net.mamoe.mirai.Bot
|
|||||||
import net.mamoe.mirai.console.MiraiConsole.INSTANCE
|
import net.mamoe.mirai.console.MiraiConsole.INSTANCE
|
||||||
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
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.PluginLoader
|
||||||
import net.mamoe.mirai.console.plugin.PluginManager
|
import net.mamoe.mirai.console.plugin.PluginManager
|
||||||
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
||||||
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||||
import net.mamoe.mirai.console.util.ConsoleInternalAPI
|
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.BotConfiguration
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
)
|
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.console.data.PluginConfig
|
|||||||
import net.mamoe.mirai.console.data.PluginData
|
import net.mamoe.mirai.console.data.PluginData
|
||||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||||
|
import net.mamoe.mirai.console.util.childScope
|
||||||
import net.mamoe.mirai.utils.minutesToMillis
|
import net.mamoe.mirai.utils.minutesToMillis
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,23 +11,23 @@ package net.mamoe.mirai.console.internal.plugin
|
|||||||
|
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
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.AbstractFilePluginLoader
|
||||||
import net.mamoe.mirai.console.plugin.PluginLoadException
|
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.ConsoleExperimentalAPI
|
||||||
import net.mamoe.mirai.console.util.childScopeContext
|
import net.mamoe.mirai.console.util.childScopeContext
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import net.mamoe.yamlkt.Yaml
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URI
|
import java.net.URLClassLoader
|
||||||
|
import java.util.*
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.streams.asSequence
|
||||||
|
|
||||||
internal object JarPluginLoaderImpl :
|
internal object JarPluginLoaderImpl :
|
||||||
AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>(".jar"),
|
AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>(".jar"),
|
||||||
@ -48,71 +48,53 @@ internal object JarPluginLoaderImpl :
|
|||||||
logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable)
|
logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable)
|
||||||
})
|
})
|
||||||
|
|
||||||
internal val classLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader)
|
internal val classLoaders: MutableList<ClassLoader> = mutableListOf()
|
||||||
|
|
||||||
init { // delayed
|
|
||||||
coroutineContext[Job]!!.invokeOnCompletion {
|
|
||||||
classLoader.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // doesn't matter
|
@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // doesn't matter
|
||||||
override val JvmPlugin.description: JvmPluginDescription
|
override val JvmPlugin.description: JvmPluginDescription
|
||||||
get() = this.description
|
get() = this.description
|
||||||
|
|
||||||
override fun Sequence<File>.mapToDescription(): List<JvmPluginDescriptionImpl> {
|
override fun Sequence<File>.extractPlugins(): List<JvmPlugin> {
|
||||||
return this.associateWith { URI("jar:file:${it.absolutePath.replace('\\', '/')}!/plugin.yml").toURL() }
|
ensureActive()
|
||||||
.mapNotNull { (file, url) ->
|
|
||||||
|
fun <T> ServiceLoader<T>.loadAll(file: File?): Sequence<T> {
|
||||||
|
return stream().asSequence().mapNotNull {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
url.readText()
|
it.type().kotlin.objectInstance ?: it.get()
|
||||||
}.fold(
|
}.onFailure {
|
||||||
onSuccess = { yaml ->
|
logger.error("Cannot load plugin ${file ?: "<no-file>"}", it)
|
||||||
Yaml.nonStrict.decodeFromString(JvmPluginDescriptionImpl.serializer(), yaml)
|
}.getOrNull()
|
||||||
},
|
|
||||||
onFailure = {
|
|
||||||
logger.error("Cannot load plugin file ${file.name}", it)
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
)?.also { it._file = file }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
@Throws(PluginLoadException::class)
|
||||||
override fun load(description: JvmPluginDescription): JvmPlugin {
|
override fun load(plugin: 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()
|
ensureActive()
|
||||||
|
runCatching {
|
||||||
check(main is JvmPlugin) { "Main class ${main::class.qualifiedNameOrTip} from plugin ${description.name} does not extend JvmPlugin." }
|
if (plugin is JvmPluginInternal) {
|
||||||
|
plugin.internalOnLoad()
|
||||||
if (main is JvmPluginInternal) {
|
} else plugin.onLoad()
|
||||||
main._description = description
|
|
||||||
main.internalOnLoad()
|
|
||||||
} else main.onLoad()
|
|
||||||
|
|
||||||
return main
|
|
||||||
}.getOrElse {
|
}.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) {
|
override fun disable(plugin: JvmPlugin) {
|
||||||
if (!plugin.isEnabled) return
|
if (!plugin.isEnabled) return
|
||||||
|
ensureActive()
|
||||||
|
|
||||||
if (plugin is JvmPluginInternal) {
|
if (plugin is JvmPluginInternal) {
|
||||||
plugin.internalOnDisable()
|
plugin.internalOnDisable()
|
||||||
|
@ -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.PluginManager.INSTANCE.safeLoader
|
||||||
import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer
|
import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer
|
||||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -35,8 +34,7 @@ internal val <T> T.job: Job where T : CoroutineScope, T : Plugin get() = this.co
|
|||||||
@PublishedApi
|
@PublishedApi
|
||||||
internal abstract class JvmPluginInternal(
|
internal abstract class JvmPluginInternal(
|
||||||
parentCoroutineContext: CoroutineContext
|
parentCoroutineContext: CoroutineContext
|
||||||
) : JvmPlugin,
|
) : JvmPlugin, CoroutineScope {
|
||||||
CoroutineScope {
|
|
||||||
|
|
||||||
final override var isEnabled: Boolean = false
|
final override var isEnabled: Boolean = false
|
||||||
|
|
||||||
@ -44,17 +42,9 @@ internal abstract class JvmPluginInternal(
|
|||||||
override fun getResourceAsStream(path: String): InputStream? = resourceContainerDelegate.getResourceAsStream(path)
|
override fun getResourceAsStream(path: String): InputStream? = resourceContainerDelegate.getResourceAsStream(path)
|
||||||
|
|
||||||
// region JvmPlugin
|
// 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 {
|
final override val logger: MiraiLogger by lazy {
|
||||||
MiraiConsole.newLogger(
|
MiraiConsole.newLogger(
|
||||||
"Plugin ${this._description.name}"
|
"Plugin ${this.description.name}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,11 +111,13 @@ internal abstract class JvmPluginInternal(
|
|||||||
internal val _intrinsicCoroutineContext: CoroutineContext by lazy {
|
internal val _intrinsicCoroutineContext: CoroutineContext by lazy {
|
||||||
CoroutineName("Plugin $name")
|
CoroutineName("Plugin $name")
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
internal val coroutineContextInitializer = {
|
internal val coroutineContextInitializer = {
|
||||||
CoroutineExceptionHandler { _, throwable ->
|
CoroutineExceptionHandler { context, throwable ->
|
||||||
if (throwable !is CancellationException) logger.error(throwable)
|
if (throwable.rootCauseOrSelf !is CancellationException) logger.error(
|
||||||
|
"Exception in coroutine ${context[CoroutineName]?.name ?: "<unnamed>"} of ${description.name}",
|
||||||
|
throwable
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.plus(parentCoroutineContext)
|
.plus(parentCoroutineContext)
|
||||||
.plus(
|
.plus(
|
||||||
@ -184,3 +176,5 @@ internal inline fun AtomicLong.updateWhen(condition: (Long) -> Boolean, update:
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val Throwable.rootCauseOrSelf: Throwable get() = generateSequence(this) { it.cause }.lastOrNull() ?: this
|
@ -15,7 +15,6 @@ import kotlinx.atomicfu.locks.withLock
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
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.cast
|
||||||
import net.mamoe.mirai.console.internal.data.mkdir
|
import net.mamoe.mirai.console.internal.data.mkdir
|
||||||
import net.mamoe.mirai.console.plugin.*
|
import net.mamoe.mirai.console.plugin.*
|
||||||
@ -84,16 +83,16 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
|||||||
|
|
||||||
// region LOADING
|
// region LOADING
|
||||||
|
|
||||||
private fun <P : Plugin, D : PluginDescription> PluginLoader<P, D>.loadPluginNoEnable(description: D): P {
|
private fun <P : Plugin, D : PluginDescription> PluginLoader<P, D>.loadPluginNoEnable(plugin: P) {
|
||||||
return kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
this.load(description).also { resolvedPlugins.add(it) }
|
this.load(plugin)
|
||||||
|
resolvedPlugins.add(plugin)
|
||||||
}.fold(
|
}.fold(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
logger.info { "Successfully loaded plugin ${description.name}" }
|
logger.info { "Successfully loaded plugin ${plugin.description.name}" }
|
||||||
it
|
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
logger.info { "Cannot load plugin ${description.name}" }
|
logger.info { "Cannot load plugin ${plugin.description.name}" }
|
||||||
throw it
|
throw it
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -138,33 +137,30 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
|||||||
|
|
||||||
private fun loadPluginLoaderProvidedByPlugins() {
|
private fun loadPluginLoaderProvidedByPlugins() {
|
||||||
loadersLock.withLock {
|
loadersLock.withLock {
|
||||||
JarPluginLoaderImpl.classLoader.pluginLoaders.asSequence()
|
JarPluginLoaderImpl.classLoaders.asSequence()
|
||||||
.flatMap { (name, pluginClassLoader) ->
|
.flatMap { pluginClassLoader ->
|
||||||
ServiceLoader.load(PluginLoader::class.java, pluginClassLoader)
|
ServiceLoader.load(PluginLoader::class.java, pluginClassLoader)
|
||||||
.stream().asSequence()
|
.stream().asSequence()
|
||||||
.associateBy { name }
|
|
||||||
.asSequence()
|
.asSequence()
|
||||||
}
|
}
|
||||||
.forEach { (name, provider) ->
|
.forEach { provider ->
|
||||||
val pluginLoader = kotlin.runCatching {
|
val pluginLoader = kotlin.runCatching {
|
||||||
provider.get()
|
provider.get()
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
logger.error(
|
logger.error(
|
||||||
{ "Could not load PluginLoader ${it::class.qualifiedNameOrTip} from plugin $name" },
|
{ "Could not load PluginLoader ${provider.type().canonicalName}." },
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
_pluginLoaders.add(pluginLoader)
|
_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() {
|
private fun List<PluginDescriptionWithLoader>.loadAndEnableAllInOrder() {
|
||||||
return this.map { (loader, desc) ->
|
return this.forEach { (loader, _, plugin) ->
|
||||||
loader to loader.loadPluginNoEnable(desc)
|
|
||||||
}.forEach { (loader, plugin) ->
|
|
||||||
loader.enablePlugin(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>>> {
|
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)
|
@Throws(PluginMissingDependencyException::class)
|
||||||
@ -231,8 +229,9 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal data class PluginDescriptionWithLoader(
|
internal data class PluginDescriptionWithLoader(
|
||||||
@JvmField val loader: PluginLoader<*, PluginDescription>, // easier type
|
@JvmField val loader: PluginLoader<Plugin, PluginDescription>, // easier type
|
||||||
@JvmField val delegate: PluginDescription
|
@JvmField val delegate: PluginDescription,
|
||||||
|
@JvmField val plugin: Plugin
|
||||||
) : PluginDescription by delegate
|
) : PluginDescription by delegate
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@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
|
if (this is PluginDescriptionWithLoader) this.delegate as D else this as D
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
internal fun PluginDescription.wrapWith(loader: PluginLoader<*, *>): PluginDescriptionWithLoader =
|
internal fun PluginDescription.wrapWith(loader: PluginLoader<*, *>, plugin: Plugin): PluginDescriptionWithLoader =
|
||||||
PluginDescriptionWithLoader(
|
PluginDescriptionWithLoader(
|
||||||
loader as PluginLoader<*, PluginDescription>, this
|
loader as PluginLoader<Plugin, PluginDescription>, this, plugin
|
||||||
)
|
)
|
||||||
|
|
||||||
internal operator fun List<PluginDescription>.contains(dependency: PluginDependency): Boolean =
|
internal operator fun List<PluginDescription>.contains(dependency: PluginDependency): Boolean =
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -42,13 +42,15 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
||||||
/**
|
/**
|
||||||
* 扫描并返回可以被加载的插件的 [描述][PluginDescription] 列表.
|
* 扫描并返回可以被加载的插件的列表.
|
||||||
|
*
|
||||||
|
* 这些插件都应处于还未被加载的状态.
|
||||||
*
|
*
|
||||||
* 在 console 启动时, [PluginManager] 会获取所有 [PluginDescription], 分析依赖关系, 确认插件加载顺序.
|
* 在 console 启动时, [PluginManager] 会获取所有 [PluginDescription], 分析依赖关系, 确认插件加载顺序.
|
||||||
*
|
*
|
||||||
* **实现细节:** 此函数*只应该*在 console 启动时被调用一次. 但取决于前端实现不同, 或由于被一些插件需要, 此函数也可能会被多次调用.
|
* **实现细节:** 此函数*只应该*在 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 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
|
||||||
*/
|
*/
|
||||||
@Throws(PluginLoadException::class)
|
@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) }
|
.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> {
|
) : PluginLoader<P, D> {
|
||||||
private val instance by lazy(initializer)
|
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 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 enable(plugin: P) = instance.enable(plugin)
|
||||||
override fun disable(plugin: P) = instance.disable(plugin)
|
override fun disable(plugin: P) = instance.disable(plugin)
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,13 @@ public interface PluginManager {
|
|||||||
*/
|
*/
|
||||||
public fun Plugin.disable(): Unit = safeLoader.disable(this)
|
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 PluginLoader<*, *>.unregister(): Boolean = PluginManagerImpl.run { unregister() }
|
||||||
public override fun Plugin.disable(): Unit = PluginManagerImpl.run { disable() }
|
public override fun Plugin.disable(): Unit = PluginManagerImpl.run { disable() }
|
||||||
public override fun Plugin.enable(): Unit = PluginManagerImpl.run { enable() }
|
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 }
|
public override val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription> get() = PluginManagerImpl.run { safeLoader }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,22 +7,17 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package net.mamoe.mirai.console.plugin.description
|
package net.mamoe.mirai.console.plugin.description
|
||||||
|
|
||||||
import com.vdurmont.semver4j.Semver
|
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
|
* @see PluginDescription.dependencies
|
||||||
*/
|
*/
|
||||||
@Serializable(with = PluginDependency.SmartSerializer::class)
|
|
||||||
public data class PluginDependency(
|
public data class PluginDependency(
|
||||||
/** 依赖插件名 */
|
/** 依赖插件名 */
|
||||||
public val name: String,
|
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)
|
* 允许 [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`, 插件在找不到此依赖时也能正常加载.
|
* 若为 `false`, 插件在找不到此依赖时也能正常加载.
|
||||||
*/
|
*/
|
||||||
public val isOptional: Boolean = false
|
public val isOptional: Boolean = false
|
||||||
) {
|
) {
|
||||||
public override fun toString(): String {
|
public constructor(name: String, version: String, isOptional: Boolean) : this(
|
||||||
return "$name v$version${if (isOptional) "?" else ""}"
|
name,
|
||||||
}
|
Semver(version, Semver.SemverType.IVY),
|
||||||
|
isOptional
|
||||||
|
|
||||||
/**
|
|
||||||
* 可支持解析 [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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -10,7 +10,6 @@
|
|||||||
package net.mamoe.mirai.console.plugin.description
|
package net.mamoe.mirai.console.plugin.description
|
||||||
|
|
||||||
import com.vdurmont.semver4j.Semver
|
import com.vdurmont.semver4j.Semver
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import net.mamoe.mirai.console.plugin.Plugin
|
import net.mamoe.mirai.console.plugin.Plugin
|
||||||
|
|
||||||
|
|
||||||
@ -56,6 +55,6 @@ public interface PluginDescription {
|
|||||||
*
|
*
|
||||||
* @see PluginDependency
|
* @see PluginDependency
|
||||||
*/
|
*/
|
||||||
public val dependencies: List<@Serializable(with = PluginDependency.SmartSerializer::class) PluginDependency>
|
public val dependencies: List<PluginDependency>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,9 +31,12 @@ import net.mamoe.mirai.utils.MiraiLogger
|
|||||||
/**
|
/**
|
||||||
* Java, Kotlin 或其他 JVM 平台插件
|
* Java, Kotlin 或其他 JVM 平台插件
|
||||||
*
|
*
|
||||||
* ### ResourceContainer
|
* ## ResourceContainer
|
||||||
* 实现为 [ClassLoader.getResourceAsStream]
|
* 实现为 [ClassLoader.getResourceAsStream]
|
||||||
*
|
*
|
||||||
|
* ## 实现 [JvmPlugin]
|
||||||
|
* j
|
||||||
|
*
|
||||||
* @see AbstractJvmPlugin 默认实现
|
* @see AbstractJvmPlugin 默认实现
|
||||||
*
|
*
|
||||||
* @see JavaPlugin Java 插件
|
* @see JavaPlugin Java 插件
|
||||||
|
@ -7,119 +7,45 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package net.mamoe.mirai.console.plugin.jvm
|
package net.mamoe.mirai.console.plugin.jvm
|
||||||
|
|
||||||
import com.vdurmont.semver4j.Semver
|
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.PluginDependency
|
||||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||||
import net.mamoe.mirai.console.plugin.description.PluginKind
|
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`
|
* JVM 插件的描述. 通常作为 `plugin.yml`
|
||||||
*
|
* @see SimpleJvmPluginDescription
|
||||||
*
|
|
||||||
* ```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
|
|
||||||
* ```
|
|
||||||
*/
|
*/
|
||||||
public interface JvmPluginDescription : PluginDescription
|
public interface JvmPluginDescription : PluginDescription
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see JvmPluginDescriptionImpl
|
* @see JvmPluginDescription
|
||||||
*/
|
*/
|
||||||
@MiraiExperimentalAPI
|
public data class SimpleJvmPluginDescription
|
||||||
@Serializable
|
@JvmOverloads public constructor(
|
||||||
public class JvmPluginDescriptionImpl internal constructor(
|
|
||||||
public override val kind: PluginKind = PluginKind.NORMAL,
|
|
||||||
public override val name: String,
|
public override val name: String,
|
||||||
@SerialName("main")
|
public override val version: Semver,
|
||||||
public val mainClassName: String,
|
|
||||||
public override val author: String = "",
|
public override val author: String = "",
|
||||||
public override val version: @Serializable(with = SemverAsStringSerializerLoose::class) Semver,
|
|
||||||
public override val info: String = "",
|
public override val info: String = "",
|
||||||
@SerialName("depends")
|
public override val dependencies: List<PluginDependency> = listOf(),
|
||||||
public override val dependencies: List<@Serializable(with = PluginDependency.SmartSerializer::class) PluginDependency> = listOf()
|
public override val kind: PluginKind = PluginKind.NORMAL,
|
||||||
) : JvmPluginDescription {
|
) : JvmPluginDescription {
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
public constructor(
|
||||||
|
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)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(!name.contains(':')) { "':' is forbidden in plugin name" }
|
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<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")
|
|
||||||
|
|
||||||
|
|
||||||
@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)"
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -20,27 +20,10 @@ import kotlin.coroutines.EmptyCoroutineContext
|
|||||||
* 必须通过 "plugin.yml" 指定主类并由 [JarPluginLoader] 加载.
|
* 必须通过 "plugin.yml" 指定主类并由 [JarPluginLoader] 加载.
|
||||||
*/
|
*/
|
||||||
public abstract class KotlinPlugin @JvmOverloads constructor(
|
public abstract class KotlinPlugin @JvmOverloads constructor(
|
||||||
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
|
public final override val description: JvmPluginDescription,
|
||||||
|
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||||
) : JvmPlugin, AbstractJvmPlugin(parentCoroutineContext)
|
) : 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()
|
public object MyPlugin : KotlinPlugin()
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
package net.mamoe.mirai.console.util
|
package net.mamoe.mirai.console.util
|
||||||
|
|
||||||
import net.mamoe.mirai.Bot
|
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
|
import net.mamoe.mirai.contact.User
|
||||||
|
|
||||||
public interface BotManager {
|
public interface BotManager {
|
||||||
|
@ -11,6 +11,7 @@ package net.mamoe.mirai.console.data
|
|||||||
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
||||||
|
import net.mamoe.mirai.console.plugin.jvm.SimpleJvmPluginDescription
|
||||||
import net.mamoe.mirai.console.util.ConsoleInternalAPI
|
import net.mamoe.mirai.console.util.ConsoleInternalAPI
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -19,7 +20,11 @@ import kotlin.test.assertSame
|
|||||||
@OptIn(ConsoleInternalAPI::class)
|
@OptIn(ConsoleInternalAPI::class)
|
||||||
internal class PluginDataTest {
|
internal class PluginDataTest {
|
||||||
|
|
||||||
object MyPlugin : KotlinPlugin()
|
object MyPlugin : KotlinPlugin(
|
||||||
|
SimpleJvmPluginDescription(
|
||||||
|
"1", "2"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class MyPluginData : AutoSavePluginData() {
|
class MyPluginData : AutoSavePluginData() {
|
||||||
var int by value(1)
|
var int by value(1)
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
object Versions {
|
object Versions {
|
||||||
const val core = "1.2.2"
|
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 consoleGraphical = "0.0.7"
|
||||||
const val consoleTerminal = "0.1.0"
|
const val consoleTerminal = "0.1.0"
|
||||||
const val consolePure = console
|
const val consolePure = console
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("plugin.serialization")
|
kotlin("plugin.serialization")
|
||||||
|
kotlin("kapt")
|
||||||
id("java")
|
id("java")
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
id("com.jfrog.bintray")
|
id("com.jfrog.bintray")
|
||||||
@ -43,6 +44,12 @@ dependencies {
|
|||||||
runtimeOnly("net.mamoe:mirai-core-qqandroid:${Versions.core}")
|
runtimeOnly("net.mamoe:mirai-core-qqandroid:${Versions.core}")
|
||||||
testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}")
|
testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}")
|
||||||
testApi(project(":mirai-console"))
|
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 {
|
ext.apply {
|
||||||
|
Loading…
Reference in New Issue
Block a user