diff --git a/build.gradle.kts b/build.gradle.kts index e8a131899..5ea90bab6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,11 +11,8 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.jetbrains.dokka.gradle.DokkaTask -import org.jetbrains.kotlin.gradle.dsl.* -import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType -import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget -import org.jetbrains.kotlin.utils.addToStdlib.safeAs buildscript { repositories { @@ -36,7 +33,7 @@ buildscript { } plugins { - kotlin("jvm") version Versions.kotlinCompiler + kotlin("jvm") // version Versions.kotlinCompiler kotlin("plugin.serialization") version Versions.kotlinCompiler id("org.jetbrains.dokka") version Versions.dokka id("net.mamoe.kotlin-jvm-blocking-bridge") version Versions.blockingBridge @@ -63,8 +60,6 @@ configure { nonPublicMarkers.add("net.mamoe.mirai.MiraiExperimentalApi") } -project.ext.set("isAndroidSDKAvailable", false) - tasks.register("publishMiraiCoreArtifactsToMavenLocal") { group = "mirai" dependsOn( @@ -74,24 +69,6 @@ tasks.register("publishMiraiCoreArtifactsToMavenLocal") { ) } -// until -// https://youtrack.jetbrains.com/issue/KT-37152, -// are fixed. - -/* -runCatching { - val keyProps = Properties().apply { - file("local.properties").takeIf { it.exists() }?.inputStream()?.use { load(it) } - } - if (keyProps.getProperty("sdk.dir", "").isNotEmpty()) { - project.ext.set("isAndroidSDKAvailable", true) - } else { - project.ext.set("isAndroidSDKAvailable", false) - } -}.exceptionOrNull()?.run { - project.ext.set("isAndroidSDKAvailable", false) -}*/ - allprojects { group = "net.mamoe" version = Versions.project @@ -136,12 +113,6 @@ subprojects { } } -fun Project.useIr() { - kotlinCompilations?.forEach { kotlinCompilation -> - kotlinCompilation.kotlinOptions.freeCompilerArgs += "-Xuse-ir" - } -} - fun Project.configureDokka() { apply(plugin = "org.jetbrains.dokka") tasks { @@ -175,169 +146,44 @@ fun Project.configureDokka() { } } -@Suppress("NOTHING_TO_INLINE") // or error -fun Project.configureJvmTarget() { - tasks.withType(KotlinJvmCompile::class.java) { - kotlinOptions.jvmTarget = "1.8" - } - - kotlinTargets.orEmpty().filterIsInstance().forEach { target -> - target.compilations.all { - kotlinOptions.jvmTarget = "1.8" - kotlinOptions.languageVersion = "1.4" - } - target.testRuns["test"].executionTask.configure { useJUnitPlatform() } - } - - extensions.findByType(JavaPluginExtension::class.java)?.run { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } -} - fun Project.configureMppShadow() { val kotlin = runCatching { + (this as ExtensionAware).extensions.getByName("kotlin") as? KotlinMultiplatformExtension }.getOrNull() ?: return - val shadowJvmJar by tasks.creating(ShadowJar::class) sd@{ - group = "mirai" - archiveClassifier.set("-all") + if (project.configurations.findByName("jvmRuntimeClasspath") != null) { + val shadowJvmJar by tasks.creating(ShadowJar::class) sd@{ + group = "mirai" + archiveClassifier.set("-all") - val compilations = - kotlin.targets.filter { it.platformType == KotlinPlatformType.jvm } - .map { it.compilations["main"] } + val compilations = + kotlin.targets.filter { it.platformType == KotlinPlatformType.jvm } + .map { it.compilations["main"] } - compilations.forEach { - dependsOn(it.compileKotlinTask) - from(it.output) - } - - from(project.configurations.getByName("jvmRuntimeClasspath")) - - this.exclude { file -> - file.name.endsWith(".sf", ignoreCase = true) - } - - /* - this.manifest { - this.attributes( - "Manifest-Version" to 1, - "Implementation-Vendor" to "Mamoe Technologies", - "Implementation-Title" to this.name.toString(), - "Implementation-Version" to this.version.toString() - ) - }*/ - } -} - -fun Project.configureEncoding() { - tasks.withType(JavaCompile::class.java) { - options.encoding = "UTF8" - } -} - -fun Project.configureKotlinTestSettings() { - tasks.withType(Test::class) { - useJUnitPlatform() - } - when { - isKotlinJvmProject -> { - dependencies { - testImplementation(kotlin("test-junit5")) - - testApi("org.junit.jupiter:junit-jupiter-api:5.2.0") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0") + compilations.forEach { + dependsOn(it.compileKotlinTask) + from(it.output) } - } - isKotlinMpp -> { - kotlinSourceSets?.forEach { sourceSet -> - if (sourceSet.name == "common") { - sourceSet.dependencies { - implementation(kotlin("test")) - implementation(kotlin("test-annotations-common")) - } - } else { - sourceSet.dependencies { - implementation(kotlin("test-junit5")) - implementation("org.junit.jupiter:junit-jupiter-api:5.2.0") - implementation("org.junit.jupiter:junit-jupiter-engine:5.2.0") - } - } + from(project.configurations.findByName("jvmRuntimeClasspath")) + + this.exclude { file -> + file.name.endsWith(".sf", ignoreCase = true) } + + /* + this.manifest { + this.attributes( + "Manifest-Version" to 1, + "Implementation-Vendor" to "Mamoe Technologies", + "Implementation-Title" to this.name.toString(), + "Implementation-Version" to this.version.toString() + ) + }*/ } + } + } - -fun Project.configureKotlinCompilerSettings() { - val kotlinCompilations = kotlinCompilations ?: return - for (kotlinCompilation in kotlinCompilations) with(kotlinCompilation) { - if (isKotlinJvmProject) { - @Suppress("UNCHECKED_CAST") - this as KotlinCompilation - } - kotlinOptions.freeCompilerArgs += "-Xjvm-default=all" - } -} - -val experimentalAnnotations = arrayOf( - "kotlin.RequiresOptIn", - "kotlin.contracts.ExperimentalContracts", - "kotlin.experimental.ExperimentalTypeInference", - "kotlin.ExperimentalUnsignedTypes", - "kotlin.time.ExperimentalTime", - "kotlin.io.path.ExperimentalPathApi", - "io.ktor.util.KtorExperimentalAPI", - - "kotlinx.serialization.ExperimentalSerializationApi", - - "net.mamoe.mirai.utils.MiraiInternalApi", - "net.mamoe.mirai.utils.MiraiExperimentalApi", - "net.mamoe.mirai.LowLevelApi", - "net.mamoe.mirai.utils.UnstableExternalImage", - - "net.mamoe.mirai.message.data.ExperimentalMessageKey", - "net.mamoe.mirai.console.ConsoleFrontEndImplementation", - "net.mamoe.mirai.console.util.ConsoleInternalApi", - "net.mamoe.mirai.console.util.ConsoleExperimentalApi" -) - -fun Project.configureKotlinExperimentalUsages() { - val sourceSets = kotlinSourceSets ?: return - - for (target in sourceSets) { - target.languageSettings.progressiveMode = true - target.languageSettings.enableLanguageFeature("InlineClasses") - experimentalAnnotations.forEach { a -> - target.languageSettings.useExperimentalAnnotation(a) - } - } -} - -fun Project.configureFlattenSourceSets() { - sourceSets { - findByName("main")?.apply { - resources.setSrcDirs(listOf(projectDir.resolve("resources"))) - java.setSrcDirs(listOf(projectDir.resolve("src"))) - } - findByName("test")?.apply { - resources.setSrcDirs(listOf(projectDir.resolve("resources"))) - java.setSrcDirs(listOf(projectDir.resolve("test"))) - } - } -} - -val Project.kotlinSourceSets get() = extensions.findByName("kotlin").safeAs()?.sourceSets - -val Project.kotlinTargets - get() = - extensions.findByName("kotlin").safeAs()?.target?.let { listOf(it) } - ?: extensions.findByName("kotlin").safeAs()?.targets - -val Project.isKotlinJvmProject: Boolean get() = extensions.findByName("kotlin") is KotlinJvmProjectExtension -val Project.isKotlinMpp: Boolean get() = extensions.findByName("kotlin") is KotlinMultiplatformExtension - -val Project.kotlinCompilations - get() = kotlinTargets?.flatMap { it.compilations } \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 7591340b9..baabeeb28 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -14,6 +14,8 @@ plugins { repositories { mavenLocal() jcenter() + google() + mavenCentral() } kotlin { @@ -46,6 +48,9 @@ dependencies { api("com.jfrog.bintray.gradle", "gradle-bintray-plugin", version("bintray")) api("com.github.jengelman.gradle.plugins", "shadow", version("shadow")) + api("org.jetbrains.kotlin", "kotlin-gradle-plugin", version("kotlinCompiler")) + api("org.jetbrains.kotlin", "kotlin-compiler-embeddable", version("kotlinCompiler")) + api("com.android.tools.build", "gradle", version("androidGradlePlugin")) api(gradleApi()) } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Mpp.kt b/buildSrc/src/main/kotlin/Mpp.kt index f6387808a..d07cb08e2 100644 --- a/buildSrc/src/main/kotlin/Mpp.kt +++ b/buildSrc/src/main/kotlin/Mpp.kt @@ -10,7 +10,7 @@ import org.gradle.api.NamedDomainObjectCollection import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Project -import org.gradle.kotlin.dsl.provideDelegate +import java.util.* /* * Copyright 2020 Mamoe Technologies and contributors. @@ -21,11 +21,31 @@ import org.gradle.kotlin.dsl.provideDelegate * https://github.com/mamoe/mirai/blob/master/LICENSE */ -val Project.isAndroidSDKAvailable: Boolean - get() { - val isAndroidSDKAvailable: Boolean by this - return isAndroidSDKAvailable +private object ProjectAndroidSdkAvailability { + val map: MutableMap = mutableMapOf() + + @Synchronized + operator fun get(project: Project): Boolean { + if (map[project.path] != null) return map[project.path]!! + + val projectAvailable = project.runCatching { + val keyProps = Properties().apply { + file("local.properties").takeIf { it.exists() }?.inputStream()?.use { load(it) } + } + keyProps.getProperty("sdk.dir", "").isNotEmpty() + }.getOrElse { false } + + + fun impl(): Boolean { + if (project === project.rootProject) return projectAvailable + return projectAvailable || get(project.rootProject) + } + map[project.path] = impl() + return map[project.path]!! } +} + +val Project.isAndroidSDKAvailable: Boolean get() = ProjectAndroidSdkAvailability[this] val NamedDomainObjectCollection.androidMain: NamedDomainObjectProvider get() = named("androidMain") @@ -43,16 +63,12 @@ val NamedDomainObjectCollection.commonMain: NamedDomainObjectProvider get() = named("commonMain") fun Project.printAndroidNotInstalled() { -// println( -// """Android SDK 可能未安装. -// $name 的 Android 目标编译将不会进行. -// 这不会影响 Android 以外的平台的编译. -// """.trimIndent() -// ) -// println( -// """Android SDK might not be installed. -// Android target of $name will not be compiled. -// It does no influence on the compilation of other platforms. -// """.trimIndent() -// ) + println( + """Android SDK 可能未安装. $name 的 Android 目标编译将不会进行. 这不会影响 Android 以外的平台的编译. + """.trimIndent() + ) + println( + """Android SDK might not be installed. Android target of $name will not be compiled. It does no influence on the compilation of other platforms. + """.trimIndent() + ) } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/ProjectConfigure.kt b/buildSrc/src/main/kotlin/ProjectConfigure.kt new file mode 100644 index 000000000..22349672b --- /dev/null +++ b/buildSrc/src/main/kotlin/ProjectConfigure.kt @@ -0,0 +1,157 @@ +@file:Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") + +import org.gradle.api.JavaVersion +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.api.tasks.testing.Test +import org.gradle.kotlin.dsl.* +import org.jetbrains.kotlin.gradle.dsl.* +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget + + +fun Project.useIr() { + kotlinCompilations?.forEach { kotlinCompilation -> + kotlinCompilation.kotlinOptions.freeCompilerArgs += "-Xuse-ir" + } +} + +@Suppress("NOTHING_TO_INLINE") // or error +fun Project.configureJvmTarget() { + tasks.withType(KotlinJvmCompile::class.java) { + kotlinOptions.jvmTarget = "1.8" + } + + kotlinTargets.orEmpty().filterIsInstance().forEach { target -> + target.compilations.all { + kotlinOptions.jvmTarget = "1.8" + kotlinOptions.languageVersion = "1.4" + } + target.testRuns["test"].executionTask.configure { useJUnitPlatform() } + } + + extensions.findByType(JavaPluginExtension::class.java)?.run { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +fun Project.configureEncoding() { + tasks.withType(JavaCompile::class.java) { + options.encoding = "UTF8" + } +} + +fun Project.configureKotlinTestSettings() { + tasks.withType(Test::class) { + useJUnitPlatform() + } + when { + isKotlinJvmProject -> { + dependencies { + "testImplementation"(kotlin("test-junit5")) + + "testApi"("org.junit.jupiter:junit-jupiter-api:5.2.0") + "testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:5.2.0") + } + } + isKotlinMpp -> { + kotlinSourceSets?.forEach { sourceSet -> + if (sourceSet.name == "common") { + sourceSet.dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-annotations-common")) + } + } else { + sourceSet.dependencies { + implementation(kotlin("test-junit5")) + + implementation("org.junit.jupiter:junit-jupiter-api:5.2.0") + implementation("org.junit.jupiter:junit-jupiter-engine:5.2.0") + } + } + } + } + } +} + +fun Project.configureKotlinCompilerSettings() { + val kotlinCompilations = kotlinCompilations ?: return + for (kotlinCompilation in kotlinCompilations) with(kotlinCompilation) { + if (isKotlinJvmProject) { + @Suppress("UNCHECKED_CAST") + this as KotlinCompilation + } + kotlinOptions.freeCompilerArgs += "-Xjvm-default=all" + } +} + +val experimentalAnnotations = arrayOf( + "kotlin.RequiresOptIn", + "kotlin.contracts.ExperimentalContracts", + "kotlin.experimental.ExperimentalTypeInference", + "kotlin.ExperimentalUnsignedTypes", + "kotlin.time.ExperimentalTime", + "kotlin.io.path.ExperimentalPathApi", + "io.ktor.util.KtorExperimentalAPI", + + "kotlinx.serialization.ExperimentalSerializationApi", + + "net.mamoe.mirai.utils.MiraiInternalApi", + "net.mamoe.mirai.utils.MiraiExperimentalApi", + "net.mamoe.mirai.LowLevelApi", + "net.mamoe.mirai.utils.UnstableExternalImage", + + "net.mamoe.mirai.message.data.ExperimentalMessageKey", + "net.mamoe.mirai.console.ConsoleFrontEndImplementation", + "net.mamoe.mirai.console.util.ConsoleInternalApi", + "net.mamoe.mirai.console.util.ConsoleExperimentalApi" +) + +fun Project.configureKotlinExperimentalUsages() { + val sourceSets = kotlinSourceSets ?: return + + for (target in sourceSets) { + target.configureKotlinExperimentalUsages() + } +} + +fun KotlinSourceSet.configureKotlinExperimentalUsages() { + languageSettings.progressiveMode = true + languageSettings.enableLanguageFeature("InlineClasses") + experimentalAnnotations.forEach { a -> + languageSettings.useExperimentalAnnotation(a) + } +} + +fun Project.configureFlattenSourceSets() { + sourceSets { + findByName("main")?.apply { + resources.setSrcDirs(listOf(projectDir.resolve("resources"))) + java.setSrcDirs(listOf(projectDir.resolve("src"))) + } + findByName("test")?.apply { + resources.setSrcDirs(listOf(projectDir.resolve("resources"))) + java.setSrcDirs(listOf(projectDir.resolve("test"))) + } + } +} + +inline fun Any?.safeAs(): T? { + return this as? T +} + +val Project.kotlinSourceSets get() = extensions.findByName("kotlin").safeAs()?.sourceSets + +val Project.kotlinTargets + get() = + extensions.findByName("kotlin").safeAs()?.target?.let { listOf(it) } + ?: extensions.findByName("kotlin").safeAs()?.targets + +val Project.isKotlinJvmProject: Boolean get() = extensions.findByName("kotlin") is KotlinJvmProjectExtension +val Project.isKotlinMpp: Boolean get() = extensions.findByName("kotlin") is KotlinMultiplatformExtension + +val Project.kotlinCompilations + get() = kotlinTargets?.flatMap { it.compilations } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 600e30066..0fb6cfe23 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -34,7 +34,8 @@ object Versions { const val blockingBridge = "1.7.4" - const val androidGradlePlugin = "3.5.3" + const val androidGradlePlugin = "4.1.1" + const val android = "4.1.1.4" const val bintray = "1.8.5" const val shadow = "6.1.0" @@ -104,3 +105,5 @@ const val `jetbrains-annotations` = "org.jetbrains:annotations:19.0.0" const val `caller-finder` = "io.github.karlatemp:caller:1.0.1" + +const val `android-runtime` = "com.google.android:android:${Versions.android}" \ No newline at end of file diff --git a/mirai-console b/mirai-console index a5481accb..3f98d8ec2 160000 --- a/mirai-console +++ b/mirai-console @@ -1 +1 @@ -Subproject commit a5481accb5f882d121ff9fc1d55e4e5f3e908e76 +Subproject commit 3f98d8ec2abfa963c5f720f32b7b27e863569bc8 diff --git a/mirai-core-all/build.gradle.kts b/mirai-core-all/build.gradle.kts index b5287d129..28bcd31b5 100644 --- a/mirai-core-all/build.gradle.kts +++ b/mirai-core-all/build.gradle.kts @@ -12,7 +12,6 @@ plugins { kotlin("jvm") kotlin("plugin.serialization") - id("java") `maven-publish` id("com.jfrog.bintray") id("net.mamoe.kotlin-jvm-blocking-bridge") diff --git a/mirai-core-api/build.gradle.kts b/mirai-core-api/build.gradle.kts index d37b18696..7312d06bb 100644 --- a/mirai-core-api/build.gradle.kts +++ b/mirai-core-api/build.gradle.kts @@ -28,9 +28,13 @@ kotlin { explicitApi() if (isAndroidSDKAvailable) { - apply(from = rootProject.file("gradle/android.gradle")) - android("android") { - publishAllLibraryVariants() +// apply(from = rootProject.file("gradle/android.gradle")) +// android("android") { +// publishAllLibraryVariants() +// } + jvm("android") { + attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm) + // publishAllLibraryVariants() } } else { printAndroidNotInstalled() @@ -47,7 +51,7 @@ kotlin { // } sourceSets { - commonMain { + val commonMain by getting { dependencies { implementation(project(":mirai-core-utils")) api(kotlin("serialization")) @@ -75,16 +79,20 @@ kotlin { } if (isAndroidSDKAvailable) { - androidMain { + val androidMain by getting { + dependsOn(commonMain) dependencies { + compileOnly(`android-runtime`) api1(`ktor-client-android`) } } } - val jvmMain by getting + val jvmMain by getting { - jvmTest { + } + + val jvmTest by getting { dependencies { runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE } diff --git a/mirai-core-api/src/androidMain/kotlin/findMiraiInstance.android.kt b/mirai-core-api/src/androidMain/kotlin/findMiraiInstance.android.kt new file mode 100644 index 000000000..0be31c5db --- /dev/null +++ b/mirai-core-api/src/androidMain/kotlin/findMiraiInstance.android.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2019-2020 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/master/LICENSE + */ + +package net.mamoe.mirai + +import java.util.* +import kotlin.reflect.full.companionObjectInstance + +@JvmSynthetic +internal actual fun findMiraiInstance(): IMirai { + return ServiceLoader.load(IMirai::class.java).firstOrNull() + ?: Class.forName("net.mamoe.mirai.internal.MiraiImpl").kotlin.companionObjectInstance as IMirai +} \ No newline at end of file diff --git a/mirai-core-api/src/androidMain/kotlin/utils/PlatformLogger.android.kt b/mirai-core-api/src/androidMain/kotlin/utils/PlatformLogger.android.kt new file mode 100644 index 000000000..6d54876d9 --- /dev/null +++ b/mirai-core-api/src/androidMain/kotlin/utils/PlatformLogger.android.kt @@ -0,0 +1,138 @@ +/* + * Copyright 2019-2020 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/master/LICENSE + */ + +@file:Suppress("MemberVisibilityCanBePrivate") + +package net.mamoe.mirai.utils + +import android.util.Log +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.* + +/** + * JVM 控制台日志实现 + * + * + * 单条日志格式 (正则) 为: + * ```regex + * ^([\w-]*\s[\w:]*)\s(\w)\/(.*?):\s(.+)$ + * ``` + * 其中 group 分别为: 日期与时间, 严重程度, [identity], 消息内容. + * + * 示例: + * ```log + * 2020-05-21 19:51:09 V/Bot 1994701021: Send: OidbSvc.0x88d_7 + * ``` + * + * 日期时间格式为 `yyyy-MM-dd HH:mm:ss`, + * + * 严重程度为 V, I, W, E. 分别对应 verbose, info, warning, error + * + * @param isColored 是否添加 ANSI 颜色 + * + * @see MiraiLogger.create + * @see SingleFileLogger 使用单一文件记录日志 + * @see DirectoryLogger 在一个目录中按日期存放文件记录日志, 自动清理过期日志 + */ +@MiraiInternalApi +public actual open class PlatformLogger constructor( + public override val identity: String? = "Mirai", + /** + * 日志输出. 不会自动添加换行 + */ + public open val output: (String) -> Unit, + public val isColored: Boolean = true +) : MiraiLoggerPlatformBase() { + public actual constructor(identity: String?) : this(identity, ::println) + public actual constructor(identity: String?, output: (String) -> Unit) : this(identity, output, true) + + /** + * 输出一条日志. [message] 末尾可能不带换行符. + */ + protected open fun printLog(message: String?, priority: SimpleLogger.LogPriority) { + Log.e(identity, message) + if (isColored) output("${priority.color}$currentTimeFormatted ${priority.simpleName}/$identity: $message${Color.RESET}") + else output("$currentTimeFormatted ${priority.simpleName}/$identity: $message") + } + + /** + * 获取指定 [SimpleLogger.LogPriority] 的颜色 + */ + protected open val SimpleLogger.LogPriority.color: Color + get() = when (this) { + SimpleLogger.LogPriority.VERBOSE -> Color.RESET + SimpleLogger.LogPriority.INFO -> Color.LIGHT_GREEN + SimpleLogger.LogPriority.WARNING -> Color.LIGHT_RED + SimpleLogger.LogPriority.ERROR -> Color.RED + SimpleLogger.LogPriority.DEBUG -> Color.LIGHT_CYAN + } + + public override fun verbose0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.VERBOSE) + + public override fun verbose0(message: String?, e: Throwable?) { + if (e != null) verbose((message ?: e.toString()) + "\n${e.stackTraceString}") + else verbose(message.toString()) + } + + public override fun info0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.INFO) + public override fun info0(message: String?, e: Throwable?) { + if (e != null) info((message ?: e.toString()) + "\n${e.stackTraceString}") + else info(message.toString()) + } + + public override fun warning0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.WARNING) + public override fun warning0(message: String?, e: Throwable?) { + if (e != null) warning((message ?: e.toString()) + "\n${e.stackTraceString}") + else warning(message.toString()) + } + + public override fun error0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.ERROR) + public override fun error0(message: String?, e: Throwable?) { + if (e != null) error((message ?: e.toString()) + "\n${e.stackTraceString}") + else error(message.toString()) + } + + public override fun debug0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.DEBUG) + public override fun debug0(message: String?, e: Throwable?) { + if (e != null) debug((message ?: e.toString()) + "\n${e.stackTraceString}") + else debug(message.toString()) + } + protected open val timeFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.SIMPLIFIED_CHINESE) + + private val currentTimeFormatted get() = timeFormat.format(Date()) + + @MiraiExperimentalApi("This is subject to change.") + protected enum class Color(private val format: String) { + RESET("\u001b[0m"), + + WHITE("\u001b[30m"), + RED("\u001b[31m"), + EMERALD_GREEN("\u001b[32m"), + GOLD("\u001b[33m"), + BLUE("\u001b[34m"), + PURPLE("\u001b[35m"), + GREEN("\u001b[36m"), + + GRAY("\u001b[90m"), + LIGHT_RED("\u001b[91m"), + LIGHT_GREEN("\u001b[92m"), + LIGHT_YELLOW("\u001b[93m"), + LIGHT_BLUE("\u001b[94m"), + LIGHT_PURPLE("\u001b[95m"), + LIGHT_CYAN("\u001b[96m") + ; + + override fun toString(): String = format + } +} + +@get:JvmSynthetic +internal val Throwable.stackTraceString + get() = this.stackTraceToString() diff --git a/mirai-core-utils/build.gradle.kts b/mirai-core-utils/build.gradle.kts index 4428bf917..3a1cfbee9 100644 --- a/mirai-core-utils/build.gradle.kts +++ b/mirai-core-utils/build.gradle.kts @@ -23,29 +23,20 @@ plugins { description = "mirai-core utilities" -val isAndroidSDKAvailable: Boolean by project - kotlin { explicitApi() if (isAndroidSDKAvailable) { - apply(from = rootProject.file("gradle/android.gradle")) - android("android") { - publishAllLibraryVariants() +// apply(from = rootProject.file("gradle/android.gradle")) +// android("android") { +// publishAllLibraryVariants() +// } + jvm("android") { + attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm) + // publishAllLibraryVariants() } } else { - println( - """Android SDK 可能未安装. - $name 的 Android 目标编译将不会进行. - 这不会影响 Android 以外的平台的编译. - """.trimIndent() - ) - println( - """Android SDK might not be installed. - Android target of $name will not be compiled. - It does no influence on the compilation of other platforms. - """.trimIndent() - ) + printAndroidNotInstalled() } jvm("common") { @@ -79,8 +70,10 @@ kotlin { } if (isAndroidSDKAvailable) { - androidMain { + val androidMain by getting { + // dependencies { + compileOnly(`android-runtime`) api1(`ktor-client-android`) } } diff --git a/mirai-core-utils/src/main/AndroidManifest.xml b/mirai-core-utils/src/main/AndroidManifest.xml new file mode 100644 index 000000000..decfa82ab --- /dev/null +++ b/mirai-core-utils/src/main/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/mirai-core/build.gradle.kts b/mirai-core/build.gradle.kts index 23d7498d9..ef3d58ce4 100644 --- a/mirai-core/build.gradle.kts +++ b/mirai-core/build.gradle.kts @@ -18,13 +18,10 @@ plugins { id("net.mamoe.kotlin-jvm-blocking-bridge") `maven-publish` id("com.jfrog.bintray") - java } description = "Mirai Protocol implementation for QQ Android" -val isAndroidSDKAvailable: Boolean by project - afterEvaluate { tasks.getByName("compileKotlinCommon").enabled = false tasks.getByName("compileTestKotlinCommon").enabled = false @@ -37,9 +34,13 @@ kotlin { explicitApi() if (isAndroidSDKAvailable) { - apply(from = rootProject.file("gradle/android.gradle")) - android("android") { - publishAllLibraryVariants() +// apply(from = rootProject.file("gradle/android.gradle")) +// android("android") { +// publishAllLibraryVariants() +// } + jvm("android") { + attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm) + // publishAllLibraryVariants() } } else { printAndroidNotInstalled() @@ -58,7 +59,7 @@ kotlin { sourceSets.apply { - commonMain { + val commonMain by getting { dependencies { api(project(":mirai-core-api")) implementation(project(":mirai-core-utils")) @@ -81,12 +82,13 @@ kotlin { } if (isAndroidSDKAvailable) { - androidMain { + val androidMain by getting { + dependsOn(commonMain) dependencies { + compileOnly(`android-runtime`) } } - - androidTest { + val androidTest by getting { dependencies { implementation(kotlin("test", Versions.kotlinCompiler)) implementation(kotlin("test-junit", Versions.kotlinCompiler)) @@ -96,17 +98,17 @@ kotlin { } } - jvmMain { + val jvmMain by getting { dependencies { implementation("org.bouncycastle:bcprov-jdk15on:1.64") // api(kotlinx("coroutines-debug", Versions.coroutines)) } } - jvmTest { + val jvmTest by getting { dependencies { implementation("org.pcap4j:pcap4j-distribution:1.8.2") - // implementation("net.mamoe:mirai-login-solver-selenium:1.0-dev-14") + // implementation("net.mamoe:mirai-login-solver-selenium:1.0-dev-14") } } } diff --git a/mirai-core/src/androidMain/kotlin/utils/crypto/ECDHJvmDesktop.kt b/mirai-core/src/androidMain/kotlin/utils/crypto/ECDHJvmDesktop.kt new file mode 100644 index 000000000..7f2e2ffe5 --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/utils/crypto/ECDHJvmDesktop.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2019-2021 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/master/LICENSE + */ + +package net.mamoe.mirai.internal.utils.crypto + +import net.mamoe.mirai.utils.md5 +import java.security.* +import java.security.spec.ECGenParameterSpec +import java.security.spec.X509EncodedKeySpec +import javax.crypto.KeyAgreement + + +@Suppress("ACTUAL_WITHOUT_EXPECT") +internal actual typealias ECDHPrivateKey = PrivateKey +@Suppress("ACTUAL_WITHOUT_EXPECT") +internal actual typealias ECDHPublicKey = PublicKey + +internal actual class ECDHKeyPairImpl( + private val delegate: KeyPair +) : ECDHKeyPair { + override val privateKey: ECDHPrivateKey get() = delegate.private + override val publicKey: ECDHPublicKey get() = delegate.public + + override val initialShareKey: ByteArray by lazy { ECDH.calculateShareKey(privateKey, initialPublicKey) } +} + +internal actual fun ECDH() = ECDH(ECDH.generateKeyPair()) + +internal actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) { + actual companion object { + private const val curveName = "secp192k1" // p-256 + + actual val isECDHAvailable: Boolean + + init { + isECDHAvailable = kotlin.runCatching { + fun testECDH() { + ECDHKeyPairImpl( + KeyPairGenerator.getInstance("ECDH") + .also { it.initialize(ECGenParameterSpec(curveName)) } + .genKeyPair()).let { + calculateShareKey(it.privateKey, it.publicKey) + } + } + + if (kotlin.runCatching { testECDH() }.isSuccess) { + return@runCatching + } + + testECDH() + }.onFailure { + it.printStackTrace() + }.isSuccess + } + + actual fun generateKeyPair(): ECDHKeyPair { + if (!isECDHAvailable) { + return ECDHKeyPair.DefaultStub + } + return ECDHKeyPairImpl( + KeyPairGenerator.getInstance("ECDH") + .also { it.initialize(ECGenParameterSpec(curveName)) } + .genKeyPair()) + } + + actual fun calculateShareKey( + privateKey: ECDHPrivateKey, + publicKey: ECDHPublicKey + ): ByteArray { + val instance = KeyAgreement.getInstance("ECDH", "BC") + instance.init(privateKey) + instance.doPhase(publicKey, true) + return instance.generateSecret().md5() + } + + actual fun constructPublicKey(key: ByteArray): ECDHPublicKey { + return KeyFactory.getInstance("EC", "BC").generatePublic(X509EncodedKeySpec(key)) + } + } + + actual fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray { + return calculateShareKey(keyPair.privateKey, peerPublicKey) + } + + actual override fun toString(): String { + return "ECDH(keyPair=$keyPair)" + } +} \ No newline at end of file