From 97a37a196f188e83ef59261dcbd6efe5d85e7ba0 Mon Sep 17 00:00:00 2001
From: Karlatemp <karlatemp@vip.qq.com>
Date: Wed, 18 May 2022 17:16:33 +0800
Subject: [PATCH] Fix nested projects building; Fix #2038

---
 .../src/integTest/kotlin/TestBuildPlugin.kt   | 173 +++++++++++++++++-
 .../src/main/kotlin/BuildMiraiPluginV2.kt     |  25 ++-
 2 files changed, 187 insertions(+), 11 deletions(-)

diff --git a/mirai-console/tools/gradle-plugin/src/integTest/kotlin/TestBuildPlugin.kt b/mirai-console/tools/gradle-plugin/src/integTest/kotlin/TestBuildPlugin.kt
index 0022bff6a..b9f6c37d1 100644
--- a/mirai-console/tools/gradle-plugin/src/integTest/kotlin/TestBuildPlugin.kt
+++ b/mirai-console/tools/gradle-plugin/src/integTest/kotlin/TestBuildPlugin.kt
@@ -17,25 +17,42 @@ import java.io.File
 import java.util.zip.ZipFile
 import kotlin.test.assertFalse
 import kotlin.test.assertNotNull
+import kotlin.test.assertNull
 import kotlin.test.assertTrue
 
 class TestBuildPlugin : AbstractTest() {
+    private fun File.wt(text: String) {
+        parentFile?.mkdirs()
+        writeText(text)
+    }
 
     @Test
     @DisplayName("project as normal dependency")
     fun buildWithMultiProjectsAsNormalDependency() {
         settingsFile.appendText(
             """
-            include("nested")
+            include("nested1")
+            include("nested0")
         """.trimIndent()
         )
-        tempDir.resolve("nested").also { it.mkdirs() }.resolve("build.gradle").writeText(
+        tempDir.resolve("nested1/build.gradle").wt(
+            """
+            plugins {
+                id("org.jetbrains.kotlin.jvm")
+            }
+            repositories {
+                mavenCentral()
+            }
+        """.trimIndent()
+        )
+        tempDir.resolve("nested0/build.gradle").wt(
             """
             plugins {
                 id("org.jetbrains.kotlin.jvm")
                 id("net.mamoe.mirai-console")
             }
             dependencies {
+                api project(":nested1") 
                 api "com.zaxxer:SparseBitSet:1.2"
             }
             repositories {
@@ -47,11 +64,29 @@ class TestBuildPlugin : AbstractTest() {
         tempDir.resolve("build.gradle").appendText(
             """
             dependencies {
-                implementation project(":nested") 
-                asNormalDep project(":nested") 
+                implementation project(":nested0") 
+                asNormalDep project(":nested0")
             }
         """.trimIndent()
         )
+        tempDir.resolve("nested0/src/main/kotlin/test.kt").wt(
+            """
+            package nested
+            public class TestClass
+        """.trimIndent()
+        )
+        tempDir.resolve("nested1/src/main/kotlin/test.kt").wt(
+            """
+            package nested1
+            public class TestClass
+        """.trimIndent()
+        )
+        tempDir.resolve("src/main/kotlin/test.kt").wt(
+            """
+            package thetop
+            public class TestClass
+        """.trimIndent()
+        )
 
         gradleRunner()
             .withArguments(":buildPlugin", "--stacktrace", "--info")
@@ -69,7 +104,97 @@ class TestBuildPlugin : AbstractTest() {
 
             assertFalse { dpShared.contains("com.zaxxer:SparseBitSet:1.2") }
             assertTrue { dpPrivate.contains("com.zaxxer:SparseBitSet:1.2") }
-            assertTrue { dpPrivate.contains(":nested") }
+            assertTrue { dpPrivate.contains(":nested0") }
+            assertTrue { dpPrivate.contains(":nested1") }
+            assertNotNull(zipFile.getEntry("thetop/TestClass.class"))
+            assertNull(zipFile.getEntry("nested/TestClass.class"))
+            assertNull(zipFile.getEntry("nested1/TestClass.class"))
+        }
+    }
+
+    @Test
+    @DisplayName("project as normal dependency 2")
+    fun buildWithMultiProjectsAsNormalDependency2() {
+        settingsFile.appendText(
+            """
+            include("nested1")
+            include("nested0")
+        """.trimIndent()
+        )
+        tempDir.resolve("nested1/build.gradle").wt(
+            """
+            plugins {
+                id("org.jetbrains.kotlin.jvm")
+            }
+            repositories {
+                mavenCentral()
+            }
+        """.trimIndent()
+        )
+        tempDir.resolve("nested0/build.gradle").wt(
+            """
+            plugins {
+                id("org.jetbrains.kotlin.jvm")
+                id("net.mamoe.mirai-console")
+            }
+            dependencies {
+                api project(":nested1") 
+                api "com.zaxxer:SparseBitSet:1.2"
+            }
+            repositories {
+                mavenCentral()
+            }
+        """.trimIndent()
+        )
+
+        tempDir.resolve("build.gradle").appendText(
+            """
+            dependencies {
+                implementation project(":nested0") 
+                asNormalDep project(":nested1")
+            }
+        """.trimIndent()
+        )
+        tempDir.resolve("nested0/src/main/kotlin/test.kt").wt(
+            """
+            package nested
+            public class TestClass
+        """.trimIndent()
+        )
+        tempDir.resolve("nested1/src/main/kotlin/test.kt").wt(
+            """
+            package nested1
+            public class TestClass
+        """.trimIndent()
+        )
+        tempDir.resolve("src/main/kotlin/test.kt").wt(
+            """
+            package thetop
+            public class TestClass
+        """.trimIndent()
+        )
+
+        gradleRunner()
+            .withArguments(":buildPlugin", "--stacktrace", "--info")
+            .build()
+
+
+        ZipFile(findJar()).use { zipFile ->
+
+            val dpPrivate = zipFile.getInputStream(
+                zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-private.txt")
+            ).use { it.readBytes().decodeToString() }
+            val dpShared = zipFile.getInputStream(
+                zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-shared.txt")
+            ).use { it.readBytes().decodeToString() }
+
+            assertFalse { dpShared.contains("com.zaxxer:SparseBitSet:1.2") }
+            assertTrue { dpPrivate.contains("com.zaxxer:SparseBitSet:1.2") }
+            assertFalse { dpPrivate.contains(":nested0") }
+            assertTrue { dpPrivate.contains(":nested1") }
+            assertNotNull(zipFile.getEntry("thetop/TestClass.class"))
+            assertNotNull(zipFile.getEntry("nested/TestClass.class"))
+            assertNull(zipFile.getEntry("nested1/TestClass.class"))
         }
     }
 
@@ -81,7 +206,7 @@ class TestBuildPlugin : AbstractTest() {
             include("nested")
         """.trimIndent()
         )
-        tempDir.resolve("nested").also { it.mkdirs() }.resolve("build.gradle").writeText(
+        tempDir.resolve("nested/build.gradle").wt(
             """
             plugins {
                 id("org.jetbrains.kotlin.jvm")
@@ -104,6 +229,19 @@ class TestBuildPlugin : AbstractTest() {
         """.trimIndent()
         )
 
+        tempDir.resolve("nested/src/main/kotlin/test.kt").wt(
+            """
+            package nested
+            public class TestClass
+        """.trimIndent()
+        )
+        tempDir.resolve("src/main/kotlin/test.kt").wt(
+            """
+            package thetop
+            public class TestClass
+        """.trimIndent()
+        )
+
         gradleRunner()
             .withArguments(":buildPlugin", "--stacktrace", "--info")
             .build()
@@ -120,6 +258,9 @@ class TestBuildPlugin : AbstractTest() {
 
             assertFalse { dpShared.contains("com.zaxxer:SparseBitSet:1.2") }
             assertTrue { dpPrivate.contains("com.zaxxer:SparseBitSet:1.2") }
+
+            assertNotNull(zipFile.getEntry("thetop/TestClass.class"))
+            assertNotNull(zipFile.getEntry("nested/TestClass.class"))
         }
     }
 
@@ -131,7 +272,7 @@ class TestBuildPlugin : AbstractTest() {
             include("nested")
         """.trimIndent()
         )
-        tempDir.resolve("nested").also { it.mkdirs() }.resolve("build.gradle").writeText(
+        tempDir.resolve("nested/build.gradle").wt(
             """
             plugins {
                 id("org.jetbrains.kotlin.jvm")
@@ -157,10 +298,28 @@ class TestBuildPlugin : AbstractTest() {
         """.trimIndent()
         )
 
+        tempDir.resolve("nested/src/main/kotlin/test.kt").wt(
+            """
+            package nested
+            public class TestClass
+        """.trimIndent()
+        )
+        tempDir.resolve("src/main/kotlin/test.kt").wt(
+            """
+            package thetop
+            public class TestClass
+        """.trimIndent()
+        )
+
         gradleRunner()
             .withArguments(":buildPlugin", "dependencies", "--stacktrace", "--info")
             .build()
         checkOutput()
+
+        ZipFile(findJar()).use { zipFile ->
+            assertNotNull(zipFile.getEntry("thetop/TestClass.class"))
+            assertNotNull(zipFile.getEntry("nested/TestClass.class"))
+        }
     }
 
     @Test
diff --git a/mirai-console/tools/gradle-plugin/src/main/kotlin/BuildMiraiPluginV2.kt b/mirai-console/tools/gradle-plugin/src/main/kotlin/BuildMiraiPluginV2.kt
index df5704c63..18f288b3c 100644
--- a/mirai-console/tools/gradle-plugin/src/main/kotlin/BuildMiraiPluginV2.kt
+++ b/mirai-console/tools/gradle-plugin/src/main/kotlin/BuildMiraiPluginV2.kt
@@ -73,6 +73,8 @@ public open class BuildMiraiPluginV2 : Jar() {
             val shadowedDependencies = mutableSetOf<String>()
             val subprojects = mutableSetOf<String>()
             val subprojects_fullpath = mutableSetOf<String>()
+            // val subprojects_unlinked_fullpath = mutableSetOf<String>()
+            val subprojects_linked_fullpath = mutableSetOf<String>()
 
             project.configurations.findByName(MiraiConsoleGradlePlugin.MIRAI_SHADOW_CONF_NAME)?.allDependencies?.forEach { dep ->
                 if (dep is ExternalModuleDependency) {
@@ -80,10 +82,16 @@ public open class BuildMiraiPluginV2 : Jar() {
                     shadowedDependencies.add(artId)
                 }
             }
-            project.configurations.findByName(MiraiConsoleGradlePlugin.MIRAI_AS_NORMAL_DEP_CONF_NAME)?.allDependencies?.forEach { dep ->
-                if (dep is ProjectDependency) {
-                    linkedDependencies.add("${dep.group}:${dep.name}")
+            project.configurations.findByName(MiraiConsoleGradlePlugin.MIRAI_AS_NORMAL_DEP_CONF_NAME)?.allDependencies?.forEach { dep1 ->
+                fun resolve0(dep: Dependency) {
+                    if (dep is ProjectDependency) {
+                        linkedDependencies.add("${dep.group}:${dep.name}")
+                        subprojects_linked_fullpath.add(dep.dependencyProject.path)
+                        dep.dependencyProject.configurations.findByName("apiElements")?.allDependencies?.forEach { resolve0(it) }
+                        dep.dependencyProject.configurations.findByName("implementation")?.allDependencies?.forEach { resolve0(it) }
+                    }
                 }
+                resolve0(dep1)
             }
 
             fun deepForeachDependencies(conf: Configuration?, action: (Dependency) -> Unit) {
@@ -168,11 +176,20 @@ public open class BuildMiraiPluginV2 : Jar() {
             }
             runtimeClasspath.firstLevelModuleDependencies.forEach { resolveDependency(it) }
 
+            /*subprojects_fullpath.forEach { usedProject ->
+                val subProj = project.project(usedProject)
+                if ("${subProj.group}:${subProj.name}" !in linkedDependencies) {
+                    subprojects_unlinked_fullpath.add(usedProject)
+                }
+            }*/
+
             logger.info { "linkedDependencies: $linkedDependencies" }
             logger.info { "linkToAPi         : $linkToApi" }
             logger.info { "api               : $api" }
             logger.info { "runtime           : $runtime" }
             logger.info { "subprojects       : $subprojects" }
+            logger.info { "subprojects_linked: $subprojects_linked_fullpath" }
+            // logger.info { "subprojects_unlink: $subprojects_unlinked_fullpath" }
 
             val lenientConfiguration = runtimeClasspath.lenientConfiguration
             if (lenientConfiguration is DefaultLenientConfiguration) {
@@ -207,7 +224,7 @@ public open class BuildMiraiPluginV2 : Jar() {
                 }
                 val cid = artId.componentIdentifier
                 if (cid is ProjectComponentIdentifier) {
-                    if (cid.projectPath in subprojects_fullpath) {
+                    if (cid.projectPath in subprojects_linked_fullpath) {
                         return@forEach
                     }
                 }