[build] Rewrite shadow relocation

This commit is contained in:
Him188
2022-11-02 19:28:04 +00:00
parent ca840f88be
commit c0ccdbe9d3
17 changed files with 699 additions and 344 deletions

View File

@@ -95,6 +95,7 @@ tasks.register("publishMiraiLocalArtifacts", Exec::class) {
"./gradlew",
publishMiraiArtifactsToMavenLocal.name,
"--no-daemon",
"--stacktrace",
"-Pkotlin.compiler.execution.strategy=in-process"
)
standardOutput = System.out

View File

@@ -23,7 +23,7 @@ abstract class AbstractTest {
const val miraiLocalVersion = "2.99.0-deps-test" // do Search Everywhere before changing this
const val REASON_LOCAL_ARTIFACT_NOT_AVAILABLE = "local artifacts not available"
private val mavenLocalDir: File by lazy {
val mavenLocalDir: File by lazy {
org.gradle.api.internal.artifacts.mvnsettings.DefaultLocalMavenRepositoryLocator(
org.gradle.api.internal.artifacts.mvnsettings.DefaultMavenSettingsProvider(DefaultMavenFileLocations())
).localMavenRepository
@@ -165,9 +165,20 @@ abstract class AbstractTest {
if (context.executionException.isPresent) {
val inst = context.requiredTestInstance as AbstractTest
println("====================== build.gradle ===========================")
println(inst.tempDir.resolve("build.gradle").readText())
println(inst.tempDir.resolveFirstExisting("build.gradle", "build.gradle.kts").readTextIfFound())
println("==================== settings.gradle ==========================")
println(inst.tempDir.resolve("settings.gradle").readText())
println(inst.tempDir.resolveFirstExisting("settings.gradle", "settings.gradle.kts").readTextIfFound())
}
}
private fun File.resolveFirstExisting(vararg files: String): File? {
return files.asSequence().map { resolve(it) }.firstOrNull { it.exists() }
}
private fun File?.readTextIfFound(): String =
when {
this == null -> "(not found)"
exists() -> readText()
else -> "($name not found)"
}
}

View File

@@ -13,17 +13,20 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.condition.EnabledIf
class CoreDependencyResolutionTest : AbstractTest() {
private val testCode = """
package test
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPERIMENTAL_API_USAGE")
fun main () {
println(net.mamoe.mirai.BotFactory)
println(net.mamoe.mirai.Mirai)
println(net.mamoe.mirai.internal.testHttpClient())
}
""".trimIndent()
@Test
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
fun `test resolve JVM root from Kotlin JVM`() {
mainSrcDir.resolve("main.kt").writeText(
"""
package test
fun main () {
println(net.mamoe.mirai.BotFactory)
}
""".trimIndent()
)
mainSrcDir.resolve("main.kt").writeText(testCode)
buildFile.writeText(
"""
plugins {
@@ -36,6 +39,9 @@ class CoreDependencyResolutionTest : AbstractTest() {
dependencies {
implementation("net.mamoe:mirai-core:$miraiLocalVersion")
}
kotlin.sourceSets.all {
languageSettings.optIn("net.mamoe.mirai.utils.TestOnly")
}
""".trimIndent()
)
runGradle("build")
@@ -44,14 +50,7 @@ class CoreDependencyResolutionTest : AbstractTest() {
@Test
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
fun `test resolve JVM from Kotlin JVM`() {
mainSrcDir.resolve("main.kt").writeText(
"""
package test
fun main () {
println(net.mamoe.mirai.BotFactory)
}
""".trimIndent()
)
mainSrcDir.resolve("main.kt").writeText(testCode)
buildFile.writeText(
"""
plugins {
@@ -64,6 +63,9 @@ class CoreDependencyResolutionTest : AbstractTest() {
dependencies {
implementation("net.mamoe:mirai-core-jvm:$miraiLocalVersion")
}
kotlin.sourceSets.all {
languageSettings.optIn("net.mamoe.mirai.utils.TestOnly")
}
""".trimIndent()
)
runGradle("build")
@@ -72,14 +74,7 @@ class CoreDependencyResolutionTest : AbstractTest() {
@Test
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
fun `test resolve JVM and Native from common`() {
commonMainSrcDir.resolve("main.kt").writeText(
"""
package test
fun main () {
println(net.mamoe.mirai.BotFactory)
}
""".trimIndent()
)
commonMainSrcDir.resolve("main.kt").writeText(testCode)
buildFile.writeText(
"""
|import org.apache.tools.ant.taskdefs.condition.Os
@@ -111,6 +106,9 @@ class CoreDependencyResolutionTest : AbstractTest() {
| }
| }
|}
|kotlin.sourceSets.all {
| languageSettings.optIn("net.mamoe.mirai.utils.TestOnly")
|}
""".trimMargin()
)
@@ -120,14 +118,7 @@ class CoreDependencyResolutionTest : AbstractTest() {
@Test
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
fun `test resolve Native from common`() {
nativeMainSrcDir.resolve("main.kt").writeText(
"""
package test
fun main () {
println(net.mamoe.mirai.BotFactory)
}
""".trimIndent()
)
nativeMainSrcDir.resolve("main.kt").writeText(testCode)
buildFile.writeText(
"""
|import org.apache.tools.ant.taskdefs.condition.Os
@@ -159,6 +150,9 @@ class CoreDependencyResolutionTest : AbstractTest() {
| }
| }
|}
|kotlin.sourceSets.all {
| languageSettings.optIn("net.mamoe.mirai.utils.TestOnly")
|}
""".trimMargin()
)

View File

@@ -11,31 +11,77 @@ package net.mamoe.mirai.deps.test
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.condition.EnabledIf
import kotlin.test.assertTrue
/**
* 为每个模块测试 relocated 的依赖是否存在于运行时
*/
class CoreShadowRelocationTest : AbstractTest() {
companion object {
private const val ByteBufferChannel = "io.ktor.utils.io.ByteBufferChannel"
private const val HttpClient = "io.ktor.client.HttpClient"
private const val KtorOkHttp = "io.ktor.client.engine.okhttp.OkHttp"
private const val OkHttp = "okhttp3.OkHttp"
private const val OkIO = "okio.ByteString"
private fun relocated(string: String): String {
return "net.mamoe.mirai.internal.deps.$string"
}
}
@Test
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
fun `test OkHttp filtered out`() {
testDir.resolve("test.kt").writeText(
fun `test mirai-core-utils`() {
val fragment = buildTestCases {
+relocated(`ktor-io`)
-both(`ktor-client-core`)
-both(`ktor-client-okhttp`)
-both(`okhttp3-okhttp`)
-both(okio)
}
applyCodeFragment(fragment)
buildFile.appendText(
"""
package test
import org.junit.jupiter.api.*
class MyTest {
@Test
fun `test base dependency`() {
assertThrows<ClassNotFoundException> {
Class.forName("io.ktor.client.engine.okhttp.OkHttp")
}
}
@Test
fun `test transitive dependency`() {
assertThrows<ClassNotFoundException> {
Class.forName("okhttp3.OkHttpClient")
}
}
dependencies {
implementation("net.mamoe:mirai-core-utils:$miraiLocalVersion")
}
""".trimIndent()
)
runGradle("check")
}
@Test
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
fun `test mirai-core-api with transitive mirai-core-utils`() {
val fragment = buildTestCases {
+relocated(`ktor-io`)
-both(`ktor-client-core`)
-both(`ktor-client-okhttp`)
-both(`okhttp3-okhttp`)
-both(okio)
}
applyCodeFragment(fragment)
buildFile.appendText(
"""
dependencies {
implementation("net.mamoe:mirai-core-api:$miraiLocalVersion")
}
""".trimIndent()
)
runGradle("check")
}
@Test
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
fun `test mirai-core with transitive mirai-core-api and mirai-core-utils`() {
val fragment = buildTestCases {
+relocated(`ktor-io`)
+relocated(`ktor-client-core`)
+relocated(`ktor-client-okhttp`)
+relocated(`okhttp3-okhttp`)
+relocated(okio)
}
applyCodeFragment(fragment)
buildFile.appendText(
"""
dependencies {
@@ -46,25 +92,20 @@ class CoreShadowRelocationTest : AbstractTest() {
runGradle("check")
}
// ktor-io is shadowed into runtime in mirai-core-utils. So without mirai-core-utils,
// we should expect no relocated ktor-io found, otherwise there will be duplicated classes on Android.
// https://github.com/mamoe/mirai/issues/2291
@Test
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
fun `no duplicated class when dependency shared across modules`() {
testDir.resolve("test.kt").writeText(
"""
package test
import org.junit.jupiter.api.*
class MyTest {
@Test
fun `test base dependency`() {
assertThrows<ClassNotFoundException> {
Class.forName("net.mamoe.mirai.internal.deps.io.ktor.utils.io.ByteBufferChannel") // should only present in mirai-core-utils
}
}
}
""".trimIndent()
)
fun `test mirai-core without transitive mirai-core-api and mirai-core-utils`() {
val fragment = buildTestCases {
-both(`ktor-io`)
+relocated(`ktor-client-core`)
+relocated(`ktor-client-okhttp`)
+relocated(`okhttp3-okhttp`)
+relocated(okio)
}
applyCodeFragment(fragment)
buildFile.appendText(
"""
dependencies {
@@ -80,23 +121,21 @@ class CoreShadowRelocationTest : AbstractTest() {
@Test
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
fun `relocated ktor presents in mirai-core-utils`() {
testDir.resolve("test.kt").writeText(
"""
package test
import org.junit.jupiter.api.*
class MyTest {
@Test
fun `test base dependency`() {
Class.forName("net.mamoe.mirai.internal.deps.io.ktor.utils.io.ByteBufferChannel")
}
}
""".trimIndent()
)
fun `test mirai-core-api without transitive mirai-core-utils`() {
val fragment = buildTestCases {
-both(`ktor-io`)
-both(`ktor-client-core`)
-both(`ktor-client-okhttp`)
-both(`okhttp3-okhttp`)
-both(okio)
}
applyCodeFragment(fragment)
buildFile.appendText(
"""
dependencies {
implementation("net.mamoe:mirai-core-utils:$miraiLocalVersion")
implementation("net.mamoe:mirai-core-api:$miraiLocalVersion") {
exclude("net.mamoe", "mirai-core-utils")
}
}
""".trimIndent()
)
@@ -105,26 +144,141 @@ class CoreShadowRelocationTest : AbstractTest() {
@Test
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
fun `relocated ktor presents transitively in mirai-core`() {
testDir.resolve("test.kt").writeText(
"""
package test
import org.junit.jupiter.api.*
class MyTest {
@Test
fun `test base dependency`() {
Class.forName("net.mamoe.mirai.internal.deps.io.ktor.utils.io.ByteBufferChannel")
}
}
""".trimIndent()
)
fun `test mirai-core-all`() {
val fragment = buildTestCases {
+relocated(`ktor-io`)
+relocated(`ktor-client-core`)
+relocated(`ktor-client-okhttp`)
+relocated(`okhttp3-okhttp`)
+relocated(okio)
}
applyCodeFragment(fragment)
// mirai-core-all-2.99.0-deps-test-all.jar
val miraiCoreAllJar =
mavenLocalDir.resolve("net/mamoe/mirai-core-all/$miraiLocalVersion/mirai-core-all-$miraiLocalVersion-all.jar")
assertTrue("'${miraiCoreAllJar.absolutePath}' does not exist") { miraiCoreAllJar.exists() }
buildFile.appendText(
"""
dependencies {
implementation("net.mamoe:mirai-core:$miraiLocalVersion")
implementation(fileTree("${miraiCoreAllJar.absolutePath}"))
}
""".trimIndent()
)
runGradle("check")
}
@Suppress("PropertyName")
private class TestBuilder {
private val result = StringBuilder(
"""
package test
import org.junit.jupiter.api.*
class MyTest {
""".trimIndent()
).append("\n").append("\n")
class TestCase(
val name: String,
val qualifiedClassName: String,
)
val `ktor-io` = TestCase("ktor-io ByteBufferChannel", ByteBufferChannel)
val `ktor-client-core` = TestCase("ktor-client-core HttpClient", HttpClient)
val `ktor-client-okhttp` = TestCase("ktor-client-core OkHttp", KtorOkHttp)
val `okhttp3-okhttp` = TestCase("okhttp3 OkHttp", OkHttp)
val okio = TestCase("okio ByteString", OkIO)
class Relocated(
val testCase: TestCase
)
class Both(
val testCase: TestCase
)
private fun appendHas(name: String, qualifiedClassName: String) {
result.append(
"""
@Test
fun `has ${name}`() {
Class.forName("$qualifiedClassName")
}
""".trimIndent()
).append("\n")
}
private fun appendNotFound(name: String, qualifiedClassName: String) {
result.append(
"""
@Test
fun `no relocated ${name}`() {
assertThrows<ClassNotFoundException> { Class.forName("$qualifiedClassName") }
}
""".trimIndent()
).append("\n")
}
/**
* Asserts a class exists. Also asserts its relocated class does not exist.
*/
operator fun TestCase.unaryPlus() {
appendHas(name, qualifiedClassName)
appendNotFound("relocated $name", Companion.relocated(qualifiedClassName))
}
/**
* Asserts a class does not exist.
*/
operator fun TestCase.unaryMinus() {
appendNotFound(name, qualifiedClassName)
}
/**
* Asserts a relocated class exists. Also asserts the original class does not exist.
*/
operator fun Relocated.unaryPlus() {
this.testCase.run {
appendHas("relocated $name", Companion.relocated(qualifiedClassName))
appendNotFound(name, qualifiedClassName)
}
}
/**
* Asserts a relocated class does not exist.
*/
operator fun Relocated.unaryMinus() {
this.testCase.run {
appendNotFound("relocated $name", Companion.relocated(qualifiedClassName))
}
}
/**
* Asserts both the class and its relocated one do not exist.
*/
operator fun Both.unaryMinus() {
-this.testCase
-relocated(this.testCase)
}
fun relocated(testCase: TestCase): Relocated = Relocated(testCase)
fun both(testCase: TestCase) = Both(testCase)
fun build(): String = result.append("\n}\n").toString()
}
private inline fun buildTestCases(action: TestBuilder.() -> Unit): String {
return TestBuilder().apply(action).build()
}
private fun applyCodeFragment(fragment: String) {
println("Applying code fragment: \n\n$fragment\n\n\n===========End of Fragment===========")
testDir.resolve("test.kt").writeText(fragment)
}
}