From 01418845f31c37c75d4e544dd060f7082c7b6a70 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Fri, 6 Nov 2020 13:41:46 +0800 Subject: [PATCH 01/52] Ansi support --- backend/mirai-console/src/MiraiConsole.kt | 13 ++ .../src/MiraiConsoleImplementation.kt | 7 + .../MiraiConsoleImplementationBridge.kt | 1 + .../src/util/AnsiMessageBuilder.kt | 165 ++++++++++++++++++ .../src/MiraiConsoleImplementationTerminal.kt | 3 +- .../src/MiraiConsoleTerminalLoader.kt | 2 +- 6 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 backend/mirai-console/src/util/AnsiMessageBuilder.kt diff --git a/backend/mirai-console/src/MiraiConsole.kt b/backend/mirai-console/src/MiraiConsole.kt index 2211eff30..2ff09ed6f 100644 --- a/backend/mirai-console/src/MiraiConsole.kt +++ b/backend/mirai-console/src/MiraiConsole.kt @@ -24,6 +24,7 @@ import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.center.PluginCenter import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader +import net.mamoe.mirai.console.util.AnsiMessageBuilder import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext @@ -151,6 +152,18 @@ public interface MiraiConsole : CoroutineScope { public val isActive: Boolean get() = job.isActive } + + + /** + * 该前端是否支持使用 Ansi 输出彩色信息 + * + * 注: 不是每个前端都可能提供 `org.fusesource.jansi:jansi` 库支持, + * 请不要直接使用 `org.fusesource.jansi:jansi` + * + * @see [AnsiMessageBuilder] + */ + @ConsoleExperimentalApi + public val isAnsiSupport: Boolean } /** diff --git a/backend/mirai-console/src/MiraiConsoleImplementation.kt b/backend/mirai-console/src/MiraiConsoleImplementation.kt index 7aa2f239f..d0b0c8c10 100644 --- a/backend/mirai-console/src/MiraiConsoleImplementation.kt +++ b/backend/mirai-console/src/MiraiConsoleImplementation.kt @@ -170,6 +170,13 @@ public interface MiraiConsoleImplementation : CoroutineScope { */ public fun createLogger(identity: String?): MiraiLogger + /** + * 该前端是否支持使用 Ansi 输出彩色信息 + * + * 注: 若为 `true`, 建议携带 `org.fusesource.jansi:jansi` + */ + public val isAnsiSupport: Boolean get() = false + public companion object { internal lateinit var instance: MiraiConsoleImplementation private val initLock = ReentrantLock() diff --git a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt index 8a2d7e0ee..ec3e78d9d 100644 --- a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt @@ -82,6 +82,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI override val dataStorageForBuiltIns: PluginDataStorage by instance::dataStorageForBuiltIns override val configStorageForBuiltIns: PluginDataStorage by instance::configStorageForBuiltIns override val consoleInput: ConsoleInput by instance::consoleInput + override val isAnsiSupport: Boolean by instance::isAnsiSupport override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver = instance.createLoginSolver(requesterBot, configuration) diff --git a/backend/mirai-console/src/util/AnsiMessageBuilder.kt b/backend/mirai-console/src/util/AnsiMessageBuilder.kt new file mode 100644 index 000000000..a716774da --- /dev/null +++ b/backend/mirai-console/src/util/AnsiMessageBuilder.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package net.mamoe.mirai.console.util + +import net.mamoe.mirai.console.command.CommandSender +import net.mamoe.mirai.console.command.ConsoleCommandSender +import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge + +public open class AnsiMessageBuilder internal constructor( + public val builder: StringBuilder +) : Appendable { + public inline fun builder(action: StringBuilder.() -> R): R = builder.action() + override fun toString(): String = builder.toString() + + public open fun ansi(code: String): AnsiMessageBuilder = append(code) + public open fun reset(): AnsiMessageBuilder = append(Color.RESET) + public open fun white(): AnsiMessageBuilder = append(Color.WHITE) + public open fun red(): AnsiMessageBuilder = append(Color.RED) + public open fun emeraldGreen(): AnsiMessageBuilder = append(Color.EMERALD_GREEN) + public open fun gold(): AnsiMessageBuilder = append(Color.GOLD) + public open fun blue(): AnsiMessageBuilder = append(Color.BLUE) + public open fun purple(): AnsiMessageBuilder = append(Color.PURPLE) + public open fun green(): AnsiMessageBuilder = append(Color.GREEN) + public open fun gray(): AnsiMessageBuilder = append(Color.GRAY) + public open fun lightRed(): AnsiMessageBuilder = append(Color.LIGHT_RED) + public open fun lightGreen(): AnsiMessageBuilder = append(Color.LIGHT_GREEN) + public open fun lightYellow(): AnsiMessageBuilder = append(Color.LIGHT_YELLOW) + public open fun lightBlue(): AnsiMessageBuilder = append(Color.LIGHT_BLUE) + public open fun lightPurple(): AnsiMessageBuilder = append(Color.LIGHT_PURPLE) + public open fun lightCyan(): AnsiMessageBuilder = append(Color.LIGHT_CYAN) + + internal object Color { + const val RESET = "\u001b[0m" + const val WHITE = "\u001b[30m" + const val RED = "\u001b[31m" + const val EMERALD_GREEN = "\u001b[32m" + const val GOLD = "\u001b[33m" + const val BLUE = "\u001b[34m" + const val PURPLE = "\u001b[35m" + const val GREEN = "\u001b[36m" + const val GRAY = "\u001b[90m" + const val LIGHT_RED = "\u001b[91m" + const val LIGHT_GREEN = "\u001b[92m" + const val LIGHT_YELLOW = "\u001b[93m" + const val LIGHT_BLUE = "\u001b[94m" + const val LIGHT_PURPLE = "\u001b[95m" + const val LIGHT_CYAN = "\u001b[96m" + } + + internal class NoAnsiMessageBuilder(builder: StringBuilder) : AnsiMessageBuilder(builder) { + override fun reset(): AnsiMessageBuilder = this + override fun white(): AnsiMessageBuilder = this + override fun red(): AnsiMessageBuilder = this + override fun emeraldGreen(): AnsiMessageBuilder = this + override fun gold(): AnsiMessageBuilder = this + override fun blue(): AnsiMessageBuilder = this + override fun purple(): AnsiMessageBuilder = this + override fun green(): AnsiMessageBuilder = this + override fun gray(): AnsiMessageBuilder = this + override fun lightRed(): AnsiMessageBuilder = this + override fun lightGreen(): AnsiMessageBuilder = this + override fun lightYellow(): AnsiMessageBuilder = this + override fun lightBlue(): AnsiMessageBuilder = this + override fun lightPurple(): AnsiMessageBuilder = this + override fun lightCyan(): AnsiMessageBuilder = this + override fun ansi(code: String): AnsiMessageBuilder = this + } + + public companion object { + public fun ansiMessageBuilder( + builder: StringBuilder, + noAnsi: Boolean = false + ): AnsiMessageBuilder = if (noAnsi) { + NoAnsiMessageBuilder(builder) + } else AnsiMessageBuilder(builder) + + public fun ansiMessageBuilder( + size: Int = 16, + noAnsi: Boolean = false + ): AnsiMessageBuilder = ansiMessageBuilder(StringBuilder(size), noAnsi) + + public inline fun buildAnsiMessage( + builder: StringBuilder, + noAnsi: Boolean = false, + action: AnsiMessageBuilder.() -> Unit + ): String = ansiMessageBuilder(builder, noAnsi).apply(action).toString() + + public inline fun buildAnsiMessage( + size: Int = 16, + noAnsi: Boolean = false, + action: AnsiMessageBuilder.() -> Unit + ): String = ansiMessageBuilder(size, noAnsi).apply(action).toString() + + @ConsoleExperimentalApi + public fun isAnsiSupport(sender: CommandSender): Boolean = + if (sender is ConsoleCommandSender) { + MiraiConsoleImplementationBridge.isAnsiSupport + } else false + + public suspend fun CommandSender.sendAnsiMessage( + size: Int = 16, + builder: AnsiMessageBuilder.() -> Unit + ) { + sendMessage(buildAnsiMessage(size, noAnsi = !isAnsiSupport(this), builder)) + } + } + + ///////////////////////////////////////////////////////////////////////////////// + override fun append(c: Char): AnsiMessageBuilder { + builder.append(c); return this + } + + override fun append(csq: CharSequence?): AnsiMessageBuilder { + builder.append(csq); return this + } + + override fun append(csq: CharSequence?, start: Int, end: Int): AnsiMessageBuilder { + builder.append(csq, start, end); return this + } + + public fun append(any: Any?): AnsiMessageBuilder { + builder.append(any); return this + } + + public fun append(value: String): AnsiMessageBuilder { + builder.append(value); return this + } + + public fun append(value: String, start: Int, end: Int): AnsiMessageBuilder { + builder.append(value, start, end); return this + } + + public fun append(value: Boolean): AnsiMessageBuilder { + builder.append(value); return this + } + + public fun append(value: Float): AnsiMessageBuilder { + builder.append(value); return this + } + + public fun append(value: Double): AnsiMessageBuilder { + builder.append(value); return this + } + + public fun append(value: Int): AnsiMessageBuilder { + builder.append(value); return this + } + + public fun append(value: Long): AnsiMessageBuilder { + builder.append(value); return this + } + + public fun append(value: Short): AnsiMessageBuilder { + builder.append(value); return this + } + ///////////////////////////////////////////////////////////////////////////////// +} \ No newline at end of file diff --git a/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt b/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt index 8708e197a..4809d08d8 100644 --- a/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt +++ b/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt @@ -75,6 +75,7 @@ class MiraiConsoleImplementationTerminal MiraiConsole.mainLogger.error("Exception in coroutine $coroutineName", throwable) }) { override val consoleInput: ConsoleInput get() = ConsoleInputImpl + override val isAnsiSupport: Boolean get() = true override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver { return DefaultLoginSolver(input = { requestInput("LOGIN> ") }) @@ -155,7 +156,7 @@ private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription { override val version: SemVersion = SemVersion(net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.versionConst) } -private val ANSI_RESET = Ansi().reset().toString() +internal val ANSI_RESET = Ansi().reset().toString() internal val LoggerCreator: (identity: String?) -> MiraiLogger = { PlatformLogger(identity = it, output = { line -> diff --git a/frontend/mirai-console-terminal/src/MiraiConsoleTerminalLoader.kt b/frontend/mirai-console-terminal/src/MiraiConsoleTerminalLoader.kt index aead7f1d8..e54b68b2f 100644 --- a/frontend/mirai-console-terminal/src/MiraiConsoleTerminalLoader.kt +++ b/frontend/mirai-console-terminal/src/MiraiConsoleTerminalLoader.kt @@ -191,7 +191,7 @@ internal fun overrideSTD() { internal object ConsoleCommandSenderImplTerminal : MiraiConsoleImplementation.ConsoleCommandSenderImpl { override suspend fun sendMessage(message: String) { kotlin.runCatching { - lineReader.printAbove(message) + lineReader.printAbove(message + ANSI_RESET) }.onFailure { exception -> // If failed. It means JLine Terminal not working... PrintStream(FileOutputStream(FileDescriptor.err)).use { From 36d14802988014b964d8817bf4af9f5e81f113c6 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Fri, 6 Nov 2020 13:51:38 +0800 Subject: [PATCH 02/52] Typo --- backend/mirai-console/src/MiraiConsole.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/mirai-console/src/MiraiConsole.kt b/backend/mirai-console/src/MiraiConsole.kt index 2ff09ed6f..1df9d5505 100644 --- a/backend/mirai-console/src/MiraiConsole.kt +++ b/backend/mirai-console/src/MiraiConsole.kt @@ -155,7 +155,7 @@ public interface MiraiConsole : CoroutineScope { /** - * 该前端是否支持使用 Ansi 输出彩色信息 + * 是否支持使用 Ansi 输出彩色信息 * * 注: 不是每个前端都可能提供 `org.fusesource.jansi:jansi` 库支持, * 请不要直接使用 `org.fusesource.jansi:jansi` From 81aa60fe5d0ca8a873a7e4d7febf0ab296d83609 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 7 Nov 2020 11:23:26 +0800 Subject: [PATCH 03/52] Review AnsiMessageBuilder - Rename isAnsiSupport to isAnsiSupported - Rename AnsiMessageBuilder.builder to delegate - Rename factory function - Move buildAnsiMessage to top-level - Move CommandSender.sendAnsiMessage to top-level - Add String.dropAnsi(): String --- backend/mirai-console/src/MiraiConsole.kt | 2 +- .../src/MiraiConsoleImplementation.kt | 2 +- .../MiraiConsoleImplementationBridge.kt | 2 +- .../src/util/AnsiMessageBuilder.kt | 116 +++++++++++------- .../src/MiraiConsoleImplementationTerminal.kt | 2 +- 5 files changed, 79 insertions(+), 45 deletions(-) diff --git a/backend/mirai-console/src/MiraiConsole.kt b/backend/mirai-console/src/MiraiConsole.kt index 1df9d5505..35ce1b201 100644 --- a/backend/mirai-console/src/MiraiConsole.kt +++ b/backend/mirai-console/src/MiraiConsole.kt @@ -163,7 +163,7 @@ public interface MiraiConsole : CoroutineScope { * @see [AnsiMessageBuilder] */ @ConsoleExperimentalApi - public val isAnsiSupport: Boolean + public val isAnsiSupported: Boolean } /** diff --git a/backend/mirai-console/src/MiraiConsoleImplementation.kt b/backend/mirai-console/src/MiraiConsoleImplementation.kt index d0b0c8c10..f10414fa3 100644 --- a/backend/mirai-console/src/MiraiConsoleImplementation.kt +++ b/backend/mirai-console/src/MiraiConsoleImplementation.kt @@ -175,7 +175,7 @@ public interface MiraiConsoleImplementation : CoroutineScope { * * 注: 若为 `true`, 建议携带 `org.fusesource.jansi:jansi` */ - public val isAnsiSupport: Boolean get() = false + public val isAnsiSupported: Boolean get() = false public companion object { internal lateinit var instance: MiraiConsoleImplementation diff --git a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt index ec3e78d9d..84a033718 100644 --- a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt @@ -82,7 +82,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI override val dataStorageForBuiltIns: PluginDataStorage by instance::dataStorageForBuiltIns override val configStorageForBuiltIns: PluginDataStorage by instance::configStorageForBuiltIns override val consoleInput: ConsoleInput by instance::consoleInput - override val isAnsiSupport: Boolean by instance::isAnsiSupport + override val isAnsiSupported: Boolean by instance::isAnsiSupported override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver = instance.createLoginSolver(requesterBot, configuration) diff --git a/backend/mirai-console/src/util/AnsiMessageBuilder.kt b/backend/mirai-console/src/util/AnsiMessageBuilder.kt index a716774da..d198887b6 100644 --- a/backend/mirai-console/src/util/AnsiMessageBuilder.kt +++ b/backend/mirai-console/src/util/AnsiMessageBuilder.kt @@ -6,21 +6,27 @@ * * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("unused", "MemberVisibilityCanBePrivate") +@file:Suppress("unused", "MemberVisibilityCanBePrivate", "FunctionName") package net.mamoe.mirai.console.util import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge +import net.mamoe.mirai.console.util.AnsiMessageBuilder.Companion.dropAnsi public open class AnsiMessageBuilder internal constructor( - public val builder: StringBuilder + public val delegate: StringBuilder ) : Appendable { - public inline fun builder(action: StringBuilder.() -> R): R = builder.action() - override fun toString(): String = builder.toString() + override fun toString(): String = delegate.toString() + /** + * 在添加 ansi code 的时候建议使用此方法. + * + * 在 `noAnsi=true` 的时候会忽略此函数的调用 + */ public open fun ansi(code: String): AnsiMessageBuilder = append(code) + public open fun reset(): AnsiMessageBuilder = append(Color.RESET) public open fun white(): AnsiMessageBuilder = append(Color.WHITE) public open fun red(): AnsiMessageBuilder = append(Color.RED) @@ -75,91 +81,119 @@ public open class AnsiMessageBuilder internal constructor( } public companion object { - public fun ansiMessageBuilder( + private val DROP_ANSI_PATTERN = """\u001b\[([0-9]+)(;[0-9]+)*m""".toRegex() + + @JvmStatic + public fun String.dropAnsi(): String = DROP_ANSI_PATTERN.replace(this, "") + + @JvmStatic + @JvmName("builder") // Java Factory Style + @JvmOverloads + public operator fun invoke( builder: StringBuilder, noAnsi: Boolean = false ): AnsiMessageBuilder = if (noAnsi) { NoAnsiMessageBuilder(builder) } else AnsiMessageBuilder(builder) - public fun ansiMessageBuilder( - size: Int = 16, + /** + * @param capacity [StringBuilder] 的初始化大小 + */ + @JvmStatic + @JvmName("builder") // Java Factory Style + @JvmOverloads + public operator fun invoke( + capacity: Int = 16, noAnsi: Boolean = false - ): AnsiMessageBuilder = ansiMessageBuilder(StringBuilder(size), noAnsi) - - public inline fun buildAnsiMessage( - builder: StringBuilder, - noAnsi: Boolean = false, - action: AnsiMessageBuilder.() -> Unit - ): String = ansiMessageBuilder(builder, noAnsi).apply(action).toString() - - public inline fun buildAnsiMessage( - size: Int = 16, - noAnsi: Boolean = false, - action: AnsiMessageBuilder.() -> Unit - ): String = ansiMessageBuilder(size, noAnsi).apply(action).toString() + ): AnsiMessageBuilder = invoke(StringBuilder(capacity), noAnsi) @ConsoleExperimentalApi - public fun isAnsiSupport(sender: CommandSender): Boolean = + @JvmStatic + public fun isAnsiSupported(sender: CommandSender): Boolean = if (sender is ConsoleCommandSender) { - MiraiConsoleImplementationBridge.isAnsiSupport + MiraiConsoleImplementationBridge.isAnsiSupported } else false - public suspend fun CommandSender.sendAnsiMessage( - size: Int = 16, - builder: AnsiMessageBuilder.() -> Unit - ) { - sendMessage(buildAnsiMessage(size, noAnsi = !isAnsiSupport(this), builder)) - } + public inline fun StringBuilder.appendAnsi( + noAnsi: Boolean = false, + action: AnsiMessageBuilder.() -> Unit + ): AnsiMessageBuilder = invoke(this, noAnsi).apply(action) + } ///////////////////////////////////////////////////////////////////////////////// override fun append(c: Char): AnsiMessageBuilder { - builder.append(c); return this + delegate.append(c); return this } override fun append(csq: CharSequence?): AnsiMessageBuilder { - builder.append(csq); return this + delegate.append(csq); return this } override fun append(csq: CharSequence?, start: Int, end: Int): AnsiMessageBuilder { - builder.append(csq, start, end); return this + delegate.append(csq, start, end); return this } public fun append(any: Any?): AnsiMessageBuilder { - builder.append(any); return this + delegate.append(any); return this } public fun append(value: String): AnsiMessageBuilder { - builder.append(value); return this + delegate.append(value); return this } public fun append(value: String, start: Int, end: Int): AnsiMessageBuilder { - builder.append(value, start, end); return this + delegate.append(value, start, end); return this } public fun append(value: Boolean): AnsiMessageBuilder { - builder.append(value); return this + delegate.append(value); return this } public fun append(value: Float): AnsiMessageBuilder { - builder.append(value); return this + delegate.append(value); return this } public fun append(value: Double): AnsiMessageBuilder { - builder.append(value); return this + delegate.append(value); return this } public fun append(value: Int): AnsiMessageBuilder { - builder.append(value); return this + delegate.append(value); return this } public fun append(value: Long): AnsiMessageBuilder { - builder.append(value); return this + delegate.append(value); return this } public fun append(value: Short): AnsiMessageBuilder { - builder.append(value); return this + delegate.append(value); return this } ///////////////////////////////////////////////////////////////////////////////// -} \ No newline at end of file +} + +public inline fun buildAnsiMessage( + capacity: Int = 16, + action: AnsiMessageBuilder.() -> Unit +): String = AnsiMessageBuilder(capacity, false).apply(action).toString() + +// 不在 top-level 使用者会得到 Internal error: Couldn't inline sendAnsiMessage +public suspend inline fun CommandSender.sendAnsiMessage( + capacity: Int = 16, + builder: AnsiMessageBuilder.() -> Unit +) { + sendMessage( + AnsiMessageBuilder(capacity, noAnsi = !AnsiMessageBuilder.isAnsiSupported(this)) + .apply(builder) + .toString() + ) +} + +public suspend inline fun CommandSender.sendAnsiMessage(message: String) { + sendMessage( + if (AnsiMessageBuilder.isAnsiSupported(this)) + message + else + message.dropAnsi() + ) +} diff --git a/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt b/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt index 4809d08d8..1884a0b39 100644 --- a/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt +++ b/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt @@ -75,7 +75,7 @@ class MiraiConsoleImplementationTerminal MiraiConsole.mainLogger.error("Exception in coroutine $coroutineName", throwable) }) { override val consoleInput: ConsoleInput get() = ConsoleInputImpl - override val isAnsiSupport: Boolean get() = true + override val isAnsiSupported: Boolean get() = true override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver { return DefaultLoginSolver(input = { requestInput("LOGIN> ") }) From cce3749661deb42ed034c30120ecb0b8cada42b7 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 8 Nov 2020 10:32:18 +0800 Subject: [PATCH 04/52] KDoc; Adjust the order of property --- backend/mirai-console/src/MiraiConsole.kt | 21 ++++++++-------- .../src/util/AnsiMessageBuilder.kt | 25 +++++++++++++++++++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/backend/mirai-console/src/MiraiConsole.kt b/backend/mirai-console/src/MiraiConsole.kt index 35ce1b201..6f044b869 100644 --- a/backend/mirai-console/src/MiraiConsole.kt +++ b/backend/mirai-console/src/MiraiConsole.kt @@ -91,6 +91,17 @@ public interface MiraiConsole : CoroutineScope { @ConsoleExperimentalApi public fun createLogger(identity: String?): MiraiLogger + /** + * 是否支持使用 Ansi 输出彩色信息 + * + * 注: 不是每个前端都可能提供 `org.fusesource.jansi:jansi` 库支持, + * 请不要直接使用 `org.fusesource.jansi:jansi` + * + * @see [AnsiMessageBuilder] + */ + @ConsoleExperimentalApi + public val isAnsiSupported: Boolean + public companion object INSTANCE : MiraiConsole by MiraiConsoleImplementationBridge { /** * 获取 [MiraiConsole] 的 [Job] @@ -154,16 +165,6 @@ public interface MiraiConsole : CoroutineScope { } - /** - * 是否支持使用 Ansi 输出彩色信息 - * - * 注: 不是每个前端都可能提供 `org.fusesource.jansi:jansi` 库支持, - * 请不要直接使用 `org.fusesource.jansi:jansi` - * - * @see [AnsiMessageBuilder] - */ - @ConsoleExperimentalApi - public val isAnsiSupported: Boolean } /** diff --git a/backend/mirai-console/src/util/AnsiMessageBuilder.kt b/backend/mirai-console/src/util/AnsiMessageBuilder.kt index d198887b6..892fc821e 100644 --- a/backend/mirai-console/src/util/AnsiMessageBuilder.kt +++ b/backend/mirai-console/src/util/AnsiMessageBuilder.kt @@ -83,6 +83,9 @@ public open class AnsiMessageBuilder internal constructor( public companion object { private val DROP_ANSI_PATTERN = """\u001b\[([0-9]+)(;[0-9]+)*m""".toRegex() + /** + * 从 [String] 中剔除 ansi 控制符 + */ @JvmStatic public fun String.dropAnsi(): String = DROP_ANSI_PATTERN.replace(this, "") @@ -107,6 +110,9 @@ public open class AnsiMessageBuilder internal constructor( noAnsi: Boolean = false ): AnsiMessageBuilder = invoke(StringBuilder(capacity), noAnsi) + /** + * 判断 [sender] 是否支持带 ansi 控制符的正确显示 + */ @ConsoleExperimentalApi @JvmStatic public fun isAnsiSupported(sender: CommandSender): Boolean = @@ -114,6 +120,9 @@ public open class AnsiMessageBuilder internal constructor( MiraiConsoleImplementationBridge.isAnsiSupported } else false + /** + * 往 [StringBuilder] 追加 ansi 控制符 + */ public inline fun StringBuilder.appendAnsi( noAnsi: Boolean = false, action: AnsiMessageBuilder.() -> Unit @@ -172,12 +181,23 @@ public open class AnsiMessageBuilder internal constructor( ///////////////////////////////////////////////////////////////////////////////// } +/** + * 构建一条 ansi 信息 + * + * @see [AnsiMessageBuilder] + */ public inline fun buildAnsiMessage( capacity: Int = 16, action: AnsiMessageBuilder.() -> Unit ): String = AnsiMessageBuilder(capacity, false).apply(action).toString() // 不在 top-level 使用者会得到 Internal error: Couldn't inline sendAnsiMessage + +/** + * 向 [CommandSender] 发送一条带有 ansi 控制符的信息 + * + * @see [AnsiMessageBuilder] + */ public suspend inline fun CommandSender.sendAnsiMessage( capacity: Int = 16, builder: AnsiMessageBuilder.() -> Unit @@ -189,6 +209,11 @@ public suspend inline fun CommandSender.sendAnsiMessage( ) } +/** + * 向 [CommandSender] 发送一条带有 ansi 控制符的信息 + * + * @see [AnsiMessageBuilder.Companion.dropAnsi] + */ public suspend inline fun CommandSender.sendAnsiMessage(message: String) { sendMessage( if (AnsiMessageBuilder.isAnsiSupported(this)) From 226a90ad05de6505b2ebbbaea545865a35cdccfb Mon Sep 17 00:00:00 2001 From: Him188 Date: Tue, 24 Nov 2020 17:15:04 +0800 Subject: [PATCH 05/52] Configure jvm, androidJvm and common targets only --- .../src/MiraiConsoleGradlePlugin.kt | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt b/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt index 06ea0c733..287d4620c 100644 --- a/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt +++ b/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.KotlinTarget @@ -33,9 +34,9 @@ class MiraiConsoleGradlePlugin : Plugin { internal const val BINTRAY_REPOSITORY_URL = "https://dl.bintray.com/him188moe/mirai" } - private fun KotlinSourceSet.configureSourceSet(project: Project) { + private fun KotlinSourceSet.configureSourceSet(project: Project, target: KotlinTarget) { languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn") - dependencies { configureDependencies(project, this@configureSourceSet) } + dependencies { configureDependencies(project, this@configureSourceSet, target) } } private fun Project.configureTarget(target: KotlinTarget) { @@ -48,24 +49,37 @@ class MiraiConsoleGradlePlugin : Plugin { if (!miraiExtension.dontConfigureKotlinJvmDefault) freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" } } - target.compilations.flatMap { it.allKotlinSourceSets }.forEach { sourceSet -> - sourceSet.configureSourceSet(project) + when (target.platformType) { + KotlinPlatformType.jvm, + KotlinPlatformType.androidJvm, + KotlinPlatformType.common + -> { + target.compilations.flatMap { it.allKotlinSourceSets }.forEach { sourceSet -> + sourceSet.configureSourceSet(project, target) + } + } + else -> { + } } } @Suppress("SpellCheckingInspection") - private fun KotlinDependencyHandler.configureDependencies(project: Project, sourceSet: KotlinSourceSet) { + private fun KotlinDependencyHandler.configureDependencies(project: Project, sourceSet: KotlinSourceSet, target: KotlinTarget) { val miraiExtension = project.miraiExtension + val isJvm = target.platformType == KotlinPlatformType.jvm || target.platformType == KotlinPlatformType.androidJvm + if (!miraiExtension.noCore) compileOnly("net.mamoe:mirai-core:${miraiExtension.coreVersion}") - if (!miraiExtension.noConsole) compileOnly("net.mamoe:mirai-console:${miraiExtension.consoleVersion}") + if (!miraiExtension.noConsole && isJvm) compileOnly("net.mamoe:mirai-console:${miraiExtension.consoleVersion}") if (sourceSet.name.endsWith("test", ignoreCase = true)) { if (!miraiExtension.noCore) api("net.mamoe:mirai-core:${miraiExtension.coreVersion}") - if (!miraiExtension.noConsole) api("net.mamoe:mirai-console:${miraiExtension.consoleVersion}") + if (!miraiExtension.noConsole && isJvm) api("net.mamoe:mirai-console:${miraiExtension.consoleVersion}") if (!miraiExtension.noTestCoreQQAndroid) api("net.mamoe:mirai-core-qqandroid:${miraiExtension.coreVersion}") - when (miraiExtension.useTestConsoleFrontEnd) { - MiraiConsoleFrontEndKind.TERMINAL -> api("net.mamoe:mirai-console-terminal:${miraiExtension.consoleVersion}") + if (isJvm) { + when (miraiExtension.useTestConsoleFrontEnd) { + MiraiConsoleFrontEndKind.TERMINAL -> api("net.mamoe:mirai-console-terminal:${miraiExtension.consoleVersion}") + } } } } From 96be869e7f4c53f79d9f59e0f2aba30eee64afb3 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Wed, 25 Nov 2020 13:56:21 +0800 Subject: [PATCH 06/52] ReportGenerator --- .../MiraiConsoleImplementationBridge.kt | 2 +- .../data/MultiFilePluginDataStorageImpl.kt | 28 ++- .../internal/util/report/ReportGenerator.kt | 204 ++++++++++++++++++ 3 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 backend/mirai-console/src/internal/util/report/ReportGenerator.kt diff --git a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt index 67b45b9b7..be92004bc 100644 --- a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt @@ -72,7 +72,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI MiraiConsole { override val pluginCenter: PluginCenter get() = throw UnsupportedOperationException("PluginCenter is not supported yet") - private val instance: MiraiConsoleImplementation by MiraiConsoleImplementation.Companion::instance + internal val instance: MiraiConsoleImplementation by MiraiConsoleImplementation.Companion::instance override val buildDate: Instant by MiraiConsoleBuildConstants::buildDate override val version: SemVersion by MiraiConsoleBuildConstants::version override val rootPath: Path by instance::rootPath diff --git a/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt b/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt index a9e9a1f2c..a099e3515 100644 --- a/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt +++ b/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt @@ -12,6 +12,7 @@ package net.mamoe.mirai.console.internal.data import kotlinx.serialization.json.Json import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.data.* +import net.mamoe.mirai.console.internal.util.report.ReportGenerator import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.SilentLogger @@ -71,17 +72,40 @@ internal open class MultiFilePluginDataStorageImpl( @ConsoleExperimentalApi public override fun store(holder: PluginDataHolder, instance: PluginData) { + var yamlRendered: String? = null getPluginDataFile(holder, instance).writeText( kotlin.runCatching { yaml.encodeToString(instance.updaterSerializer, Unit).also { + yamlRendered = it yaml.decodeAnyFromString(it) // test yaml + error("Test error") } - }.recoverCatching { + }.recoverCatching { exception -> // Just use mainLogger for convenience. MiraiConsole.mainLogger.warning( "Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. " + "Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new", - it + exception + ) + val reportPath = ReportGenerator.generateReport("YamlKt-Format-") { + pw.println("Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. ") + pw.println("Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new") + pw.println() + yamlRendered?.let { + title("Rendered YAML") + pw.println(it) + pw.println() + } + title("Exception") + renderException(exception) + renderCurrentThread() + } + MiraiConsole.mainLogger.warning( + "Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. " + + "Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new" + ) + MiraiConsole.mainLogger.warning( + "Error Report location: $reportPath" ) json.encodeToString(instance.updaterSerializer, Unit) }.getOrElse { diff --git a/backend/mirai-console/src/internal/util/report/ReportGenerator.kt b/backend/mirai-console/src/internal/util/report/ReportGenerator.kt new file mode 100644 index 000000000..35f3b9442 --- /dev/null +++ b/backend/mirai-console/src/internal/util/report/ReportGenerator.kt @@ -0,0 +1,204 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.internal.util.report + +import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants +import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge +import net.mamoe.mirai.console.internal.data.isDirectory +import net.mamoe.mirai.console.internal.data.isFile +import net.mamoe.mirai.console.internal.data.mkdir +import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl +import net.mamoe.mirai.console.permission.PermissionService +import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description +import java.io.* +import java.lang.management.LockInfo +import java.lang.management.ManagementFactory +import java.lang.management.MonitorInfo +import java.lang.management.ThreadInfo +import java.nio.file.Files +import java.nio.file.Path +import java.time.ZoneId +import java.time.format.DateTimeFormatter + +@Suppress("unused") +internal class ReportGenerator( + val pw: PrintWriter +) : Closeable { + companion object { + internal val threadMXBean = ManagementFactory.getThreadMXBean() + internal val directory by lazy { + MiraiConsole.rootPath.resolve("error-reports") + } + + fun ThreadInfo.dumpTo(sb: Appendable) { + sb.run { + append('\"') + append(threadName) + append("\" Id=") + append(threadId.toString()) + append(" ") + append(threadState.toString()) + + lockName?.let { append(" on ").append(it) } + lockOwnerName?.let { append(" owned by \"").append(it).append("\" Id=").append(lockOwnerId.toString()) } + if (isSuspended) { + sb.append(" (suspended)") + } + if (isInNative) { + sb.append(" (in native)") + } + sb.append('\n') + var i = 0 + while (i < stackTrace.size) { + val ste: StackTraceElement = stackTrace[i] + sb.append("\tat $ste") + sb.append('\n') + if (i == 0 && lockInfo != null) { + when (threadState) { + Thread.State.BLOCKED -> { + sb.append("\t- blocked on $lockInfo") + sb.append('\n') + } + Thread.State.WAITING -> { + sb.append("\t- waiting on $lockInfo") + sb.append('\n') + } + Thread.State.TIMED_WAITING -> { + sb.append("\t- waiting on $lockInfo") + sb.append('\n') + } + else -> { + } + } + } + for (mi: MonitorInfo in lockedMonitors) { + if (mi.lockedStackDepth == i) { + sb.append("\t- locked $mi") + sb.append('\n') + } + } + i++ + } + val locks: Array = lockedSynchronizers + if (locks.isNotEmpty()) { + sb.append("\n\tNumber of locked synchronizers = " + locks.size) + sb.append('\n') + for (li: LockInfo in locks) { + sb.append("\t- $li") + sb.append('\n') + } + } + } + } + + fun generateToString(action: ReportGenerator.() -> Unit): String { + return StringWriter().apply { + ReportGenerator(PrintWriter(this)).use(action) + }.toString() + } + + fun generateReport( + prefix: String = "", + action: ReportGenerator.() -> Unit + ): Path { + val now = System.currentTimeMillis() + var counter = 0 + var outputName = "$prefix$now.log" + directory.mkdir() + var path: Path + do { + path = directory.resolve(outputName) + if (!path.isFile && !path.isDirectory) { + break + } + outputName = "$prefix$now-$counter.log" + counter++ + } while (true) + ReportGenerator(PrintWriter(BufferedWriter(OutputStreamWriter(Files.newOutputStream(path))))) + .use(action) + return path + } + } + + fun renderCurrentThread() { + title("Current Thread") + renderThread(Thread.currentThread()) + } + + fun renderThread(thread: Thread) { + threadMXBean.getThreadInfo( + longArrayOf(thread.id), + true, + true + )[0].dumpTo(pw) + } + + fun title(title: String) { + pw.append("=============== [ ").append(title).append(" ] ===============") + pw.println() + } + + fun dumpSystemEnv() { + title("System Env") + + pw.println("SysEnv") + pw.println() + pw.println("```") + System.getenv().forEach { (key, value) -> + pw.println("$key\t=\t$value") + } + pw.println("```") + pw.println() + pw.println("JavaProp") + pw.println() + pw.println("```") + System.getProperties().store(pw, null) + pw.println("```") + pw.println() + } + + fun dumpConsoleEnv() { + title("Mirai Console Env") + val buildDateFormatted = + MiraiConsoleBuildConstants.buildDate.atZone(ZoneId.systemDefault()) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + pw.append("MiraiConsole v${MiraiConsoleBuildConstants.versionConst}, built on ") + .append(buildDateFormatted) + .append(".\n") + pw.println("FrontEnd:") + pw.append("\t").println(MiraiConsoleImplementationBridge.instance.javaClass.name) + pw.append("\t").println(MiraiConsoleImplementationBridge.frontEndDescription.render()) + pw.println() + pw.println("Plugins:") + PluginManagerImpl.resolvedPlugins.forEach { plugin -> + val desc = plugin.description + pw.append("\t").append(desc.name).append(" v").append(desc.version.toString()).append(" by ").append(desc.author).println() + pw.append("\t\t `-- ").println(plugin.javaClass.name) + } + pw.println() + pw.println("PermissionService: ") + pw.append("\t").println(PermissionService.INSTANCE) + pw.append("\t\t`- ").println(PermissionService.INSTANCE.javaClass) + } + + fun renderException(throwable: Throwable) { + throwable.printStackTrace(pw) + pw.println() + } + + fun hr() { + pw.println("====================================================") + } + + override fun close() { + pw.close() + } +} \ No newline at end of file From bbdd2c2508ff236e573be21cddb3109a53ca606e Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Wed, 25 Nov 2020 13:58:51 +0800 Subject: [PATCH 07/52] Remove testing code --- .../src/internal/data/MultiFilePluginDataStorageImpl.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt b/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt index a099e3515..e27a62041 100644 --- a/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt +++ b/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt @@ -78,7 +78,6 @@ internal open class MultiFilePluginDataStorageImpl( yaml.encodeToString(instance.updaterSerializer, Unit).also { yamlRendered = it yaml.decodeAnyFromString(it) // test yaml - error("Test error") } }.recoverCatching { exception -> // Just use mainLogger for convenience. From c433450be22b6857f62dbcc04273c787991d9622 Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 25 Nov 2020 17:19:52 +0800 Subject: [PATCH 08/52] Update dependencies --- backend/mirai-console/test/TestMiraiConosle.kt | 6 +++--- build.gradle.kts | 1 + buildSrc/src/main/kotlin/Versions.kt | 8 ++++---- .../src/main/kotlin/org/example/myplugin/MyPluginMain.kt | 3 +++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/backend/mirai-console/test/TestMiraiConosle.kt b/backend/mirai-console/test/TestMiraiConosle.kt index d5186fabe..2c919c5d5 100644 --- a/backend/mirai-console/test/TestMiraiConosle.kt +++ b/backend/mirai-console/test/TestMiraiConosle.kt @@ -14,7 +14,6 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.data.MemoryPluginDataStorage import net.mamoe.mirai.console.data.PluginDataStorage -import net.mamoe.mirai.console.logging.LoggerController import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.ConsoleExperimentalApi @@ -27,12 +26,13 @@ import java.nio.file.Path import kotlin.coroutines.Continuation import kotlin.coroutines.CoroutineContext import kotlin.coroutines.resume +import kotlin.io.path.createTempDirectory import kotlin.test.assertNotNull -@OptIn(ConsoleInternalApi::class) +@OptIn(ConsoleInternalApi::class, kotlin.io.path.ExperimentalPathApi::class) fun initTestEnvironment() { object : MiraiConsoleImplementation { - override val rootPath: Path = createTempDir().toPath() + override val rootPath: Path = createTempDirectory() @ConsoleExperimentalApi override val frontEndDescription: MiraiConsoleFrontEndDescription diff --git a/build.gradle.kts b/build.gradle.kts index 29fe2bea4..f2faf5936 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,6 +53,7 @@ val experimentalAnnotations = arrayOf( "kotlin.experimental.ExperimentalTypeInference", "kotlinx.coroutines.ExperimentalCoroutinesApi", "kotlinx.serialization.ExperimentalSerializationApi", + "kotlin.io.path.ExperimentalPathApi", "io.ktor.util.KtorExperimentalAPI", "net.mamoe.mirai.utils.MiraiInternalAPI", diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 81dc13016..6bc49035d 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -18,8 +18,8 @@ object Versions { const val kotlinCompiler = "1.4.20" const val kotlinStdlib = "1.4.20" - const val kotlinIntellijPlugin = "1.4.20-RC-IJ2020.2-1" // -release - const val intellij = "2020.2.1" + const val kotlinIntellijPlugin = "1.4.20-release-IJ2020.2-1" // keep to newest as kotlinCompiler + const val intellij = "2020.2.1" // don't update easily unless you want your disk space -= 500MB const val coroutines = "1.4.0" @@ -34,7 +34,7 @@ object Versions { const val blockingBridge = "1.4.1" @Suppress("SpellCheckingInspection") - const val yamlkt = "0.7.3" + const val yamlkt = "0.7.4" const val intellijGradlePlugin = "0.4.16" } @@ -42,7 +42,7 @@ object Versions { const val `kotlin-compiler` = "org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}" const val `kotlin-stdlib` = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinStdlib}" -const val `kotlin-stdlib-jdk8` = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinStdlib}" +const val `kotlin-stdlib-jdk8` = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlinStdlib}" const val `kotlin-reflect` = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinStdlib}" const val `kotlin-test` = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlinStdlib}" const val `kotlin-test-junit5` = "org.jetbrains.kotlin:kotlin-test-junit5:${Versions.kotlinStdlib}" diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt index 34a50f190..5f3ad337a 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt @@ -32,6 +32,9 @@ object MyPluginMain : KotlinPlugin( } +val x = "弱智黄色" + + object MyData : AutoSavePluginData("") { val value by value("") val value2 by value>() From 1c9f5f50db2ffa1cfcafe5c88fc08ea34d01d7ae Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 25 Nov 2020 17:22:10 +0800 Subject: [PATCH 09/52] Fix link --- backend/codegen/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/codegen/README.md b/backend/codegen/README.md index 013c05c7b..477674b6d 100644 --- a/backend/codegen/README.md +++ b/backend/codegen/README.md @@ -2,5 +2,5 @@ 后端代码生成模块,用于最小化重复代码的人工成本。 -- `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/main/kotlin/net/mamoe/mirai/console/codegen/MessageScopeCodegen.kt#L33) -- `Value` 和 `PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/main/kotlin/net/mamoe/mirai/console/codegen/ValuePluginDataCodegen.kt#L18) +- `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/MessageScopeCodegen.kt#L33) +- `Value` 和 `PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/ValuePluginDataCodegen.kt#L18) From 0e0271ec7868e03c95c60f06d941bcd2863fb827 Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 25 Nov 2020 17:30:52 +0800 Subject: [PATCH 10/52] Add notes --- tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt | 5 +++++ .../src/diagnostics/MiraiConsoleErrorsRendering.kt | 3 +++ .../src/diagnostics/ContextualParametersChecker.kt | 1 + 3 files changed, 9 insertions(+) diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt index 7f2544a39..7f981069c 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt @@ -18,6 +18,11 @@ import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtTypeProjection +/** + * 如何增加一个错误: + * 1. 在 [MiraiConsoleErrors] 添加 + * 2. 在 [MiraiConsoleErrorsRendering] 添加对应的 render + */ object MiraiConsoleErrors { @JvmField val ILLEGAL_PLUGIN_DESCRIPTION = create(ERROR) diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt index effb040ff..5858bea53 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt @@ -22,6 +22,9 @@ import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap import org.jetbrains.kotlin.diagnostics.rendering.Renderers +/** + * @see MiraiConsoleErrors + */ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { private val MAP = DiagnosticFactoryToRendererMap("MiraiConsole").apply { put( diff --git a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt index 67d62adc1..4163b04d4 100644 --- a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt +++ b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt @@ -116,6 +116,7 @@ class ContextualParametersChecker : DeclarationChecker { @Suppress("UNUSED_PARAMETER") fun checkVersionRequirement(inspectionTarget: PsiElement, value: String): Diagnostic? { // TODO: 2020/10/23 checkVersionRequirement + // 实现: 先在 MiraiConsoleErrors 添加一个 error, 再检测 value 并 report 一个错误. return null } } From 335f8cb0adda839ffaf2d6ff34402a4641fdd323 Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 25 Nov 2020 17:46:52 +0800 Subject: [PATCH 11/52] Update embedded run --- docs/Run.md | 81 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/docs/Run.md b/docs/Run.md index d7c7edbdd..f48365e1e 100644 --- a/docs/Run.md +++ b/docs/Run.md @@ -9,8 +9,10 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。 ## 手动配置独立启动 +强烈建议使用自动启动工具,若无法使用,可以参考如下手动启动方式。 + ### 环境 -- JRE 11+ / JDK 11+ +- JRE 8+ / JDK 8+ ### 准备文件 @@ -22,7 +24,7 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。 只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 terminal 前端可用。 -#### 从JCenter下载模块 +#### 从 JCenter 下载模块 mirai 在版本发布时会将发布的构建存放与 [mirai-bintray-repo]。 @@ -32,15 +34,15 @@ mirai 在版本发布时会将发布的构建存放与 [mirai-bintray-repo]。 ```shell script # 注: 自行更换对应版本号 -# Download Mirai Core All +# Download mirai-core-all curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-core-all/1.3.3/mirai-core-all-1.3.3-all.jar -o mirai-core-all-1.3.3.jar -# Download Mirai Console All +# Download mirai-console curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-console/1.0.0/mirai-console-1.0.0-all.jar -o mirai-console-1.0.0.jar -# Download Mirai Console Terminal +# Download mirai-console-terminal curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-console-terminal/1.0.0/mirai-console-terminal-1.0.0-all.jar -o mirai-console-terminal-1.0.0.jar @@ -48,7 +50,7 @@ curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-console-termi ### 启动 mirai-console-terminal 前端 -1. 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`)(详见 [下载模块](#从JCenter下载模块)): +1. 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`)(详见 [下载模块](#从-jcenter-下载模块)): - mirai-core-all - mirai-console - mirai-console-terminal @@ -77,19 +79,66 @@ echo -e '\033]2;Mirai Console\007' java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $* ``` -然后就可以开始使用 mirai-console 了 +然后就可以开始使用 mirai-console 了。 #### mirai-console-terminal 前端参数 -使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数 - - -### 启动 mirai-console-pure 前端 - -与启动 `mirai-console-terminal` 前端大体相同 -- 下载 `mirai-console-terminal` 改成下载 `mirai-console-pure` -- 启动入口从 `net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader` 改成 `net.mamoe.mirai.console.pure.MiraiConsolePureLoader` - +使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数。 [mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow [mirai-bintray-repo]: https://bintray.com/him188moe/mirai [mirai-core-all]: https://bintray.com/him188moe/mirai/mirai-core-all + + +## 嵌入应用启动(实验性) + +Mirai Console 可以嵌入一个 JVM 应用启动。 + +### 环境 + +- JDK 1.8+ / Android SDK 26+ (Android 8+) +- Kotlin 1.4+ + +### 添加依赖 + +[选择版本](ConfiguringProjects.md#选择版本) + +`build.gradle.kts`: +```kotlin +repositories { + jcenter() +} +dependencies { + implementation("net.mamoe:mirai-console:1.0.1") + implementation("net.mamoe:mirai-console-terminal:1.0.1") + implementation("net.mamoe:mirai-core:1.3.3") +} +``` + +### 启动 Terminal 前端 + +一行启动: +```kotlin +MiraiConsoleTerminalLoader.startAsDaemon() +``` + +注意, Mirai Console 将会以 '守护进程' 形式启动,不会阻止主线程退出。 + +### 从内存加载 JVM 插件(实验性) + +在嵌入使用时,插件可以直接加载: + +```kotlin +```kotlin +MiraiConsoleTerminalLoader.startAsDaemon() +// 先启动 Mirai Console + +// Kotlin +Plugin.load() // 扩展函数 +Plugin.enable() // 扩展函数 + +// Java +PluginManager.INSTANCE.loadPlugin(Plugin) +PluginManager.INSTANCE.enablePlugin(Plugin) +``` + +但注意:这种方法目前是实验性的——一些特定的功能如注册扩展可能不会正常工作。 \ No newline at end of file From ac36a560fbbdc2229e7f862558b325e803f4ebef Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 25 Nov 2020 18:13:52 +0800 Subject: [PATCH 12/52] Update publishing guides --- docs/Contributing.md | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/Contributing.md b/docs/Contributing.md index f4e4befbb..92595d39b 100644 --- a/docs/Contributing.md +++ b/docs/Contributing.md @@ -26,7 +26,35 @@ Mirai Console 项目由四个模块组成:后端,前端,Gradle 插件,In ### 构建 ```shell script -gradlew build +./gradlew build ``` -首次加载和构建 mirai-console 项目可能要花费数小时时间。 \ No newline at end of file +首次加载和构建 mirai-console 项目可能要花费数小时时间。 + +### 发布版本 + +(针对拥有 Mirai Console write 权限的项目成员) + +若你要发布一个 Mirai Console dev release: + +1. 更新 buildSrc/Versions.kt 中 `project` 版本号为目标版本; +2. 本地执行 `./gradlew fillBuildConstants`; +3. Push 第 1,2 步的修改为同一个 commit,commit 备注为版本号名称,如 `1.0.1-dev-1`; +4. 添加 Git 版本号 tag,格式为 `1.0.1-dev-1`(不带 `v`); +5. `git push --tags` 推送 tag 更新,GitHub Actions 将会检测到 tag 更新并执行 JCenter 发布。 + + +若你要发布一个 Mirai Console 稳定版 release,请按顺序进行如下检查: + + +1. 在 GitHub [milestones](https://github.com/mamoe/mirai-console/milestones) 确认目标版本的工作已经处理完毕; +2. Close milestone; +3. 更新 buildSrc/Versions.kt 中 `project` 版本号为目标版本; +4. 在 [ConfiguringProjects](ConfiguringProjects.md#选择版本) 更新稳定版本号; +5. 本地执行 `./gradlew fillBuildConstants`; +6. Push 前几步的修改为同一个 commit,commit 备注为版本号名称,如 `1.1.0`; +7. 在 GitHub release 根据 Git commit 记录编写更新记录: + - 描述所有来自社区的 PR 记录; + - 完整列举公开 API 的任何变动,简要描述或省略内部变动; + - 为更改按 “后端”,“前端”,“IDE 插件”,“Gradle 插件” 分类; +8. 点击 `Publish`。GitHub Actions 将会检测到 tag 更新并执行 JCenter 发布。 From 8f61943c70e8c9bec4d5a8fa9800b3f935310185 Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 25 Nov 2020 18:19:06 +0800 Subject: [PATCH 13/52] Add MiraiConsoleImplementation.isInitialized --- .../src/MiraiConsoleImplementation.kt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/backend/mirai-console/src/MiraiConsoleImplementation.kt b/backend/mirai-console/src/MiraiConsoleImplementation.kt index 418b50536..361f678b9 100644 --- a/backend/mirai-console/src/MiraiConsoleImplementation.kt +++ b/backend/mirai-console/src/MiraiConsoleImplementation.kt @@ -18,8 +18,8 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge -import net.mamoe.mirai.console.logging.LoggerController import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl +import net.mamoe.mirai.console.logging.LoggerController import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.ConsoleInput @@ -185,12 +185,27 @@ public interface MiraiConsoleImplementation : CoroutineScope { * 可由前端调用, 获取当前的 [MiraiConsoleImplementation] 实例 * * 必须在 [start] 之后才能使用, 否则抛出 [UninitializedPropertyAccessException] + * + * @see isInitialized */ @JvmStatic @ConsoleFrontEndImplementation public fun getInstance(): MiraiConsoleImplementation = instance - /** 由前端调用, 初始化 [MiraiConsole] 实例并启动 */ + /** + * 当 [MiraiConsoleImplementation] 已经初始化后返回 `true` + */ + @JvmStatic + @ConsoleFrontEndImplementation + public val isInitialized: Boolean + get() = ::instance.isInitialized + + /** + * 由前端调用, 初始化 [MiraiConsole] 实例并启动 + * + * @see getInstance + * @see isInitialized + */ @JvmStatic @ConsoleFrontEndImplementation @Throws(MalformedMiraiConsoleImplementationError::class) From 73533c37cb24a0032a8847e7d9201e4e4326493a Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 25 Nov 2020 18:29:03 +0800 Subject: [PATCH 14/52] Fix inconsistent exceptions specified in docs and actually thrown. --- .../internal/plugin/BuiltInJvmPluginLoaderImpl.kt | 12 +++++++++--- .../mirai-console/src/plugin/loader/PluginLoader.kt | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt b/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt index 46fe659ae..8e3537675 100644 --- a/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt +++ b/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt @@ -20,6 +20,7 @@ import net.mamoe.mirai.console.internal.util.PluginServiceHelper.loadAllServices import net.mamoe.mirai.console.plugin.jvm.* import net.mamoe.mirai.console.plugin.loader.AbstractFilePluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoadException +import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope import net.mamoe.mirai.utils.MiraiLogger import java.io.File @@ -93,12 +94,17 @@ internal object BuiltInJvmPluginLoaderImpl : return filePlugins.toSet().map { it.value } } + private val loadedPlugins = ConcurrentHashMap() + @Throws(PluginLoadException::class) override fun load(plugin: JvmPlugin) { ensureActive() + if (loadedPlugins.put(plugin, Unit) != null) { + error("Plugin '${plugin.name}' is already loaded and cannot be reloaded.") + } runCatching { - check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin" } + check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin to be loaded by JvmPluginLoader.BuiltIn" } plugin.internalOnLoad(plugin.componentStorage) }.getOrElse { throw PluginLoadException("Exception while loading ${plugin.description.name}", it) @@ -106,7 +112,7 @@ internal object BuiltInJvmPluginLoaderImpl : } override fun enable(plugin: JvmPlugin) { - if (plugin.isEnabled) return + if (plugin.isEnabled) error("Plugin '${plugin.name}' is already enabled and cannot be re-enabled.") ensureActive() runCatching { if (plugin is JvmPluginInternal) { @@ -118,7 +124,7 @@ internal object BuiltInJvmPluginLoaderImpl : } override fun disable(plugin: JvmPlugin) { - if (!plugin.isEnabled) return + if (!plugin.isEnabled) error("Plugin '${plugin.name}' is not already disabled and cannot be re-disabled.") if (MiraiConsole.isActive) ensureActive() diff --git a/backend/mirai-console/src/plugin/loader/PluginLoader.kt b/backend/mirai-console/src/plugin/loader/PluginLoader.kt index e4b9e863f..22021a7df 100644 --- a/backend/mirai-console/src/plugin/loader/PluginLoader.kt +++ b/backend/mirai-console/src/plugin/loader/PluginLoader.kt @@ -76,7 +76,7 @@ public interface PluginLoader

{ * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等). * @throws IllegalStateException 在插件已经被加载时抛出. 这属于意料之外的情况. */ - @Throws(PluginLoadException::class) + @Throws(IllegalStateException::class, PluginLoadException::class) public fun load(plugin: P) /** From 26f4ce5228182146fbda7acbc2e580fc51ad7ead Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 25 Nov 2020 18:31:00 +0800 Subject: [PATCH 15/52] Fix mistakenly exposed JvmPluginInternal.isEnabled --- backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt b/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt index 941fa1f95..a96917e3e 100644 --- a/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt +++ b/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt @@ -54,6 +54,7 @@ internal abstract class JvmPluginInternal( } final override var isEnabled: Boolean = false + internal set private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() } final override fun getResourceAsStream(path: String): InputStream? = From 23a692e950808fb729e9cad14131c84bdc066478 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 26 Nov 2020 07:50:35 +0800 Subject: [PATCH 16/52] Collect components just after plugin load --- .../src/internal/MiraiConsoleImplementationBridge.kt | 10 ---------- .../src/internal/extension/ComponentStorageInternal.kt | 10 +++++++++- .../src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt | 2 +- .../src/internal/plugin/JvmPluginInternal.kt | 9 +++++---- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt index be92004bc..8db5fa58d 100644 --- a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt @@ -34,7 +34,6 @@ import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.Pa import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig -import net.mamoe.mirai.console.internal.data.castOrNull import net.mamoe.mirai.console.internal.extension.BuiltInSingletonExtensionSelector import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl @@ -49,7 +48,6 @@ import net.mamoe.mirai.console.permission.PermissionService.Companion.permit import net.mamoe.mirai.console.permission.RootPermission import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.center.PluginCenter -import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.ConsoleExperimentalApi @@ -167,14 +165,6 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." } } - phase `collect extensions`@{ - for (resolvedPlugin in PluginManagerImpl.resolvedPlugins) { - resolvedPlugin.castOrNull()?.let { - GlobalComponentStorage.mergeWith(it.componentStorage) - } - } - } - phase `load SingletonExtensionSelector`@{ SingletonExtensionSelector.init() val instance = SingletonExtensionSelector.instance diff --git a/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt b/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt index dedf18192..f8697dcc6 100644 --- a/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt +++ b/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt @@ -50,6 +50,8 @@ internal data class DataExtensionRegistry( ) : ExtensionRegistry internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { + private val instances: MutableMap, MutableSet>> = ConcurrentHashMap() + @Suppress("UNCHECKED_CAST") internal fun ExtensionPoint.getExtensions(): Set> { val userDefined = instances.getOrPut(this, ::CopyOnWriteArraySet) as Set> @@ -61,6 +63,13 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { return builtins?.plus(userDefined) ?: userDefined } + // unused for now + internal fun removeExtensionsRegisteredByPlugin(plugin: Plugin) { + instances.forEach { (_, u) -> + u.removeAll { it.plugin == plugin } + } + } + internal fun mergeWith(another: AbstractConcurrentComponentStorage) { for ((ep, list) in another.instances) { for (extensionRegistry in list) { @@ -154,7 +163,6 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { internal inline fun ExtensionPoint.useExtensions(block: (extension: T, plugin: Plugin?) -> Unit): Unit = withExtensions(block) - private val instances: MutableMap, MutableSet>> = ConcurrentHashMap() override fun contribute( extensionPoint: ExtensionPoint, plugin: Plugin, diff --git a/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt b/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt index 8e3537675..782de114f 100644 --- a/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt +++ b/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt @@ -105,7 +105,7 @@ internal object BuiltInJvmPluginLoaderImpl : } runCatching { check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin to be loaded by JvmPluginLoader.BuiltIn" } - plugin.internalOnLoad(plugin.componentStorage) + plugin.internalOnLoad() }.getOrElse { throw PluginLoadException("Exception while loading ${plugin.description.name}", it) } diff --git a/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt b/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt index a96917e3e..3b7fe0c95 100644 --- a/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt +++ b/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt @@ -16,6 +16,7 @@ import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.data.runCatchingLog import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.internal.data.mkdir +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.plugin.Plugin @@ -43,9 +44,6 @@ internal abstract class JvmPluginInternal( parentCoroutineContext: CoroutineContext, ) : JvmPlugin, CoroutineScope { - @Suppress("LeakingThis") - internal val componentStorage: PluginComponentStorage = PluginComponentStorage(this) - final override val parentPermission: Permission by lazy { PermissionService.INSTANCE.register( PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*"), @@ -101,13 +99,16 @@ internal abstract class JvmPluginInternal( } @Throws(Throwable::class) - internal fun internalOnLoad(componentStorage: PluginComponentStorage) { + internal fun internalOnLoad() { + val componentStorage = PluginComponentStorage(this) onLoad(componentStorage) + GlobalComponentStorage.mergeWith(componentStorage) } internal fun internalOnEnable(): Boolean { parentPermission if (!firstRun) refreshCoroutineContext() + kotlin.runCatching { onEnable() }.fold( From fe5bf0e10c21da0c2904a06b94015df89427700c Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 26 Nov 2020 07:51:48 +0800 Subject: [PATCH 17/52] 1.0.1-dev-2 --- .../mirai-console/src/internal/MiraiConsoleBuildConstants.kt | 4 ++-- buildSrc/src/main/kotlin/Versions.kt | 2 +- tools/gradle-plugin/src/VersionConstants.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt b/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt index 7d8d8614b..3d3148a97 100644 --- a/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt +++ b/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt @@ -14,8 +14,8 @@ import java.time.Instant internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic - val buildDate: Instant = Instant.ofEpochSecond(1606185513) - const val versionConst: String = "1.0.1-dev-1" + val buildDate: Instant = Instant.ofEpochSecond(1606348297) + const val versionConst: String = "1.0.1-dev-2" @JvmStatic val version: SemVersion = SemVersion(versionConst) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 6bc49035d..aa52c2c34 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -11,7 +11,7 @@ object Versions { const val core = "1.3.3" - const val console = "1.0.1-dev-1" + const val console = "1.0.1-dev-2" const val consoleGraphical = "0.0.7" const val consoleTerminal = console diff --git a/tools/gradle-plugin/src/VersionConstants.kt b/tools/gradle-plugin/src/VersionConstants.kt index 919c42906..4a756e84f 100644 --- a/tools/gradle-plugin/src/VersionConstants.kt +++ b/tools/gradle-plugin/src/VersionConstants.kt @@ -10,6 +10,6 @@ package net.mamoe.mirai.console.gradle internal object VersionConstants { - const val CONSOLE_VERSION = "1.0.1-dev-1" // value is written here automatically during build + const val CONSOLE_VERSION = "1.0.1-dev-2" // value is written here automatically during build const val CORE_VERSION = "1.3.3" // value is written here automatically during build } \ No newline at end of file From c514883557093aac9f3ef6ed29bbb22905fb0d08 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 26 Nov 2020 12:42:44 +0800 Subject: [PATCH 18/52] 1.0.1 --- buildSrc/src/main/kotlin/Versions.kt | 2 +- docs/ConfiguringProjects.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index aa52c2c34..106302720 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -11,7 +11,7 @@ object Versions { const val core = "1.3.3" - const val console = "1.0.1-dev-2" + const val console = "1.0.1" const val consoleGraphical = "0.0.7" const val consoleTerminal = console diff --git a/docs/ConfiguringProjects.md b/docs/ConfiguringProjects.md index 9f876c25a..17ee1530b 100644 --- a/docs/ConfiguringProjects.md +++ b/docs/ConfiguringProjects.md @@ -20,8 +20,8 @@ console 由后端和前端一起工作. 使用时必须选择一个前端. | 版本类型 | 版本号 | |:------:|:------------------------------:| -| 稳定 | 1.0.0 | -| 预览 | - | +| 稳定 | 1.0.1 | +| 预览 | - | | 开发 | [![Version]][Bintray Download] | ## 配置项目 From ecef9f8c0184b3e97645930400c691ce6d597b16 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 26 Nov 2020 12:46:13 +0800 Subject: [PATCH 19/52] Revert "ReportGenerator", postpone to 1.1.0 release. This reverts commit 96be869e --- .../MiraiConsoleImplementationBridge.kt | 2 +- .../data/MultiFilePluginDataStorageImpl.kt | 27 +-- .../internal/util/report/ReportGenerator.kt | 204 ------------------ 3 files changed, 3 insertions(+), 230 deletions(-) delete mode 100644 backend/mirai-console/src/internal/util/report/ReportGenerator.kt diff --git a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt index 8db5fa58d..3904fa52d 100644 --- a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt @@ -70,7 +70,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI MiraiConsole { override val pluginCenter: PluginCenter get() = throw UnsupportedOperationException("PluginCenter is not supported yet") - internal val instance: MiraiConsoleImplementation by MiraiConsoleImplementation.Companion::instance + private val instance: MiraiConsoleImplementation by MiraiConsoleImplementation.Companion::instance override val buildDate: Instant by MiraiConsoleBuildConstants::buildDate override val version: SemVersion by MiraiConsoleBuildConstants::version override val rootPath: Path by instance::rootPath diff --git a/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt b/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt index e27a62041..a9e9a1f2c 100644 --- a/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt +++ b/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt @@ -12,7 +12,6 @@ package net.mamoe.mirai.console.internal.data import kotlinx.serialization.json.Json import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.data.* -import net.mamoe.mirai.console.internal.util.report.ReportGenerator import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.SilentLogger @@ -72,39 +71,17 @@ internal open class MultiFilePluginDataStorageImpl( @ConsoleExperimentalApi public override fun store(holder: PluginDataHolder, instance: PluginData) { - var yamlRendered: String? = null getPluginDataFile(holder, instance).writeText( kotlin.runCatching { yaml.encodeToString(instance.updaterSerializer, Unit).also { - yamlRendered = it yaml.decodeAnyFromString(it) // test yaml } - }.recoverCatching { exception -> + }.recoverCatching { // Just use mainLogger for convenience. MiraiConsole.mainLogger.warning( "Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. " + "Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new", - exception - ) - val reportPath = ReportGenerator.generateReport("YamlKt-Format-") { - pw.println("Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. ") - pw.println("Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new") - pw.println() - yamlRendered?.let { - title("Rendered YAML") - pw.println(it) - pw.println() - } - title("Exception") - renderException(exception) - renderCurrentThread() - } - MiraiConsole.mainLogger.warning( - "Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. " + - "Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new" - ) - MiraiConsole.mainLogger.warning( - "Error Report location: $reportPath" + it ) json.encodeToString(instance.updaterSerializer, Unit) }.getOrElse { diff --git a/backend/mirai-console/src/internal/util/report/ReportGenerator.kt b/backend/mirai-console/src/internal/util/report/ReportGenerator.kt deleted file mode 100644 index 35f3b9442..000000000 --- a/backend/mirai-console/src/internal/util/report/ReportGenerator.kt +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.console.internal.util.report - -import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants -import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge -import net.mamoe.mirai.console.internal.data.isDirectory -import net.mamoe.mirai.console.internal.data.isFile -import net.mamoe.mirai.console.internal.data.mkdir -import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl -import net.mamoe.mirai.console.permission.PermissionService -import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description -import java.io.* -import java.lang.management.LockInfo -import java.lang.management.ManagementFactory -import java.lang.management.MonitorInfo -import java.lang.management.ThreadInfo -import java.nio.file.Files -import java.nio.file.Path -import java.time.ZoneId -import java.time.format.DateTimeFormatter - -@Suppress("unused") -internal class ReportGenerator( - val pw: PrintWriter -) : Closeable { - companion object { - internal val threadMXBean = ManagementFactory.getThreadMXBean() - internal val directory by lazy { - MiraiConsole.rootPath.resolve("error-reports") - } - - fun ThreadInfo.dumpTo(sb: Appendable) { - sb.run { - append('\"') - append(threadName) - append("\" Id=") - append(threadId.toString()) - append(" ") - append(threadState.toString()) - - lockName?.let { append(" on ").append(it) } - lockOwnerName?.let { append(" owned by \"").append(it).append("\" Id=").append(lockOwnerId.toString()) } - if (isSuspended) { - sb.append(" (suspended)") - } - if (isInNative) { - sb.append(" (in native)") - } - sb.append('\n') - var i = 0 - while (i < stackTrace.size) { - val ste: StackTraceElement = stackTrace[i] - sb.append("\tat $ste") - sb.append('\n') - if (i == 0 && lockInfo != null) { - when (threadState) { - Thread.State.BLOCKED -> { - sb.append("\t- blocked on $lockInfo") - sb.append('\n') - } - Thread.State.WAITING -> { - sb.append("\t- waiting on $lockInfo") - sb.append('\n') - } - Thread.State.TIMED_WAITING -> { - sb.append("\t- waiting on $lockInfo") - sb.append('\n') - } - else -> { - } - } - } - for (mi: MonitorInfo in lockedMonitors) { - if (mi.lockedStackDepth == i) { - sb.append("\t- locked $mi") - sb.append('\n') - } - } - i++ - } - val locks: Array = lockedSynchronizers - if (locks.isNotEmpty()) { - sb.append("\n\tNumber of locked synchronizers = " + locks.size) - sb.append('\n') - for (li: LockInfo in locks) { - sb.append("\t- $li") - sb.append('\n') - } - } - } - } - - fun generateToString(action: ReportGenerator.() -> Unit): String { - return StringWriter().apply { - ReportGenerator(PrintWriter(this)).use(action) - }.toString() - } - - fun generateReport( - prefix: String = "", - action: ReportGenerator.() -> Unit - ): Path { - val now = System.currentTimeMillis() - var counter = 0 - var outputName = "$prefix$now.log" - directory.mkdir() - var path: Path - do { - path = directory.resolve(outputName) - if (!path.isFile && !path.isDirectory) { - break - } - outputName = "$prefix$now-$counter.log" - counter++ - } while (true) - ReportGenerator(PrintWriter(BufferedWriter(OutputStreamWriter(Files.newOutputStream(path))))) - .use(action) - return path - } - } - - fun renderCurrentThread() { - title("Current Thread") - renderThread(Thread.currentThread()) - } - - fun renderThread(thread: Thread) { - threadMXBean.getThreadInfo( - longArrayOf(thread.id), - true, - true - )[0].dumpTo(pw) - } - - fun title(title: String) { - pw.append("=============== [ ").append(title).append(" ] ===============") - pw.println() - } - - fun dumpSystemEnv() { - title("System Env") - - pw.println("SysEnv") - pw.println() - pw.println("```") - System.getenv().forEach { (key, value) -> - pw.println("$key\t=\t$value") - } - pw.println("```") - pw.println() - pw.println("JavaProp") - pw.println() - pw.println("```") - System.getProperties().store(pw, null) - pw.println("```") - pw.println() - } - - fun dumpConsoleEnv() { - title("Mirai Console Env") - val buildDateFormatted = - MiraiConsoleBuildConstants.buildDate.atZone(ZoneId.systemDefault()) - .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) - pw.append("MiraiConsole v${MiraiConsoleBuildConstants.versionConst}, built on ") - .append(buildDateFormatted) - .append(".\n") - pw.println("FrontEnd:") - pw.append("\t").println(MiraiConsoleImplementationBridge.instance.javaClass.name) - pw.append("\t").println(MiraiConsoleImplementationBridge.frontEndDescription.render()) - pw.println() - pw.println("Plugins:") - PluginManagerImpl.resolvedPlugins.forEach { plugin -> - val desc = plugin.description - pw.append("\t").append(desc.name).append(" v").append(desc.version.toString()).append(" by ").append(desc.author).println() - pw.append("\t\t `-- ").println(plugin.javaClass.name) - } - pw.println() - pw.println("PermissionService: ") - pw.append("\t").println(PermissionService.INSTANCE) - pw.append("\t\t`- ").println(PermissionService.INSTANCE.javaClass) - } - - fun renderException(throwable: Throwable) { - throwable.printStackTrace(pw) - pw.println() - } - - fun hr() { - pw.println("====================================================") - } - - override fun close() { - pw.close() - } -} \ No newline at end of file From 3b39be6bdc2626c50bca23a3bb672edda4f03cb1 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 26 Nov 2020 12:53:23 +0800 Subject: [PATCH 20/52] Revert "Add MiraiConsoleImplementation.isInitialized", postpone to 1.1.0 This reverts commit 8f61943c --- .../src/MiraiConsoleImplementation.kt | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/backend/mirai-console/src/MiraiConsoleImplementation.kt b/backend/mirai-console/src/MiraiConsoleImplementation.kt index 361f678b9..3adce0f46 100644 --- a/backend/mirai-console/src/MiraiConsoleImplementation.kt +++ b/backend/mirai-console/src/MiraiConsoleImplementation.kt @@ -185,27 +185,12 @@ public interface MiraiConsoleImplementation : CoroutineScope { * 可由前端调用, 获取当前的 [MiraiConsoleImplementation] 实例 * * 必须在 [start] 之后才能使用, 否则抛出 [UninitializedPropertyAccessException] - * - * @see isInitialized */ @JvmStatic @ConsoleFrontEndImplementation public fun getInstance(): MiraiConsoleImplementation = instance - /** - * 当 [MiraiConsoleImplementation] 已经初始化后返回 `true` - */ - @JvmStatic - @ConsoleFrontEndImplementation - public val isInitialized: Boolean - get() = ::instance.isInitialized - - /** - * 由前端调用, 初始化 [MiraiConsole] 实例并启动 - * - * @see getInstance - * @see isInitialized - */ + /** 由前端调用, 初始化 [MiraiConsole] 实例并启动 */ @JvmStatic @ConsoleFrontEndImplementation @Throws(MalformedMiraiConsoleImplementationError::class) From 635d0bfdecdc4451a33b65766c5c567fe12e125d Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Thu, 26 Nov 2020 22:47:59 +0800 Subject: [PATCH 21/52] Redesign Requirement parsing --- .../internal/util/semver/RangeTokenReader.kt | 250 ------------- .../internal/util/semver/RequirementParser.kt | 335 ++++++++++++++++++ .../util/semver/SemVersionInternal.kt | 37 +- 3 files changed, 356 insertions(+), 266 deletions(-) delete mode 100644 backend/mirai-console/src/internal/util/semver/RangeTokenReader.kt create mode 100644 backend/mirai-console/src/internal/util/semver/RequirementParser.kt diff --git a/backend/mirai-console/src/internal/util/semver/RangeTokenReader.kt b/backend/mirai-console/src/internal/util/semver/RangeTokenReader.kt deleted file mode 100644 index 17064132b..000000000 --- a/backend/mirai-console/src/internal/util/semver/RangeTokenReader.kt +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -@file:Suppress("MemberVisibilityCanBePrivate") - -package net.mamoe.mirai.console.internal.util.semver - -import net.mamoe.mirai.console.util.SemVersion -import kotlin.math.max -import kotlin.math.min - -internal object RangeTokenReader { - enum class TokenType { - STRING, - - /* 左括号 */ - LEFT, - - /* 右括号 */ - RIGHT, - - /* || */ - OR, - - /* && */ - AND, - GROUP - } - - sealed class Token { - abstract val type: TokenType - abstract val value: String - abstract val position: Int - - class LeftBracket(override val position: Int) : Token() { - override val type: TokenType get() = TokenType.LEFT - override val value: String get() = "{" - - override fun toString(): String = "LB{" - } - - class RightBracket(override val position: Int) : Token() { - override val type: TokenType get() = TokenType.RIGHT - override val value: String get() = "}" - - override fun toString(): String = "RB}" - } - - class Or(override val position: Int) : Token() { - override val type: TokenType get() = TokenType.OR - override val value: String get() = "||" - override fun toString(): String = "OR||" - } - - class And(override val position: Int) : Token() { - override val type: TokenType get() = TokenType.AND - override val value: String get() = "&&" - - override fun toString(): String = "AD&&" - } - - class Group(val values: List, override val position: Int) : Token() { - override val type: TokenType get() = TokenType.GROUP - override val value: String get() = "" - } - - class Raw(val source: String, val start: Int, val end: Int) : Token() { - override val value: String get() = source.substring(start, end) - override val position: Int - get() = start - override val type: TokenType get() = TokenType.STRING - - override fun toString(): String = "R:$value" - } - } - - fun parseToTokens(source: String): List = ArrayList( - max(source.length / 3, 16) - ).apply { - var index = 0 - var position = 0 - fun flushOld() { - if (position > index) { - val id = index - index = position - for (i in id until position) { - if (!source[i].isWhitespace()) { - add(Token.Raw(source, id, position)) - return - } - } - } - } - - val iterator = source.indices.iterator() - for (i in iterator) { - position = i - when (source[i]) { - '{' -> { - flushOld() - add(Token.LeftBracket(i)) - index = i + 1 - } - '|' -> { - if (source.getOrNull(i + 1) == '|') { - flushOld() - add(Token.Or(i)) - index = i + 2 - iterator.nextInt() - } - } - '&' -> { - if (source.getOrNull(i + 1) == '&') { - flushOld() - add(Token.And(i)) - index = i + 2 - iterator.nextInt() - } - } - '}' -> { - flushOld() - add(Token.RightBracket(i)) - index = i + 1 - } - } - } - position = source.length - flushOld() - } - - fun collect(source: String, tokens: Iterator, root: Boolean): List = ArrayList().apply { - tokens.forEach { token -> - if (token is Token.LeftBracket) { - add(Token.Group(collect(source, tokens, false), token.position)) - } else if (token is Token.RightBracket) { - if (root) { - throw IllegalArgumentException("Syntax error: Unexpected }, ${buildMsg(source, token.position)}") - } else { - return@apply - } - } else add(token) - } - if (!root) { - throw IllegalArgumentException("Syntax error: Excepted }, ${buildMsg(source, source.length)}") - } - } - - private fun buildMsg(source: String, position: Int): String { - val ed = min(position + 10, source.length) - val st = max(0, position - 10) - return buildString { - append('`') - if (st != 0) append("...") - append(source, st, ed) - if (ed != source.length) append("...") - append("` at ").append(position) - } - } - - fun check(source: String, tokens: Iterator, group: Token.Group?) { - if (!tokens.hasNext()) { - throw IllegalArgumentException("Syntax error: empty rule, ${buildMsg(source, group?.position ?: 0)}") - } - var type = false - do { - val next = tokens.next() - if (type) { - if (next is Token.Group || next is Token.Raw) { - throw IllegalArgumentException("Syntax error: Except logic but got expression, ${buildMsg(source, next.position)}") - } - } else { - if (next is Token.Or || next is Token.And) { - throw IllegalArgumentException("Syntax error: Except expression but got logic, ${buildMsg(source, next.position)}") - } - if (next is Token.Group) { - check(source, next.values.iterator(), next) - } - } - type = !type - } while (tokens.hasNext()) - if (!type) { - throw IllegalArgumentException("Syntax error: Except more expression, ${buildMsg(source, group?.values?.last()?.position ?: source.length)}") - } - } - - fun parse(source: String, token: Token): RequirementInternal { - return when (token) { - is Token.Group -> { - if (token.values.size == 1) { - parse(source, token.values.first()) - } else { - val logic = token.values.asSequence().map { it.type }.filter { - it == TokenType.OR || it == TokenType.AND - }.toSet() - if (logic.size == 2) { - throw IllegalArgumentException("Syntax error: || and && cannot use in one group, ${buildMsg(source, token.position)}") - } - val rules = token.values.asSequence().filter { - it is Token.Raw || it is Token.Group - }.map { parse(source, it) }.toList() - when (logic.first()) { - TokenType.OR -> { - return object : RequirementInternal { - override fun test(version: SemVersion): Boolean { - rules.forEach { if (it.test(version)) return true } - return false - } - } - } - TokenType.AND -> { - return object : RequirementInternal { - override fun test(version: SemVersion): Boolean { - rules.forEach { if (!it.test(version)) return false } - return true - } - } - } - else -> throw AssertionError() - } - } - } - is Token.Raw -> SemVersionInternal.parseRule(token.value) - else -> throw AssertionError() - } - } - - fun StringBuilder.dump(prefix: String, token: Token) { - when (token) { - is Token.LeftBracket -> append("${prefix}LF {\n") - - is Token.RightBracket -> append("${prefix}LR }\n") - - is Token.Or -> append("${prefix}OR ||\n") - - is Token.And -> append("${prefix}AND &&\n") - is Token.Group -> { - append("${prefix}GROUP {\n") - token.values.forEach { dump("$prefix ", it) } - append("${prefix}}\n") - } - is Token.Raw -> append("${prefix}RAW ${token.value}\n") - } - } -} \ No newline at end of file diff --git a/backend/mirai-console/src/internal/util/semver/RequirementParser.kt b/backend/mirai-console/src/internal/util/semver/RequirementParser.kt new file mode 100644 index 000000000..011f18954 --- /dev/null +++ b/backend/mirai-console/src/internal/util/semver/RequirementParser.kt @@ -0,0 +1,335 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.internal.util.semver + +import kotlin.math.max +import kotlin.math.min + +internal class RequirementParser { + sealed class Token { + open var line: Int = -1 + open var pos: Int = -1 + open var sourcePos: Int = -1 + open lateinit var content: String + + sealed class GroupBod : Token() { + class Left : GroupBod() { + override var content: String + get() = "{" + set(_) {} + } + + class Right : GroupBod() { + override var content: String + get() = "}" + set(_) {} + } + } + + sealed class Logic : Token() { + class And : Logic() { + override var content: String + get() = "&&" + set(_) {} + } + + class Or : Logic() { + override var content: String + get() = "||" + set(_) {} + } + } + + class Content : Token() + class Ending : Token() { + override var content: String + get() = "" + set(_) {} + } + + object Begin : Token() { + override var content: String + get() = "" + set(_) {} + override var line: Int + get() = 0 + set(_) {} + override var pos: Int + get() = 0 + set(_) {} + override var sourcePos: Int + get() = 0 + set(_) {} + } + + override fun toString(): String { + return javaClass.canonicalName.substringAfterLast('.') + " - $content [$line, $pos]" + } + } + + companion object { + const val END = '\u0000' + } + + class TokenReader( + @JvmField val content: String + ) { + @JvmField + var pos: Int = 0 + + @JvmField + var line: Int = 0 + + @JvmField + var posi: Int = 0 + + @JvmField + var latestToken: Token = Token.Begin + + @JvmField + var insertToken: Token? = Token.Begin + fun peekChar(): Char { + if (pos < content.length) + return content[pos] + return END + } + + fun peekNextChar(): Char { + if (pos + 1 < content.length) + return content[pos + 1] + return END + } + + fun nextChar(): Char { + val char = peekChar() + pos++ + if (char == '\n') { + line++ + posi = 0 + } else { + posi++ + } + return char + } + + fun nextToken(): Token { + insertToken?.let { insertToken = null; return it } + return nextToken0().also { latestToken = it } + } + + private fun nextToken0(): Token { + if (pos < content.length) { + while (peekChar().isWhitespace()) { + nextChar() + } + val startIndex = pos + if (startIndex >= content.length) { + return Token.Ending().also { + it.line = line + it.pos = posi + it.sourcePos = content.length + } + } + val pline = line + val ppos = posi + nextChar() + when (content[startIndex]) { + '&' -> { + if (peekChar() == '&') { + return Token.Logic.And().also { + it.pos = ppos + it.line = pline + it.sourcePos = startIndex + nextChar() + } + } + } + '|' -> { + if (peekChar() == '|') { + return Token.Logic.Or().also { + nextChar() + it.pos = ppos + it.line = pline + it.sourcePos = startIndex + } + } + } + '{' -> { + return Token.GroupBod.Left().also { + it.pos = ppos + it.line = pline + it.sourcePos = startIndex + } + } + '}' -> { + return Token.GroupBod.Right().also { + it.pos = ppos + it.line = pline + it.sourcePos = startIndex + } + } + } + while (true) { + when (val c = peekChar()) { + '&', '|' -> { + if (c == peekNextChar()) { + break + } + nextChar() + } + '{', '}' -> { + break + } + END -> break + else -> nextChar() + } + } + val endIndex = pos + return Token.Content().also { + it.content = content.substring(startIndex, endIndex) + it.pos = ppos + it.line = pline + it.sourcePos = startIndex + } + } + return Token.Ending().also { + it.line = line + it.pos = posi + it.sourcePos = content.length + } + } + } + + interface TokensProcessor { + fun process(reader: TokenReader): R + fun processLine(reader: TokenReader): R + fun processLogic(isAnd: Boolean, chunks: Iterable): R + } + + abstract class ProcessorBase : TokensProcessor { + fun Token.ia(reader: TokenReader, msg: String, cause: Throwable? = null): Nothing { + throw IllegalArgumentException("$msg (at [$line, $pos], ${cutSource(reader, sourcePos)})", cause) + } + + fun cutSource(reader: TokenReader, index: Int): String { + val content = reader.content + val s = max(0, index - 10) + val e = min(content.length, index + 10) + return content.substring(s, e) + } + + override fun process(reader: TokenReader): R { + return when (val nextToken = reader.nextToken()) { + is Token.Begin, + is Token.GroupBod.Left -> { + val first = when (val next = reader.nextToken()) { + is Token.Content -> { + processString(reader, next) + } + is Token.GroupBod.Right -> { + nextToken.ia( + reader, if (nextToken is Token.Begin) + "Invalid token `}`" + else "The first token cannot be Group Ending" + ) + } + is Token.Logic -> { + nextToken.ia(reader, "The first token cannot be Token.Logic") + } + is Token.Ending -> { + nextToken.ia( + reader, if (nextToken is Token.Begin) + "Requirement cannot be blank" + else "Except more tokens" + ) + } + is Token.GroupBod.Left -> { + reader.insertToken = next + process(reader) + } + else -> { + next.ia(reader, "Bad token $next") + } + } + // null -> not set + // true -> AND mode + // false-> OR mode + var mode: Boolean? = null + val chunks = arrayListOf(first) + while (true) { + when (val next = reader.nextToken()) { + is Token.Ending, + is Token.GroupBod.Right -> { + val isEndingOfGroup = next is Token.GroupBod.Right + val isStartingOfGroup = nextToken is Token.GroupBod.Left + if (isStartingOfGroup != isEndingOfGroup) { + fun getType(type: Boolean) = if (type) "`}`" else "" + next.ia(reader, "Except ${getType(isStartingOfGroup)} but got ${getType(isEndingOfGroup)}") + } else { + // reader.insertToken = next + break + } + } + is Token.Logic -> { + val stx = next is Token.Logic.And + if (mode == null) mode = stx + else if (mode != stx) { + fun getMode(type: Boolean) = if (type) "`&&`" else "`||`" + next.ia( + reader, "Cannot change logic mode after setting. " + + "Except ${getMode(mode)} but got ${getMode(stx)}" + ) + } + chunks.add(process(reader)) + } + else -> { + next.ia( + reader, "Except ${ + when (mode) { + null -> "`&&` or `||`" + true -> "`&&`" + false -> "`||`" + } + } but get `${next.content}`" + ) + } + } + } + if (mode == null) { + first + } else { + processLogic(mode, chunks) + } + } + is Token.Content -> { + processString(reader, nextToken) + } + is Token.Ending -> { + nextToken.ia(reader, "Except more values.") + } + else -> { + nextToken.ia(reader, "Assert Error: $nextToken") + } + } + } + + abstract fun processString(reader: TokenReader, token: Token.Content): R + + + override fun processLine(reader: TokenReader): R { + return process(reader).also { + val tok = reader.nextToken() + if (reader.nextToken() !is Token.Ending) { + tok.ia(reader, "Token reader stream not done") + } + } + } + } +} diff --git a/backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt b/backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt index 1d9c1af03..bb9f9a947 100644 --- a/backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt +++ b/backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt @@ -9,7 +9,6 @@ package net.mamoe.mirai.console.internal.util.semver -import net.mamoe.mirai.console.internal.util.semver.RangeTokenReader.dump import net.mamoe.mirai.console.util.SemVersion import kotlin.math.max import kotlin.math.min @@ -166,21 +165,27 @@ internal object SemVersionInternal { @JvmStatic - fun parseRangeRequirement(requirement: String): RequirementInternal { - if (requirement.isBlank()) { - throw IllegalArgumentException("Invalid requirement: Empty requirement rule.") - } - val tokens = RangeTokenReader.parseToTokens(requirement) - val collected = RangeTokenReader.collect(requirement, tokens.iterator(), true) - RangeTokenReader.check(requirement, collected.iterator(), null) - return kotlin.runCatching { - RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0)) - }.onFailure { error -> - throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString { - collected.forEach { dump("", it) } - }, error) - }.getOrThrow() - } + fun parseRangeRequirement(requirement: String): RequirementInternal = + object : RequirementParser.ProcessorBase() { + override fun processLogic(isAnd: Boolean, chunks: Iterable): RequirementInternal { + return if (isAnd) object : RequirementInternal { + override fun test(version: SemVersion): Boolean { + return chunks.all { it.test(version) } + } + } else object : RequirementInternal { + override fun test(version: SemVersion): Boolean { + return chunks.any { it.test(version) } + } + } + } + + override fun processString( + reader: RequirementParser.TokenReader, + token: RequirementParser.Token.Content + ): RequirementInternal = kotlin.runCatching { + parseRule(token.content) + }.getOrElse { token.ia(reader, "Error in parsing rule `${token.content}`", it) } + }.processLine(RequirementParser.TokenReader(requirement)) @JvmStatic fun compareInternal(source: SemVersion, other: SemVersion): Int { From 2132e4d0957afe2326137560e893c3de3ed4481d Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Thu, 26 Nov 2020 22:56:51 +0800 Subject: [PATCH 22/52] IDEA Plugin: Requirement checking --- .../src/diagnostics/MiraiConsoleErrors.kt | 3 + .../MiraiConsoleErrorsRendering.kt | 8 + .../org/example/myplugin/MyPluginMain.kt | 13 + .../ContextualParametersChecker.kt | 18 +- .../src/util/RequirementHelper.kt | 43 +++ .../src/util/RequirementParser.kt | 335 ++++++++++++++++++ 6 files changed, 415 insertions(+), 5 deletions(-) create mode 100644 tools/intellij-plugin/src/util/RequirementHelper.kt create mode 100644 tools/intellij-plugin/src/util/RequirementParser.kt diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt index 7f981069c..53df01f1d 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt @@ -51,6 +51,9 @@ object MiraiConsoleErrors { @JvmField val ILLEGAL_PERMISSION_REGISTER_USE = create(ERROR) + @JvmField + val ILLEGAL_VERSION_REQUIREMENT = create(ERROR) + @Suppress("ObjectPropertyName", "unused") @JvmField @Deprecated("", level = DeprecationLevel.ERROR) diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt index 5858bea53..73e60cd4b 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt @@ -16,6 +16,7 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.IL import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAMESPACE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_REGISTER_USE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.UNSERIALIZABLE_TYPE import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages @@ -87,6 +88,13 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { Renderers.DECLARATION_NAME, Renderers.STRING ) + + put( + ILLEGAL_VERSION_REQUIREMENT, + "{1}", + Renderers.STRING, + Renderers.STRING + ) } override fun getMap() = MAP diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt index 5f3ad337a..64c9095ad 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt @@ -8,6 +8,7 @@ import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin +import net.mamoe.mirai.console.util.SemVersion const val T = "org.example" // 编译期常量 @@ -24,6 +25,18 @@ object MyPluginMain : KotlinPlugin( PermissionService.INSTANCE.register(permissionId("dvs"), "ok") PermissionService.INSTANCE.register(permissionId("perm with space"), "error") PermissionId("Namespace with space", "Name with space") + SemVersion.parseRangeRequirement("") + SemVersion.parseRangeRequirement("
") + SemVersion.parseRangeRequirement("SB YELLOW") + SemVersion.parseRangeRequirement("1.0.0 || 2.0.0 || ") + SemVersion.parseRangeRequirement("1.0.0 || 2.0.0") + SemVersion.parseRangeRequirement("1.0.0 || 2.0.0 && 3.0.0") + SemVersion.parseRangeRequirement("{}") + SemVersion.parseRangeRequirement("||") + SemVersion.parseRangeRequirement(">= 114.514 || = 1919.810 || (1.1, 1.2)") + SemVersion.parseRangeRequirement("0.0.0 || {90.48}") + SemVersion.parseRangeRequirement("{114514.1919810}") + SemVersion.parseRangeRequirement("}") } fun test() { diff --git a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt index 4163b04d4..86a906bf0 100644 --- a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt +++ b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt @@ -15,11 +15,14 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.IL import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAME import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAMESPACE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds import net.mamoe.mirai.console.intellij.resolve.resolveAllCalls import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues import net.mamoe.mirai.console.intellij.resolve.valueParametersWithArguments +import net.mamoe.mirai.console.intellij.util.RequirementHelper +import net.mamoe.mirai.console.intellij.util.RequirementParser import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.psi.KtDeclaration @@ -45,8 +48,10 @@ class ContextualParametersChecker : DeclarationChecker { fun checkPluginId(inspectionTarget: PsiElement, value: String): Diagnostic? { if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 不能为空. \n插件 Id$syntax") - if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, - "插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax") + if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on( + inspectionTarget, + "插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax" + ) val lowercaseId = value.toLowerCase() @@ -115,9 +120,12 @@ class ContextualParametersChecker : DeclarationChecker { @Suppress("UNUSED_PARAMETER") fun checkVersionRequirement(inspectionTarget: PsiElement, value: String): Diagnostic? { - // TODO: 2020/10/23 checkVersionRequirement - // 实现: 先在 MiraiConsoleErrors 添加一个 error, 再检测 value 并 report 一个错误. - return null + return try { + RequirementHelper.RequirementChecker.processLine(RequirementParser.TokenReader(value)) + null + } catch (err: Throwable) { + ILLEGAL_VERSION_REQUIREMENT.on(inspectionTarget, value, err.message ?: err.toString()) + } } } diff --git a/tools/intellij-plugin/src/util/RequirementHelper.kt b/tools/intellij-plugin/src/util/RequirementHelper.kt new file mode 100644 index 000000000..f3e99c56c --- /dev/null +++ b/tools/intellij-plugin/src/util/RequirementHelper.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.intellij.util + +@Suppress("RegExpRedundantEscape") +object RequirementHelper { + private val directVersion = """^[0-9]+(\.[0-9]+)+(|[\-+].+)$""".toRegex() + private val versionSelect = """^[0-9]+(\.[0-9]+)*\.x$""".toRegex() + private val versionMathRange = + """([\[\(])([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))([\]\)])""".toRegex() + private val versionRule = """^((\>\=)|(\<\=)|(\=)|(\!\=)|(\>)|(\<))\s*([0-9]+(\.[0-9]+)+(|[\-+].+))$""".toRegex() + + private val SEM_VERSION_REGEX = + """^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$""".toRegex() + + fun isValid(rule: String): Boolean { + return rule.trim().let { + directVersion.matches(it) || + versionSelect.matches(it) || + versionMathRange.matches(it) || + versionRule.matches(it) + } + } + + internal object RequirementChecker : RequirementParser.ProcessorBase() { + override fun processLogic(isAnd: Boolean, chunks: Iterable) { + } + + override fun processString(reader: RequirementParser.TokenReader, token: RequirementParser.Token.Content) { + if (!isValid(token.content)) { + token.ia(reader, "`${token.content}` 无效.") + } + } + + } +} \ No newline at end of file diff --git a/tools/intellij-plugin/src/util/RequirementParser.kt b/tools/intellij-plugin/src/util/RequirementParser.kt new file mode 100644 index 000000000..892cd75cc --- /dev/null +++ b/tools/intellij-plugin/src/util/RequirementParser.kt @@ -0,0 +1,335 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.intellij.util + +import kotlin.math.max +import kotlin.math.min + +internal class RequirementParser { + sealed class Token { + open var line: Int = -1 + open var pos: Int = -1 + open var sourcePos: Int = -1 + open lateinit var content: String + + sealed class GroupBod : Token() { + class Left : GroupBod() { + override var content: String + get() = "{" + set(_) {} + } + + class Right : GroupBod() { + override var content: String + get() = "}" + set(_) {} + } + } + + sealed class Logic : Token() { + class And : Logic() { + override var content: String + get() = "&&" + set(_) {} + } + + class Or : Logic() { + override var content: String + get() = "||" + set(_) {} + } + } + + class Content : Token() + class Ending : Token() { + override var content: String + get() = "" + set(_) {} + } + + object Begin : Token() { + override var content: String + get() = "" + set(_) {} + override var line: Int + get() = 0 + set(_) {} + override var pos: Int + get() = 0 + set(_) {} + override var sourcePos: Int + get() = 0 + set(_) {} + } + + override fun toString(): String { + return javaClass.canonicalName.substringAfterLast('.') + " - $content [$line, $pos]" + } + } + + companion object { + const val END = '\u0000' + } + + class TokenReader( + @JvmField val content: String + ) { + @JvmField + var pos: Int = 0 + + @JvmField + var line: Int = 0 + + @JvmField + var posi: Int = 0 + + @JvmField + var latestToken: Token = Token.Begin + + @JvmField + var insertToken: Token? = Token.Begin + fun peekChar(): Char { + if (pos < content.length) + return content[pos] + return END + } + + fun peekNextChar(): Char { + if (pos + 1 < content.length) + return content[pos + 1] + return END + } + + fun nextChar(): Char { + val char = peekChar() + pos++ + if (char == '\n') { + line++ + posi = 0 + } else { + posi++ + } + return char + } + + fun nextToken(): Token { + insertToken?.let { insertToken = null; return it } + return nextToken0().also { latestToken = it } + } + + private fun nextToken0(): Token { + if (pos < content.length) { + while (peekChar().isWhitespace()) { + nextChar() + } + val startIndex = pos + if (startIndex >= content.length) { + return Token.Ending().also { + it.line = line + it.pos = posi + it.sourcePos = content.length + } + } + val pline = line + val ppos = posi + nextChar() + when (content[startIndex]) { + '&' -> { + if (peekChar() == '&') { + return Token.Logic.And().also { + it.pos = ppos + it.line = pline + it.sourcePos = startIndex + nextChar() + } + } + } + '|' -> { + if (peekChar() == '|') { + return Token.Logic.Or().also { + nextChar() + it.pos = ppos + it.line = pline + it.sourcePos = startIndex + } + } + } + '{' -> { + return Token.GroupBod.Left().also { + it.pos = ppos + it.line = pline + it.sourcePos = startIndex + } + } + '}' -> { + return Token.GroupBod.Right().also { + it.pos = ppos + it.line = pline + it.sourcePos = startIndex + } + } + } + while (true) { + when (val c = peekChar()) { + '&', '|' -> { + if (c == peekNextChar()) { + break + } + nextChar() + } + '{', '}' -> { + break + } + END -> break + else -> nextChar() + } + } + val endIndex = pos + return Token.Content().also { + it.content = content.substring(startIndex, endIndex) + it.pos = ppos + it.line = pline + it.sourcePos = startIndex + } + } + return Token.Ending().also { + it.line = line + it.pos = posi + it.sourcePos = content.length + } + } + } + + interface TokensProcessor { + fun process(reader: TokenReader): R + fun processLine(reader: TokenReader): R + fun processLogic(isAnd: Boolean, chunks: Iterable): R + } + + abstract class ProcessorBase : TokensProcessor { + fun Token.ia(reader: TokenReader, msg: String, cause: Throwable? = null): Nothing { + throw IllegalArgumentException("$msg (at [$line, $pos], ${cutSource(reader, sourcePos)})", cause) + } + + fun cutSource(reader: TokenReader, index: Int): String { + val content = reader.content + val s = max(0, index - 10) + val e = min(content.length, index + 10) + return content.substring(s, e) + } + + override fun process(reader: TokenReader): R { + return when (val nextToken = reader.nextToken()) { + is Token.Begin, + is Token.GroupBod.Left -> { + val first = when (val next = reader.nextToken()) { + is Token.Content -> { + processString(reader, next) + } + is Token.GroupBod.Right -> { + nextToken.ia( + reader, if (nextToken is Token.Begin) + "无效的关键字 `}`" + else "空规则组" + ) + } + is Token.Logic -> { + nextToken.ia(reader, "规则不允许以逻辑操作符开始") + } + is Token.Ending -> { + nextToken.ia( + reader, if (nextToken is Token.Begin) + "规则为空" + else "需要更多内容" + ) + } + is Token.GroupBod.Left -> { + reader.insertToken = next + process(reader) + } + else -> { + next.ia(reader, "Bad token $next") + } + } + // null -> not set + // true -> AND mode + // false-> OR mode + var mode: Boolean? = null + val chunks = arrayListOf(first) + while (true) { + when (val next = reader.nextToken()) { + is Token.Ending, + is Token.GroupBod.Right -> { + val isEndingOfGroup = next is Token.GroupBod.Right + val isStartingOfGroup = nextToken is Token.GroupBod.Left + if (isStartingOfGroup != isEndingOfGroup) { + fun getType(type: Boolean) = if (type) "`}`" else "<结束>" + next.ia(reader, "需要 ${getType(isStartingOfGroup)}, 但是找到了 ${getType(isEndingOfGroup)}") + } else { + // reader.insertToken = next + break + } + } + is Token.Logic -> { + val stx = next is Token.Logic.And + if (mode == null) mode = stx + else if (mode != stx) { + fun getMode(type: Boolean) = if (type) "`&&`" else "`||`" + next.ia( + reader, "为了避免语义混乱, 不允许在一层规则组混合使用 `&&` 和 `||`, 请显式使用 `{}` 分离. " + + "需要 ${getMode(mode)}, 但是找到了 ${getMode(stx)}" + ) + } + chunks.add(process(reader)) + } + else -> { + next.ia( + reader, "Except ${ + when (mode) { + null -> "`&&` or `||`" + true -> "`&&`" + false -> "`||`" + } + } but get `${next.content}`" + ) + } + } + } + if (mode == null) { + first + } else { + processLogic(mode, chunks) + } + } + is Token.Content -> { + processString(reader, nextToken) + } + is Token.Ending -> { + nextToken.ia(reader, "需要更多值.") + } + else -> { + nextToken.ia(reader, "Assert Error: $nextToken") + } + } + } + + abstract fun processString(reader: TokenReader, token: Token.Content): R + + + override fun processLine(reader: TokenReader): R { + return process(reader).also { + val tok = reader.nextToken() + if (reader.nextToken() !is Token.Ending) { + tok.ia(reader, "Token Reader 未完成解析") + } + } + } + } +} From baa447bccb27fd0e7f0d53c1abade8d564357e80 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 27 Nov 2020 13:04:59 +0800 Subject: [PATCH 23/52] Update docs --- tools/gradle-plugin/README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tools/gradle-plugin/README.md b/tools/gradle-plugin/README.md index 493d793dd..d1d1c79d0 100644 --- a/tools/gradle-plugin/README.md +++ b/tools/gradle-plugin/README.md @@ -9,10 +9,14 @@ Mirai Console Gradle 插件。 ## 功能 - 为 `main` 源集配置 `mirai-core`,`mirai-console` 依赖 -- 为 `test` 源集配置 `mirai-core-qqandroid`, `mirai-console-terminal` 的依赖 (用于启动测试) -- 添加 mirai 依赖仓库链接 -- 配置插件 JAR 打包构建任务 `buildPlugin` (带依赖) +- 为 `test` 源集配置 `mirai-core-qqandroid`, `mirai-console-terminal` 的依赖 (用于启动测试) +- 配置 Kotlin 编译目标为 Java 1.8 +- 配置 Kotlin 编译器 jvm-default 设置为 `all`, 即为所有接口中的默认实现生成 Java 1.8 起支持的 `default` 方法 +- 配置 Java 编译目标为 Java 1.8 +- 配置 Java 编译编码为 UTF-8 +- 配置插件 JAR 打包构建任务 `buildPlugin`(带依赖, 成品 JAR 可以被 Mirai Console 加载) +支持 Kotlin 多平台项目(Multiplatform Projects)。每个 JVM 或 Android 目标平台都会被如上配置,对应打包任务带有编译目标的名称,如 `buildPluginJvm` ### `buildPlugin` @@ -20,7 +24,7 @@ Mirai Console Gradle 插件。 #### 执行 `buildPlugin` ```shell script -$ gradlew buildPlugin +./gradlew buildPlugin ``` 打包结果存放在 `build/mirai/` 目录下。 @@ -45,3 +49,5 @@ mirai { excludeDependency("com.google.code.gson", "gson") } ``` + +插件一般不需要手动排除依赖。Mirai Console 已经包含的依赖都会自动在打包过程中被排除。 \ No newline at end of file From b201583136e2e84e2335e217a7642ef4d543ca21 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 28 Nov 2020 12:44:29 +0800 Subject: [PATCH 24/52] update docs --- docs/Contributing.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/Contributing.md b/docs/Contributing.md index 92595d39b..a9d0bf32d 100644 --- a/docs/Contributing.md +++ b/docs/Contributing.md @@ -31,9 +31,15 @@ Mirai Console 项目由四个模块组成:后端,前端,Gradle 插件,In 首次加载和构建 mirai-console 项目可能要花费数小时时间。 -### 发布版本 +## 贡献代码 -(针对拥有 Mirai Console write 权限的项目成员) +### 代码风格 +- 请优先使用 Kotlin +- 请遵守 [Kotlin 官方代码风格](https://www.kotlincn.net/docs/reference/coding-conventions.html) + +## 发布版本 + +(以下内容针对拥有 Mirai Console write 权限的项目成员) 若你要发布一个 Mirai Console dev release: From cc4737887308f76a2c31eb9e0bd5042ba0f7bedb Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 28 Nov 2020 13:02:32 +0800 Subject: [PATCH 25/52] Inline delegate functions --- .../src/util/AnsiMessageBuilder.kt | 59 ++++--------------- 1 file changed, 12 insertions(+), 47 deletions(-) diff --git a/backend/mirai-console/src/util/AnsiMessageBuilder.kt b/backend/mirai-console/src/util/AnsiMessageBuilder.kt index 892fc821e..d70be3501 100644 --- a/backend/mirai-console/src/util/AnsiMessageBuilder.kt +++ b/backend/mirai-console/src/util/AnsiMessageBuilder.kt @@ -131,53 +131,18 @@ public open class AnsiMessageBuilder internal constructor( } ///////////////////////////////////////////////////////////////////////////////// - override fun append(c: Char): AnsiMessageBuilder { - delegate.append(c); return this - } - - override fun append(csq: CharSequence?): AnsiMessageBuilder { - delegate.append(csq); return this - } - - override fun append(csq: CharSequence?, start: Int, end: Int): AnsiMessageBuilder { - delegate.append(csq, start, end); return this - } - - public fun append(any: Any?): AnsiMessageBuilder { - delegate.append(any); return this - } - - public fun append(value: String): AnsiMessageBuilder { - delegate.append(value); return this - } - - public fun append(value: String, start: Int, end: Int): AnsiMessageBuilder { - delegate.append(value, start, end); return this - } - - public fun append(value: Boolean): AnsiMessageBuilder { - delegate.append(value); return this - } - - public fun append(value: Float): AnsiMessageBuilder { - delegate.append(value); return this - } - - public fun append(value: Double): AnsiMessageBuilder { - delegate.append(value); return this - } - - public fun append(value: Int): AnsiMessageBuilder { - delegate.append(value); return this - } - - public fun append(value: Long): AnsiMessageBuilder { - delegate.append(value); return this - } - - public fun append(value: Short): AnsiMessageBuilder { - delegate.append(value); return this - } + override fun append(c: Char): AnsiMessageBuilder = apply { delegate.append(c) } + override fun append(csq: CharSequence?): AnsiMessageBuilder = apply { delegate.append(csq) } + override fun append(csq: CharSequence?, start: Int, end: Int): AnsiMessageBuilder = apply { delegate.append(csq, start, end) } + public fun append(any: Any?): AnsiMessageBuilder = apply { delegate.append(any) } + public fun append(value: String): AnsiMessageBuilder = apply { delegate.append(value) } + public fun append(value: String, start: Int, end: Int): AnsiMessageBuilder = apply { delegate.append(value, start, end) } + public fun append(value: Boolean): AnsiMessageBuilder = apply { delegate.append(value) } + public fun append(value: Float): AnsiMessageBuilder = apply { delegate.append(value) } + public fun append(value: Double): AnsiMessageBuilder = apply { delegate.append(value) } + public fun append(value: Int): AnsiMessageBuilder = apply { delegate.append(value) } + public fun append(value: Long): AnsiMessageBuilder = apply { delegate.append(value) } + public fun append(value: Short): AnsiMessageBuilder = apply { delegate.append(value) } ///////////////////////////////////////////////////////////////////////////////// } From e1f6e692434ecf9d1424ebc63e641ae53148ff29 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 28 Nov 2020 13:04:14 +0800 Subject: [PATCH 26/52] Adjustment AnsiMessageBuilder factory function names --- .../src/util/AnsiMessageBuilder.kt | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/backend/mirai-console/src/util/AnsiMessageBuilder.kt b/backend/mirai-console/src/util/AnsiMessageBuilder.kt index d70be3501..7ec49129d 100644 --- a/backend/mirai-console/src/util/AnsiMessageBuilder.kt +++ b/backend/mirai-console/src/util/AnsiMessageBuilder.kt @@ -15,7 +15,7 @@ import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.util.AnsiMessageBuilder.Companion.dropAnsi -public open class AnsiMessageBuilder internal constructor( +public open class AnsiMessageBuilder public constructor( public val delegate: StringBuilder ) : Appendable { override fun toString(): String = delegate.toString() @@ -24,6 +24,9 @@ public open class AnsiMessageBuilder internal constructor( * 在添加 ansi code 的时候建议使用此方法. * * 在 `noAnsi=true` 的时候会忽略此函数的调用 + * + * @see from + * @see builder */ public open fun ansi(code: String): AnsiMessageBuilder = append(code) @@ -90,9 +93,8 @@ public open class AnsiMessageBuilder internal constructor( public fun String.dropAnsi(): String = DROP_ANSI_PATTERN.replace(this, "") @JvmStatic - @JvmName("builder") // Java Factory Style @JvmOverloads - public operator fun invoke( + public fun from( builder: StringBuilder, noAnsi: Boolean = false ): AnsiMessageBuilder = if (noAnsi) { @@ -103,12 +105,11 @@ public open class AnsiMessageBuilder internal constructor( * @param capacity [StringBuilder] 的初始化大小 */ @JvmStatic - @JvmName("builder") // Java Factory Style @JvmOverloads - public operator fun invoke( + public fun builder( capacity: Int = 16, noAnsi: Boolean = false - ): AnsiMessageBuilder = invoke(StringBuilder(capacity), noAnsi) + ): AnsiMessageBuilder = from(StringBuilder(capacity), noAnsi) /** * 判断 [sender] 是否支持带 ansi 控制符的正确显示 @@ -126,7 +127,7 @@ public open class AnsiMessageBuilder internal constructor( public inline fun StringBuilder.appendAnsi( noAnsi: Boolean = false, action: AnsiMessageBuilder.() -> Unit - ): AnsiMessageBuilder = invoke(this, noAnsi).apply(action) + ): AnsiMessageBuilder = from(this, noAnsi).apply(action) } @@ -154,7 +155,7 @@ public open class AnsiMessageBuilder internal constructor( public inline fun buildAnsiMessage( capacity: Int = 16, action: AnsiMessageBuilder.() -> Unit -): String = AnsiMessageBuilder(capacity, false).apply(action).toString() +): String = AnsiMessageBuilder.builder(capacity, false).apply(action).toString() // 不在 top-level 使用者会得到 Internal error: Couldn't inline sendAnsiMessage @@ -168,7 +169,7 @@ public suspend inline fun CommandSender.sendAnsiMessage( builder: AnsiMessageBuilder.() -> Unit ) { sendMessage( - AnsiMessageBuilder(capacity, noAnsi = !AnsiMessageBuilder.isAnsiSupported(this)) + AnsiMessageBuilder.builder(capacity, noAnsi = !AnsiMessageBuilder.isAnsiSupported(this)) .apply(builder) .toString() ) @@ -187,3 +188,5 @@ public suspend inline fun CommandSender.sendAnsiMessage(message: String) { message.dropAnsi() ) } + +public fun AnsiMessageBuilder(capacity: Int = 16): AnsiMessageBuilder = AnsiMessageBuilder(StringBuilder(capacity)) From fab3727b4450a2ca327a5dfc187348ad40546a2e Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 28 Nov 2020 13:53:53 +0800 Subject: [PATCH 27/52] Plugin publishing fundamentals, #227 --- tools/gradle-plugin/build.gradle.kts | 2 + .../src/MiraiConsoleExtension.kt | 162 ++++++++++++++++++ .../src/MiraiConsoleGradlePlugin.kt | 3 +- tools/gradle-plugin/src/publishing.kt | 119 +++++++++++++ 4 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 tools/gradle-plugin/src/publishing.kt diff --git a/tools/gradle-plugin/build.gradle.kts b/tools/gradle-plugin/build.gradle.kts index 4f50923f1..d2a0034bf 100644 --- a/tools/gradle-plugin/build.gradle.kts +++ b/tools/gradle-plugin/build.gradle.kts @@ -15,6 +15,7 @@ plugins { dependencies { compileOnly(gradleApi()) + compileOnly(gradleKotlinDsl()) compileOnly(kotlin("gradle-plugin-api").toString()) { exclude("org.jetbrains.kotlin", "kotlin-stdlib") } @@ -26,6 +27,7 @@ dependencies { api("com.github.jengelman.gradle.plugins:shadow:6.0.0") api(`jetbrains-annotations`) + api("com.jfrog.bintray.gradle:gradle-bintray-plugin:${Versions.bintray}") } version = Versions.console diff --git a/tools/gradle-plugin/src/MiraiConsoleExtension.kt b/tools/gradle-plugin/src/MiraiConsoleExtension.kt index 572a74a76..d0bb3d968 100644 --- a/tools/gradle-plugin/src/MiraiConsoleExtension.kt +++ b/tools/gradle-plugin/src/MiraiConsoleExtension.kt @@ -12,7 +12,10 @@ package net.mamoe.mirai.console.gradle import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import com.jfrog.bintray.gradle.BintrayExtension import org.gradle.api.JavaVersion +import org.gradle.api.XmlProvider +import org.gradle.api.publish.maven.MavenPublication /** * ``` @@ -119,6 +122,165 @@ open class MiraiConsoleExtension { fun excludeDependency(group: String, name: String) { excludedDependencies.add(ExcludedDependency(group, name)) } + + /** + * Bintray 插件成品 JAR 发布 配置. + * + * @see Publishing + * @since 1.1.0 + */ + val publishing: Publishing = Publishing() + + /** + * 控制自动配置 Bintray 发布 + * @since 1.1.0 + */ + var publishingEnabled = true + + /** + * Bintray 插件成品 JAR 发布 配置. + * + * @see [Publishing] + * @since 1.1.0 + */ + inline fun publishing(block: Publishing.() -> Unit) = publishing.run(block) + + /** + * @see publishingEnabled + * @since 1.1.0 + */ + fun disablePublishing() { + publishingEnabled = false + } + + /** + * Bintray 插件成品 JAR 发布 配置. + * + * 对于一个属性 PROP, 会按以下顺序依次尝试读取: + * 1. Gradle 参数 + * - "gradle.properties" + * - ext + * - Gradle -P 启动参数 + * 2. [System.getProperty] "PROP" + * 3. 当前和所有父 project 根目录下 "PROP" 文件的内容 + * 4. [System.getenv] "PROP" + * + * @see publishing + * @since 1.1.0 + */ + class Publishing internal constructor() { + /////////////////////////////////////////////////////////////////////////// + // Required arguments + /////////////////////////////////////////////////////////////////////////// + + /** + * Bintray 账户名. 必须. + * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.user" + * + * @see [Publishing] + */ + var user: String? = null + + /** + * Bintray 账户 key. 必须. + * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.key" + */ + var key: String? = null + + /** + * 目标仓库名称. 必须. + * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.repo" + */ + var repo: String? = null + + /** + * 目标仓库名称. 必须. + * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.package" + */ + var packageName: String? = null + + + /////////////////////////////////////////////////////////////////////////// + // Optional arguments + /////////////////////////////////////////////////////////////////////////// + + // Artifact + + /** + * 发布的 artifact id. 默认为 `project.name`. + * + * artifact id 是类似于 "net.mamoe:mirai-console:1.1.0" 中的 "mirai-console" + */ + var artifactId: String? = null + + /** + * 发布的 group id. 默认为 `project.group`. + * + * group id 是类似于 "net.mamoe:mirai-console:1.1.0" 中的 "net.mamoe" + */ + var groupId: String? = null + + /** + * 发布的版本号, 默认为 `project.version` + * + * 版本号是类似于 "net.mamoe:mirai-console:1.1.0" 中的 "1.1.0" + */ + var version: String? = null + + // Bintray + + /** + * Bintray organization 名. 可选. + * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.org". + * 仍然无法获取时发布到 [user] 账号下的仓库 [repo], 否则发布到指定 [org] 下的仓库 [repo]. + */ + var org: String? = null + + /** + * 上传后自动发布. 默认 `true`. + */ + var publish: Boolean = true + + /** + * 当文件冲突时覆盖. 默认 `false`. + */ + var override: Boolean = false + + // Custom configurations + + internal val bintrayConfigs = mutableListOf Unit>() + internal val bintrayPackageConfigConfigs = mutableListOf Unit>() + internal val mavenPomConfigs = mutableListOf Unit>() + internal val mavenPublicationConfigs = mutableListOf Unit>() + + /** + * 自定义配置 [BintrayExtension],覆盖 + */ + fun bintray(config: BintrayExtension.() -> Unit) { + bintrayConfigs.add(config) + } + + /** + * 自定义配置 [BintrayExtension.PackageConfig] + */ + fun packageConfig(config: BintrayExtension.PackageConfig.() -> Unit) { + bintrayPackageConfigConfigs.add(config) + } + + /** + * 自定义配置 maven pom.xml [XmlProvider] + */ + fun mavenPom(config: XmlProvider.() -> Unit) { + mavenPomConfigs.add(config) + } + + /** + * 自定义配置 [MavenPublication] + */ + fun mavenPublication(config: MavenPublication.() -> Unit) { + mavenPublicationConfigs.add(config) + } + } } enum class MiraiConsoleFrontEndKind { diff --git a/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt b/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt index 287d4620c..cea8c4286 100644 --- a/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt +++ b/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt @@ -151,8 +151,9 @@ class MiraiConsoleGradlePlugin : Plugin { afterEvaluate { configureCompileTarget() - registerBuildPluginTasks() kotlinTargets.forEach { configureTarget(it) } + registerBuildPluginTasks() + registerPublishPluginTask() } } } diff --git a/tools/gradle-plugin/src/publishing.kt b/tools/gradle-plugin/src/publishing.kt new file mode 100644 index 000000000..c10f01d8c --- /dev/null +++ b/tools/gradle-plugin/src/publishing.kt @@ -0,0 +1,119 @@ +/* + * Copyright 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.console.gradle + +import org.gradle.api.Project +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.tasks.bundling.Jar +import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.getValue +import org.gradle.kotlin.dsl.provideDelegate +import org.gradle.kotlin.dsl.registering + + +private val Project.selfAndParentProjects: Sequence + get() = generateSequence(this) { it.parent } + +private fun Project.findPropertySmart(propName: String): String? { + return findProperty(propName)?.toString() + ?: System.getProperty(propName) + ?: selfAndParentProjects.map { it.projectDir.resolve(propName) }.find { it.exists() }?.readText() + ?: System.getenv(propName) +} + +private fun Project.findPropertySmartOrFail(propName: String): String { + return findPropertySmart(propName) ?: error("[Mirai Console] Cannot find property for publication: $propName. Please check your 'mirai' configuration.") +} + +internal fun Project.registerPublishPluginTask() { + val mirai = miraiExtension + + bintray { + user = mirai.publishing.user ?: findPropertySmartOrFail("bintray.user") + key = mirai.publishing.key ?: findPropertySmartOrFail("bintray.key") + + setPublications("mavenJava") + setConfigurations("archives") + + publish = mirai.publishing.publish + override = mirai.publishing.override + + pkg.apply { + repo = mirai.publishing.repo ?: findPropertySmartOrFail("bintray.repo") + name = mirai.publishing.packageName ?: findPropertySmartOrFail("bintray.package") + userOrg = mirai.publishing.org ?: findPropertySmart("bintray.org") + + mirai.publishing.bintrayPackageConfigConfigs.forEach { it.invoke(this) } + } + + mirai.publishing.bintrayConfigs.forEach { it.invoke(this) } + } + + @Suppress("DEPRECATION") + val sourcesJar by tasks.registering(Jar::class) { + classifier = "sources" + from(sourceSets["main"].allSource) + } + + publishing { + /* + repositories { + maven { + // change to point to your repo, e.g. http://my.org/repo + url = uri("$buildDir/repo") + } + }*/ + publications.register("mavenJava", MavenPublication::class.java) { publication -> + with(publication) { + from(components["java"]) + + this.groupId = mirai.publishing.groupId ?: project.group.toString() + this.artifactId = mirai.publishing.artifactId ?: project.name + this.version = mirai.publishing.version ?: project.version.toString() + + pom.withXml { xml -> + val root = xml.asNode() + root.appendNode("description", project.description) + root.appendNode("name", project.name) + // root.appendNode("url", vcs) + root.children().last() + + mirai.publishing.mavenPomConfigs.forEach { it.invoke(xml) } + } + + artifact(sourcesJar.get()) + + // TODO: 2020/11/28 -miraip metadata artifact + // TODO: 2020/11/28 -all shadowed artifact + + mirai.publishing.mavenPublicationConfigs.forEach { it.invoke(this) } + } + } + } +} + + +/** + * Configures the [bintray][com.jfrog.bintray.gradle.BintrayExtension] extension. + */ +@PublishedApi +internal fun Project.bintray(configure: com.jfrog.bintray.gradle.BintrayExtension.() -> Unit): Unit = + (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("bintray", configure) + +@PublishedApi +internal val Project.sourceSets: org.gradle.api.tasks.SourceSetContainer + get() = (this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("sourceSets") as org.gradle.api.tasks.SourceSetContainer + +/** + * Configures the [publishing][org.gradle.api.publish.PublishingExtension] extension. + */ +@PublishedApi +internal fun Project.publishing(configure: org.gradle.api.publish.PublishingExtension.() -> Unit): Unit = + (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("publishing", configure) From 4880601fa760a008d529ebf30f82d6f95bd10b7f Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 28 Nov 2020 13:57:58 +0800 Subject: [PATCH 28/52] Explicit API mode for gradle-plugin module. --- tools/gradle-plugin/build.gradle.kts | 4 ++ .../src/MiraiConsoleExtension.kt | 67 ++++++++++--------- .../src/MiraiConsoleGradlePlugin.kt | 4 +- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/tools/gradle-plugin/build.gradle.kts b/tools/gradle-plugin/build.gradle.kts index d2a0034bf..9c5b6a004 100644 --- a/tools/gradle-plugin/build.gradle.kts +++ b/tools/gradle-plugin/build.gradle.kts @@ -33,6 +33,10 @@ dependencies { version = Versions.console description = "Gradle plugin for Mirai Console" +kotlin { + explicitApi() +} + pluginBundle { website = "https://github.com/mamoe/mirai-console" vcsUrl = "https://github.com/mamoe/mirai-console" diff --git a/tools/gradle-plugin/src/MiraiConsoleExtension.kt b/tools/gradle-plugin/src/MiraiConsoleExtension.kt index d0bb3d968..9c7eed49d 100644 --- a/tools/gradle-plugin/src/MiraiConsoleExtension.kt +++ b/tools/gradle-plugin/src/MiraiConsoleExtension.kt @@ -25,41 +25,41 @@ import org.gradle.api.publish.maven.MavenPublication * ``` */ // must be open -open class MiraiConsoleExtension { +public open class MiraiConsoleExtension { /** * 为 `true` 时不自动添加 mirai-core 的依赖 * * 默认: `false` */ - var noCore: Boolean = false + public var noCore: Boolean = false /** * 为 `true` 时不自动为 test 模块添加 mirai-core-qqandroid 的依赖. * * 默认: `false` */ - var noTestCoreQQAndroid: Boolean = false + public var noTestCoreQQAndroid: Boolean = false /** * 为 `true` 时不自动添加 mirai-console 的依赖. * * 默认: `false` */ - var noConsole: Boolean = false + public var noConsole: Boolean = false /** * 自动添加的 mirai-core 和 mirai-core-qqandroid 的版本. * * 默认: 与本 Gradle 插件编译时的 mirai-core 版本相同. [VersionConstants.CORE_VERSION] */ - var coreVersion: String = VersionConstants.CORE_VERSION + public var coreVersion: String = VersionConstants.CORE_VERSION /** * 自动添加的 mirai-console 后端和前端的版本. * * 默认: 与本 Gradle 插件版本相同. [VersionConstants.CONSOLE_VERSION] */ - var consoleVersion: String = VersionConstants.CONSOLE_VERSION + public var consoleVersion: String = VersionConstants.CONSOLE_VERSION /** * 自动为 test 模块添加的前端依赖名称 @@ -68,7 +68,7 @@ open class MiraiConsoleExtension { * * 默认: [MiraiConsoleFrontEndKind.TERMINAL] */ - var useTestConsoleFrontEnd: MiraiConsoleFrontEndKind? = MiraiConsoleFrontEndKind.TERMINAL + public var useTestConsoleFrontEnd: MiraiConsoleFrontEndKind? = MiraiConsoleFrontEndKind.TERMINAL /** * Java 和 Kotlin 编译目标. 至少为 [JavaVersion.VERSION_1_8]. @@ -77,7 +77,7 @@ open class MiraiConsoleExtension { * * 默认: [JavaVersion.VERSION_1_8] */ - var jvmTarget: JavaVersion = JavaVersion.VERSION_1_8 + public var jvmTarget: JavaVersion = JavaVersion.VERSION_1_8 /** * 默认会配置 Kotlin 编译器参数 "-Xjvm-default=all". 将此项设置为 `false` 可避免配置. @@ -86,7 +86,7 @@ open class MiraiConsoleExtension { * * 默认: `false` */ - var dontConfigureKotlinJvmDefault: Boolean = false + public var dontConfigureKotlinJvmDefault: Boolean = false internal val shadowConfigurations: MutableList Unit> = mutableListOf() internal val excludedDependencies: MutableSet = mutableSetOf() @@ -99,7 +99,7 @@ open class MiraiConsoleExtension { /** * 配置 [ShadowJar] (即打包插件) */ - fun configureShadow(configure: ShadowJar.() -> Unit) { + public fun configureShadow(configure: ShadowJar.() -> Unit) { shadowConfigurations.add(configure) } @@ -108,7 +108,7 @@ open class MiraiConsoleExtension { * * @param notation 格式为 "groupId:name". 如 "org.jetbrains.kotlin:kotlin-stdlib" */ - fun excludeDependency(notation: String) { + public fun excludeDependency(notation: String) { requireNotNull(notation.count { it == ':' } == 1) { "Invalid dependency notation $notation." } excludedDependencies.add(ExcludedDependency(notation.substringBefore(':'), notation.substringAfter(':'))) } @@ -119,7 +119,7 @@ open class MiraiConsoleExtension { * @param group 如 "org.jetbrains.kotlin" * @param name 如 "kotlin-stdlib" */ - fun excludeDependency(group: String, name: String) { + public fun excludeDependency(group: String, name: String) { excludedDependencies.add(ExcludedDependency(group, name)) } @@ -129,13 +129,13 @@ open class MiraiConsoleExtension { * @see Publishing * @since 1.1.0 */ - val publishing: Publishing = Publishing() + public val publishing: Publishing = Publishing() /** * 控制自动配置 Bintray 发布 * @since 1.1.0 */ - var publishingEnabled = true + public var publishingEnabled: Boolean = true /** * Bintray 插件成品 JAR 发布 配置. @@ -143,13 +143,13 @@ open class MiraiConsoleExtension { * @see [Publishing] * @since 1.1.0 */ - inline fun publishing(block: Publishing.() -> Unit) = publishing.run(block) + public inline fun publishing(block: Publishing.() -> Unit): Unit = publishing.run(block) /** * @see publishingEnabled * @since 1.1.0 */ - fun disablePublishing() { + public fun disablePublishing() { publishingEnabled = false } @@ -168,7 +168,7 @@ open class MiraiConsoleExtension { * @see publishing * @since 1.1.0 */ - class Publishing internal constructor() { + public class Publishing internal constructor() { /////////////////////////////////////////////////////////////////////////// // Required arguments /////////////////////////////////////////////////////////////////////////// @@ -179,25 +179,25 @@ open class MiraiConsoleExtension { * * @see [Publishing] */ - var user: String? = null + public var user: String? = null /** * Bintray 账户 key. 必须. * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.key" */ - var key: String? = null + public var key: String? = null /** * 目标仓库名称. 必须. * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.repo" */ - var repo: String? = null + public var repo: String? = null /** * 目标仓库名称. 必须. * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.package" */ - var packageName: String? = null + public var packageName: String? = null /////////////////////////////////////////////////////////////////////////// @@ -211,21 +211,21 @@ open class MiraiConsoleExtension { * * artifact id 是类似于 "net.mamoe:mirai-console:1.1.0" 中的 "mirai-console" */ - var artifactId: String? = null + public var artifactId: String? = null /** * 发布的 group id. 默认为 `project.group`. * * group id 是类似于 "net.mamoe:mirai-console:1.1.0" 中的 "net.mamoe" */ - var groupId: String? = null + public var groupId: String? = null /** * 发布的版本号, 默认为 `project.version` * * 版本号是类似于 "net.mamoe:mirai-console:1.1.0" 中的 "1.1.0" */ - var version: String? = null + public var version: String? = null // Bintray @@ -234,17 +234,17 @@ open class MiraiConsoleExtension { * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.org". * 仍然无法获取时发布到 [user] 账号下的仓库 [repo], 否则发布到指定 [org] 下的仓库 [repo]. */ - var org: String? = null + public var org: String? = null /** * 上传后自动发布. 默认 `true`. */ - var publish: Boolean = true + public var publish: Boolean = true /** * 当文件冲突时覆盖. 默认 `false`. */ - var override: Boolean = false + public var override: Boolean = false // Custom configurations @@ -256,33 +256,36 @@ open class MiraiConsoleExtension { /** * 自定义配置 [BintrayExtension],覆盖 */ - fun bintray(config: BintrayExtension.() -> Unit) { + public fun bintray(config: BintrayExtension.() -> Unit) { bintrayConfigs.add(config) } /** * 自定义配置 [BintrayExtension.PackageConfig] */ - fun packageConfig(config: BintrayExtension.PackageConfig.() -> Unit) { + public fun packageConfig(config: BintrayExtension.PackageConfig.() -> Unit) { bintrayPackageConfigConfigs.add(config) } /** * 自定义配置 maven pom.xml [XmlProvider] */ - fun mavenPom(config: XmlProvider.() -> Unit) { + public fun mavenPom(config: XmlProvider.() -> Unit) { mavenPomConfigs.add(config) } /** * 自定义配置 [MavenPublication] */ - fun mavenPublication(config: MavenPublication.() -> Unit) { + public fun mavenPublication(config: MavenPublication.() -> Unit) { mavenPublicationConfigs.add(config) } } } -enum class MiraiConsoleFrontEndKind { +/** + * @see MiraiConsoleExtension.useTestConsoleFrontEnd + */ +public enum class MiraiConsoleFrontEndKind { TERMINAL, } \ No newline at end of file diff --git a/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt b/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt index cea8c4286..30b79cc7b 100644 --- a/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt +++ b/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt @@ -29,8 +29,8 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.KotlinTarget -class MiraiConsoleGradlePlugin : Plugin { - companion object { +public class MiraiConsoleGradlePlugin : Plugin { + public companion object { internal const val BINTRAY_REPOSITORY_URL = "https://dl.bintray.com/him188moe/mirai" } From 08c61dee0ec1f14c55c5f8b98cd3f1cbc32135d4 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 28 Nov 2020 13:59:47 +0800 Subject: [PATCH 29/52] 1.1.0-dev-1 --- .../mirai-console/src/internal/MiraiConsoleBuildConstants.kt | 4 ++-- buildSrc/src/main/kotlin/Versions.kt | 2 +- tools/gradle-plugin/src/VersionConstants.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt b/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt index 3d3148a97..747eeca6a 100644 --- a/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt +++ b/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt @@ -14,8 +14,8 @@ import java.time.Instant internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic - val buildDate: Instant = Instant.ofEpochSecond(1606348297) - const val versionConst: String = "1.0.1-dev-2" + val buildDate: Instant = Instant.ofEpochSecond(1606543118) + const val versionConst: String = "1.1.0-dev-1" @JvmStatic val version: SemVersion = SemVersion(versionConst) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 106302720..3fcffb690 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -11,7 +11,7 @@ object Versions { const val core = "1.3.3" - const val console = "1.0.1" + const val console = "1.1.0-dev-1" const val consoleGraphical = "0.0.7" const val consoleTerminal = console diff --git a/tools/gradle-plugin/src/VersionConstants.kt b/tools/gradle-plugin/src/VersionConstants.kt index 4a756e84f..25c330d87 100644 --- a/tools/gradle-plugin/src/VersionConstants.kt +++ b/tools/gradle-plugin/src/VersionConstants.kt @@ -10,6 +10,6 @@ package net.mamoe.mirai.console.gradle internal object VersionConstants { - const val CONSOLE_VERSION = "1.0.1-dev-2" // value is written here automatically during build + const val CONSOLE_VERSION = "1.1.0-dev-1" // value is written here automatically during build const val CORE_VERSION = "1.3.3" // value is written here automatically during build } \ No newline at end of file From 3a203ae1d4bff24776252e9c8ba3a2adbea19c94 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 28 Nov 2020 20:47:07 +0800 Subject: [PATCH 30/52] Update DROP_ANSI_PATTERN --- backend/mirai-console/src/util/AnsiMessageBuilder.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/backend/mirai-console/src/util/AnsiMessageBuilder.kt b/backend/mirai-console/src/util/AnsiMessageBuilder.kt index 7ec49129d..21177d332 100644 --- a/backend/mirai-console/src/util/AnsiMessageBuilder.kt +++ b/backend/mirai-console/src/util/AnsiMessageBuilder.kt @@ -84,7 +84,17 @@ public open class AnsiMessageBuilder public constructor( } public companion object { - private val DROP_ANSI_PATTERN = """\u001b\[([0-9]+)(;[0-9]+)*m""".toRegex() + // CSI序列由ESC [、若干个(包括0个)“参数字节”、若干个“中间字节”,以及一个“最终字节”组成。各部分的字符范围如下: + // + // CSI序列在ESC [之后各个组成部分的字符范围[12]:5.4 + // 组成部分 字符范围 ASCII + // 参数字节 0x30–0x3F 0–9:;<=>? + // 中间字节 0x20–0x2F 空格、!"#$%&'()*+,-./ + // 最终字节 0x40–0x7E @A–Z[\]^_`a–z{|}~ + // + // @see https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#CSI%E5%BA%8F%E5%88%97 + @Suppress("RegExpRedundantEscape") + private val DROP_ANSI_PATTERN = """\u001b\[([\u0030-\u003F])*?([\u0020-\u002F])*?[\u0040-\u007E]""".toRegex() /** * 从 [String] 中剔除 ansi 控制符 From 6c2a4081acd386a7697488c1fdb4229753475be4 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 28 Nov 2020 20:47:58 +0800 Subject: [PATCH 31/52] Update KDoc for AnsiMessageBuilder --- .../src/util/AnsiMessageBuilder.kt | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/backend/mirai-console/src/util/AnsiMessageBuilder.kt b/backend/mirai-console/src/util/AnsiMessageBuilder.kt index 21177d332..1f3c0f0b2 100644 --- a/backend/mirai-console/src/util/AnsiMessageBuilder.kt +++ b/backend/mirai-console/src/util/AnsiMessageBuilder.kt @@ -21,9 +21,13 @@ public open class AnsiMessageBuilder public constructor( override fun toString(): String = delegate.toString() /** - * 在添加 ansi code 的时候建议使用此方法. + * 同 [append] 方法, 在 `noAnsi=true` 的时候会忽略此函数的调用 * - * 在 `noAnsi=true` 的时候会忽略此函数的调用 + * 参考资料: + * - [ANSI转义序列](https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97) + * - [ANSI转义序列#颜色](https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#%E9%A2%9C%E8%89%B2) + * + * @param code Ansi 操作码 * * @see from * @see builder @@ -102,6 +106,11 @@ public open class AnsiMessageBuilder public constructor( @JvmStatic public fun String.dropAnsi(): String = DROP_ANSI_PATTERN.replace(this, "") + /** + * 使用 [builder] 封装一个 [AnsiMessageBuilder] + * + * @param noAnsi 为 `true` 时忽略全部与 ansi 有关的方法的调用 + */ @JvmStatic @JvmOverloads public fun from( @@ -113,6 +122,8 @@ public open class AnsiMessageBuilder public constructor( /** * @param capacity [StringBuilder] 的初始化大小 + * + * @param noAnsi 为 `true` 时忽略全部与 ansi 有关的方法的调用 */ @JvmStatic @JvmOverloads @@ -157,6 +168,11 @@ public open class AnsiMessageBuilder public constructor( ///////////////////////////////////////////////////////////////////////////////// } +/** + * @param capacity [StringBuilder] 初始化大小 + */ +public fun AnsiMessageBuilder(capacity: Int = 16): AnsiMessageBuilder = AnsiMessageBuilder(StringBuilder(capacity)) + /** * 构建一条 ansi 信息 * @@ -198,5 +214,3 @@ public suspend inline fun CommandSender.sendAnsiMessage(message: String) { message.dropAnsi() ) } - -public fun AnsiMessageBuilder(capacity: Int = 16): AnsiMessageBuilder = AnsiMessageBuilder(StringBuilder(capacity)) From f4ebf5a7baef0d3917e35392a1de3d0779f70e99 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 28 Nov 2020 21:04:38 +0800 Subject: [PATCH 32/52] Remove param `noAnsi` in appendAnsi --- backend/mirai-console/src/util/AnsiMessageBuilder.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/mirai-console/src/util/AnsiMessageBuilder.kt b/backend/mirai-console/src/util/AnsiMessageBuilder.kt index 1f3c0f0b2..dfa9082be 100644 --- a/backend/mirai-console/src/util/AnsiMessageBuilder.kt +++ b/backend/mirai-console/src/util/AnsiMessageBuilder.kt @@ -146,9 +146,8 @@ public open class AnsiMessageBuilder public constructor( * 往 [StringBuilder] 追加 ansi 控制符 */ public inline fun StringBuilder.appendAnsi( - noAnsi: Boolean = false, action: AnsiMessageBuilder.() -> Unit - ): AnsiMessageBuilder = from(this, noAnsi).apply(action) + ): AnsiMessageBuilder = from(this).apply(action) } From 480666e3f043f7ae11d5e9ca8c9154181a3ed93f Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 28 Nov 2020 21:28:43 +0800 Subject: [PATCH 33/52] Improve String.dropAnsi() --- .../src/util/AnsiMessageBuilder.kt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/backend/mirai-console/src/util/AnsiMessageBuilder.kt b/backend/mirai-console/src/util/AnsiMessageBuilder.kt index dfa9082be..f33e83267 100644 --- a/backend/mirai-console/src/util/AnsiMessageBuilder.kt +++ b/backend/mirai-console/src/util/AnsiMessageBuilder.kt @@ -98,13 +98,28 @@ public open class AnsiMessageBuilder public constructor( // // @see https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#CSI%E5%BA%8F%E5%88%97 @Suppress("RegExpRedundantEscape") - private val DROP_ANSI_PATTERN = """\u001b\[([\u0030-\u003F])*?([\u0020-\u002F])*?[\u0040-\u007E]""".toRegex() + private val DROP_CSI_PATTERN = """\u001b\[([\u0030-\u003F])*?([\u0020-\u002F])*?[\u0040-\u007E]""".toRegex() + + // 序列具有不同的长度。所有序列都以ASCII字符ESC(27 / 十六进制 0x1B)开头, + // 第二个字节则是0x40–0x5F(ASCII @A–Z[\]^_)范围内的字符。[12]:5.3.a + // + // 标准规定,在8位环境中,这两个字节的序列可以合并为0x80-0x9F范围内的单个字节(详情请参阅C1控制字符集)。 + // 但是,在现代设备上,这些代码通常用于其他目的,例如UTF-8的一部分或CP-1252字符,因此并不使用这种合并的方式。 + // + // 除ESC之外的其他C0代码(通常是BEL,BS,CR,LF,FF,TAB,VT,SO和SI)在输出时也可能会产生与某些控制序列相似或相同的效果。 + // + // @see https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97 + // + // 注: 缺少详细资料, 只能认定 ansi 长度固定为二字节 (CSI除外) + private val DROP_ANSI_PATTERN = """\u001b[\u0040–\u005F]""".toRegex() /** * 从 [String] 中剔除 ansi 控制符 */ @JvmStatic - public fun String.dropAnsi(): String = DROP_ANSI_PATTERN.replace(this, "") + public fun String.dropAnsi(): String = this + .replace(DROP_CSI_PATTERN, "") // 先进行 CSI 剔除后进行 ANSI 剔除 + .replace(DROP_ANSI_PATTERN, "") /** * 使用 [builder] 封装一个 [AnsiMessageBuilder] From 04c1d951aa45486249e590e95048f2cbecc80f95 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 28 Nov 2020 21:50:26 +0800 Subject: [PATCH 34/52] Rename `AnsiMessageBuilder.builder` to `create` --- backend/mirai-console/src/util/AnsiMessageBuilder.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/mirai-console/src/util/AnsiMessageBuilder.kt b/backend/mirai-console/src/util/AnsiMessageBuilder.kt index f33e83267..0215a5bd2 100644 --- a/backend/mirai-console/src/util/AnsiMessageBuilder.kt +++ b/backend/mirai-console/src/util/AnsiMessageBuilder.kt @@ -30,7 +30,7 @@ public open class AnsiMessageBuilder public constructor( * @param code Ansi 操作码 * * @see from - * @see builder + * @see create */ public open fun ansi(code: String): AnsiMessageBuilder = append(code) @@ -142,7 +142,7 @@ public open class AnsiMessageBuilder public constructor( */ @JvmStatic @JvmOverloads - public fun builder( + public fun create( capacity: Int = 16, noAnsi: Boolean = false ): AnsiMessageBuilder = from(StringBuilder(capacity), noAnsi) @@ -195,7 +195,7 @@ public fun AnsiMessageBuilder(capacity: Int = 16): AnsiMessageBuilder = AnsiMess public inline fun buildAnsiMessage( capacity: Int = 16, action: AnsiMessageBuilder.() -> Unit -): String = AnsiMessageBuilder.builder(capacity, false).apply(action).toString() +): String = AnsiMessageBuilder.create(capacity, false).apply(action).toString() // 不在 top-level 使用者会得到 Internal error: Couldn't inline sendAnsiMessage @@ -209,7 +209,7 @@ public suspend inline fun CommandSender.sendAnsiMessage( builder: AnsiMessageBuilder.() -> Unit ) { sendMessage( - AnsiMessageBuilder.builder(capacity, noAnsi = !AnsiMessageBuilder.isAnsiSupported(this)) + AnsiMessageBuilder.create(capacity, noAnsi = !AnsiMessageBuilder.isAnsiSupported(this)) .apply(builder) .toString() ) From 9a2ea32e9341eacc26d5da258ad86641e171f83e Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 29 Nov 2020 00:15:03 +0800 Subject: [PATCH 35/52] Plugin publishing via gradle plugin --- .../gradle-plugin/src/BuildMiraiPluginTask.kt | 18 +++ .../src/MiraiConsoleExtension.kt | 77 +++++++----- .../src/MiraiConsoleGradlePlugin.kt | 33 ++--- tools/gradle-plugin/src/publishing.kt | 115 ++++++++++++++++-- 4 files changed, 194 insertions(+), 49 deletions(-) create mode 100644 tools/gradle-plugin/src/BuildMiraiPluginTask.kt diff --git a/tools/gradle-plugin/src/BuildMiraiPluginTask.kt b/tools/gradle-plugin/src/BuildMiraiPluginTask.kt new file mode 100644 index 000000000..f316b56ef --- /dev/null +++ b/tools/gradle-plugin/src/BuildMiraiPluginTask.kt @@ -0,0 +1,18 @@ +package net.mamoe.mirai.console.gradle + +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.api.tasks.CacheableTask +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import java.io.File + +@CacheableTask +public open class BuildMiraiPluginTask : ShadowJar() { + internal var targetField: KotlinTarget? = null + + public val target: KotlinTarget get() = targetField!! + + /** + * ShadowJar 打包结果 + */ + public val output: File get() = outputs.files.singleFile +} \ No newline at end of file diff --git a/tools/gradle-plugin/src/MiraiConsoleExtension.kt b/tools/gradle-plugin/src/MiraiConsoleExtension.kt index 9c7eed49d..c1c868b6f 100644 --- a/tools/gradle-plugin/src/MiraiConsoleExtension.kt +++ b/tools/gradle-plugin/src/MiraiConsoleExtension.kt @@ -13,8 +13,10 @@ package net.mamoe.mirai.console.gradle import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import com.jfrog.bintray.gradle.BintrayExtension +import com.jfrog.bintray.gradle.BintrayPlugin import org.gradle.api.JavaVersion import org.gradle.api.XmlProvider +import org.gradle.api.plugins.PluginContainer import org.gradle.api.publish.maven.MavenPublication /** @@ -126,31 +128,44 @@ public open class MiraiConsoleExtension { /** * Bintray 插件成品 JAR 发布 配置. * - * @see Publishing + * @see PluginPublishing * @since 1.1.0 */ - public val publishing: Publishing = Publishing() + public val publishing: PluginPublishing = PluginPublishing() /** - * 控制自动配置 Bintray 发布 - * @since 1.1.0 - */ - public var publishingEnabled: Boolean = true - - /** - * Bintray 插件成品 JAR 发布 配置. + * 控制自动配置 Bintray 发布. 默认为 `false`, 表示不自动配置发布. + * + * 开启后将会: + * - 创建名为 "mavenJava" 的 [MavenPublication] + * - [应用][PluginContainer.apply] [BintrayPlugin], 配置 Bintray 相关参数 + * - 创建 task "publishPlugin" * - * @see [Publishing] * @since 1.1.0 */ - public inline fun publishing(block: Publishing.() -> Unit): Unit = publishing.run(block) + public var publishingEnabled: Boolean = false /** + * 开启自动配置 Bintray 插件成品 JAR 发布, 并以 [configure] 配置 [PluginPublishing]. + * + * @see [PluginPublishing] * @see publishingEnabled * @since 1.1.0 */ - public fun disablePublishing() { - publishingEnabled = false + public inline fun publishing(crossinline configure: PluginPublishing.() -> Unit) { + publishingEnabled = true + publishing.run(configure) + } + + /** + * 开启自动配置 Bintray 插件成品 JAR 发布. + * + * @see [PluginPublishing] + * @see publishingEnabled + * @since 1.1.0 + */ + public fun publishing() { + publishingEnabled = true } /** @@ -166,36 +181,37 @@ public open class MiraiConsoleExtension { * 4. [System.getenv] "PROP" * * @see publishing + * @see publishingEnabled * @since 1.1.0 */ - public class Publishing internal constructor() { + public class PluginPublishing internal constructor() { /////////////////////////////////////////////////////////////////////////// // Required arguments /////////////////////////////////////////////////////////////////////////// /** * Bintray 账户名. 必须. - * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.user" + * 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.user" * - * @see [Publishing] + * @see [PluginPublishing] */ public var user: String? = null /** * Bintray 账户 key. 必须. - * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.key" + * 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.key" */ public var key: String? = null /** * 目标仓库名称. 必须. - * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.repo" + * 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.repo" */ public var repo: String? = null /** * 目标仓库名称. 必须. - * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.package" + * 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.package" */ public var packageName: String? = null @@ -227,11 +243,16 @@ public open class MiraiConsoleExtension { */ public var version: String? = null + /** + * 发布的描述, 默认为 `project.description` + */ + public var description: String? = null + // Bintray /** * Bintray organization 名. 可选. - * 若为 `null`, 将会以 [Publishing] 中描述的步骤获取 "bintray.org". + * 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.org". * 仍然无法获取时发布到 [user] 账号下的仓库 [repo], 否则发布到指定 [org] 下的仓库 [repo]. */ public var org: String? = null @@ -256,29 +277,29 @@ public open class MiraiConsoleExtension { /** * 自定义配置 [BintrayExtension],覆盖 */ - public fun bintray(config: BintrayExtension.() -> Unit) { - bintrayConfigs.add(config) + public fun bintray(configure: BintrayExtension.() -> Unit) { + bintrayConfigs.add(configure) } /** * 自定义配置 [BintrayExtension.PackageConfig] */ - public fun packageConfig(config: BintrayExtension.PackageConfig.() -> Unit) { - bintrayPackageConfigConfigs.add(config) + public fun packageConfig(configure: BintrayExtension.PackageConfig.() -> Unit) { + bintrayPackageConfigConfigs.add(configure) } /** * 自定义配置 maven pom.xml [XmlProvider] */ - public fun mavenPom(config: XmlProvider.() -> Unit) { - mavenPomConfigs.add(config) + public fun mavenPom(configure: XmlProvider.() -> Unit) { + mavenPomConfigs.add(configure) } /** * 自定义配置 [MavenPublication] */ - public fun mavenPublication(config: MavenPublication.() -> Unit) { - mavenPublicationConfigs.add(config) + public fun mavenPublication(configure: MavenPublication.() -> Unit) { + mavenPublicationConfigs.add(configure) } } } diff --git a/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt b/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt index 30b79cc7b..7fa6ad695 100644 --- a/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt +++ b/tools/gradle-plugin/src/MiraiConsoleGradlePlugin.kt @@ -13,7 +13,7 @@ package net.mamoe.mirai.console.gradle import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import com.jfrog.bintray.gradle.BintrayPlugin import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPlugin @@ -101,18 +101,18 @@ public class MiraiConsoleGradlePlugin : Plugin { tasks.findByName("shadowJar")?.enabled = false - fun registerBuildPluginTask(target: KotlinTarget, isSinglePlatform: Boolean) { - tasks.create(if (isSinglePlatform) "buildPlugin" else "buildPlugin${target.name.capitalize()}", ShadowJar::class.java).apply shadow@{ + fun registerBuildPluginTask(target: KotlinTarget, isSingleTarget: Boolean) { + tasks.create("buildPlugin".wrapNameWithPlatform(target, isSingleTarget), BuildMiraiPluginTask::class.java).apply shadow@{ group = "mirai" + targetField = target + + archiveExtension.set("mirai.jar") val compilations = target.compilations.filter { it.name == MAIN_COMPILATION_NAME } compilations.forEach { dependsOn(it.compileKotlinTask) - from(it.output) - for (allKotlinSourceSet in it.allKotlinSourceSets) { - from(allKotlinSourceSet.resources) - } + from(it.output.allOutputs) } from(project.configurations.getByName("runtimeClasspath").copyRecursive { dependency -> @@ -142,18 +142,20 @@ public class MiraiConsoleGradlePlugin : Plugin { } override fun apply(target: Project): Unit = with(target) { - target.extensions.create("mirai", MiraiConsoleExtension::class.java) + extensions.create("mirai", MiraiConsoleExtension::class.java) - target.plugins.apply(JavaPlugin::class.java) - target.plugins.apply(ShadowPlugin::class.java) - - target.repositories.maven { it.setUrl(BINTRAY_REPOSITORY_URL) } + plugins.apply(JavaPlugin::class.java) + plugins.apply("org.gradle.maven-publish") + plugins.apply("org.gradle.maven") + plugins.apply(ShadowPlugin::class.java) + plugins.apply(BintrayPlugin::class.java) + repositories.maven { it.setUrl(BINTRAY_REPOSITORY_URL) } afterEvaluate { configureCompileTarget() kotlinTargets.forEach { configureTarget(it) } registerBuildPluginTasks() - registerPublishPluginTask() + configurePublishing() } } } @@ -171,4 +173,7 @@ internal val Project.kotlinTargets: Collection is KotlinSingleTargetExtension -> listOf(kotlinExtension.target) else -> error("[MiraiConsole] Internal error: kotlinExtension is neither KotlinMultiplatformExtension nor KotlinSingleTargetExtension") } - } \ No newline at end of file + } + +internal val Project.kotlinJvmOrAndroidTargets: Collection + get() = kotlinTargets.filter { it.platformType == KotlinPlatformType.jvm || it.platformType == KotlinPlatformType.androidJvm } diff --git a/tools/gradle-plugin/src/publishing.kt b/tools/gradle-plugin/src/publishing.kt index c10f01d8c..db01b23de 100644 --- a/tools/gradle-plugin/src/publishing.kt +++ b/tools/gradle-plugin/src/publishing.kt @@ -9,13 +9,19 @@ package net.mamoe.mirai.console.gradle +import com.google.gson.Gson +import com.jfrog.bintray.gradle.tasks.BintrayUploadTask import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.getValue import org.gradle.kotlin.dsl.provideDelegate import org.gradle.kotlin.dsl.registering +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import java.io.File private val Project.selfAndParentProjects: Sequence @@ -29,17 +35,105 @@ private fun Project.findPropertySmart(propName: String): String? { } private fun Project.findPropertySmartOrFail(propName: String): String { - return findPropertySmart(propName) ?: error("[Mirai Console] Cannot find property for publication: $propName. Please check your 'mirai' configuration.") + return findPropertySmart(propName) + ?: error("[Mirai Console] Cannot find property for publication: '$propName'. Please check your 'mirai' configuration.") } -internal fun Project.registerPublishPluginTask() { +internal fun Project.configurePublishing() { + if (!miraiExtension.publishingEnabled) return + val isSingleTarget = kotlinJvmOrAndroidTargets.size == 1 + + kotlinJvmOrAndroidTargets.forEach { + registerPublishPluginTasks(it, isSingleTarget) + registerMavenPublications(it, isSingleTarget) + } + + registerBintrayPublish() +} + +private inline fun TaskContainer.getSingleTask(): T = filterIsInstance().single() + +private fun Project.registerPublishPluginTasks() { + val isSingleTarget = kotlinJvmOrAndroidTargets.size == 1 + kotlinJvmOrAndroidTargets.forEach { registerPublishPluginTasks(it, isSingleTarget) } +} + +// effectively public +internal data class PluginMetadata( + val groupId: String, + val artifactId: String, + val version: String, + val description: String?, + val dependencies: List +) + +internal fun String.wrapNameWithPlatform(target: KotlinTarget, isSingleTarget: Boolean): String { + return if (isSingleTarget) this else "$this${target.name.capitalize()}" +} + +private fun Project.registerPublishPluginTasks(target: KotlinTarget, isSingleTarget: Boolean) { + val generateMetadataTask = + tasks.register("generatePluginMetadata".wrapNameWithPlatform(target, isSingleTarget)).get().apply { + group = "mirai" + + val metadataFile = + project.buildDir.resolve("mirai").resolve(if (isSingleTarget) "mirai-plugin.metadata" else "mirai-plugin-${target.name}.metadata") + outputs.file(metadataFile) + + + + doLast { + val mirai = miraiExtension + + val output = outputs.files.singleFile + output.parentFile.mkdir() + + val dependencies = configurations[target.compilations["main"].apiConfigurationName].allDependencies.map { + "${it.group}:${it.name}:${it.version}" + } + + val json = Gson().toJson(PluginMetadata( + groupId = mirai.publishing.groupId ?: project.group.toString(), + artifactId = mirai.publishing.artifactId ?: project.name, + version = mirai.publishing.version ?: project.version.toString(), + description = mirai.publishing.description ?: project.description, + dependencies = dependencies + )) + + logger.info("Generated mirai plugin metadata json: $json") + + output.writeText(json) + } + } + + val bintrayUpload = tasks.getByName(BintrayUploadTask.getTASK_NAME()).dependsOn( + "buildPlugin".wrapNameWithPlatform(target, isSingleTarget), + generateMetadataTask, + // "shadowJar", + tasks.filterIsInstance().single { it.target == target } + ) + tasks.register("publishPlugin".wrapNameWithPlatform(target, isSingleTarget)).get().apply { + group = "mirai" + dependsOn(bintrayUpload) + } +} + +internal inline fun File.renamed(block: File.(nameWithoutExtension: String) -> String): File = this.resolveSibling(block(this, nameWithoutExtension)) + +private fun Project.registerBintrayPublish() { val mirai = miraiExtension bintray { user = mirai.publishing.user ?: findPropertySmartOrFail("bintray.user") key = mirai.publishing.key ?: findPropertySmartOrFail("bintray.key") - setPublications("mavenJava") + val targets = kotlinJvmOrAndroidTargets + if (targets.size == 1) { + setPublications("mavenJava") + } else { + setPublications(*targets.map { "mavenJava".wrapNameWithPlatform(it, false) }.toTypedArray()) + } + setConfigurations("archives") publish = mirai.publishing.publish @@ -49,12 +143,17 @@ internal fun Project.registerPublishPluginTask() { repo = mirai.publishing.repo ?: findPropertySmartOrFail("bintray.repo") name = mirai.publishing.packageName ?: findPropertySmartOrFail("bintray.package") userOrg = mirai.publishing.org ?: findPropertySmart("bintray.org") + desc = mirai.publishing.description ?: project.description mirai.publishing.bintrayPackageConfigConfigs.forEach { it.invoke(this) } } mirai.publishing.bintrayConfigs.forEach { it.invoke(this) } } +} + +private fun Project.registerMavenPublications(target: KotlinTarget, isSingleTarget: Boolean) { + val mirai = miraiExtension @Suppress("DEPRECATION") val sourcesJar by tasks.registering(Jar::class) { @@ -70,7 +169,7 @@ internal fun Project.registerPublishPluginTask() { url = uri("$buildDir/repo") } }*/ - publications.register("mavenJava", MavenPublication::class.java) { publication -> + publications.register("mavenJava".wrapNameWithPlatform(target, isSingleTarget), MavenPublication::class.java) { publication -> with(publication) { from(components["java"]) @@ -89,9 +188,11 @@ internal fun Project.registerPublishPluginTask() { } artifact(sourcesJar.get()) - - // TODO: 2020/11/28 -miraip metadata artifact - // TODO: 2020/11/28 -all shadowed artifact + artifact(tasks.filterIsInstance().single { it.target == target }) + artifact(mapOf( + "source" to tasks.getByName("generatePluginMetadata".wrapNameWithPlatform(target, isSingleTarget)).outputs.files.singleFile, + "extension" to "metadata" + )) mirai.publishing.mavenPublicationConfigs.forEach { it.invoke(this) } } From d2b0ebbd799859aba27ed3e0fc90072c92e46273 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 29 Nov 2020 00:15:21 +0800 Subject: [PATCH 36/52] Enable parallel tasks --- gradle.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gradle.properties b/gradle.properties index 20544ecbe..829238342 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,5 @@ # style guide kotlin.code.style=official org.gradle.vfs.watch=true +kotlin.parallel.tasks.in.project=true +org.gradle.parallel=true \ No newline at end of file From 3a5d95aee316fa9da4931fc4b95c9677a92b6aa6 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 29 Nov 2020 00:15:45 +0800 Subject: [PATCH 37/52] 1.1.0-dev-29 --- .../mirai-console/src/internal/MiraiConsoleBuildConstants.kt | 4 ++-- buildSrc/src/main/kotlin/Versions.kt | 2 +- tools/gradle-plugin/src/VersionConstants.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt b/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt index 747eeca6a..e6b4e299b 100644 --- a/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt +++ b/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt @@ -14,8 +14,8 @@ import java.time.Instant internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic - val buildDate: Instant = Instant.ofEpochSecond(1606543118) - const val versionConst: String = "1.1.0-dev-1" + val buildDate: Instant = Instant.ofEpochSecond(1606580129) + const val versionConst: String = "1.1.0-dev-29" @JvmStatic val version: SemVersion = SemVersion(versionConst) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 3fcffb690..eb2d2d136 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -11,7 +11,7 @@ object Versions { const val core = "1.3.3" - const val console = "1.1.0-dev-1" + const val console = "1.1.0-dev-29" const val consoleGraphical = "0.0.7" const val consoleTerminal = console diff --git a/tools/gradle-plugin/src/VersionConstants.kt b/tools/gradle-plugin/src/VersionConstants.kt index 25c330d87..cc19b5573 100644 --- a/tools/gradle-plugin/src/VersionConstants.kt +++ b/tools/gradle-plugin/src/VersionConstants.kt @@ -10,6 +10,6 @@ package net.mamoe.mirai.console.gradle internal object VersionConstants { - const val CONSOLE_VERSION = "1.1.0-dev-1" // value is written here automatically during build + const val CONSOLE_VERSION = "1.1.0-dev-29" // value is written here automatically during build const val CORE_VERSION = "1.3.3" // value is written here automatically during build } \ No newline at end of file From b36ddf29e29e0f732a5553fc3152ab963e859c68 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 29 Nov 2020 00:26:13 +0800 Subject: [PATCH 38/52] Fix gradle tasks --- tools/gradle-plugin/src/BuildMiraiPluginTask.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tools/gradle-plugin/src/BuildMiraiPluginTask.kt b/tools/gradle-plugin/src/BuildMiraiPluginTask.kt index f316b56ef..53227973f 100644 --- a/tools/gradle-plugin/src/BuildMiraiPluginTask.kt +++ b/tools/gradle-plugin/src/BuildMiraiPluginTask.kt @@ -2,17 +2,24 @@ package net.mamoe.mirai.console.gradle import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputFile import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import java.io.File @CacheableTask public open class BuildMiraiPluginTask : ShadowJar() { - internal var targetField: KotlinTarget? = null + @Internal + public var targetField: KotlinTarget? = null - public val target: KotlinTarget get() = targetField!! + @get:Internal + public val target: KotlinTarget + get() = targetField!! /** * ShadowJar 打包结果 */ - public val output: File get() = outputs.files.singleFile + @get:OutputFile + public val output: File + get() = outputs.files.singleFile } \ No newline at end of file From 525b759d9e1523e68424898f32fd7bd7d4f0315b Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 29 Nov 2020 00:27:03 +0800 Subject: [PATCH 39/52] 1.1.0-dev-30 --- .../mirai-console/src/internal/MiraiConsoleBuildConstants.kt | 4 ++-- buildSrc/src/main/kotlin/Versions.kt | 2 +- tools/gradle-plugin/src/VersionConstants.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt b/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt index e6b4e299b..778eb7221 100644 --- a/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt +++ b/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt @@ -14,8 +14,8 @@ import java.time.Instant internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic - val buildDate: Instant = Instant.ofEpochSecond(1606580129) - const val versionConst: String = "1.1.0-dev-29" + val buildDate: Instant = Instant.ofEpochSecond(1606580812) + const val versionConst: String = "1.1.0-dev-30" @JvmStatic val version: SemVersion = SemVersion(versionConst) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index eb2d2d136..5921aaae0 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -11,7 +11,7 @@ object Versions { const val core = "1.3.3" - const val console = "1.1.0-dev-29" + const val console = "1.1.0-dev-30" const val consoleGraphical = "0.0.7" const val consoleTerminal = console diff --git a/tools/gradle-plugin/src/VersionConstants.kt b/tools/gradle-plugin/src/VersionConstants.kt index cc19b5573..e847d618c 100644 --- a/tools/gradle-plugin/src/VersionConstants.kt +++ b/tools/gradle-plugin/src/VersionConstants.kt @@ -10,6 +10,6 @@ package net.mamoe.mirai.console.gradle internal object VersionConstants { - const val CONSOLE_VERSION = "1.1.0-dev-29" // value is written here automatically during build + const val CONSOLE_VERSION = "1.1.0-dev-30" // value is written here automatically during build const val CORE_VERSION = "1.3.3" // value is written here automatically during build } \ No newline at end of file From e3919366791876866b99814ed86fe2a66fb1bb60 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 28 Nov 2020 13:25:30 +0800 Subject: [PATCH 40/52] PathBased LoggerController; KDoc close #228 --- backend/mirai-console/src/MiraiConsole.kt | 4 + .../internal/data/builtins/LoggerConfig.kt | 1 + .../internal/logging/LoggerControllerImpl.kt | 9 ++- .../src/logging/AbstractLoggerController.kt | 74 +++++++++++++++++++ .../src/logging/LoggerController.kt | 7 +- .../test/logging/TestALC_PathBased.kt | 55 ++++++++++++++ 6 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 backend/mirai-console/test/logging/TestALC_PathBased.kt diff --git a/backend/mirai-console/src/MiraiConsole.kt b/backend/mirai-console/src/MiraiConsole.kt index fd4a21560..8360bd388 100644 --- a/backend/mirai-console/src/MiraiConsole.kt +++ b/backend/mirai-console/src/MiraiConsole.kt @@ -30,6 +30,7 @@ import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.utils.BotConfiguration +import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.MiraiLogger import java.io.File import java.nio.file.Path @@ -140,6 +141,9 @@ public interface MiraiConsole : CoroutineScope { var config = BotConfiguration().apply { fileBasedDeviceInfo() redirectNetworkLogToDirectory() + this.botLoggerSupplier = { + DefaultLogger("Bot.${it.id}") + } parentCoroutineContext = MiraiConsole.childScopeContext("Bot $id") this.loginSolver = MiraiConsoleImplementationBridge.createLoginSolver(id, this) diff --git a/backend/mirai-console/src/internal/data/builtins/LoggerConfig.kt b/backend/mirai-console/src/internal/data/builtins/LoggerConfig.kt index 49375d866..447051689 100644 --- a/backend/mirai-console/src/internal/data/builtins/LoggerConfig.kt +++ b/backend/mirai-console/src/internal/data/builtins/LoggerConfig.kt @@ -27,6 +27,7 @@ internal object LoggerConfig : AutoSavePluginConfig("Logger") { mapOf( "example.logger" to AbstractLoggerController.LogPriority.NONE, "console.debug" to AbstractLoggerController.LogPriority.NONE, + "Bot" to AbstractLoggerController.LogPriority.ALL, ) ) diff --git a/backend/mirai-console/src/internal/logging/LoggerControllerImpl.kt b/backend/mirai-console/src/internal/logging/LoggerControllerImpl.kt index 836583e7e..2f7273d79 100644 --- a/backend/mirai-console/src/internal/logging/LoggerControllerImpl.kt +++ b/backend/mirai-console/src/internal/logging/LoggerControllerImpl.kt @@ -16,15 +16,18 @@ import net.mamoe.mirai.console.util.ConsoleInternalApi @ConsoleFrontEndImplementation @ConsoleInternalApi -internal object LoggerControllerImpl : AbstractLoggerController() { +internal object LoggerControllerImpl : AbstractLoggerController.PathBased() { internal var initialized = false - override fun getPriority(identity: String?): LogPriority { + override fun findPriority(identity: String?): LogPriority? { if (!initialized) return LogPriority.NONE return if (identity == null) { LoggerConfig.defaultPriority } else { - LoggerConfig.loggers[identity] ?: LoggerConfig.defaultPriority + LoggerConfig.loggers[identity] } } + + override val defaultPriority: LogPriority + get() = if (initialized) LoggerConfig.defaultPriority else LogPriority.NONE } \ No newline at end of file diff --git a/backend/mirai-console/src/logging/AbstractLoggerController.kt b/backend/mirai-console/src/logging/AbstractLoggerController.kt index a3105bcac..c9a58e965 100644 --- a/backend/mirai-console/src/logging/AbstractLoggerController.kt +++ b/backend/mirai-console/src/logging/AbstractLoggerController.kt @@ -13,19 +13,33 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.SimpleLogger import java.util.* +/** + * 日志控制器的基本实现 + */ @ConsoleExperimentalApi public abstract class AbstractLoggerController : LoggerController { + /** + * @param priority 尝试判断的日志等级 + * @param settings 配置中的日志等级 (see [getPriority]) + */ protected open fun shouldLog( priority: LogPriority, settings: LogPriority, ): Boolean = settings <= priority + /** + * 获取配置中与 [identity] 对应的 [LogPriority] + */ protected abstract fun getPriority(identity: String?): LogPriority override fun shouldLog(identity: String?, priority: SimpleLogger.LogPriority): Boolean = shouldLog(LogPriority.by(priority), getPriority(identity)) + /** + * 便于进行配置存储的 [LogPriority], + * 等级优先级与 [SimpleLogger.LogPriority] 对应 + */ @Suppress("unused") @ConsoleExperimentalApi public enum class LogPriority { @@ -59,4 +73,64 @@ public abstract class AbstractLoggerController : LoggerController { } + /** + * 路径形式实现的基本日志控制器 + * + * Example: + * 配置文件: + * ``` + * defaultPriority: ALL + * loggers: + * t: NONE + * t.sub: VERBOSE + * t.sub.1: NONE + * ``` + * + * ``` + * "logger.1" + * -> "logger.1" << null + * -> "logger" << null + * -> defaultPriority << ALL + * + * "t.sub.1" + * -> "t.sub.1" << NONE + * + * "t.sub.2" + * -> "t.sub.2" << null + * -> "t.sub" << VERBOSE + * + * ...... + * ``` + */ + @ConsoleExperimentalApi + public abstract class PathBased + @JvmOverloads public constructor( + protected open val spliterator: Char = '.' + ) : AbstractLoggerController() { + protected abstract val defaultPriority: LogPriority + protected abstract fun findPriority(identity: String?): LogPriority? + + /** + * 从 [path] 析出下一次应该进行搜索的二次 path (@see [getPriority]) + * + * @return 如果返回了 `null`, 会令 [getPriority] 返回 `findPriority(null) ?: defaultPriority`) + */ + protected open fun nextPath(path: String): String? { + val lastIndex = path.lastIndexOf(spliterator) + if (lastIndex == -1) return null + return path.substring(0, lastIndex) + } + + override fun getPriority(identity: String?): LogPriority { + if (identity == null) { + return findPriority(null) ?: defaultPriority + } else { + var path: String = identity + while (true) { + findPriority(path)?.let { return it } + path = nextPath(path) ?: return (findPriority(null) ?: defaultPriority) + } + } + } + } } diff --git a/backend/mirai-console/src/logging/LoggerController.kt b/backend/mirai-console/src/logging/LoggerController.kt index 59b60457b..cdb96a203 100644 --- a/backend/mirai-console/src/logging/LoggerController.kt +++ b/backend/mirai-console/src/logging/LoggerController.kt @@ -9,14 +9,17 @@ package net.mamoe.mirai.console.logging -import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl +import net.mamoe.mirai.console.MiraiConsoleImplementation +import net.mamoe.mirai.console.internal.logging.MiraiConsoleLogger import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.SimpleLogger /** * 日志控制系统 * - * @see [LoggerControllerImpl] + * @see [AbstractLoggerController] + * @see [MiraiConsoleImplementation.loggerController] + * @see [MiraiConsoleLogger] */ @ConsoleExperimentalApi public interface LoggerController { diff --git a/backend/mirai-console/test/logging/TestALC_PathBased.kt b/backend/mirai-console/test/logging/TestALC_PathBased.kt new file mode 100644 index 000000000..72c45beb2 --- /dev/null +++ b/backend/mirai-console/test/logging/TestALC_PathBased.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.logging + +import org.junit.jupiter.api.Test + +@Suppress("ClassName") +internal class TestALC_PathBased { + @Test + fun `test AbstractLoggerController$PathBased`() { + val config = mapOf( + "test" to "ALL", + "test.test" to "VERBOSE", + "test.test.test" to "NONE", + ).mapValues { AbstractLoggerController.LogPriority.valueOf(it.value) } + + val c = object : AbstractLoggerController.PathBased() { + override val defaultPriority: LogPriority + get() = LogPriority.NONE + + override fun findPriority(identity: String?): LogPriority? { + if (identity == null) return defaultPriority + return config[identity] + } + + fun priority(i: String?): LogPriority = getPriority(i) + } + + fun assertSame(path: String?, p: String) { + kotlin.test.assertSame(c.priority(path), AbstractLoggerController.LogPriority.valueOf(p)) + } + + assertSame("test.test.test", "NONE") + assertSame("test.test.test.more.test", "NONE") + + assertSame("test.test.t1", "VERBOSE") + assertSame("test.test.t15w", "VERBOSE") + assertSame("test.test", "VERBOSE") + + assertSame("test", "ALL") + assertSame("test.tes1ww", "ALL") + assertSame("test.asldjawe.awej2oi3", "ALL") + + assertSame("AWawex", "NONE") + assertSame("awpejaszx.aljewkz", "NONE") + assertSame("test0.awekjo23xxxxx", "NONE") + } +} From e5bc588da75a2baf494a8dda4c86306e4fa09c1e Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 29 Nov 2020 10:03:24 +0800 Subject: [PATCH 41/52] Stop input service when catch any error Fix #229 --- .../src/ConsoleThread.kt | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/frontend/mirai-console-terminal/src/ConsoleThread.kt b/frontend/mirai-console-terminal/src/ConsoleThread.kt index b6cdcaa28..2756b7da9 100644 --- a/frontend/mirai-console-terminal/src/ConsoleThread.kt +++ b/frontend/mirai-console-terminal/src/ConsoleThread.kt @@ -55,8 +55,8 @@ internal fun startupConsoleThread() { } MiraiConsole.launch(CoroutineName("Console Command")) { while (true) { - try { - val next = MiraiConsole.requestInput("").let { + val next = try { + MiraiConsole.requestInput("").let { when { it.isBlank() -> it it.startsWith(CommandManager.commandPrefix) -> it @@ -64,9 +64,25 @@ internal fun startupConsoleThread() { else -> CommandManager.commandPrefix + it } } - if (next.isBlank()) { - continue - } + } catch (e: InterruptedException) { + return@launch + } catch (e: CancellationException) { + return@launch + } catch (e: UserInterruptException) { + BuiltInCommands.StopCommand.run { ConsoleCommandSender.handle() } + return@launch + } catch (eof: EndOfFileException) { + consoleLogger.warning("Closing input service...") + return@launch + } catch (e: Throwable) { + consoleLogger.error("Error in reading next command", e) + consoleLogger.warning("Closing input service...") + return@launch + } + if (next.isBlank()) { + continue + } + try { // consoleLogger.debug("INPUT> $next") when (val result = ConsoleCommandSender.executeCommand(next)) { is Success -> { @@ -97,12 +113,6 @@ internal fun startupConsoleThread() { return@launch } catch (e: CancellationException) { return@launch - } catch (e: UserInterruptException) { - BuiltInCommands.StopCommand.run { ConsoleCommandSender.handle() } - return@launch - } catch (eof: EndOfFileException) { - consoleLogger.warning("Closing input service...") - return@launch } catch (e: Throwable) { consoleLogger.error("Unhandled exception", e) } From 062227e072b9bac9e86dfd756e04c641a24d9d38 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 29 Nov 2020 10:41:36 +0800 Subject: [PATCH 42/52] IJ plugin: More flexible internal structure for element checkers --- .../ContextualParametersChecker.kt | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt index 86a906bf0..4defb39d8 100644 --- a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt +++ b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt @@ -9,7 +9,6 @@ package net.mamoe.mirai.console.intellij.diagnostics -import com.intellij.psi.PsiElement import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_NAME import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_ID import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAME @@ -26,9 +25,12 @@ import net.mamoe.mirai.console.intellij.util.RequirementParser import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import java.util.* +import kotlin.reflect.KFunction2 /** * Checks parameters with [ResolveContextKind] @@ -46,7 +48,7 @@ class ContextualParametersChecker : DeclarationChecker { private val SEMANTIC_VERSIONING_REGEX = Regex("""^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?${'$'}""") - fun checkPluginId(inspectionTarget: PsiElement, value: String): Diagnostic? { + fun checkPluginId(inspectionTarget: KtElement, value: String): Diagnostic? { if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 不能为空. \n插件 Id$syntax") if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on( inspectionTarget, @@ -65,7 +67,7 @@ class ContextualParametersChecker : DeclarationChecker { return null } - fun checkPluginName(inspectionTarget: PsiElement, value: String): Diagnostic? { + fun checkPluginName(inspectionTarget: KtElement, value: String): Diagnostic? { if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件名不能为空") val lowercaseName = value.toLowerCase() FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal -> @@ -74,14 +76,14 @@ class ContextualParametersChecker : DeclarationChecker { return null } - fun checkPluginVersion(inspectionTarget: PsiElement, value: String): Diagnostic? { + fun checkPluginVersion(inspectionTarget: KtElement, value: String): Diagnostic? { if (!SEMANTIC_VERSIONING_REGEX.matches(value)) { return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "版本号无效: '$value'. \nhttps://semver.org/lang/zh-CN/") } return null } - fun checkCommandName(inspectionTarget: PsiElement, value: String): Diagnostic? { + fun checkCommandName(inspectionTarget: KtElement, value: String): Diagnostic? { return when { value.isBlank() -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不能为空") value.any { it.isWhitespace() } -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "暂时不允许指令名中存在空格") @@ -91,7 +93,7 @@ class ContextualParametersChecker : DeclarationChecker { } } - fun checkPermissionNamespace(inspectionTarget: PsiElement, value: String): Diagnostic? { + fun checkPermissionNamespace(inspectionTarget: KtElement, value: String): Diagnostic? { return when { value.isBlank() -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不能为空") value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "不允许权限命名空间中存在空格") @@ -100,7 +102,7 @@ class ContextualParametersChecker : DeclarationChecker { } } - fun checkPermissionName(inspectionTarget: PsiElement, value: String): Diagnostic? { + fun checkPermissionName(inspectionTarget: KtElement, value: String): Diagnostic? { return when { value.isBlank() -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不能为空") value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "不允许权限名称中存在空格") @@ -109,7 +111,7 @@ class ContextualParametersChecker : DeclarationChecker { } } - fun checkPermissionId(inspectionTarget: PsiElement, value: String): Diagnostic? { + fun checkPermissionId(inspectionTarget: KtElement, value: String): Diagnostic? { return when { value.isBlank() -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "权限 Id 不能为空") value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "暂时不允许权限 Id 中存在空格") @@ -119,7 +121,7 @@ class ContextualParametersChecker : DeclarationChecker { } @Suppress("UNUSED_PARAMETER") - fun checkVersionRequirement(inspectionTarget: PsiElement, value: String): Diagnostic? { + fun checkVersionRequirement(inspectionTarget: KtElement, value: String): Diagnostic? { return try { RequirementHelper.RequirementChecker.processLine(RequirementParser.TokenReader(value)) null @@ -129,8 +131,26 @@ class ContextualParametersChecker : DeclarationChecker { } } - private val checkersMap: EnumMap Diagnostic?> = - EnumMap Diagnostic?>(ResolveContextKind::class.java).apply { + fun interface ElementChecker { + operator fun invoke(declaration: KtElement, valueArgument: ValueArgument, value: String?): Diagnostic? + } + + private val stringCheckersMap: EnumMap = + EnumMap(ResolveContextKind::class.java).apply { + + fun put(key: ResolveContextKind, value: KFunction2): ElementChecker? { + return put(key) { d, _, v -> + if (v != null) value(d, v) + else null + } + } + + fun put(key: ResolveContextKind, value: KFunction2): ElementChecker? { + return put(key) { d, v, _ -> + value(d, v) + } + } + put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName) put(ResolveContextKind.PLUGIN_ID, ::checkPluginId) put(ResolveContextKind.SEMANTIC_VERSION, ::checkPluginVersion) @@ -153,20 +173,20 @@ class ContextualParametersChecker : DeclarationChecker { } .mapNotNull { (p, a) -> p.resolveContextKinds - ?.map(checkersMap::get) + ?.map(stringCheckersMap::get) ?.mapNotNull { if (it == null) null else it to a } } .flatMap { it.asSequence() } .mapNotNull { (kind, argument) -> - argument.resolveStringConstantValues()?.let { const -> - Triple(kind, argument, const) - } + Triple(kind, argument, argument.resolveStringConstantValues()) } .forEach { (fn, argument, resolvedConstants) -> - for (resolvedConstant in resolvedConstants) { - fn(argument.asElement(), resolvedConstant)?.let { context.report(it) } + if (resolvedConstants == null) { + fn(argument.asElement(), argument, null)?.let { context.report(it) } + } else for (resolvedConstant in resolvedConstants) { + fn(argument.asElement(), argument, resolvedConstant)?.let { context.report(it) } } } return From bc290f63bbadaca96323501918f5054fcf558b68 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 29 Nov 2020 15:05:39 +0800 Subject: [PATCH 43/52] New inspection: ILLEGAL_COMMAND_DECLARATION_RECEIVER, close #174 --- .../src/diagnostics/MiraiConsoleErrors.kt | 8 ++++ .../MiraiConsoleErrorsRendering.kt | 12 +++++ .../src/resolve/resolveTypes.kt | 1 + .../org/example/myplugin/MySimpleCommand.kt | 2 +- .../src/IDEContainerContributor.kt | 2 + .../diagnostics/CommandDeclarationChecker.kt | 44 +++++++++++++++++++ .../src/resolve/resolveIdea.kt | 33 ++++++++++++++ 7 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 tools/intellij-plugin/src/diagnostics/CommandDeclarationChecker.kt diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt index 53df01f1d..a9b5b9ada 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt @@ -10,6 +10,7 @@ package net.mamoe.mirai.console.compiler.common.diagnostics import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0.create import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1.create import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2.create import org.jetbrains.kotlin.diagnostics.Errors @@ -17,6 +18,7 @@ import org.jetbrains.kotlin.diagnostics.Severity.ERROR import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtTypeProjection +import org.jetbrains.kotlin.psi.KtTypeReference /** * 如何增加一个错误: @@ -54,6 +56,12 @@ object MiraiConsoleErrors { @JvmField val ILLEGAL_VERSION_REQUIREMENT = create(ERROR) +// @JvmField +// val INAPPLICABLE_COMMAND_ANNOTATION = create(ERROR) + + @JvmField + val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create(ERROR) + @Suppress("ObjectPropertyName", "unused") @JvmField @Deprecated("", level = DeprecationLevel.ERROR) diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt index 73e60cd4b..3d808000b 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.console.compiler.common.diagnostics +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_DECLARATION_RECEIVER import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_NAME import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_REGISTER_USE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_ID @@ -95,6 +96,17 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { Renderers.STRING, Renderers.STRING ) + + put( + ILLEGAL_COMMAND_DECLARATION_RECEIVER, + "指令函数的接收者参数必须为 CommandSender 及其子类或无接收者.", + ) + +// put( +// INAPPLICABLE_COMMAND_ANNOTATION, +// "''{0}'' 无法在顶层函数使用.", +// Renderers.STRING, +// ) } override fun getMap() = MAP diff --git a/tools/compiler-common/src/resolve/resolveTypes.kt b/tools/compiler-common/src/resolve/resolveTypes.kt index c8d5bce00..7d4fb7c07 100644 --- a/tools/compiler-common/src/resolve/resolveTypes.kt +++ b/tools/compiler-common/src/resolve/resolveTypes.kt @@ -32,6 +32,7 @@ val AUTO_SERVICE = FqName("com.google.auto.service.AutoService") val COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.CompositeCommand.SubCommand") val SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.SimpleCommand.Handler") +val COMMAND_SENDER_FQ_NAME = FqName("net.mamoe.mirai.console.command.CommandSender") /////////////////////////////////////////////////////////////////////////// // Plugin diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt index 85199308a..61fc4a46e 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt @@ -17,7 +17,7 @@ object MySimpleCommand000 : SimpleCommand( } object DataTest : AutoSavePluginConfig("data") { - val pp by value(NoDefaultValue(1)) + val pp by value(NoDefaultValue(1)) } @Serializable diff --git a/tools/intellij-plugin/src/IDEContainerContributor.kt b/tools/intellij-plugin/src/IDEContainerContributor.kt index 00b331fc7..c6b8d1f59 100644 --- a/tools/intellij-plugin/src/IDEContainerContributor.kt +++ b/tools/intellij-plugin/src/IDEContainerContributor.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.console.intellij +import net.mamoe.mirai.console.intellij.diagnostics.CommandDeclarationChecker import net.mamoe.mirai.console.intellij.diagnostics.ContextualParametersChecker import net.mamoe.mirai.console.intellij.diagnostics.PluginDataValuesChecker import org.jetbrains.kotlin.container.StorageComponentContainer @@ -24,5 +25,6 @@ class IDEContainerContributor : StorageComponentContainerContributor { ) { container.useInstance(ContextualParametersChecker()) container.useInstance(PluginDataValuesChecker()) + container.useInstance(CommandDeclarationChecker()) } } \ No newline at end of file diff --git a/tools/intellij-plugin/src/diagnostics/CommandDeclarationChecker.kt b/tools/intellij-plugin/src/diagnostics/CommandDeclarationChecker.kt new file mode 100644 index 000000000..f840fc5c4 --- /dev/null +++ b/tools/intellij-plugin/src/diagnostics/CommandDeclarationChecker.kt @@ -0,0 +1,44 @@ +package net.mamoe.mirai.console.intellij.diagnostics + +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_DECLARATION_RECEIVER +import net.mamoe.mirai.console.compiler.common.resolve.COMMAND_SENDER_FQ_NAME +import net.mamoe.mirai.console.intellij.resolve.hasSuperType +import net.mamoe.mirai.console.intellij.resolve.isCompositeCommandSubCommand +import net.mamoe.mirai.console.intellij.resolve.isSimpleCommandHandler +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext + +class CommandDeclarationChecker : DeclarationChecker { + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + if (declaration !is KtNamedFunction) return + + // exclusive checks + when { + declaration.isSimpleCommandHandler() -> { + } + + declaration.isCompositeCommandSubCommand() -> { + } + else -> return + } + + // common checks + checkCommandReceiverParameter(declaration)?.let { context.report(it) } + } + + companion object { + fun checkCommandReceiverParameter(declaration: KtNamedFunction): Diagnostic? { + val receiverTypeRef = declaration.receiverTypeReference ?: return null // no receiver, accept. + val receiver = receiverTypeRef.resolveReferencedType() ?: return null // unresolved type + if (!receiver.hasSuperType(COMMAND_SENDER_FQ_NAME)) { + return ILLEGAL_COMMAND_DECLARATION_RECEIVER.on(receiverTypeRef) + } + + return null + } + } +} \ No newline at end of file diff --git a/tools/intellij-plugin/src/resolve/resolveIdea.kt b/tools/intellij-plugin/src/resolve/resolveIdea.kt index 3cb91405f..e4c7a659a 100644 --- a/tools/intellij-plugin/src/resolve/resolveIdea.kt +++ b/tools/intellij-plugin/src/resolve/resolveIdea.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.console.intellij.resolve +import com.intellij.psi.PsiClass import com.intellij.psi.PsiDeclarationStatement import com.intellij.psi.PsiElement import com.intellij.psi.util.parentsWithSelf @@ -60,6 +61,19 @@ val KtPureClassOrObject.allSuperTypes: Sequence } } +val PsiClass.allSuperTypes: Sequence + get() = sequence { + interfaces.forEach { + yield(it) + yieldAll(it.allSuperTypes) + } + val superClass = superClass + if (superClass != null) { + yield(superClass) + yieldAll(superClass.allSuperTypes) + } + } + fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? { val reference = typeReference if (reference != null) { @@ -71,7 +85,26 @@ fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? { return null } +fun KtClassOrObject.hasSuperType(fqName: FqName): Boolean = allSuperNames.contains(fqName) +fun KtClass.hasSuperType(fqName: FqName): Boolean = allSuperNames.contains(fqName) + +@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") +@kotlin.internal.LowPriorityInOverloadResolution +fun PsiElement.hasSuperType(fqName: FqName): Boolean = allSuperNames.contains(fqName) + val KtClassOrObject.allSuperNames: Sequence get() = allSuperTypes.mapNotNull { it.getKotlinFqName() } +val PsiClass.allSuperNames: Sequence get() = allSuperTypes.mapNotNull { clazz -> clazz.qualifiedName?.let { FqName(it) } } + +@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") +@kotlin.internal.LowPriorityInOverloadResolution +val PsiElement.allSuperNames: Sequence + get() { + return when (this) { + is KtClassOrObject -> allSuperNames + is PsiClass -> allSuperNames + else -> emptySequence() + } + } fun getElementForLineMark(callElement: PsiElement): PsiElement = when (callElement) { From 9db8c88aa83475c4b8fd09c7af1acd5f95439edb Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 29 Nov 2020 15:10:31 +0800 Subject: [PATCH 44/52] Fix Plugin repeated disable --- .../mirai-console/src/internal/plugin/PluginManagerImpl.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt b/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt index 70bbdd4c5..bd2d72257 100644 --- a/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt +++ b/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt @@ -71,7 +71,10 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol init { MiraiConsole.coroutineContext[Job]!!.invokeOnCompletion { - plugins.forEach { disablePlugin(it) } + plugins.forEach { plugin -> + if (plugin.isEnabled) + disablePlugin(plugin) + } } } From 1bc6e97630ade110211020cbf8549d37490250fd Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 29 Nov 2020 15:12:15 +0800 Subject: [PATCH 45/52] Colorful StatusCommand --- .../src/command/BuiltInCommands.kt | 122 ++++++++++++++---- 1 file changed, 100 insertions(+), 22 deletions(-) diff --git a/backend/mirai-console/src/command/BuiltInCommands.kt b/backend/mirai-console/src/command/BuiltInCommands.kt index 9da243d3b..33141108d 100644 --- a/backend/mirai-console/src/command/BuiltInCommands.kt +++ b/backend/mirai-console/src/command/BuiltInCommands.kt @@ -41,8 +41,10 @@ import net.mamoe.mirai.console.permission.PermissionService.Companion.permit import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.plugin.version +import net.mamoe.mirai.console.util.AnsiMessageBuilder import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInternalApi +import net.mamoe.mirai.console.util.sendAnsiMessage import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.message.nextMessageOrNull import net.mamoe.mirai.utils.secondsToMillis @@ -355,48 +357,102 @@ public object BuiltInCommands { ), BuiltInCommandInternal { @Handler public suspend fun CommandSender.handle() { - sendMessage(buildString { + sendAnsiMessage { val buildDateFormatted = MiraiConsoleBuildConstants.buildDate.atZone(ZoneId.systemDefault()) .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) - - append("Running MiraiConsole v${MiraiConsoleBuildConstants.versionConst}, built on ").append(buildDateFormatted) - .append(".\n") + append("Running MiraiConsole v") + gold().append(MiraiConsoleBuildConstants.versionConst) + reset().append(", built on ") + lightBlue().append(buildDateFormatted).reset().append(".\n") append(MiraiConsoleImplementationBridge.frontEndDescription.render()).append("\n\n") append("Permission Service: ").append( if (PermissionService.INSTANCE is BuiltInPermissionService) { + lightYellow() "Built In Permission Service" } else { val plugin = PermissionServiceProvider.providerPlugin if (plugin == null) { PermissionService.INSTANCE.toString() } else { - "${plugin.name} v${plugin.version}" + green().append(plugin.name).reset().append(" v").gold() + plugin.version.toString() } } ) - append("\n\n") + reset().append("\n\n") append("Plugins: ") if (PluginManagerImpl.resolvedPlugins.isEmpty()) { - append("") + gray().append("") } else { PluginManagerImpl.resolvedPlugins.joinTo(this) { plugin -> - "${plugin.name} v${plugin.version}" + green().append(plugin.name).reset().append(" v").gold() + plugin.version.toString() } } - append("\n\n") + reset().append("\n\n") + val memoryMXBean = ManagementFactory.getMemoryMXBean() append("Object Pending Finalization Count: ") + .emeraldGreen() .append(memoryMXBean.objectPendingFinalizationCount) + .reset() .append("\n") + val l1 = arrayOf("committed", "init", "used", "max") + val l2 = renderMemoryUsage(memoryMXBean.heapMemoryUsage) + val l3 = renderMemoryUsage(memoryMXBean.nonHeapMemoryUsage) + val lmax = calculateMax(l1, l2.first, l3.first) + + append(" ") + l1.forEachIndexed { index, s -> + if (index != 0) append(" | ") + renderMUNum(lmax[index], s.length) { append(s); reset() } + } + reset() + append("\n") + + fun rendMU(l: Pair, LongArray>) { + val max = l.second[3] + val e50 = max / 2 + val e90 = max * 90 / 100 + l.first.forEachIndexed { index, s -> + if (index != 0) append(" | ") + renderMUNum(lmax[index], s.length) { + if (index == 3) { + // MAX + append(s) + } else { + if (max < 0L) { + append(s) + } else { + val v = l.second[index] + when { + v < e50 -> { + green() + } + v < e90 -> { + lightRed() + } + else -> { + red() + } + } + append(s) + reset() + } + } + } + } + } append(" Heap Memory: ") - renderMemoryUsage(memoryMXBean.heapMemoryUsage) + rendMU(l2) append("\nNon-Heap Memory: ") + rendMU(l3) renderMemoryUsage(memoryMXBean.nonHeapMemoryUsage) - }) + } } private const val MEM_B = 1024L @@ -408,7 +464,7 @@ public object BuiltInCommands { private inline fun StringBuilder.appendDouble(number: Double): StringBuilder = append(floor(number * 100) / 100) - private fun StringBuilder.renderMemoryUsageNumber(num: Long) { + private fun renderMemoryUsageNumber(num: Long) = buildString { when { num == -1L -> { append(num) @@ -428,17 +484,39 @@ public object BuiltInCommands { } } + private fun AnsiMessageBuilder.renderMemoryUsage(usage: MemoryUsage) = arrayOf( + renderMemoryUsageNumber(usage.committed), + renderMemoryUsageNumber(usage.init), + renderMemoryUsageNumber(usage.used), + renderMemoryUsageNumber(usage.max), + ) to longArrayOf( + usage.committed, + usage.init, + usage.used, + usage.max, + ) - private fun StringBuilder.renderMemoryUsage(usage: MemoryUsage) { - append("(committed / init / used / max) [") - renderMemoryUsageNumber(usage.committed) - append(", ") - renderMemoryUsageNumber(usage.init) - append(", ") - renderMemoryUsageNumber(usage.used) - append(", ") - renderMemoryUsageNumber(usage.max) - append("]") + private var emptyLine = " ".repeat(10) + private fun Appendable.emptyLine(size: Int) { + if (emptyLine.length <= size) { + emptyLine = String(CharArray(size) { ' ' }) + } + append(emptyLine, 0, size) + } + + private inline fun AnsiMessageBuilder.renderMUNum(size: Int, contentLength: Int, code: () -> Unit) { + val s = size - contentLength + val left = s / 2 + val right = s - left + emptyLine(left) + code() + emptyLine(right) + } + + private fun calculateMax( + vararg lines: Array + ): IntArray = IntArray(lines[0].size) { r -> + lines.maxOf { it[r].length } } } } \ No newline at end of file From a66ecbf8c8f926cbd0b32d4719ea3c2e1cdc9c23 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 29 Nov 2020 16:12:49 +0800 Subject: [PATCH 46/52] New inspection: RESTRICTED_CONSOLE_COMMAND_OWNER, close #216 --- backend/mirai-console/src/command/Command.kt | 2 + .../src/command/CompositeCommand.kt | 3 +- .../mirai-console/src/command/RawCommand.kt | 8 ++- .../src/command/SimpleCommand.kt | 3 +- .../src/command/java/JCompositeCommand.kt | 3 +- .../src/command/java/JRawCommand.kt | 8 ++- .../src/command/java/JSimpleCommand.kt | 3 +- .../src/common/ResolveContext.kt | 4 +- buildSrc/src/main/kotlin/Versions.kt | 2 +- .../src/diagnostics/MiraiConsoleErrors.kt | 8 +-- .../MiraiConsoleErrorsRendering.kt | 6 +++ .../src/resolve/resolveTypes.kt | 4 +- tools/gradle-plugin/src/VersionConstants.kt | 2 +- .../projects/test-project/build.gradle.kts | 35 ++---------- .../projects/test-project/settings.gradle.kts | 7 +++ .../org/example/myplugin/MySimpleCommand.kt | 3 +- .../ContextualParametersChecker.kt | 54 ++++++++++++++----- .../src/resolve/resolveIdea.kt | 4 +- 18 files changed, 96 insertions(+), 63 deletions(-) diff --git a/backend/mirai-console/src/command/Command.kt b/backend/mirai-console/src/command/Command.kt index 6ea89734e..675481731 100644 --- a/backend/mirai-console/src/command/Command.kt +++ b/backend/mirai-console/src/command/Command.kt @@ -16,6 +16,7 @@ import net.mamoe.mirai.console.command.descriptor.CommandSignature import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.util.ConsoleExperimentalApi @@ -89,6 +90,7 @@ public interface Command { * 指令拥有者. * @see CommandOwner */ + @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) public val owner: CommandOwner public companion object { diff --git a/backend/mirai-console/src/command/CompositeCommand.kt b/backend/mirai-console/src/command/CompositeCommand.kt index 46be20fdc..b3598c6f2 100644 --- a/backend/mirai-console/src/command/CompositeCommand.kt +++ b/backend/mirai-console/src/command/CompositeCommand.kt @@ -20,6 +20,7 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.internal.command.CommandReflector import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.permission.Permission @@ -82,7 +83,7 @@ import kotlin.annotation.AnnotationTarget.FUNCTION * @see buildCommandArgumentContext */ public abstract class CompositeCommand( - owner: CommandOwner, + @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner, @ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) vararg secondaryNames: String, description: String = "no description available", diff --git a/backend/mirai-console/src/command/RawCommand.kt b/backend/mirai-console/src/command/RawCommand.kt index fda863120..e68e34bad 100644 --- a/backend/mirai-console/src/command/RawCommand.kt +++ b/backend/mirai-console/src/command/RawCommand.kt @@ -15,6 +15,7 @@ import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.java.JRawCommand import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.internal.command.findOrCreateCommandPermission import net.mamoe.mirai.console.internal.data.typeOf0 import net.mamoe.mirai.console.permission.Permission @@ -38,11 +39,14 @@ public abstract class RawCommand( * 指令拥有者. * @see CommandOwner */ + @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) public override val owner: CommandOwner, /** 主指令名. */ - @ResolveContext(COMMAND_NAME) public override val primaryName: String, + @ResolveContext(COMMAND_NAME) + public override val primaryName: String, /** 次要指令名. */ - @ResolveContext(COMMAND_NAME) public override vararg val secondaryNames: String, + @ResolveContext(COMMAND_NAME) + public override vararg val secondaryNames: String, /** 用法说明, 用于发送给用户 */ public override val usage: String = "", /** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */ diff --git a/backend/mirai-console/src/command/SimpleCommand.kt b/backend/mirai-console/src/command/SimpleCommand.kt index 6f78c8a42..46b0a2343 100644 --- a/backend/mirai-console/src/command/SimpleCommand.kt +++ b/backend/mirai-console/src/command/SimpleCommand.kt @@ -21,6 +21,7 @@ import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.java.JSimpleCommand import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.internal.command.CommandReflector import net.mamoe.mirai.console.internal.command.IllegalCommandDeclarationException import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver @@ -53,7 +54,7 @@ import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER * @see [CommandManager.executeCommand] */ public abstract class SimpleCommand( - owner: CommandOwner, + @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner, @ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) vararg secondaryNames: String, description: String = "no description available", diff --git a/backend/mirai-console/src/command/java/JCompositeCommand.kt b/backend/mirai-console/src/command/java/JCompositeCommand.kt index aba439d1c..c7be80672 100644 --- a/backend/mirai-console/src/command/java/JCompositeCommand.kt +++ b/backend/mirai-console/src/command/java/JCompositeCommand.kt @@ -17,6 +17,7 @@ import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.permission.Permission /** @@ -71,7 +72,7 @@ import net.mamoe.mirai.console.permission.Permission */ public abstract class JCompositeCommand @JvmOverloads constructor( - owner: CommandOwner, + @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner, @ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) vararg secondaryNames: String, parentPermission: Permission = owner.parentPermission, diff --git a/backend/mirai-console/src/command/java/JRawCommand.kt b/backend/mirai-console/src/command/java/JRawCommand.kt index e3cf80512..a1652296b 100644 --- a/backend/mirai-console/src/command/java/JRawCommand.kt +++ b/backend/mirai-console/src/command/java/JRawCommand.kt @@ -16,6 +16,7 @@ import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.internal.command.findOrCreateCommandPermission import net.mamoe.mirai.console.permission.Permission @@ -51,9 +52,12 @@ public abstract class JRawCommand * 指令拥有者. * @see CommandOwner */ + @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) public override val owner: CommandOwner, - @ResolveContext(COMMAND_NAME) public override val primaryName: String, - @ResolveContext(COMMAND_NAME) public override vararg val secondaryNames: String, + @ResolveContext(COMMAND_NAME) + public override val primaryName: String, + @ResolveContext(COMMAND_NAME) + public override vararg val secondaryNames: String, parentPermission: Permission = owner.parentPermission, ) : Command { /** 用法说明, 用于发送给用户 */ diff --git a/backend/mirai-console/src/command/java/JSimpleCommand.kt b/backend/mirai-console/src/command/java/JSimpleCommand.kt index d5aa6d3ed..cf80c1ab3 100644 --- a/backend/mirai-console/src/command/java/JSimpleCommand.kt +++ b/backend/mirai-console/src/command/java/JSimpleCommand.kt @@ -16,6 +16,7 @@ import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.permission.Permission /** @@ -43,7 +44,7 @@ import net.mamoe.mirai.console.permission.Permission * @see [CommandManager.executeCommand] */ public abstract class JSimpleCommand( - owner: CommandOwner, + @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner, @ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) vararg secondaryNames: String, basePermission: Permission, diff --git a/backend/mirai-console/src/common/ResolveContext.kt b/backend/mirai-console/src/common/ResolveContext.kt index 93a5006e3..592c2179f 100644 --- a/backend/mirai-console/src/common/ResolveContext.kt +++ b/backend/mirai-console/src/common/ResolveContext.kt @@ -59,7 +59,7 @@ public annotation class ResolveContext( /** * @see SemVersion.Companion.parseRangeRequirement */ - VERSION_REQUIREMENT, // ILLEGAL_VERSION_REQUIREMENT // TODO + VERSION_REQUIREMENT, // ILLEGAL_VERSION_REQUIREMENT /** * @see Command.allNames @@ -87,5 +87,7 @@ public annotation class ResolveContext( * @see PluginData.value */ RESTRICTED_NO_ARG_CONSTRUCTOR, // NOT_CONSTRUCTABLE_TYPE + + RESTRICTED_CONSOLE_COMMAND_OWNER, } } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 5921aaae0..19f1befac 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -11,7 +11,7 @@ object Versions { const val core = "1.3.3" - const val console = "1.1.0-dev-30" + const val console = "1.1.0-dev-32" const val consoleGraphical = "0.0.7" const val consoleTerminal = console diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt index a9b5b9ada..fce7be61d 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt @@ -15,10 +15,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1.create import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2.create import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Severity.ERROR -import org.jetbrains.kotlin.psi.KtCallExpression -import org.jetbrains.kotlin.psi.KtNamedDeclaration -import org.jetbrains.kotlin.psi.KtTypeProjection -import org.jetbrains.kotlin.psi.KtTypeReference +import org.jetbrains.kotlin.psi.* /** * 如何增加一个错误: @@ -59,6 +56,9 @@ object MiraiConsoleErrors { // @JvmField // val INAPPLICABLE_COMMAND_ANNOTATION = create(ERROR) + @JvmField + val RESTRICTED_CONSOLE_COMMAND_OWNER = create(ERROR) + @JvmField val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create(ERROR) diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt index 3d808000b..b1974df60 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt @@ -19,6 +19,7 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.IL import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.UNSERIALIZABLE_TYPE import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap @@ -102,6 +103,11 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { "指令函数的接收者参数必须为 CommandSender 及其子类或无接收者.", ) + put( + RESTRICTED_CONSOLE_COMMAND_OWNER, + "插件不允许使用 ConsoleCommandOwner 构造指令, 请使用插件主类作为 CommandOwner", + ) + // put( // INAPPLICABLE_COMMAND_ANNOTATION, // "''{0}'' 无法在顶层函数使用.", diff --git a/tools/compiler-common/src/resolve/resolveTypes.kt b/tools/compiler-common/src/resolve/resolveTypes.kt index 7d4fb7c07..f2c981e3f 100644 --- a/tools/compiler-common/src/resolve/resolveTypes.kt +++ b/tools/compiler-common/src/resolve/resolveTypes.kt @@ -33,6 +33,7 @@ val AUTO_SERVICE = FqName("com.google.auto.service.AutoService") val COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.CompositeCommand.SubCommand") val SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.SimpleCommand.Handler") val COMMAND_SENDER_FQ_NAME = FqName("net.mamoe.mirai.console.command.CommandSender") +val CONSOLE_COMMAND_SENDER_FQ_NAME = FqName("net.mamoe.mirai.console.command.ConsoleCommandSender") /////////////////////////////////////////////////////////////////////////// // Plugin @@ -70,7 +71,8 @@ enum class ResolveContextKind { PERMISSION_NAME, PERMISSION_ID, - RESTRICTED_NO_ARG_CONSTRUCTOR + RESTRICTED_NO_ARG_CONSTRUCTOR, + RESTRICTED_CONSOLE_COMMAND_OWNER, ; companion object { diff --git a/tools/gradle-plugin/src/VersionConstants.kt b/tools/gradle-plugin/src/VersionConstants.kt index e847d618c..a7c77b185 100644 --- a/tools/gradle-plugin/src/VersionConstants.kt +++ b/tools/gradle-plugin/src/VersionConstants.kt @@ -10,6 +10,6 @@ package net.mamoe.mirai.console.gradle internal object VersionConstants { - const val CONSOLE_VERSION = "1.1.0-dev-30" // value is written here automatically during build + const val CONSOLE_VERSION = "1.1.0-dev-32" // value is written here automatically during build const val CORE_VERSION = "1.3.3" // value is written here automatically during build } \ No newline at end of file diff --git a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts index 3938bea1d..dacfba69d 100644 --- a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts +++ b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts @@ -1,8 +1,7 @@ plugins { - kotlin("jvm") version "1.4.10" - kotlin("plugin.serialization") version "1.4.10" - kotlin("kapt") version "1.4.10" - id("com.github.johnrengelman.shadow") version "5.2.0" + kotlin("jvm") version "1.4.20" + kotlin("plugin.serialization") version "1.4.20" + id("net.mamoe.mirai-console") version "1.1.0-dev-32" } group = "org.example" @@ -12,32 +11,4 @@ repositories { mavenLocal() jcenter() mavenCentral() -} - -kotlin.sourceSets.all { - languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn") -} - -dependencies { - compileOnly(kotlin("stdlib-jdk8")) - - val core = "1.3.2" - val console = "1.0-RC-1" - - compileOnly("net.mamoe:mirai-console:$console") - compileOnly("net.mamoe:mirai-core:$core") - - val autoService = "1.0-rc7" - kapt("com.google.auto.service", "auto-service", autoService) - compileOnly("com.google.auto.service", "auto-service-annotations", autoService) - - testImplementation("net.mamoe:mirai-console:$console") - testImplementation("net.mamoe:mirai-core:$core") - testImplementation("net.mamoe:mirai-console-terminal:$console") - testImplementation(kotlin("stdlib-jdk8")) -} - -kotlin.target.compilations.all { - kotlinOptions.freeCompilerArgs += "-Xjvm-default=enable" - kotlinOptions.jvmTarget = "1.8" } \ No newline at end of file diff --git a/tools/intellij-plugin/run/projects/test-project/settings.gradle.kts b/tools/intellij-plugin/run/projects/test-project/settings.gradle.kts index 0e9d380ad..fcee558c6 100644 --- a/tools/intellij-plugin/run/projects/test-project/settings.gradle.kts +++ b/tools/intellij-plugin/run/projects/test-project/settings.gradle.kts @@ -1,2 +1,9 @@ rootProject.name = "test-project" +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + jcenter() + } +} \ No newline at end of file diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt index 61fc4a46e..a20342ca6 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt @@ -2,12 +2,13 @@ package org.example.myplugin import kotlinx.serialization.Serializable import net.mamoe.mirai.console.command.CommandSender +import net.mamoe.mirai.console.command.ConsoleCommandOwner import net.mamoe.mirai.console.command.SimpleCommand import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.value object MySimpleCommand000 : SimpleCommand( - MyPluginMain, "foo", + ConsoleCommandOwner, "foo", description = "示例指令" ) { @Handler diff --git a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt index 4defb39d8..c4ec82714 100644 --- a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt +++ b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt @@ -15,6 +15,8 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.IL import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAMESPACE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RESTRICTED_CONSOLE_COMMAND_OWNER +import net.mamoe.mirai.console.compiler.common.resolve.COMMAND_SENDER_FQ_NAME import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds import net.mamoe.mirai.console.intellij.resolve.resolveAllCalls @@ -24,9 +26,8 @@ import net.mamoe.mirai.console.intellij.util.RequirementHelper import net.mamoe.mirai.console.intellij.util.RequirementParser import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic -import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.psi.KtElement -import org.jetbrains.kotlin.psi.ValueArgument +import org.jetbrains.kotlin.idea.inspections.collections.isCalling +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import java.util.* @@ -129,28 +130,44 @@ class ContextualParametersChecker : DeclarationChecker { ILLEGAL_VERSION_REQUIREMENT.on(inspectionTarget, value, err.message ?: err.toString()) } } + + fun checkConsoleCommandOwner(context: DeclarationCheckerContext, inspectionTarget: KtElement, argument: ValueArgument): Diagnostic? { + val expr = argument.getArgumentExpression() ?: return null + + if (expr is KtReferenceExpression) { + expr.getResolvedCall(context)?.isCalling(COMMAND_SENDER_FQ_NAME) ?: return null + } + + return RESTRICTED_CONSOLE_COMMAND_OWNER.on(inspectionTarget) + } } fun interface ElementChecker { - operator fun invoke(declaration: KtElement, valueArgument: ValueArgument, value: String?): Diagnostic? + operator fun invoke(context: DeclarationCheckerContext, declaration: KtElement, valueArgument: ValueArgument, value: String?): Diagnostic? } - private val stringCheckersMap: EnumMap = + private val checkersMap: EnumMap = EnumMap(ResolveContextKind::class.java).apply { fun put(key: ResolveContextKind, value: KFunction2): ElementChecker? { - return put(key) { d, _, v -> + return put(key) { _, d, _, v -> if (v != null) value(d, v) else null } } fun put(key: ResolveContextKind, value: KFunction2): ElementChecker? { - return put(key) { d, v, _ -> + return put(key) { _, d, v, _ -> value(d, v) } } + fun put(key: ResolveContextKind, value: (DeclarationCheckerContext, KtElement, ValueArgument) -> Diagnostic?): ElementChecker? { + return put(key) { c, d, v, _ -> + value(c, d, v) + } + } + put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName) put(ResolveContextKind.PLUGIN_ID, ::checkPluginId) put(ResolveContextKind.SEMANTIC_VERSION, ::checkPluginVersion) @@ -159,6 +176,7 @@ class ContextualParametersChecker : DeclarationChecker { put(ResolveContextKind.PERMISSION_NAMESPACE, ::checkPermissionNamespace) put(ResolveContextKind.PERMISSION_ID, ::checkPermissionId) put(ResolveContextKind.VERSION_REQUIREMENT, ::checkVersionRequirement) + put(ResolveContextKind.RESTRICTED_CONSOLE_COMMAND_OWNER, ::checkConsoleCommandOwner) } override fun check( @@ -166,6 +184,17 @@ class ContextualParametersChecker : DeclarationChecker { descriptor: DeclarationDescriptor, context: DeclarationCheckerContext, ) { + when (declaration) { + is KtClassOrObject -> { + + } + + is KtNamedFunction -> { + + } + + } + declaration.resolveAllCalls(context.bindingContext) .asSequence() .flatMap { call -> @@ -173,7 +202,7 @@ class ContextualParametersChecker : DeclarationChecker { } .mapNotNull { (p, a) -> p.resolveContextKinds - ?.map(stringCheckersMap::get) + ?.map(checkersMap::get) ?.mapNotNull { if (it == null) null else it to a } @@ -182,11 +211,12 @@ class ContextualParametersChecker : DeclarationChecker { .mapNotNull { (kind, argument) -> Triple(kind, argument, argument.resolveStringConstantValues()) } - .forEach { (fn, argument, resolvedConstants) -> - if (resolvedConstants == null) { - fn(argument.asElement(), argument, null)?.let { context.report(it) } + .forEach { (fn, argument, resolvedConstantsSequence) -> + val resolvedConstants = resolvedConstantsSequence?.toList().orEmpty() + if (resolvedConstants.isEmpty()) { + fn(context, argument.asElement(), argument, null)?.let { context.report(it) } } else for (resolvedConstant in resolvedConstants) { - fn(argument.asElement(), argument, resolvedConstant)?.let { context.report(it) } + fn(context, argument.asElement(), argument, resolvedConstant)?.let { context.report(it) } } } return diff --git a/tools/intellij-plugin/src/resolve/resolveIdea.kt b/tools/intellij-plugin/src/resolve/resolveIdea.kt index e4c7a659a..0395d4ca5 100644 --- a/tools/intellij-plugin/src/resolve/resolveIdea.kt +++ b/tools/intellij-plugin/src/resolve/resolveIdea.kt @@ -123,8 +123,8 @@ fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean = fun KtDeclaration.resolveAllCalls(bindingContext: BindingContext): Sequence> { return allChildrenWithSelf - .filterIsInstance() - .mapNotNull { it.calleeExpression?.getResolvedCall(bindingContext) } + .filterIsInstance() + .mapNotNull { it.getResolvedCall(bindingContext) } } fun KtDeclaration.resolveAllCallsWithElement(bindingContext: BindingContext): Sequence, KtCallExpression>> { From e63775676ab18712c00aa55e08a84edd5df897dd Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 29 Nov 2020 16:21:54 +0800 Subject: [PATCH 47/52] Fix RESTRICTED_CONSOLE_COMMAND_OWNER --- tools/compiler-common/src/resolve/resolveTypes.kt | 1 + .../main/kotlin/org/example/myplugin/MySimpleCommand.kt | 7 ++++++- .../src/diagnostics/ContextualParametersChecker.kt | 8 +++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tools/compiler-common/src/resolve/resolveTypes.kt b/tools/compiler-common/src/resolve/resolveTypes.kt index f2c981e3f..9f3ce2a51 100644 --- a/tools/compiler-common/src/resolve/resolveTypes.kt +++ b/tools/compiler-common/src/resolve/resolveTypes.kt @@ -34,6 +34,7 @@ val COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.comm val SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.SimpleCommand.Handler") val COMMAND_SENDER_FQ_NAME = FqName("net.mamoe.mirai.console.command.CommandSender") val CONSOLE_COMMAND_SENDER_FQ_NAME = FqName("net.mamoe.mirai.console.command.ConsoleCommandSender") +val CONSOLE_COMMAND_OWNER_FQ_NAME = FqName("net.mamoe.mirai.console.command.ConsoleCommandOwner") /////////////////////////////////////////////////////////////////////////// // Plugin diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt index a20342ca6..9ac1aaf83 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt @@ -7,9 +7,14 @@ import net.mamoe.mirai.console.command.SimpleCommand import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.value -object MySimpleCommand000 : SimpleCommand( +object MySimpleCommand0001 : SimpleCommand( ConsoleCommandOwner, "foo", description = "示例指令" +) {} + +object MySimpleCommand000 : SimpleCommand( + MyPluginMain, "foo", + description = "示例指令" ) { @Handler suspend fun CommandSender.handle(int: Int, str: String) { diff --git a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt index c4ec82714..703841607 100644 --- a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt +++ b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt @@ -16,7 +16,7 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.IL import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RESTRICTED_CONSOLE_COMMAND_OWNER -import net.mamoe.mirai.console.compiler.common.resolve.COMMAND_SENDER_FQ_NAME +import net.mamoe.mirai.console.compiler.common.resolve.CONSOLE_COMMAND_OWNER_FQ_NAME import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds import net.mamoe.mirai.console.intellij.resolve.resolveAllCalls @@ -135,10 +135,12 @@ class ContextualParametersChecker : DeclarationChecker { val expr = argument.getArgumentExpression() ?: return null if (expr is KtReferenceExpression) { - expr.getResolvedCall(context)?.isCalling(COMMAND_SENDER_FQ_NAME) ?: return null + if (expr.getResolvedCall(context)?.isCalling(CONSOLE_COMMAND_OWNER_FQ_NAME) == true) { + return RESTRICTED_CONSOLE_COMMAND_OWNER.on(inspectionTarget) + } } - return RESTRICTED_CONSOLE_COMMAND_OWNER.on(inspectionTarget) + return null } } From 4803a21488bd53d91e02931d3565636858748d07 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 29 Nov 2020 16:28:31 +0800 Subject: [PATCH 48/52] Improve inspection performance --- .../ContextualParametersChecker.kt | 19 +++++++++++-------- .../src/resolve/resolveIdea.kt | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt index 703841607..7f6b9d10b 100644 --- a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt +++ b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt @@ -19,6 +19,7 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RE import net.mamoe.mirai.console.compiler.common.resolve.CONSOLE_COMMAND_OWNER_FQ_NAME import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds +import net.mamoe.mirai.console.intellij.resolve.findChild import net.mamoe.mirai.console.intellij.resolve.resolveAllCalls import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues import net.mamoe.mirai.console.intellij.resolve.valueParametersWithArguments @@ -28,6 +29,7 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.idea.inspections.collections.isCalling import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import java.util.* @@ -186,19 +188,20 @@ class ContextualParametersChecker : DeclarationChecker { descriptor: DeclarationDescriptor, context: DeclarationCheckerContext, ) { - when (declaration) { + val calls: Sequence> = when (declaration) { is KtClassOrObject -> { - + declaration.findChild()?.resolveAllCalls(context.bindingContext) + // ignore class body, which will be [check]ed } - is KtNamedFunction -> { +// is KtNamedFunction -> { +// +// } + else -> declaration.resolveAllCalls(context.bindingContext) - } + } ?: return - } - - declaration.resolveAllCalls(context.bindingContext) - .asSequence() + calls .flatMap { call -> call.valueParametersWithArguments().asSequence() } diff --git a/tools/intellij-plugin/src/resolve/resolveIdea.kt b/tools/intellij-plugin/src/resolve/resolveIdea.kt index 0395d4ca5..2117a2ef7 100644 --- a/tools/intellij-plugin/src/resolve/resolveIdea.kt +++ b/tools/intellij-plugin/src/resolve/resolveIdea.kt @@ -121,7 +121,7 @@ val KtAnnotationEntry.annotationClass: KtClass? fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean = this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName } -fun KtDeclaration.resolveAllCalls(bindingContext: BindingContext): Sequence> { +fun KtElement.resolveAllCalls(bindingContext: BindingContext): Sequence> { return allChildrenWithSelf .filterIsInstance() .mapNotNull { it.getResolvedCall(bindingContext) } From 595c9480a693602d232b6d7cdb6e81e4b2eea4c8 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 29 Nov 2020 18:00:48 +0800 Subject: [PATCH 49/52] Check Command.owner, #216 --- .../mirai-console/src/command/AbstractCommand.kt | 2 ++ backend/mirai-console/src/command/Command.kt | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/backend/mirai-console/src/command/AbstractCommand.kt b/backend/mirai-console/src/command/AbstractCommand.kt index c9048e221..bb5163bc9 100644 --- a/backend/mirai-console/src/command/AbstractCommand.kt +++ b/backend/mirai-console/src/command/AbstractCommand.kt @@ -37,6 +37,8 @@ public abstract class AbstractCommand init { Command.checkCommandName(primaryName) + @Suppress("LeakingThis") + Command.checkCommandOwner(this) secondaryNames.forEach(Command.Companion::checkCommandName) } diff --git a/backend/mirai-console/src/command/Command.kt b/backend/mirai-console/src/command/Command.kt index 675481731..532e9b048 100644 --- a/backend/mirai-console/src/command/Command.kt +++ b/backend/mirai-console/src/command/Command.kt @@ -117,5 +117,19 @@ public interface Command { name.contains('.') -> throw IllegalArgumentException("'.' is forbidden in command name.") } } + + /** + * 检查指令 [owner] 的合法性, 在非法时抛出 [IllegalArgumentException] + */ + @JvmStatic + @Throws(IllegalArgumentException::class) + public fun checkCommandOwner(command: Command) { + val owner = command.owner + if (owner is ConsoleCommandOwner) { + if (command.javaClass.enclosingClass != BuiltInCommands::class.java) { + throw IllegalArgumentException("ConsoleCommandOwner is not allowed") + } + } + } } } From be33cb88df447f1ac4068c1487a5f737398eef43 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 29 Nov 2020 18:07:02 +0800 Subject: [PATCH 50/52] Using TestUnitCommandOwner for test --- .../mirai-console/test/command/TestCommand.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/backend/mirai-console/test/command/TestCommand.kt b/backend/mirai-console/test/command/TestCommand.kt index 7b93718b8..c2c060379 100644 --- a/backend/mirai-console/test/command/TestCommand.kt +++ b/backend/mirai-console/test/command/TestCommand.kt @@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test import kotlin.test.* object TestCompositeCommand : CompositeCommand( - ConsoleCommandOwner, + owner, "testComposite", "tsC" ) { @SubCommand @@ -49,7 +49,7 @@ object TestCompositeCommand : CompositeCommand( } object TestRawCommand : RawCommand( - ConsoleCommandOwner, + owner, "testRaw" ) { override suspend fun CommandSender.onCommand(args: MessageChain) { @@ -65,7 +65,9 @@ object TestSimpleCommand : RawCommand(owner, "testSimple", "tsS") { } internal val sender by lazy { ConsoleCommandSender } -internal val owner by lazy { ConsoleCommandOwner } + +internal object TestUnitCommandOwner : CommandOwner by ConsoleCommandOwner +internal val owner by lazy { TestUnitCommandOwner } @OptIn(ExperimentalCommandDescriptors::class) @@ -88,12 +90,13 @@ internal class TestCommand { fun testRegister() { try { unregisterAllCommands(ConsoleCommandOwner) // builtins + unregisterAllCommands(owner) // testing unit unregisterCommand(TestSimpleCommand) assertTrue(TestCompositeCommand.register()) assertFalse(TestCompositeCommand.register()) - assertEquals(1, getRegisteredCommands(ConsoleCommandOwner).size) + assertEquals(1, getRegisteredCommands(owner).size) assertEquals(1, CommandManagerImpl._registeredCommands.size) assertEquals(2, @@ -198,7 +201,7 @@ internal class TestCommand { fun `composite sub command resolution conflict`() { runBlocking { val composite = object : CompositeCommand( - ConsoleCommandOwner, + owner, "tr" ) { @Suppress("UNUSED_PARAMETER") @@ -233,7 +236,7 @@ internal class TestCommand { ) val composite = object : CompositeCommand( - ConsoleCommandOwner, + owner, "test22", overrideContext = buildCommandArgumentContext { add(object : CommandValueArgumentParser { @@ -291,7 +294,7 @@ internal class TestCommand { fun `test optional argument command`() { runBlocking { val optionCommand = object : CompositeCommand( - ConsoleCommandOwner, + owner, "testOptional" ) { @SubCommand @@ -315,7 +318,7 @@ internal class TestCommand { fun `test vararg`() { runBlocking { val optionCommand = object : CompositeCommand( - ConsoleCommandOwner, + owner, "test" ) { @SubCommand From 55c2bdefd29b2ae6c97062571ab5c8fde3e083ed Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 29 Nov 2020 19:17:28 +0800 Subject: [PATCH 51/52] Change RESTRICTED_CONSOLE_COMMAND_OWNER to warning --- tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt index fce7be61d..95acfc18b 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1.create import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2.create import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Severity.ERROR +import org.jetbrains.kotlin.diagnostics.Severity.WARNING import org.jetbrains.kotlin.psi.* /** @@ -57,7 +58,7 @@ object MiraiConsoleErrors { // val INAPPLICABLE_COMMAND_ANNOTATION = create(ERROR) @JvmField - val RESTRICTED_CONSOLE_COMMAND_OWNER = create(ERROR) + val RESTRICTED_CONSOLE_COMMAND_OWNER = create(WARNING) @JvmField val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create(ERROR) From dfc9957c243d4a556dccddaf29cded0dad2ad83c Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 29 Nov 2020 19:26:05 +0800 Subject: [PATCH 52/52] Revert "Check Command.owner, #216" This reverts commit 595c9480 --- .../mirai-console/src/command/AbstractCommand.kt | 2 -- backend/mirai-console/src/command/Command.kt | 14 -------------- 2 files changed, 16 deletions(-) diff --git a/backend/mirai-console/src/command/AbstractCommand.kt b/backend/mirai-console/src/command/AbstractCommand.kt index bb5163bc9..c9048e221 100644 --- a/backend/mirai-console/src/command/AbstractCommand.kt +++ b/backend/mirai-console/src/command/AbstractCommand.kt @@ -37,8 +37,6 @@ public abstract class AbstractCommand init { Command.checkCommandName(primaryName) - @Suppress("LeakingThis") - Command.checkCommandOwner(this) secondaryNames.forEach(Command.Companion::checkCommandName) } diff --git a/backend/mirai-console/src/command/Command.kt b/backend/mirai-console/src/command/Command.kt index 532e9b048..675481731 100644 --- a/backend/mirai-console/src/command/Command.kt +++ b/backend/mirai-console/src/command/Command.kt @@ -117,19 +117,5 @@ public interface Command { name.contains('.') -> throw IllegalArgumentException("'.' is forbidden in command name.") } } - - /** - * 检查指令 [owner] 的合法性, 在非法时抛出 [IllegalArgumentException] - */ - @JvmStatic - @Throws(IllegalArgumentException::class) - public fun checkCommandOwner(command: Command) { - val owner = command.owner - if (owner is ConsoleCommandOwner) { - if (command.javaClass.enclosingClass != BuiltInCommands::class.java) { - throw IllegalArgumentException("ConsoleCommandOwner is not allowed") - } - } - } } }