Просмотр исходного кода

Allow override console non-hard-link dependencies

Karlatemp 4 лет назад
Родитель
Сommit
22d2bd79df

+ 45 - 3
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<Task> {
@@ -47,8 +48,9 @@ object DependencyDumper {
             doLast {
                 val dependencies = HashSet<String>()
                 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<Task> {
+        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<String>()
+                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) }
+    }
 }

+ 3 - 0
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

+ 8 - 0
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
+        )
     }
 }

+ 16 - 1
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<Copy>("processResources").configure {
+    from(graphDump) {
+        into("META-INF/mirai-console")
+    }
+}
+
 configurePublishing("mirai-console")
 configureBinaryValidator(null)

+ 26 - 0
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
+}

+ 41 - 7
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<JvmPluginClassLoaderN> = 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)
         }
     }