Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[console] Load plugins with plugin.yml #2735

Merged
merged 3 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
net.mamoe.consoleit.plugin-with-yml:plugin-library:0.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/

package net.mamoe.console.itest.pluginwithpluginyml.library

public object PluginLibrary {
@JvmStatic
public fun ok() {
println("Plugin with plugin.yml using libraries under clinit ok")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
net.mamoe.consoleit.plugin-with-yml:plugin-library:0.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#
# Copyright 2019-2023 Mamoe Technologies and contributors.
#
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
#
# https://github.com/mamoe/mirai/blob/dev/LICENSE
#

net.mamoe.console.itest.pluginwithpluginyml.clinit.PluginWithPluginYmlClinitTest
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
id: net.mamoe.console.itest.plugin-with-yml-can-use-library-while-clinit
version: 0.0.0

dependencies:
- net.mamoe.console.itest.plugin-with-yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/

package net.mamoe.console.itest.pluginwithpluginyml.clinit

import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin

internal class PluginWithPluginYmlClinitTest : KotlinPlugin() {

companion object {
init {
// this is <clinit>

Thread.dumpStack()
Class.forName("net.mamoe.console.itest.pluginwithpluginyml.library.PluginLibrary")
.getMethod("ok").invoke(null)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#
# Copyright 2019-2023 Mamoe Technologies and contributors.
#
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
#
# https://github.com/mamoe/mirai/blob/dev/LICENSE
#

net.mamoe.console.itest.pluginwithpluginyml.PluginWithPluginYml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
id: net.mamoe.console.itest.plugin-with-yml
version: 0.0.0

dependencies:
- net.mamoe.console.itest.serviceloader
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package net.mamoe.console.itest.pluginwithpluginyml

import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
import kotlin.test.assertTrue

/*
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/

internal object PluginWithPluginYml : KotlinPlugin() {
override fun onEnable() {
println(description)
println(description.id)
val pluginId = description.id

assertTrue {
PluginManager.plugins.first { it.description.id == pluginId } === PluginWithPluginYml
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1769,6 +1769,10 @@ public final class net/mamoe/mirai/console/permission/PermitteeId$Companion {
public final fun hasChild (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/PermitteeId;)Z
}

public abstract interface class net/mamoe/mirai/console/plugin/NotYetLoadedPlugin : net/mamoe/mirai/console/plugin/Plugin {
public abstract fun resolve ()Lnet/mamoe/mirai/console/plugin/Plugin;
}

public abstract interface class net/mamoe/mirai/console/plugin/Plugin : net/mamoe/mirai/console/command/CommandOwner {
public abstract fun getLoader ()Lnet/mamoe/mirai/console/plugin/loader/PluginLoader;
public abstract fun isEnabled ()Z
Expand Down Expand Up @@ -1957,8 +1961,12 @@ public abstract class net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin : net
public fun <init> ()V
public fun <init> (Lkotlin/coroutines/CoroutineContext;)V
public synthetic fun <init> (Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;)V
public fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;Lkotlin/coroutines/CoroutineContext;)V
public synthetic fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getAutoSaveIntervalMillis ()Lkotlin/ranges/LongRange;
public final fun getDataHolderName ()Ljava/lang/String;
public final fun getDescription ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;
protected final fun getJvmPluginClasspath ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginClasspath;
public final fun getLoader ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginLoader;
public synthetic fun getLoader ()Lnet/mamoe/mirai/console/plugin/loader/PluginLoader;
Expand All @@ -1979,10 +1987,12 @@ public final class net/mamoe/mirai/console/plugin/jvm/AbstractJvmPluginKt {
}

public abstract class net/mamoe/mirai/console/plugin/jvm/JavaPlugin : net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin, net/mamoe/mirai/console/plugin/jvm/JvmPlugin {
public fun <init> ()V
public fun <init> (Lkotlin/coroutines/CoroutineContext;)V
public synthetic fun <init> (Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;)V
public fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;Lkotlin/coroutines/CoroutineContext;)V
public synthetic fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getDescription ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;
public final fun getScheduler ()Lnet/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler;
}

Expand Down Expand Up @@ -2098,10 +2108,12 @@ public final class net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader$BuiltIn :
}

public abstract class net/mamoe/mirai/console/plugin/jvm/KotlinPlugin : net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin, net/mamoe/mirai/console/plugin/jvm/JvmPlugin {
public fun <init> ()V
public fun <init> (Lkotlin/coroutines/CoroutineContext;)V
public synthetic fun <init> (Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;)V
public fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;Lkotlin/coroutines/CoroutineContext;)V
public synthetic fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getDescription ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;
}

public abstract class net/mamoe/mirai/console/plugin/loader/AbstractFilePluginLoader : net/mamoe/mirai/console/plugin/loader/FilePluginLoader {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import net.mamoe.mirai.console.plugin.loader.PluginLoadException
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.*
import net.mamoe.yamlkt.Yaml
import java.io.File
import java.nio.file.Path
import java.util.concurrent.ConcurrentHashMap
Expand Down Expand Up @@ -177,7 +178,7 @@ internal class BuiltInJvmPluginLoaderImpl(
override fun Sequence<File>.extractPlugins(): List<JvmPlugin> {
ensureActive()

fun Sequence<Map.Entry<File, JvmPluginClassLoaderN>>.findAllInstances(): Sequence<Map.Entry<File, JvmPlugin>> {
fun Sequence<Map.Entry<File, JvmPluginClassLoaderN>>.initialize(): Sequence<Map.Entry<File, JvmPluginClassLoaderN>> {
return onEach { (_, pluginClassLoader) ->
val exportManagers = pluginClassLoader.findServices(
ExportManager::class
Expand All @@ -192,7 +193,11 @@ internal class BuiltInJvmPluginLoaderImpl(
} else {
pluginClassLoader.declaredFilter = exportManagers[0]
}
}.map { (f, pluginClassLoader) ->
}
}

fun Sequence<Map.Entry<File, JvmPluginClassLoaderN>>.findAllInstances(): Sequence<Map.Entry<File, JvmPlugin>> {
return map { (f, pluginClassLoader) ->
f to pluginClassLoader.findServices(
JvmPlugin::class,
KotlinPlugin::class,
Expand All @@ -206,19 +211,80 @@ internal class BuiltInJvmPluginLoaderImpl(
}
}

fun Map.Entry<File, JvmPluginClassLoaderN>.loadWithoutPluginDescription(): Sequence<Pair<File, JvmPlugin>> {
return sequenceOf(this).initialize().findAllInstances().map { (k, v) -> k to v }
}

fun Map.Entry<File, JvmPluginClassLoaderN>.loadWithPluginDescription(description: JvmPluginDescription): Sequence<Pair<File, JvmPlugin>> {
val pluginClassLoader = this.value
val pluginFile = this.key
pluginClassLoader.pluginDescriptionFromPluginResource = description

val pendingPlugin = object : NotYetLoadedJvmPlugin(
description = description,
classLoaderN = pluginClassLoader,
) {
private val plugin by lazy {
val services = pluginClassLoader.findServices(
JvmPlugin::class,
KotlinPlugin::class,
JavaPlugin::class
).loadAllServices()
if (services.isEmpty()) {
error("No plugin instance found in $pluginFile")
}
if (services.size > 1) {
error(
"Only one plugin can exist at the same time when using plugin.yml:\n\nPlugins found:\n" + services.joinToString(
separator = "\n"
) { it.javaClass.name + " (from " + it.javaClass.classLoader + ")" }
)
}

return@lazy services[0]
}

override fun resolve(): JvmPlugin = plugin
}
pluginClassLoader.linkedLogger = pendingPlugin.logger


return sequenceOf(pluginFile to pendingPlugin)
}

val filePlugins = this.filterNot {
pluginFileToInstanceMap.containsKey(it)
}.associateWith {
JvmPluginClassLoaderN.newLoader(it, jvmPluginLoadingCtx)
}.onEach { (_, classLoader) ->
classLoaders.add(classLoader)
}.asSequence().findAllInstances().onEach {
//logger.verbose { "Successfully initialized JvmPlugin ${loaded}." }
}.asSequence().flatMap { entry ->
val (file, pluginClassLoader) = entry

val pluginDescriptionDefine = pluginClassLoader.getResourceAsStream("plugin.yml")
if (pluginDescriptionDefine == null) {
entry.loadWithoutPluginDescription()
} else {
val desc = kotlin.runCatching {
pluginDescriptionDefine.bufferedReader().use { resource ->
Yaml.decodeFromString(
SimpleJvmPluginDescription.SerialData.serializer(),
resource.readText()
).toJvmPluginDescription()
}
}.onFailure { err ->
throw PluginLoadException("Invalid plugin.yml in " + file.absolutePath, err)
}.getOrThrow()

entry.loadWithPluginDescription(desc)
}
}.onEach {
logger.verbose { "Successfully initialized JvmPlugin ${it.second}." }
}.onEach { (file, plugin) ->
pluginFileToInstanceMap[file] = plugin
} + pluginFileToInstanceMap.asSequence()
}

return filePlugins.toSet().map { it.value }
return filePlugins.toSet().map { it.second }
}

private val loadedPlugins = ConcurrentHashMap<JvmPlugin, Unit>()
Expand Down Expand Up @@ -266,9 +332,17 @@ internal class BuiltInJvmPluginLoaderImpl(
// move nameFolder in config and data to idFolder
PluginManager.pluginsDataPath.moveNameFolder(plugin)
PluginManager.pluginsConfigPath.moveNameFolder(plugin)
check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin to be loaded by JvmPluginLoader.BuiltIn" }

check(plugin is JvmPluginInternal || plugin is NotYetLoadedJvmPlugin) {
"A JvmPlugin must extend AbstractJvmPlugin to be loaded by JvmPluginLoader.BuiltIn"
}


// region Link dependencies
plugin.javaClass.classLoader.safeCast<JvmPluginClassLoaderN>()?.let { jvmPluginClassLoaderN ->
when (plugin) {
is NotYetLoadedJvmPlugin -> plugin.classLoaderN
else -> plugin.javaClass.classLoader
}.safeCast<JvmPluginClassLoaderN>()?.let { jvmPluginClassLoaderN ->
// Link plugin dependencies
plugin.description.dependencies.asSequence().mapNotNull { dependency ->
plugin.logger.verbose { "Linking dependency: ${dependency.id}" }
Expand All @@ -282,8 +356,19 @@ internal class BuiltInJvmPluginLoaderImpl(
}
jvmPluginClassLoaderN.linkPluginLibraries(plugin.logger)
}

val realPlugin = when (plugin) {
is NotYetLoadedJvmPlugin -> plugin.resolve().also { realPlugin ->
check(plugin.description === realPlugin.description) {
"A JvmPlugin loaded by plugin.yml must has same description reference"
}
}
else -> plugin
}

check(realPlugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin to be loaded by JvmPluginLoader.BuiltIn" }
// endregion
plugin.internalOnLoad()
realPlugin.internalOnLoad()
}.getOrElse {
throw PluginLoadException("Exception while loading ${plugin.description.smartToString()}", it)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ package net.mamoe.mirai.console.internal.plugin

import net.mamoe.mirai.console.plugin.jvm.ExportManager
import net.mamoe.mirai.console.plugin.jvm.JvmPluginClasspath
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.*
import org.eclipse.aether.artifact.Artifact
Expand Down Expand Up @@ -245,6 +246,8 @@ internal class DynLibClassLoader : DynamicClasspathClassLoader {
}

internal class JvmPluginClassLoaderN : URLClassLoader {
var pluginDescriptionFromPluginResource: JvmPluginDescription? = null

val openaccess: JvmPluginClasspath = OpenAccess()
val file: File
val ctx: JvmPluginsLoadingCtx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import net.mamoe.mirai.console.plugin.id
import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
Expand Down Expand Up @@ -106,7 +107,13 @@ internal abstract class JvmPluginInternal(
}
error("Failed to switch plugin '$id' status from $nowStatus to $update, current status = ${pluginStatus.value}")
}
error("Failed to switch plugin '$id' status to $update because current status $nowStatus doesn't contain flag ${Integer.toBinaryString(expectFlag)}")
error(
"Failed to switch plugin '$id' status to $update because current status $nowStatus doesn't contain flag ${
Integer.toBinaryString(
expectFlag
)
}"
)
}

@JvmSynthetic
Expand Down Expand Up @@ -364,3 +371,11 @@ internal inline fun AtomicLong.updateWhen(condition: (Long) -> Boolean, update:
}

internal val Throwable.rootCauseOrSelf: Throwable get() = generateSequence(this) { it.cause }.lastOrNull() ?: this

internal fun Class<out JvmPluginInternal>.loadPluginDescriptionFromClassLoader(): JvmPluginDescription {
val classLoader =
this.classLoader as? JvmPluginClassLoaderN ?: error("Plugin $this is not loaded by JvmPluginClassLoader")

return classLoader.pluginDescriptionFromPluginResource ?: error("Missing `plugin.yml`")
}

Loading