mirror of
https://github.com/mamoe/mirai.git
synced 2024-12-29 10:00:13 +08:00
[console] 优化插件 classpath 策略 (#2666)
* [console/it] Fix testers not run when testers modified * [console] Add options to control plugin classpath resolving - Also add `META-INF/mirai-console-plugin/options.properties` * [console/it] Testers for options.properties * api dump * update property names * doc update
This commit is contained in:
parent
60d360baad
commit
8b4af6d8cf
@ -100,7 +100,10 @@ allprojects {
|
||||
runCatching {
|
||||
val tk = tasks.named<Jar>("jar")
|
||||
subplugins.add(tk)
|
||||
mcit_test.configure { dependsOn(tk) }
|
||||
mcit_test.configure {
|
||||
dependsOn(tk)
|
||||
inputs.files(tk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
consoleittest.optionproperties.independent.Independent
|
@ -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 consoleittest.optionproperties.independent
|
||||
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
||||
import kotlin.test.assertFails
|
||||
|
||||
public object Independent : KotlinPlugin(
|
||||
JvmPluginDescription("net.mamoe.console.itest.options_properties.independent_plugin", "0.0.0")
|
||||
) {
|
||||
override fun onEnable() {
|
||||
assertFails {
|
||||
// parent's class.loading.be-resolvable-to-independent = false
|
||||
Class.forName("consoleittest.optionproperties.main.OptionsProperties")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
# suppress inspection "UnusedProperty" for whole file
|
||||
|
||||
resources.resolve-console-system-resources=true
|
||||
|
||||
|
||||
class.loading.be-resolvable-to-independent=false
|
||||
class.loading.resolve-independent=false
|
@ -0,0 +1 @@
|
||||
consoleittest.optionproperties.main.OptionsProperties
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 consoleittest.optionproperties.main
|
||||
|
||||
import net.mamoe.mirai.console.extension.PluginComponentStorage
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
||||
import kotlin.test.assertFails
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
public object OptionsProperties : KotlinPlugin(
|
||||
JvmPluginDescription("net.mamoe.console.itest.options_properties.main", "0.0.0")
|
||||
) {
|
||||
override fun PluginComponentStorage.onLoad() {
|
||||
assertTrue { jvmPluginClasspath.shouldResolveConsoleSystemResource }
|
||||
assertFalse { jvmPluginClasspath.shouldBeResolvableToIndependent }
|
||||
assertFalse { jvmPluginClasspath.shouldResolveIndependent }
|
||||
}
|
||||
|
||||
override fun onEnable() {
|
||||
assertFails {
|
||||
// class.loading.load-independent = false
|
||||
Class.forName("consoleittest.optionproperties.independent.Independent")
|
||||
}
|
||||
}
|
||||
}
|
@ -2025,8 +2025,12 @@ public abstract interface class net/mamoe/mirai/console/plugin/jvm/JvmPluginClas
|
||||
public abstract fun getPluginFile ()Ljava/io/File;
|
||||
public abstract fun getPluginIndependentLibrariesClassLoader ()Ljava/lang/ClassLoader;
|
||||
public abstract fun getPluginSharedLibrariesClassLoader ()Ljava/lang/ClassLoader;
|
||||
public abstract fun getShouldBeResolvableToIndependent ()Z
|
||||
public abstract fun getShouldResolveConsoleSystemResource ()Z
|
||||
public abstract fun getShouldResolveIndependent ()Z
|
||||
public abstract fun setShouldBeResolvableToIndependent (Z)V
|
||||
public abstract fun setShouldResolveConsoleSystemResource (Z)V
|
||||
public abstract fun setShouldResolveIndependent (Z)V
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription : net/mamoe/mirai/console/plugin/description/PluginDescription {
|
||||
|
@ -307,6 +307,33 @@ internal class JvmPluginClassLoaderN : URLClassLoader {
|
||||
.forEach { pkg ->
|
||||
pluginMainPackages.add(pkg)
|
||||
}
|
||||
|
||||
zipFile.getEntry("META-INF/mirai-console-plugin/options.properties")?.let { optionsEntry ->
|
||||
runCatching {
|
||||
val options = Properties()
|
||||
zipFile.getInputStream(optionsEntry).bufferedReader().use { reader ->
|
||||
options.load(reader)
|
||||
}
|
||||
|
||||
openaccess.shouldBeResolvableToIndependent = options.prop(
|
||||
"class.loading.be-resolvable-to-independent", "true"
|
||||
) { it.toBooleanStrict() }
|
||||
|
||||
openaccess.shouldResolveIndependent = options.prop(
|
||||
"class.loading.resolve-independent", "true"
|
||||
) { it.toBooleanStrict() }
|
||||
|
||||
openaccess.shouldResolveConsoleSystemResource = options.prop(
|
||||
"resources.resolve-console-system-resources", "false"
|
||||
) { it.toBooleanStrict() }
|
||||
|
||||
}.onFailure { err ->
|
||||
throw IllegalStateException(
|
||||
"Exception while reading META-INF/mirai-console-plugin/options.properties",
|
||||
err
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
pluginSharedCL = DynLibClassLoader.newInstance(
|
||||
ctx.sharedLibrariesLoader, "SharedCL{${file.name}}", "${file.name}[shared]"
|
||||
@ -450,9 +477,17 @@ internal class JvmPluginClassLoaderN : URLClassLoader {
|
||||
return super.findClass(name)
|
||||
}
|
||||
} catch (error: ClassNotFoundException) {
|
||||
if (!openaccess.shouldResolveIndependent) {
|
||||
return ctx.consoleClassLoader.loadClass(name)
|
||||
}
|
||||
|
||||
// Finally, try search from other plugins and console system
|
||||
ctx.pluginClassLoaders.forEach { other ->
|
||||
if (other !== this && other !in dependencies) {
|
||||
|
||||
if (!other.openaccess.shouldBeResolvableToIndependent)
|
||||
return@forEach
|
||||
|
||||
other.resolvePluginPublicClass(name)?.let {
|
||||
if (undefinedDependencies.add(other.file.name)) {
|
||||
linkedLogger.warning { "Linked class $name in ${other.file.name} but plugin not depend on it." }
|
||||
@ -545,6 +580,8 @@ internal class JvmPluginClassLoaderN : URLClassLoader {
|
||||
get() = pluginIndependentCL
|
||||
|
||||
override var shouldResolveConsoleSystemResource: Boolean = false
|
||||
override var shouldBeResolvableToIndependent: Boolean = true
|
||||
override var shouldResolveIndependent: Boolean = true
|
||||
|
||||
private val permitted by lazy {
|
||||
arrayOf(
|
||||
@ -632,3 +669,19 @@ private fun <E> compoundEnumerations(iter: Iterator<Enumeration<E>>): Enumeratio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Properties.prop(key: String, def: String): String {
|
||||
try {
|
||||
return getProperty(key, def)
|
||||
} catch (err: Throwable) {
|
||||
throw IllegalStateException("Exception while reading `$key`", err)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> Properties.prop(key: String, def: String, dec: (String) -> T): T {
|
||||
try {
|
||||
return getProperty(key, def).let(dec)
|
||||
} catch (err: Throwable) {
|
||||
throw IllegalStateException("Exception while reading `$key`", err)
|
||||
}
|
||||
}
|
||||
|
@ -45,9 +45,32 @@ public interface JvmPluginClasspath {
|
||||
* [pluginClassLoader] 是否可以通过 [ClassLoader.getResource] 获取 Mirai Console (包括依赖) 的相关资源
|
||||
*
|
||||
* 默认为 `false`
|
||||
*
|
||||
* @since 2.15.0
|
||||
*/
|
||||
@SettingProperty("resources.resolve-console-system-resources", defaultValue = "false")
|
||||
public var shouldResolveConsoleSystemResource: Boolean
|
||||
|
||||
/**
|
||||
* 当前插件是否可以被没有依赖此插件的插件使用
|
||||
*
|
||||
* 默认为 `true`
|
||||
*
|
||||
* @since 2.15.0
|
||||
*/
|
||||
@SettingProperty("class.loading.be-resolvable-to-independent", defaultValue = "true")
|
||||
public var shouldBeResolvableToIndependent: Boolean
|
||||
|
||||
/**
|
||||
* 当前插件是否应该搜索未依赖的插件的类路径
|
||||
*
|
||||
* 默认为 `true`
|
||||
*
|
||||
* @since 2.15.0
|
||||
*/
|
||||
@SettingProperty("class.loading.resolve-independent", defaultValue = "true")
|
||||
public var shouldResolveIndependent: Boolean
|
||||
|
||||
/**
|
||||
* 将 [file] 加入 [classLoader] 的搜索路径内
|
||||
*
|
||||
@ -70,4 +93,18 @@ public interface JvmPluginClasspath {
|
||||
*/
|
||||
@kotlin.jvm.Throws(IllegalArgumentException::class, Exception::class)
|
||||
public fun downloadAndAddToPath(classLoader: ClassLoader, dependencies: Collection<String>)
|
||||
|
||||
|
||||
/**
|
||||
* 此注解仅用于注释 `options.properties` 的键值
|
||||
*
|
||||
* Note: `META-INF/mirai-console-plugin/options.properties`
|
||||
*
|
||||
* @since 2.15.0
|
||||
*/
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
private annotation class SettingProperty(
|
||||
val name: String,
|
||||
val defaultValue: String = "",
|
||||
)
|
||||
}
|
||||
|
@ -416,6 +416,62 @@ public final class JExample extends JavaPlugin {
|
||||
|
||||
详细查看 [JavaPluginScheduler]。
|
||||
|
||||
### 控制插件类路径
|
||||
|
||||
[JvmPluginClasspath]: ../../backend/mirai-console/src/plugin/jvm/JvmPluginClasspath.kt
|
||||
|
||||
Mirai Console 支持动态按需下载依赖和按需链接依赖 (通过 `JvmPluginClasspath.addToPath` 和 `JvmPluginClasspath.downloadAndAddToPath`)
|
||||
|
||||
`JvmPluginClasspath` 还支持控制是否应该引用其他插件的类路径 & 是否允许其他非依赖此插件的插件使用此插件的类路径
|
||||
|
||||
*Java* (Kotlin 类似)
|
||||
```java
|
||||
|
||||
public final class JExample extends JavaPlugin {
|
||||
//......
|
||||
@Override
|
||||
public void onLoad(PluginComponentStorage storage) {
|
||||
getLogger().info(String.valueOf(getJvmPluginClasspath().getShouldResolveIndependent()));
|
||||
getJvmPluginClasspath().addToPath(
|
||||
getJvmPluginClasspath().getPluginIndependentLibrariesClassLoader(),
|
||||
resolveDataFile("mylib.jar")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
详细查看 [JvmPluginClasspath]
|
||||
|
||||
#### 通过配置文件控制类路径选项
|
||||
|
||||
[JvmPluginClasspath] 中的部分选项可以通过配置文件指定, 虽然在代码中也可以修改, 但是通过配置文件指定是最好的。
|
||||
|
||||
> 因为如果在代码中修改, 类链接会在选项修改之前完成,从而导致一些不正常的逻辑
|
||||
|
||||
要使用配置文件控制 JvmPluginClasspath 中的选项, 需要创建名为 `META-INF/mirai-console-plugin/options.properties` 的资源文件
|
||||
|
||||
> 通常情况这个文件的位置是 `src/main/resources/META-INF/mirai-console-plugin/options.properties`
|
||||
>
|
||||
> 如果没有资源文件夹, Intellij IDEA 在创建文件夹时会提示 resources 补全
|
||||
>
|
||||
> ![CreateResourcesDir](./images/CreateResourcesDir.png)
|
||||
|
||||
选项的键值已经在 [JvmPluginClasspath] 源文件中使用 `@SettingProperty` 注明
|
||||
|
||||
示例:
|
||||
|
||||
```properties
|
||||
# suppress inspection "UnusedProperty" for whole file
|
||||
|
||||
resources.resolve-console-system-resources=false
|
||||
|
||||
class.loading.be-resolvable-to-independent=false
|
||||
class.loading.resolve-independent=false
|
||||
|
||||
```
|
||||
|
||||
## 访问数据目录和配置目录
|
||||
|
||||
[`JvmPlugin`] 实现接口 [`PluginFileExtensions`]。插件可通过 `resolveDataFile`
|
||||
|
BIN
mirai-console/docs/plugin/images/CreateResourcesDir.png
Normal file
BIN
mirai-console/docs/plugin/images/CreateResourcesDir.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
Loading…
Reference in New Issue
Block a user