Răsfoiți Sursa

native: 优化 build script 及 actions,修复 ECDH 相关资源释放问题 (#2110)

* build: fix build on Windows

* test: fix ContentEqualsTest on native

* build: allow disabling targets with property

* fix: free ECDH-related resources properly on native
avoid memory leaking (usually on *nix) or crash (usually on Windows)

Signed-off-by: AdoptOSS <[email protected]>

* build(workflow): remove invalid options

* fix(styling): `mirai.target` property

* build(workflow): try to limit memory usage during mirai-console:tools:gradle-plugin:integTest

* enhance(test): use buildList

* build(workflow): retry

Co-authored-by: ArcticLampyrid <[email protected]>
AdoptOSS 3 ani în urmă
părinte
comite
7325c1f7e2

+ 20 - 17
.github/workflows/build.yml

@@ -13,7 +13,7 @@ jobs:
           - windows-2022
           - macos-12
     env:
-      gradleArgs: --scan "-Pkotlin.compiler.execution.strategy=in-process" "-Dorg.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 --illegal-access=permit -Dkotlin.daemon.jvm.options=--illegal-access=permit --add-opens java.base/java.util=ALL-UNNAMED"
+      gradleArgs: --scan "-Dmirai.target=jvm;android;!other" "-Pkotlin.compiler.execution.strategy=in-process" "-Dorg.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8"
       isMac: ${{ startsWith(matrix.os, 'macos') }}
       isWindows: ${{ startsWith(matrix.os, 'windows') }}
       isUbuntu: ${{ startsWith(matrix.os, 'ubuntu') }}
@@ -34,6 +34,13 @@ jobs:
       - if: ${{ env.isUnix == 'true' }}
         run: chmod -R 777 *
 
+      - if: ${{ env.isWindows == 'true' }}
+        name: Setup Memory Environment on Windows
+        run: >
+          wmic pagefileset where name="D:\\pagefile.sys" set InitialSize=1024,MaximumSize=9216 &
+          net stop mongodb
+        shell: cmd
+        continue-on-error: true
 
       - name: Clean and download dependencies
         run: ./gradlew clean ${{ env.gradleArgs }}
@@ -295,22 +302,24 @@ jobs:
           - macos-11
         include:
         - os: windows-2022
-          testTaskName: mingwX64Test
+          targetName: mingwX64
         - os: ubuntu-20.04
-          testTaskName: linuxX64Test
+          targetName: linuxX64
         - os: ubuntu-18.04
-          testTaskName: linuxX64Test
+          targetName: linuxX64
         - os: macos-12
-          testTaskName: macosX64Test
+          targetName: macosX64
         - os: macos-11
-          testTaskName: macosX64Test
+          targetName: macosX64
     env:
-      gradleArgs: --scan "-Pkotlin.compiler.execution.strategy=in-process" "-Dorg.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 --illegal-access=permit -Dkotlin.daemon.jvm.options=--illegal-access=permit --add-opens java.base/java.util=ALL-UNNAMED"
+      # FIXME there must be two or more targets, or we'll get error on `@OptionalExpectation`
+      # > Declaration annotated with '@OptionalExpectation' can only be used in common module sources
+      gradleArgs: --scan  "-Dmirai.target=jvm;${{ matrix.targetName }};!other" "-Pkotlin.compiler.execution.strategy=in-process" "-Dorg.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8"
       isMac: ${{ startsWith(matrix.os, 'macos') }}
       isWindows: ${{ startsWith(matrix.os, 'windows') }}
       isUbuntu: ${{ startsWith(matrix.os, 'ubuntu') }}
       isUnix: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') }}
-      VCPKG_DEFAULT_BINARY_CACHE: ${{ startsWith(matrix.os, 'windows') && 'C:\\vcpkg\\binary_cache' || '/usr/local/share/vcpkg/binary_cache' }}
+      VCPKG_DEFAULT_BINARY_CACHE: ${{ startsWith(matrix.os, 'windows') && 'C:\vcpkg\binary_cache' || '/usr/local/share/vcpkg/binary_cache' }}
     steps:
       - uses: actions/checkout@v2
         with:
@@ -373,12 +382,6 @@ jobs:
         shell: cmd
         continue-on-error: true
 
-      - if: ${{ env.isWindows == 'true' }}
-        name: Setup C++ Toolchain Environment on Windows
-        run: |
-          'echo "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.32.31326\bin\Hostx64\x64" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append'
-          'echo "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append'
-
       - if: ${{ env.isWindows == 'true' }}
         name: Install OpenSSL & cURL on Windows
         run: |
@@ -393,10 +396,10 @@ jobs:
         run: ./gradlew clean ${{ env.gradleArgs }}
 
       - name: "Test mirai-core-utils for ${{ matrix.os }}"
-        run: ./gradlew :mirai-core-utils:${{ matrix.testTaskName }} ${{ env.gradleArgs }}
+        run: ./gradlew :mirai-core-utils:${{ matrix.targetName }}Test ${{ env.gradleArgs }}
 
       - name: "Test mirai-core-api for ${{ matrix.os }}"
-        run: ./gradlew :mirai-core-api:${{ matrix.testTaskName }} ${{ env.gradleArgs }}
+        run: ./gradlew :mirai-core-api:${{ matrix.targetName }}Test ${{ env.gradleArgs }}
 
       - name: "Test mirai-core for ${{ matrix.os }}"
-        run: ./gradlew :mirai-core:${{ matrix.testTaskName }} ${{ env.gradleArgs }}
+        run: ./gradlew :mirai-core:${{ matrix.targetName }}Test ${{ env.gradleArgs }}

+ 1 - 1
buildSrc/src/main/kotlin/BinaryCompatibilityConfigurator.kt

@@ -21,7 +21,7 @@ import java.io.File
  */
 
 object BinaryCompatibilityConfigurator {
-    fun Project.configureBinaryValidators(vararg targetNames: String) {
+    fun Project.configureBinaryValidators(targetNames: Set<String>) {
         targetNames.forEach { configureBinaryValidator(it) }
     }
 

+ 93 - 51
buildSrc/src/main/kotlin/HmppConfigure.kt

@@ -30,10 +30,6 @@ private val miraiPlatform = Attribute.of(
 )
 
 val IDEA_ACTIVE = System.getProperty("idea.active") == "true" && System.getProperty("publication.test") != "true"
-val WINDOWS_TARGET_ENABLED = System.getProperty("mirai.windows.target") != "false"
-
-val NATIVE_ENABLED = System.getProperty("mirai.enable.native", "true").toBoolean()
-val ANDROID_ENABLED = System.getProperty("mirai.enable.android", "true").toBoolean()
 
 val OS_NAME = System.getProperty("os.name").toLowerCase()
 
@@ -50,7 +46,7 @@ sealed class HostKind(
     val targetName: String
 ) {
     object LINUX : HostKind("linuxX64")
-    object WINDOWS : HostKind("windowsX64")
+    object WINDOWS : HostKind("mingwX64")
 
     abstract class MACOS(targetName: String) : HostKind(targetName)
 
@@ -68,6 +64,7 @@ val HOST_KIND by lazy {
                 HostKind.MACOS_X64
             }
         }
+
         else -> HostKind.LINUX
     }
 }
@@ -76,8 +73,31 @@ enum class HostArch {
     X86, X64, ARM32, ARM64
 }
 
+/// eg. "!a;!b" means to enable all targets but a or b
+/// eg. "a;b;!other" means to disable all targets but a or b
+val ENABLED_TARGETS by lazy {
+    System.getProperty(
+        "mirai.target",
+        if (IDEA_ACTIVE)
+            "jvm;android;${HOST_KIND.targetName};!other"
+        else
+            ""
+    ).split(';').toSet()
+}
+
+fun isTargetEnabled(name: String): Boolean {
+    return when {
+        name in ENABLED_TARGETS -> true
+        "!$name" in ENABLED_TARGETS -> false
+        else -> "!other" !in ENABLED_TARGETS
+    }
+}
+
+fun Set<String>.filterTargets() =
+    this.filter { isTargetEnabled(it) }.toSet()
+
 val MAC_TARGETS: Set<String> by lazy {
-    if (!IDEA_ACTIVE) setOf(
+    setOf(
 //        "watchosX86",
         "macosX64",
         "macosArm64",
@@ -98,23 +118,22 @@ val MAC_TARGETS: Set<String> by lazy {
 //        "tvosX64",
 //        "tvosArm64",
 //        "tvosSimulatorArm64",
-    ) else setOf(
-        // IDEA active, reduce load
-        HOST_KIND.targetName
-    )
+    ).filterTargets()
 }
 
-val WIN_TARGETS = if (WINDOWS_TARGET_ENABLED) setOf("mingwX64") else emptySet()
+val WIN_TARGETS by lazy { setOf("mingwX64").filterTargets() }
 
-val LINUX_TARGETS = setOf("linuxX64")
+val LINUX_TARGETS by lazy { setOf("linuxX64").filterTargets() }
 
 val UNIX_LIKE_TARGETS by lazy { LINUX_TARGETS + MAC_TARGETS }
 
 val NATIVE_TARGETS by lazy { UNIX_LIKE_TARGETS + WIN_TARGETS }
 
-
 fun Project.configureJvmTargetsHierarchical() {
     extensions.getByType(KotlinMultiplatformExtension::class.java).apply {
+        val commonMain by sourceSets.getting
+        val commonTest by sourceSets.getting
+
         if (IDEA_ACTIVE) {
             jvm("jvmBase") { // dummy target for resolution, not published
                 compilations.all {
@@ -125,19 +144,42 @@ fun Project.configureJvmTargetsHierarchical() {
             }
         }
 
-        if (isAndroidSDKAvailable && ANDROID_ENABLED) {
-            jvm("android") {
-                attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm)
-                if (IDEA_ACTIVE) {
-                    attributes.attribute(miraiPlatform, "android") // avoid resolution
+        val jvmBaseMain by lazy {
+            sourceSets.maybeCreate("jvmBaseMain").apply {
+                dependsOn(commonMain)
+            }
+        }
+        val jvmBaseTest by lazy {
+            sourceSets.maybeCreate("jvmBaseTest").apply {
+                dependsOn(commonTest)
+            }
+        }
+
+        if (isTargetEnabled("android")) {
+            if (isAndroidSDKAvailable) {
+                jvm("android") {
+                    attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm)
+                    if (IDEA_ACTIVE) {
+                        attributes.attribute(miraiPlatform, "android") // avoid resolution
+                    }
                 }
+                val androidMain by sourceSets.getting
+                val androidTest by sourceSets.getting
+                androidMain.dependsOn(jvmBaseMain)
+                androidTest.dependsOn(jvmBaseTest)
+            } else {
+                printAndroidNotInstalled()
             }
-        } else {
-            printAndroidNotInstalled()
         }
 
-        jvm("jvm") {
+        if (isTargetEnabled("jvm")) {
+            jvm("jvm") {
 
+            }
+            val jvmMain by sourceSets.getting
+            val jvmTest by sourceSets.getting
+            jvmMain.dependsOn(jvmBaseMain)
+            jvmTest.dependsOn(jvmBaseTest)
         }
     }
 }
@@ -188,21 +230,39 @@ fun KotlinMultiplatformExtension.configureNativeTargetsHierarchical(
 
     val commonMain by sourceSets.getting
     val commonTest by sourceSets.getting
-    val jvmBaseMain = this.sourceSets.maybeCreate("jvmBaseMain")
-    val jvmBaseTest = this.sourceSets.maybeCreate("jvmBaseTest")
-    val jvmMain by sourceSets.getting
-    val jvmTest by sourceSets.getting
-    val androidMain by sourceSets.getting
-    val androidTest by sourceSets.getting
 
-    val nativeMain = this.sourceSets.maybeCreate("nativeMain")
-    val nativeTest = this.sourceSets.maybeCreate("nativeTest")
+    val nativeMain by lazy {
+        this.sourceSets.maybeCreate("nativeMain").apply {
+            dependsOn(commonMain)
+        }
+    }
+    val nativeTest by lazy {
+        this.sourceSets.maybeCreate("nativeTest").apply {
+            dependsOn(commonTest)
+        }
+    }
 
-    val unixMain = this.sourceSets.maybeCreate("unixMain")
-    val unixTest = this.sourceSets.maybeCreate("unixTest")
+    val unixMain by lazy {
+        this.sourceSets.maybeCreate("unixMain").apply {
+            dependsOn(nativeMain)
+        }
+    }
+    val unixTest by lazy {
+        this.sourceSets.maybeCreate("unixTest").apply {
+            dependsOn(nativeTest)
+        }
+    }
 
-    val darwinMain = this.sourceSets.maybeCreate("darwinMain")
-    val darwinTest = this.sourceSets.maybeCreate("darwinTest")
+    val darwinMain by lazy {
+        this.sourceSets.maybeCreate("darwinMain").apply {
+            dependsOn(unixMain)
+        }
+    }
+    val darwinTest by lazy {
+        this.sourceSets.maybeCreate("darwinTest").apply {
+            dependsOn(unixTest)
+        }
+    }
 
     presets.filter { it.name in MAC_TARGETS }.forEach { preset ->
         addNativeTarget(preset).run {
@@ -293,24 +353,6 @@ fun KotlinMultiplatformExtension.configureNativeTargetsHierarchical(
             }
         }
     }
-
-    jvmBaseMain.dependsOn(commonMain)
-    jvmBaseTest.dependsOn(commonTest)
-
-    nativeMain.dependsOn(commonMain)
-    nativeTest.dependsOn(commonTest)
-
-    unixMain.dependsOn(nativeMain)
-    unixTest.dependsOn(nativeTest)
-
-    darwinMain.dependsOn(unixMain)
-    darwinTest.dependsOn(unixTest)
-
-    jvmMain.dependsOn(jvmBaseMain)
-    jvmTest.dependsOn(jvmBaseTest)
-
-    androidMain.dependsOn(jvmBaseMain)
-    androidTest.dependsOn(jvmBaseTest)
 }
 
 private fun KotlinNativeTarget.findOrCreateTest(buildType: NativeBuildType, configure: TestExecutable.() -> Unit) =

+ 10 - 3
mirai-console/tools/gradle-plugin/src/integTest/kotlin/AbstractTest.kt

@@ -29,14 +29,21 @@ abstract class AbstractTest {
     lateinit var propertiesFile: File
 
 
-    fun gradleRunner(): GradleRunner {
-        println(PluginUnderTestMetadataReading.readImplementationClasspath())
-        return GradleRunner.create()
+    @OptIn(ExperimentalStdlibApi::class)
+    fun runGradle(vararg arguments: String) {
+        System.gc()
+        GradleRunner.create()
             .withProjectDir(tempDir)
             .withPluginClasspath()
             .withGradleVersion("7.2")
             .forwardOutput()
             .withEnvironment(System.getenv())
+            .withArguments(buildList {
+                addAll(arguments)
+                add("-Pkotlin.compiler.execution.strategy=in-process")
+                add("-Dorg.gradle.jvmargs=-Xmx256m -Dfile.encoding=UTF-8")
+            })
+            .build()
     }
 
     @BeforeEach

+ 9 - 27
mirai-console/tools/gradle-plugin/src/integTest/kotlin/TestBuildPlugin.kt

@@ -88,9 +88,7 @@ class TestBuildPlugin : AbstractTest() {
         """.trimIndent()
         )
 
-        gradleRunner()
-            .withArguments(":buildPlugin", "--stacktrace", "--info")
-            .build()
+        runGradle(":buildPlugin", "--stacktrace", "--info")
 
 
         ZipFile(findJar()).use { zipFile ->
@@ -174,9 +172,7 @@ class TestBuildPlugin : AbstractTest() {
         """.trimIndent()
         )
 
-        gradleRunner()
-            .withArguments(":buildPlugin", "--stacktrace", "--info")
-            .build()
+        runGradle(":buildPlugin", "--stacktrace", "--info")
 
 
         ZipFile(findJar()).use { zipFile ->
@@ -242,9 +238,7 @@ class TestBuildPlugin : AbstractTest() {
         """.trimIndent()
         )
 
-        gradleRunner()
-            .withArguments(":buildPlugin", "--stacktrace", "--info")
-            .build()
+        runGradle(":buildPlugin", "--stacktrace", "--info")
 
 
         ZipFile(findJar()).use { zipFile ->
@@ -311,9 +305,7 @@ class TestBuildPlugin : AbstractTest() {
         """.trimIndent()
         )
 
-        gradleRunner()
-            .withArguments(":buildPlugin", "dependencies", "--stacktrace", "--info")
-            .build()
+        runGradle(":buildPlugin", "dependencies", "--stacktrace", "--info")
         checkOutput()
 
         ZipFile(findJar()).use { zipFile ->
@@ -334,9 +326,7 @@ class TestBuildPlugin : AbstractTest() {
             }
         """.trimIndent()
         )
-        gradleRunner()
-            .withArguments("buildPlugin", "dependencies", "--stacktrace", "--info")
-            .build()
+        runGradle("buildPlugin", "dependencies", "--stacktrace", "--info")
         checkOutput()
     }
 
@@ -349,9 +339,7 @@ class TestBuildPlugin : AbstractTest() {
             }
         """.trimIndent()
         )
-        gradleRunner()
-            .withArguments("buildPlugin", "dependencies", "--stacktrace", "--info")
-            .build()
+        runGradle("buildPlugin", "dependencies", "--stacktrace", "--info")
 
         ZipFile(findJar()).use { zipFile ->
 
@@ -372,9 +360,7 @@ class TestBuildPlugin : AbstractTest() {
             }
         """.trimIndent()
         )
-        gradleRunner()
-            .withArguments("buildPlugin", "dependencies", "--stacktrace", "--info")
-            .build()
+        runGradle("buildPlugin", "dependencies", "--stacktrace", "--info")
 
         ZipFile(findJar()).use { zipFile ->
 
@@ -397,9 +383,7 @@ class TestBuildPlugin : AbstractTest() {
             }
         """.trimIndent()
         )
-        gradleRunner()
-            .withArguments("buildPlugin", "dependencies", "--stacktrace", "--info")
-            .build()
+        runGradle("buildPlugin", "dependencies", "--stacktrace", "--info")
 
         ZipFile(findJar()).use { zipFile ->
 
@@ -423,9 +407,7 @@ class TestBuildPlugin : AbstractTest() {
             }
         """.trimIndent()
         )
-        gradleRunner()
-            .withArguments("buildPlugin", "dependencies", "--stacktrace", "--info")
-            .build()
+        runGradle("buildPlugin", "dependencies", "--stacktrace", "--info")
         ZipFile(findJar()).use { zipFile ->
             assertNotNull(zipFile.getEntry("cn/hutool/core/annotation/Alias.class"))
 

+ 1 - 3
mirai-console/tools/gradle-plugin/src/integTest/kotlin/TestPluginApply.kt

@@ -15,8 +15,6 @@ class TestPluginApply : AbstractTest() {
 
     @Test
     fun `can apply plugin`() {
-        gradleRunner()
-            .withArguments("clean", "--stacktrace")
-            .build()
+        runGradle("clean", "--stacktrace")
     }
 }

+ 10 - 12
mirai-core-api/build.gradle.kts

@@ -55,7 +55,7 @@ kotlin {
             }
         }
 
-        val jvmBaseMain by getting {
+        findByName("jvmBaseMain")?.apply {
             dependencies {
                 api(`kotlinx-coroutines-jdk8`) // use -jvm modules for this magic target 'jvmBase'
                 implementation(`jetbrains-annotations`)
@@ -64,34 +64,32 @@ kotlin {
             }
         }
 
-        if (isAndroidSDKAvailable) {
-            val androidMain by getting {
-                dependsOn(commonMain)
-                dependencies {
-                    compileOnly(`android-runtime`)
+        findByName("androidMain")?.apply {
+            dependsOn(commonMain)
+            dependencies {
+                compileOnly(`android-runtime`)
 //                    api(`ktor-client-android`)
-                }
             }
         }
 
-        val jvmMain by getting {
+        findByName("jvmMain")?.apply {
 
         }
 
-        val jvmTest by getting {
+        findByName("jvmTest")?.apply {
             dependencies {
                 runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE
             }
         }
 
-        val nativeMain by getting {
+        findByName("nativeMain")?.apply {
             dependencies {
             }
         }
     }
 }
 
-if (isAndroidSDKAvailable) {
+if (tasks.findByName("androidMainClasses") != null) {
     tasks.register("checkAndroidApiLevel") {
         doFirst {
             analyzes.AndroidApiLevelCheck.check(
@@ -107,4 +105,4 @@ if (isAndroidSDKAvailable) {
 }
 
 configureMppPublishing()
-configureBinaryValidators("jvm", "android")
+configureBinaryValidators(setOf("jvm", "android").filterTargets())

+ 0 - 2
mirai-core-api/src/nativeMain/kotlin/message/data/FileMessage.kt

@@ -12,7 +12,6 @@ package net.mamoe.mirai.message.data
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
-import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
 import net.mamoe.mirai.Mirai
 import net.mamoe.mirai.contact.FileSupported
 import net.mamoe.mirai.contact.file.AbsoluteFile
@@ -42,7 +41,6 @@ import net.mamoe.mirai.utils.safeCast
 @Serializable(FileMessage.Serializer::class)
 @SerialName(FileMessage.SERIAL_NAME)
 @NotStableForInheritance
-@JvmBlockingBridge
 public actual interface FileMessage : MessageContent, ConstrainSingle, CodableMessage {
     /**
      * 服务器需要的某种 ID.

+ 11 - 11
mirai-core-utils/build.gradle.kts

@@ -47,31 +47,31 @@ kotlin {
             }
         }
 
-        val jvmBaseMain by getting {
+        findByName("jvmBaseMain")?.apply {
             dependencies {
                 implementation(`jetbrains-annotations`)
             }
         }
 
-        if (isAndroidSDKAvailable) {
-            val androidMain by getting {
-                //
-                dependencies {
-                    compileOnly(`android-runtime`)
+        findByName("androidMain")?.apply {
+            //
+            dependencies {
+                compileOnly(`android-runtime`)
 //                    api1(`ktor-client-android`)
-                }
             }
         }
 
-        val jvmMain by getting
+        findByName("jvmMain")?.apply {
+
+        }
 
-        val jvmTest by getting {
+        findByName("jvmTest")?.apply {
             dependencies {
                 runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE
             }
         }
 
-        val nativeMain by getting {
+        findByName("nativeMain")?.apply {
             dependencies {
 //                implementation("com.soywiz.korlibs.krypto:krypto:2.4.12") // ':mirai-core-utils:compileNativeMainKotlinMetadata' fails because compiler cannot find reference
             }
@@ -79,7 +79,7 @@ kotlin {
     }
 }
 
-if (isAndroidSDKAvailable) {
+if (tasks.findByName("androidMainClasses") != null) {
     tasks.register("checkAndroidApiLevel") {
         doFirst {
             analyzes.AndroidApiLevelCheck.check(

+ 20 - 22
mirai-core/build.gradle.kts

@@ -55,7 +55,7 @@ kotlin {
             }
         }
 
-        val jvmBaseMain by getting {
+        findByName("jvmBaseMain")?.apply {
             dependencies {
                 implementation(bouncycastle)
                 implementation(`log4j-api`)
@@ -65,45 +65,43 @@ kotlin {
             }
         }
 
-        val jvmBaseTest by getting {
+        findByName("jvmBaseTest")?.apply {
             dependencies {
                 implementation(`kotlinx-coroutines-debug`)
             }
         }
 
-        if (isAndroidSDKAvailable) {
-            val androidMain by getting {
-                dependsOn(commonMain)
-                dependencies {
-                    compileOnly(`android-runtime`)
-                }
+        findByName("androidMain")?.apply {
+            dependsOn(commonMain)
+            dependencies {
+                compileOnly(`android-runtime`)
             }
-            val androidTest by getting {
-                dependencies {
-                    implementation(kotlin("test", Versions.kotlinCompiler))
-                    implementation(kotlin("test-junit5", Versions.kotlinCompiler))
-                    implementation(kotlin("test-annotations-common"))
-                    implementation(kotlin("test-common"))
-                    //implementation("org.bouncycastle:bcprov-jdk15on:1.64")
-                }
+        }
+        findByName("androidTest")?.apply {
+            dependencies {
+                implementation(kotlin("test", Versions.kotlinCompiler))
+                implementation(kotlin("test-junit5", Versions.kotlinCompiler))
+                implementation(kotlin("test-annotations-common"))
+                implementation(kotlin("test-common"))
+                //implementation("org.bouncycastle:bcprov-jdk15on:1.64")
             }
         }
 
-        val jvmMain by getting {
+        findByName("jvmMain")?.apply {
             dependencies {
                 //implementation("org.bouncycastle:bcprov-jdk15on:1.64")
                 // api(kotlinx("coroutines-debug", Versions.coroutines))
             }
         }
 
-        val jvmTest by getting {
+        findByName("jvmTest")?.apply {
             dependencies {
                 api(`kotlinx-coroutines-debug`)
                 //  implementation("net.mamoe:mirai-login-solver-selenium:1.0-dev-14")
             }
         }
 
-        val nativeMain by getting {
+        findByName("nativeMain")?.apply {
             dependencies {
             }
         }
@@ -151,7 +149,7 @@ kotlin {
             }
         }
 
-        val darwinMain by getting {
+        findByName("darwinMain")?.apply {
             dependencies {
                 implementation(`ktor-client-darwin`)
             }
@@ -188,7 +186,7 @@ afterEvaluate {
     }
 }
 
-if (isAndroidSDKAvailable) {
+if (tasks.findByName("androidMainClasses") != null) {
     tasks.register("checkAndroidApiLevel") {
         doFirst {
             analyzes.AndroidApiLevelCheck.check(
@@ -204,4 +202,4 @@ if (isAndroidSDKAvailable) {
 }
 
 configureMppPublishing()
-configureBinaryValidators("jvm", "android")
+configureBinaryValidators(setOf("jvm", "android").filterTargets())

+ 2 - 1
mirai-core/src/commonTest/kotlin/message/data/ContentEqualsTest.kt

@@ -9,6 +9,7 @@
 
 package net.mamoe.mirai.internal.message.data
 
+import net.mamoe.mirai.internal.test.AbstractTest
 import net.mamoe.mirai.message.data.*
 import net.mamoe.mirai.utils.safeCast
 import kotlin.test.Test
@@ -26,7 +27,7 @@ internal class TestConstrainSingleMessage : ConstrainSingle, Any() {
         get() = Key
 }
 
-internal class ContentEqualsTest {
+internal class ContentEqualsTest: AbstractTest() {
 
     @Test
     fun testContentEquals() {

+ 23 - 15
mirai-core/src/nativeMain/kotlin/utils/crypto/ECDH.kt

@@ -15,7 +15,6 @@ import net.mamoe.mirai.utils.md5
 import net.mamoe.mirai.utils.toUHexString
 import openssl.*
 import platform.posix.errno
-import platform.posix.free
 
 private const val curveId = NID_X9_62_prime256v1
 
@@ -27,13 +26,26 @@ private val convForm by lazy { EC_GROUP_get_point_conversion_form(group) }
 // shared, not freed!
 private val bnCtx by lazy { BN_CTX_new() }
 
+// ====ATTENTION====
+// Do not use [platform.posix.free] easily
+// For anything allocated by OpenSSL, <type>_free or CRYPTO_free
+// (the underlying of OPENSSL_free macro) should be called.
+// It's more than dangerous to assume OpenSSL uses the same memory manager as general posix functions,
+// easily causing memory leaking (usually on *nix) or crash (usually on Windows)
 
 internal actual interface ECDHPublicKey : OpenSSLKey {
     val encoded: ByteArray
+
+    /**
+     * @return It is the caller's responsibility to free this memory with a subsequent call to [EC_POINT_free]
+     */
     fun toPoint(): CPointer<EC_POINT>
 }
 
 internal actual interface ECDHPrivateKey : OpenSSLKey {
+    /**
+     * @return It is the caller's responsibility to free this memory with a subsequent call to [BN_free]
+     */
     fun toBignum(): CPointer<BIGNUM>
 }
 
@@ -52,16 +64,14 @@ internal class OpenSslPrivateKey(
 
     companion object {
         fun fromKey(key: CPointer<EC_KEY>): OpenSslPrivateKey {
+            // Note that the private key (bignum) is associated with the key
+            // We can't free it, or it'll crash when EC_KEY_free
             val bn = EC_KEY_get0_private_key(key) ?: error("Failed EC_KEY_get0_private_key")
+            val ptr = BN_bn2hex(bn) ?: error("Failed EC_POINT_bn2point")
             val hex = try {
-                val ptr = BN_bn2hex(bn) ?: error("Failed EC_POINT_bn2point")
-                try {
-                    ptr.toKString()
-                } finally {
-                    free(ptr)
-                }
+                ptr.toKString()
             } finally {
-                BN_free(bn)
+                CRYPTO_free(ptr, "OpenSslPrivateKey.Companion.fromKey(key: CPointer<EC_KEY>)", -1)
             }
             return OpenSslPrivateKey(hex)
         }
@@ -115,7 +125,7 @@ private fun CPointer<EC_POINT>.toKtHex(): String {
     return try {
         ptr.toKString()
     } finally {
-        free(ptr)
+        CRYPTO_free(ptr, "CPointer<EC_POINT>.toKtHex()", -1)
     }
 }
 
@@ -159,15 +169,13 @@ internal actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
         actual fun generateKeyPair(initialPublicKey: ECDHPublicKey): ECDHKeyPair {
             val key: CPointer<EC_KEY> = EC_KEY_new_by_curve_name(curveId)
                 ?: throw IllegalStateException("Failed to create key curve, $errno")
-
-            if (1 != EC_KEY_generate_key(key)) {
-                throw IllegalStateException("Failed to generate key, $errno")
-            }
-
             try {
+                if (1 != EC_KEY_generate_key(key)) {
+                    throw IllegalStateException("Failed to generate key, $errno")
+                }
                 return ECDHKeyPairImpl.fromKey(key, initialPublicKey)
             } finally {
-                free(key) // TODO: THIS MAY CAUSE MEMORY LEAK. But EC_KEY_free() will terminate the process for unknown reason.
+                EC_KEY_free(key)
             }
         }