From 22d2bd79df504f7cc23abec9581fd85a3abb2604 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 7 Feb 2022 16:56:26 +0800 Subject: [PATCH] Allow override console non-hard-link dependencies --- buildSrc/src/main/kotlin/DependencyDumper.kt | 48 +++++++++++++++++-- .../dependencies-private.txt | 3 ++ .../src/P.kt | 8 ++++ .../backend/mirai-console/build.gradle.kts | 17 ++++++- .../plugin/AllDependenciesClassesHolder.kt | 26 ++++++++++ .../internal/plugin/JvmPluginClassLoader.kt | 48 ++++++++++++++++--- 6 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 mirai-console/backend/mirai-console/src/internal/plugin/AllDependenciesClassesHolder.kt diff --git a/buildSrc/src/main/kotlin/DependencyDumper.kt b/buildSrc/src/main/kotlin/DependencyDumper.kt index 9dd36c446..55075a05d 100644 --- a/buildSrc/src/main/kotlin/DependencyDumper.kt +++ b/buildSrc/src/main/kotlin/DependencyDumper.kt @@ -12,6 +12,7 @@ import org.gradle.api.Task import org.gradle.api.artifacts.ResolvedDependency import org.gradle.api.tasks.TaskProvider import java.io.File +import java.util.zip.ZipFile object DependencyDumper { fun registerDumpTask(project: Project, confName: String, out: File): TaskProvider { @@ -47,8 +48,9 @@ object DependencyDumper { doLast { val dependencies = HashSet() fun emit(dep: ResolvedDependency) { - dependencies.add(dep.moduleGroup + ":" + dep.moduleName) - dep.children.forEach { emit(it) } + if (dependencies.add(dep.moduleGroup + ":" + dep.moduleName)) { + dep.children.forEach { emit(it) } + } } project.configurations.getByName(confName).resolvedConfiguration.firstLevelModuleDependencies.forEach { dependency -> emit(dependency) @@ -57,7 +59,47 @@ object DependencyDumper { stdep.sort() action(stdep) } - } + }.also { dependenciesDump.dependsOn(it) } } + fun registerDumpClassGraph(project: Project, confName: String, out: String): TaskProvider { + val dependenciesDump = project.tasks.maybeCreate("dependenciesDump") + dependenciesDump.group = "mirai" + return project.tasks.register("dependenciesDumpGraph_${confName.capitalize()}") { + group = "mirai" + val outFile = temporaryDir.resolve(out) + outputs.file(outFile) + val conf = project.configurations.getByName(confName) + dependsOn(conf) + + doLast { + outFile.parentFile.mkdirs() + + val classes = HashSet() + conf.resolvedConfiguration.files.forEach { file -> + if (file.isFile) { + ZipFile(file).use { zipFile -> + zipFile.entries().asSequence() + .filter { it.name.endsWith(".class") } + .filterNot { it.name.startsWith("META-INF") } + .map { it.name.substringBeforeLast('.').replace('/', '.') } + .map { it.removePrefix(".") } + .forEach { classes.add(it) } + } + } else if (file.isDirectory) { + file.walk() + .filter { it.isFile } + .filter { it.name.endsWith(".class") } + .map { it.relativeTo(file).path.substringBeforeLast('.') } + .map { it.replace('\\', '.').replace('/', '.') } + .map { it.removePrefix(".") } + .forEach { classes.add(it) } + } + } + outFile.bufferedWriter().use { writer -> + classes.sorted().forEach { writer.append(it).append('\n') } + } + } + }.also { dependenciesDump.dependsOn(it) } + } } diff --git a/mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/resources/META-INF/mirai-console-plugin/dependencies-private.txt b/mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/resources/META-INF/mirai-console-plugin/dependencies-private.txt index f3986671c..c87bdd6fe 100644 --- a/mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/resources/META-INF/mirai-console-plugin/dependencies-private.txt +++ b/mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/resources/META-INF/mirai-console-plugin/dependencies-private.txt @@ -1 +1,4 @@ com.zaxxer:SparseBitSet:1.2 + +## Test console non-hard-link override +org.bouncycastle:bcprov-jdk15on:1.63 diff --git a/mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/src/P.kt b/mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/src/P.kt index 2328ef177..f82eef076 100644 --- a/mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/src/P.kt +++ b/mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/src/P.kt @@ -12,6 +12,7 @@ package net.mamoe.console.integrationtest.ep.pddd 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.assertEquals /* PluginDynamicDependenciesDownload: 测试动态运行时下载 @@ -26,5 +27,12 @@ internal object P : KotlinPlugin( override fun PluginComponentStorage.onLoad() { Class.forName("com.google.gson.Gson") // shared Class.forName("com.zaxxer.sparsebits.SparseBitSet") // private + + // console-non-hard-link dependency + // mirai-core used 1.64 current + assertEquals( + "1.63.0", + Class.forName("org.bouncycastle.LICENSE").`package`.implementationVersion + ) } } diff --git a/mirai-console/backend/mirai-console/build.gradle.kts b/mirai-console/backend/mirai-console/build.gradle.kts index 055778cd2..29d900b69 100644 --- a/mirai-console/backend/mirai-console/build.gradle.kts +++ b/mirai-console/backend/mirai-console/build.gradle.kts @@ -30,7 +30,13 @@ kotlin { explicitApiWarning() } -configurations.register("consoleRuntimeClasspath") + +// 搜索 mirai-console (包括 core) 直接使用并对外公开的类 (api) +configurations.create("consoleRuntimeClasspath").attributes { + attribute(Usage.USAGE_ATTRIBUTE, + project.objects.named(Usage::class.java, Usage.JAVA_API) + ) +} dependencies { compileAndTestRuntime(project(":mirai-core-api")) @@ -59,6 +65,8 @@ dependencies { testApi(`kotlin-stdlib-jdk8`) "consoleRuntimeClasspath"(project) + "consoleRuntimeClasspath"(project(":mirai-core-utils")) + "consoleRuntimeClasspath"(project(":mirai-core-api")) "consoleRuntimeClasspath"(project(":mirai-core")) } @@ -90,5 +98,12 @@ tasks.getByName("compileKotlin").dependsOn( ) ) +val graphDump = DependencyDumper.registerDumpClassGraph(project, "consoleRuntimeClasspath", "allclasses.txt") +tasks.named("processResources").configure { + from(graphDump) { + into("META-INF/mirai-console") + } +} + configurePublishing("mirai-console") configureBinaryValidator(null) \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/src/internal/plugin/AllDependenciesClassesHolder.kt b/mirai-console/backend/mirai-console/src/internal/plugin/AllDependenciesClassesHolder.kt new file mode 100644 index 000000000..4851b4899 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/internal/plugin/AllDependenciesClassesHolder.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2019-2022 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.mirai.console.internal.plugin + +// Same as LazyLoad +internal object AllDependenciesClassesHolder { + @JvmField + internal val allclasses = AllDependenciesClassesHolder::class.java + .getResourceAsStream("/META-INF/mirai-console/allclasses.txt")!! + .bufferedReader().use { reader -> + reader.useLines { lines -> + lines.filterNot { it.isBlank() } + .toHashSet() + } + } + + @JvmField + internal val appClassLoader: ClassLoader = AllDependenciesClassesHolder::class.java.classLoader +} diff --git a/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt b/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt index 6c079a74b..e9ede18c9 100644 --- a/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt +++ b/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt @@ -26,7 +26,7 @@ import java.util.zip.ZipFile Class resolving: | -`- Resolve standard classes: by super class loader. +`- Resolve standard classes: hard linked by console (@see AllDependenciesClassesHolder) `- Resolve classes in shared libraries (Shared in all plugins) | |-===== SANDBOX ===== @@ -35,6 +35,7 @@ Class resolving: `- Resolve classes in independent libraries (Can only be loaded by current plugin) `- Resolve classes in current jar. `- Resolve classes from other plugin jar +`- Resolve by AppClassLoader */ @@ -58,6 +59,17 @@ internal class DynLibClassLoader( } } + internal fun loadClassInThisClassLoader(name: String): Class<*>? { + synchronized(getClassLoadingLock(name)) { + findLoadedClass(name)?.let { return it } + try { + return findClass(name) + } catch (ignored: ClassNotFoundException) { + } + } + return null + } + internal fun addLib(url: URL) { addURL(url) } @@ -75,6 +87,7 @@ internal class DynLibClassLoader( internal class JvmPluginClassLoaderN : URLClassLoader { val file: File val ctx: JvmPluginsLoadingCtx + val sharedLibrariesLogger: DynLibClassLoader val dependencies: MutableCollection = hashSetOf() @@ -85,6 +98,7 @@ internal class JvmPluginClassLoaderN : URLClassLoader { private constructor(file: File, ctx: JvmPluginsLoadingCtx, unused: Unit) : super( arrayOf(), ctx.sharedLibrariesLoader ) { + this.sharedLibrariesLogger = ctx.sharedLibrariesLoader this.file = file this.ctx = ctx init0() @@ -94,6 +108,7 @@ internal class JvmPluginClassLoaderN : URLClassLoader { file.name, arrayOf(), ctx.sharedLibrariesLoader ) { + this.sharedLibrariesLogger = ctx.sharedLibrariesLoader this.file = file this.ctx = ctx init0() @@ -184,12 +199,28 @@ internal class JvmPluginClassLoaderN : URLClassLoader { internal fun resolvePluginPublicClass(name: String): Class<*>? { if (pluginMainPackages.contains(name.pkgName())) { if (declaredFilter?.isExported(name) == false) return null - return loadClass(name) + synchronized(getClassLoadingLock(name)) { + findLoadedClass(name)?.let { return it } + return super.findClass(name) + } } return null } - override fun findClass(name: String): Class<*> { + override fun loadClass(name: String, resolve: Boolean): Class<*> = loadClass(name) + + override fun loadClass(name: String): Class<*> { + if (name.startsWith("io.netty") || name in AllDependenciesClassesHolder.allclasses) { + return AllDependenciesClassesHolder.appClassLoader.loadClass(name) + } + if (name.startsWith("net.mamoe.mirai.")) { // Avoid plugin classing cheating + try { + return AllDependenciesClassesHolder.appClassLoader.loadClass(name) + } catch (ignored: ClassNotFoundException) { + } + } + sharedLibrariesLogger.loadClassInThisClassLoader(name)?.let { return it } + // Search dependencies first dependencies.forEach { dependency -> dependency.resolvePluginSharedLibAndPluginClass(name)?.let { return it } @@ -202,15 +233,18 @@ internal class JvmPluginClassLoaderN : URLClassLoader { } try { - return super.findClass(name) + synchronized(getClassLoadingLock(name)) { + findLoadedClass(name)?.let { return it } + return super.findClass(name) + } } catch (error: ClassNotFoundException) { - // Finally, try search from other plugins + // Finally, try search from other plugins and console system ctx.pluginClassLoaders.forEach { other -> - if (other !== this) { + if (other !== this && other !in dependencies) { other.resolvePluginPublicClass(name)?.let { return it } } } - throw error + return AllDependenciesClassesHolder.appClassLoader.loadClass(name) } }