From 62527f0ed0ce4e2d676341c3bfd884eb9c98c72e Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 20 Sep 2020 22:48:54 +0800 Subject: [PATCH 01/11] Extract SemVersion.major, minor, patch --- .../internal/util/SemVersionInternal.kt | 19 ++++++------ .../mamoe/mirai/console/util/SemVersion.kt | 30 ++++++++++++------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt index 02cec4490..2cbe56ec4 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt @@ -69,8 +69,11 @@ internal object SemVersionInternal { } } } + val mainVersion = version.substring(0, mainVersionEnd).parseMainVersion() return SemVersion( - mainVersion = version.substring(0, mainVersionEnd).parseMainVersion(), + major = mainVersion[0], + minor = mainVersion[1], + patch = mainVersion.getOrNull(2), identifier = identifier, metadata = metadata ) @@ -176,16 +179,12 @@ internal object SemVersionInternal { // If $this equals $other (without metadata), // return same. - if (other.mainVersion.contentEquals(source.mainVersion) && source.identifier == other.identifier) { - return 0 - } - fun IntArray.getSafe(index: Int) = getOrElse(index) { 0 } - // Compare main-version - for (index in 0 until (max(source.mainVersion.size, other.mainVersion.size))) { - val result = source.mainVersion.getSafe(index).compareTo(other.mainVersion.getSafe(index)) - if (result != 0) return result - } + + source.major.compareTo(other.major).takeUnless { it == 0 }?.let { return it } + source.minor.compareTo(other.minor).takeUnless { it == 0 }?.let { return it } + (source.patch ?: 0).compareTo(other.patch ?: 0).takeUnless { it == 0 }?.let { return it } + // If main-versions are same. var identifier0 = source.identifier var identifier1 = other.identifier diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 095280b90..bed586514 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -32,10 +32,13 @@ import net.mamoe.mirai.console.util.SemVersion.Requirement * * 解析示例: * - * `1.0.0-M4+c25733b8` 将会解析出三个内容, mainVersion (核心版本号), [identifier] (先行版本号) 和 [metadata] (元数据). + * `1.0.0-M4+c25733b8` 将会解析出下面的内容, + * [major] (主本号), [minor] (次版本号), [patch] (修订号), [identifier] (先行版本号) 和 [metadata] (元数据). * ``` * SemVersion( - * mainVersion = IntArray [1, 0, 0], + * major = 1, + * minor = 0, + * patch = 0, * identifier = "M4" * metadata = "c25733b8" * ) @@ -53,8 +56,12 @@ public data class SemVersion * @see SemVersion.invoke 字符串解析 */ internal constructor( - /** 核心版本号, 由主版本号, 次版本号和修订号组成, 其中修订号不一定存在 */ - public val mainVersion: IntArray, + /** 主版本号 */ + public val major: Int, + /** 次版本号 */ + public val minor: Int, + /** 修订号 */ + public val patch: Int?, /** 先行版本号识别符 */ public val identifier: String? = null, /** 版本号元数据, 不参与版本号对比([compareTo]), 但是参与版本号严格对比([equals]) */ @@ -151,14 +158,14 @@ internal constructor( @Transient private val toString: String by lazy(LazyThreadSafetyMode.NONE) { buildString { - mainVersion.joinTo(this, ".") + append(major) + append('.').append(minor) + patch?.let { append('.').append(it) } identifier?.let { identifier -> - append('-') - append(identifier) + append('-').append(identifier) } metadata?.let { metadata -> - append('+') - append(metadata) + append('+').append(metadata) } } } @@ -169,7 +176,7 @@ internal constructor( * 将 [SemVersion] 转为 Kotlin data class 风格的 [String] */ public fun toStructuredString(): String { - return "SemVersion(mainVersion=${mainVersion.contentToString()}, identifier=$identifier, metadata=$metadata)" + return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)" } override fun equals(other: Any?): Boolean { @@ -182,7 +189,8 @@ internal constructor( } override fun hashCode(): Int { - var result = mainVersion.contentHashCode() + var result = major shl minor + result *= (patch ?: 1) result = 31 * result + (identifier?.hashCode() ?: 0) result = 31 * result + (metadata?.hashCode() ?: 0) return result From 1c909ae752a493c42041d062dd2d347ad165a997 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 12:15:01 +0800 Subject: [PATCH 02/11] Better Requirement Rule --- .../internal/util/semver/RangeTokenReader.kt | 251 ++++++++++++++++++ .../util/{ => semver}/SemVersionInternal.kt | 36 ++- .../mamoe/mirai/console/util/SemVersion.kt | 2 +- .../mirai/console/util/TestSemVersion.kt | 30 ++- 4 files changed, 294 insertions(+), 25 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/{ => semver}/SemVersionInternal.kt (91%) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt new file mode 100644 index 000000000..00a0524b2 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt @@ -0,0 +1,251 @@ +/* + * 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 via 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): SemVersion.Requirement { + 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 : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean { + rules.forEach { if (it.test(version)) return true } + return false + } + } + } + TokenType.AND -> { + return object : SemVersion.Requirement { + 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/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt similarity index 91% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt index 2cbe56ec4..afe950ce3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt @@ -8,12 +8,9 @@ * */ -/* - * @author Karlatemp - */ - -package net.mamoe.mirai.console.internal.util +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 @@ -80,8 +77,8 @@ internal object SemVersionInternal { } @JvmStatic - private fun String.parseRule(): SemVersion.Requirement { - val trimmed = trim() + internal fun parseRule(rule: String): SemVersion.Requirement { + val trimmed = rule.trim() if (directVersion.matches(trimmed)) { val parsed = SemVersion.invoke(trimmed) return object : SemVersion.Requirement { @@ -143,7 +140,7 @@ internal object SemVersionInternal { else -> error("operator=$operator, version=$version1") } } - throw IllegalArgumentException("Cannot parse $this") + throw IllegalArgumentException("Cannot parse $rule") } private fun SemVersion.Requirement.withRule(rule: String): SemVersion.Requirement { @@ -158,19 +155,16 @@ internal object SemVersionInternal { if (requirement.isBlank()) { throw IllegalArgumentException("Invalid requirement: Empty requirement rule.") } - return requirement.split("||").map { - it.parseRule().withRule(it) - }.let { checks -> - if (checks.size == 1) return checks[0] - object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean { - checks.forEach { rule -> - if (rule.test(version)) return true - } - return false - } - }.withRule(requirement) - } + 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)).withRule(requirement) + }.onFailure { error -> + throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString { + collected.forEach { dump("", it) } + }, error) + }.getOrThrow() } @JvmStatic diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index bed586514..de1d647f8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_VERSION import net.mamoe.mirai.console.internal.data.map -import net.mamoe.mirai.console.internal.util.SemVersionInternal +import net.mamoe.mirai.console.internal.util.semver.SemVersionInternal import net.mamoe.mirai.console.util.SemVersion.Companion.equals import net.mamoe.mirai.console.util.SemVersion.Requirement diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index c7325c177..ac7975d51 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -48,6 +48,12 @@ internal class TestSemVersion { return this } + fun assertInvalid(requirement: String) { + kotlin.runCatching { + SemVersion.parseRangeRequirement(requirement) + }.onSuccess { assert(false) { requirement } } + } + fun SemVersion.Requirement.assertFalse(version: String): SemVersion.Requirement { assert(!test(version)) { version } return this @@ -59,6 +65,8 @@ internal class TestSemVersion { .assert("1.0").assert("1.1") .assert("1.5").assert("1.14514") .assertFalse("2.33") + SemVersion.parseRangeRequirement("2.0||1.2.x") + SemVersion.parseRangeRequirement("{2.0||1.2.x} && 1.1.0 &&1.2.3") SemVersion.parseRangeRequirement("2.0 || 1.2.x") .assert("2.0").assert("2.0.0") .assertFalse("2.1") @@ -79,9 +87,24 @@ internal class TestSemVersion { .assertFalse("0.98774587") SemVersion.parseRangeRequirement("> 1.0.0") .assertFalse("1.0.0") - kotlin.runCatching { SemVersion.parseRangeRequirement("WPOXAXW") } - .onSuccess { assert(false) } + SemVersion.parseRangeRequirement("> 1.0.0 || < 0.9.0") + .assertFalse("1.0.0") + .assert("0.8.0") + .assertFalse("0.9.0") + SemVersion.parseRangeRequirement("{>= 1.0.0 && <= 1.2.3} || {>= 2.0.0 && <= 2.2.3}") + .assertFalse("1.3.0") + .assert("1.0.0").assert("1.2.3") + .assertFalse("0.9.0") + .assert("2.0.0").assert("2.2.3").assertFalse("2.3.4") + + assertInvalid("WPOXAXW") + assertInvalid("1.0.0 || 1.0.0 && 1.0.0") + assertInvalid("{") + assertInvalid("}") + assertInvalid("") + assertInvalid("1.5.78 &&") + assertInvalid("|| 1.0.0") } private fun String.check() { @@ -112,8 +135,9 @@ internal class TestSemVersion { "5.1+68-7".check() "5.1+68-".check() } + @Test - internal fun testSemVersionOfficial(){ + internal fun testSemVersionOfficial() { """ 1.0-RC 0.0.4 From 00781c215a15dcaf8fa23fe84d9b01cf49c32822 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 12:31:12 +0800 Subject: [PATCH 03/11] Update Math interval --- .../util/semver/SemVersionInternal.kt | 31 ++++++++++++++----- .../mirai/console/util/TestSemVersion.kt | 14 ++++++--- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt index afe950ce3..f71be72f6 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt @@ -19,9 +19,8 @@ import kotlin.math.min internal object SemVersionInternal { private val directVersion = """^[0-9]+(\.[0-9]+)+(|[\-+].+)$""".toRegex() private val versionSelect = """^[0-9]+(\.[0-9]+)*\.x$""".toRegex() - private val versionRange = """([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\-\s*([0-9]+(\.[0-9]+)+(|[\-+].+))""".toRegex() private val versionMathRange = - """\[([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))\]""".toRegex() + """([\[\(])([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 = @@ -95,17 +94,35 @@ internal object SemVersionInternal { override fun test(version: SemVersion): Boolean = regex.matches(version.toString()) } } - (versionRange.matchEntire(trimmed) ?: versionMathRange.matchEntire(trimmed))?.let { range -> - var start = SemVersion.invoke(range.groupValues[1]) - var end = SemVersion.invoke(range.groupValues[4]) + versionMathRange.matchEntire(trimmed)?.let { range -> + // 1 mode + // 2 first + // 5 sec + // 8 type + var typeStart = range.groupValues[1][0] + var typeEnd = range.groupValues[8][0] + var start = SemVersion.invoke(range.groupValues[2]) + var end = SemVersion.invoke(range.groupValues[5]) if (start > end) { val c = end end = start start = c + val x = typeEnd + typeEnd = typeStart + typeStart = x + } + val a: (SemVersion) -> Boolean = when (typeStart) { + '[', ']' -> ({ start <= it }) + '(', ')' -> ({ start < it }) + else -> throw AssertionError() + } + val b: (SemVersion) -> Boolean = when (typeEnd) { + '[', ']' -> ({ it <= end }) + '(', ')' -> ({ it < end }) + else -> throw AssertionError() } - val compareRange = start..end return object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean = version in compareRange + override fun test(version: SemVersion): Boolean = a(version) && b(version) } } versionRule.matchEntire(trimmed)?.let { result -> diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index ac7975d51..e7de240c9 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -72,14 +72,17 @@ internal class TestSemVersion { .assertFalse("2.1") .assert("1.2.5").assert("1.2.0").assertFalse("1.2") .assertFalse("1.0.0") - SemVersion.parseRangeRequirement("1.0.0 - 114.514.1919") - .assert("1.0.0") - .assert("114.514").assert("114.514.1919") - .assertFalse("0.0.1") - .assertFalse("4444.4444") SemVersion.parseRangeRequirement("[1.0.0, 19190.0]") .assert("1.0.0").assertFalse("0.1.0") .assert("19190.0").assertFalse("19198.10") + SemVersion.parseRangeRequirement("[1.0.0, 2.0.0)") + .assert("1.0.0").assert("1.2.3").assertFalse("2.0.0") + SemVersion.parseRangeRequirement("(2.0.0, 1.0.0]") + .assert("1.0.0").assert("1.2.3").assertFalse("2.0.0") + SemVersion.parseRangeRequirement("(2.0.0, 1.0.0)") + .assertFalse("1.0.0").assert("1.2.3").assertFalse("2.0.0") + SemVersion.parseRangeRequirement("(1.0.0, 2.0.0)") + .assertFalse("1.0.0").assert("1.2.3").assertFalse("2.0.0") SemVersion.parseRangeRequirement(" >= 1.0.0") .assert("1.0.0") .assert("114.514.1919") @@ -103,6 +106,7 @@ internal class TestSemVersion { assertInvalid("{") assertInvalid("}") assertInvalid("") + assertInvalid("1.2.3 - 3.2.1") assertInvalid("1.5.78 &&") assertInvalid("|| 1.0.0") } From 209bc97b32824b6e03805dc86a593c47a7953ecf Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 12:42:05 +0800 Subject: [PATCH 04/11] Update KDoc --- .../internal/util/semver/SemVersionInternal.kt | 9 +++++++-- .../net/mamoe/mirai/console/util/SemVersion.kt | 13 ++++++++++--- .../net/mamoe/mirai/console/util/TestSemVersion.kt | 5 +++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt index f71be72f6..d57c226e9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt @@ -21,7 +21,7 @@ internal object SemVersionInternal { 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 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() @@ -127,7 +127,7 @@ internal object SemVersionInternal { } versionRule.matchEntire(trimmed)?.let { result -> val operator = result.groupValues[1] - val version1 = SemVersion.invoke(result.groupValues[7]) + val version1 = SemVersion.invoke(result.groupValues[8]) return when (operator) { ">=" -> { object : SemVersion.Requirement { @@ -154,6 +154,11 @@ internal object SemVersionInternal { override fun test(version: SemVersion): Boolean = version.compareTo(version1) == 0 } } + "!=" -> { + object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = version.compareTo(version1) != 0 + } + } else -> error("operator=$operator, version=$version1") } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index de1d647f8..a80c467a2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -113,21 +113,28 @@ internal constructor( * * - `1.0.0-M4` 要求 1.0.0-M4 版本, 且只能是 1.0.0-M4 版本 * - `1.x` 要求 1.x 版本 - * - `1.0.0 - 1.2.0` 要求 1.0.0 到 1.2.0 的任意版本, 注意 `-` 两边必须要有空格 - * - `[1.0.0, 1.2.0]` 与 `1.0.0 - 1.2.0` 一致 * - `> 1.0.0-RC` 要求 1.0.0-RC 之后的版本, 不能是 1.0.0-RC * - `>= 1.0.0-RC` 要求 1.0.0-RC 或之后的版本, 可以是 1.0.0-RC * - `< 1.0.0-RC` 要求 1.0.0-RC 之前的版本, 不能是 1.0.0-RC * - `<= 1.0.0-RC` 要求 1.0.0-RC 或之前的版本, 可以是 1.0.0-RC + * - `!= 1.0.0-RC` 要求 除了1.0.0-RC 的任何版本 + * - `[1.0.0, 1.2.0]` + * - `(1.0.0, 1.2.0]` + * - `[1.0.0, 1.2.0)` + * - `(1.0.0, 1.2.0)` [数学区间](https://baike.baidu.com/item/%E5%8C%BA%E9%97%B4/1273117) * - * 对于多个规则, 也允许使用 `||` 拼接. + * 对于多个规则, 允许使用逻辑符号 `{}`, `||`, `&&` * 例如: * - `1.x || 2.x || 3.0.0` * - `<= 0.5.3 || >= 1.0.0` + * - `{> 1.0 && < 1.5} || {> 1.8}` + * - `{> 1.0 && < 1.5} || {> 1.8}` + * - `> 1.0.0 && != 1.2.0` * * 特别注意: * - 依赖规则版本号不需要携带版本号元数据, 元数据不参与依赖需求的检查 * - 如果目标版本号携带有先行版本号, 请不要忘记先行版本号 + * - 因为 `()` 已经用于数学区间, 使用 `{}` 替代 `()` */ @Throws(IllegalArgumentException::class) @JvmStatic diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index e7de240c9..3f74dd191 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -90,6 +90,11 @@ internal class TestSemVersion { .assertFalse("0.98774587") SemVersion.parseRangeRequirement("> 1.0.0") .assertFalse("1.0.0") + SemVersion.parseRangeRequirement("!= 1.0.0 && != 2.0.0") + .assert("1.2.3").assert("2.1.1") + .assertFalse("1.0").assertFalse("1.0.0") + .assertFalse("2.0").assertFalse("2.0.0") + .assert("2.0.1").assert("1.0.1") SemVersion.parseRangeRequirement("> 1.0.0 || < 0.9.0") .assertFalse("1.0.0") From dc0ba1d8ffdd27335cdc72ebbcdc51346b4e3c65 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 12:47:53 +0800 Subject: [PATCH 05/11] Fix SemVersionRangeRequirementBuilder --- .../console/util/SemVersionRangeRequirementBuilder.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt index 74145d1f8..be4b936ce 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt @@ -26,7 +26,7 @@ public object SemVersionRangeRequirementBuilder { } override fun toString(): String { - return "(${this@or}) or ($other)" + return "{${this@or}} || {$other}" } } } @@ -48,7 +48,7 @@ public object SemVersionRangeRequirementBuilder { } override fun toString(): String { - return "(${this@and}) or ($other)" + return "{${this@and}} && {$other}" } } } @@ -64,7 +64,12 @@ public object SemVersionRangeRequirementBuilder { @Suppress("NOTHING_TO_INLINE") @ILoveHim188moeForever - public inline fun custom(rule: SemVersion.Requirement): SemVersion.Requirement = rule + public fun custom(rule: (SemVersion) -> Boolean): SemVersion.Requirement = object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = rule(version) + override fun toString(): String { + return "Custom{$rule}" + } + } /** * 标注一个 [SemVersionRangeRequirementBuilder] DSL From 2a6d98ba168d7ca025d33b2a796f8caa7a0360ee Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 21 Sep 2020 13:22:37 +0800 Subject: [PATCH 06/11] Add shadowJarMd5 for shadowed files --- .../internal/MiraiConsoleBuildConstants.kt | 2 +- buildSrc/src/main/kotlin/PublishingHelpers.kt | 74 +++++++++++++++++++ buildSrc/src/main/kotlin/Versions.kt | 2 +- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt index f314be39c..95306d28e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt @@ -14,7 +14,7 @@ import java.time.Instant internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic - val buildDate: Instant = Instant.ofEpochSecond(1600596035) + val buildDate: Instant = Instant.ofEpochSecond(1600663022) @JvmStatic val version: SemVersion = SemVersion("1.0-RC-dev-28") diff --git a/buildSrc/src/main/kotlin/PublishingHelpers.kt b/buildSrc/src/main/kotlin/PublishingHelpers.kt index 69b954dac..f7b2821c0 100644 --- a/buildSrc/src/main/kotlin/PublishingHelpers.kt +++ b/buildSrc/src/main/kotlin/PublishingHelpers.kt @@ -1,5 +1,6 @@ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "NOTHING_TO_INLINE", "RemoveRedundantBackticks") +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.publish.maven.MavenPublication @@ -7,6 +8,10 @@ import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.* import upload.Bintray +import java.io.File +import java.io.InputStream +import java.io.OutputStream +import java.security.MessageDigest import java.util.* import kotlin.reflect.KProperty @@ -51,6 +56,44 @@ internal fun org.gradle.api.Project.`publishing`(configure: org.gradle.api.publi (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("publishing", configure) +fun InputStream.md5(): ByteArray { + val digest = MessageDigest.getInstance("md5") + digest.reset() + use { input -> + object : OutputStream() { + override fun write(b: Int) { + digest.update(b.toByte()) + } + }.use { output -> + input.copyTo(output) + } + } + return digest.digest() +} + +@OptIn(ExperimentalUnsignedTypes::class) +@JvmOverloads +fun ByteArray.toUHexString( + separator: String = " ", + offset: Int = 0, + length: Int = this.size - offset +): String { + if (length == 0) { + return "" + } + val lastIndex = offset + length + return buildString(length * 2) { + this@toUHexString.forEachIndexed { index, it -> + if (index in offset until lastIndex) { + var ret = it.toUByte().toString(16).toUpperCase() + if (ret.length == 1) ret = "0$ret" + append(ret) + if (index < lastIndex - 1) append(separator) + } + } + } +} + inline fun Project.setupPublishing( artifactId: String, bintrayRepo: String = "mirai", @@ -66,6 +109,32 @@ inline fun Project.setupPublishing( } } + afterEvaluate { + + val shadowJar = tasks.filterIsInstance().firstOrNull() ?: return@afterEvaluate + + tasks.register("shadowJarMd5") { + val outFiles = shadowJar.outputs.files.map { file -> + File(file.parentFile, file.name.removeSuffix(".jar").removeSuffix("-all") + "-all.jar.md5") + } + + outFiles.forEach { file -> + outputs.files(file) + } + + doLast { + for (file in outFiles) { + file + .also { it.createNewFile() } + .writeText(file.inputStream().md5().toUHexString().trim(Char::isWhitespace)) + } + } + + tasks.getByName("publish").dependsOn(this) + tasks.getByName("bintrayUpload").dependsOn(this) + } + } + if (Bintray.isBintrayAvailable(project)) { bintray { val keyProps = Properties() @@ -104,6 +173,11 @@ inline fun Project.setupPublishing( publications { register("mavenJava", MavenPublication::class) { from(components["java"]) + afterEvaluate { + for (file in tasks.getByName("shadowJarMd5").outputs.files) { + artifact(provider { file }) + } + } groupId = rootProject.group.toString() this.artifactId = artifactId diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index ef3a362e1..801f93344 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,7 +9,7 @@ object Versions { const val core = "1.3.0" - const val console = "1.0-RC-dev-28" + const val console = "1.0-RC-dev-29" const val consoleGraphical = "0.0.7" const val consoleTerminal = console From 2520e4bb35827b746c94068a209647adc38baef6 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 18:09:01 +0800 Subject: [PATCH 07/11] delete SemVersionRangeRequirementBuilder --- .../plugin/jvm/JvmPluginDescription.kt | 26 ----- .../util/SemVersionRangeRequirementBuilder.kt | 95 ------------------- 2 files changed, 121 deletions(-) delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index 736517b52..954d19919 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -16,7 +16,6 @@ import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.* import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.util.SemVersion -import net.mamoe.mirai.console.util.SemVersionRangeRequirementBuilder /** * JVM 插件的描述. 通常作为 `plugin.yml` @@ -187,31 +186,6 @@ public class JvmPluginDescriptionBuilder( this.dependencies.add(PluginDependency(pluginId, null, isOptional)) } - /** - * 示例: - * - * ``` - * dependsOn("org.example.test-plugin") { "1.0.0".."1.2.0" } - * dependsOn("org.example.test-plugin") { npmPattern("1.x || >=2.5.0 || 5.0.0 - 7.2.3") } - * dependsOn("org.example.test-plugin") { ivyPattern("[1.0,2.0[") } - * dependsOn("org.example.test-plugin") { custom { it.toString() == "1.0.0" } } - * ``` - * - * @see PluginDependency - * @see SemVersionRangeRequirementBuilder - */ - @ILoveKuriyamaMiraiForever - public fun dependsOn( - @ResolveContext(PLUGIN_ID) pluginId: String, - isOptional: Boolean = false, - versionRequirement: SemVersionRangeRequirementBuilder.() -> SemVersion.Requirement, - ): JvmPluginDescriptionBuilder = - apply { - this.dependencies.add(PluginDependency(pluginId, - SemVersionRangeRequirementBuilder.run(versionRequirement), - isOptional)) - } - @Suppress("DEPRECATION_ERROR") public fun build(): JvmPluginDescription = diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt deleted file mode 100644 index be4b936ce..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt +++ /dev/null @@ -1,95 +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 via the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - * - */ - -package net.mamoe.mirai.console.util - -/** - * 构造 [SemVersion.Requirement] 的 DSL - */ -public object SemVersionRangeRequirementBuilder { - /** @see [SemVersion.parseRangeRequirement] */ - @ILoveHim188moeForever - public fun parse(rule: String): SemVersion.Requirement = SemVersion.parseRangeRequirement(rule) - - @ILoveHim188moeForever - public infix fun SemVersion.Requirement.or(other: SemVersion.Requirement): SemVersion.Requirement { - return object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean { - return this@or.test(version) || other.test(version) - } - - override fun toString(): String { - return "{${this@or}} || {$other}" - } - } - } - - @ILoveHim188moeForever - public infix fun String.or(other: String): SemVersion.Requirement = parse(this) or parse(other) - - @ILoveHim188moeForever - public infix fun SemVersion.Requirement.or(other: String): SemVersion.Requirement = or(parse(other)) - - @ILoveHim188moeForever - public infix fun String.or(other: SemVersion.Requirement): SemVersion.Requirement = parse(this) or other - - @ILoveHim188moeForever - public infix fun SemVersion.Requirement.and(other: SemVersion.Requirement): SemVersion.Requirement { - return object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean { - return this@and.test(version) && other.test(version) - } - - override fun toString(): String { - return "{${this@and}} && {$other}" - } - } - } - - @ILoveHim188moeForever - public infix fun String.and(other: String): SemVersion.Requirement = parse(this) and parse(other) - - @ILoveHim188moeForever - public infix fun SemVersion.Requirement.and(other: String): SemVersion.Requirement = and(parse(other)) - - @ILoveHim188moeForever - public infix fun String.and(other: SemVersion.Requirement): SemVersion.Requirement = parse(this) and other - - @Suppress("NOTHING_TO_INLINE") - @ILoveHim188moeForever - public fun custom(rule: (SemVersion) -> Boolean): SemVersion.Requirement = object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean = rule(version) - override fun toString(): String { - return "Custom{$rule}" - } - } - - /** - * 标注一个 [SemVersionRangeRequirementBuilder] DSL - */ - @Suppress("SpellCheckingInspection") - @Retention(AnnotationRetention.BINARY) - @DslMarker - internal annotation class ILoveHim188moeForever - - /** [SemVersionRangeRequirementBuilder] 的使用示例 */ - @Suppress("unused") - private class ExampleOfBuilder { - val e1 = SemVersionRangeRequirementBuilder.run { - "1.0.0" or "1.1.5" - } - val e2 = SemVersionRangeRequirementBuilder.run { - parse("> 1.0.0") and parse("< 1.2.3") - } - val e3 = SemVersionRangeRequirementBuilder.run { - ("> 1.0.0" and "< 1.2.3") or "2.0.0" - } - } -} \ No newline at end of file From b21188f9e149759663991f4c98809adb1539f1d2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 23 Sep 2020 10:08:59 +0800 Subject: [PATCH 08/11] Add project icon --- .idea/icon.png | Bin 0 -> 31103 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .idea/icon.png diff --git a/.idea/icon.png b/.idea/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c872c7a5d945f42daf8ad0e3410ed4d0cbe9738d GIT binary patch literal 31103 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 literal 0 HcmV?d00001 From e478f1025020b290c28b8c1be1836e0245aac6c7 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 24 Sep 2020 07:41:09 +0800 Subject: [PATCH 09/11] Update Run.md --- docs/Run.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/Run.md b/docs/Run.md index c4651e4bf..2e794c54f 100644 --- a/docs/Run.md +++ b/docs/Run.md @@ -2,9 +2,10 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。 -## 使用第三方工具自动独立启动 +## 使用工具自动独立启动 -https://github.com/LXY1226/MiraiOK +官方: https://github.com/iTXTech/mirai-console-loader +第三方: https://github.com/LXY1226/MiraiOK ## 手动配置独立启动 From 1425594a70764705cb8b3d3ea0a01d088faf3127 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 24 Sep 2020 14:01:47 +0800 Subject: [PATCH 10/11] Remove md5 --- buildSrc/src/main/kotlin/PublishingHelpers.kt | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/buildSrc/src/main/kotlin/PublishingHelpers.kt b/buildSrc/src/main/kotlin/PublishingHelpers.kt index f7b2821c0..95e16283a 100644 --- a/buildSrc/src/main/kotlin/PublishingHelpers.kt +++ b/buildSrc/src/main/kotlin/PublishingHelpers.kt @@ -1,6 +1,5 @@ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "NOTHING_TO_INLINE", "RemoveRedundantBackticks") -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.publish.maven.MavenPublication @@ -8,7 +7,6 @@ import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.* import upload.Bintray -import java.io.File import java.io.InputStream import java.io.OutputStream import java.security.MessageDigest @@ -109,32 +107,6 @@ inline fun Project.setupPublishing( } } - afterEvaluate { - - val shadowJar = tasks.filterIsInstance().firstOrNull() ?: return@afterEvaluate - - tasks.register("shadowJarMd5") { - val outFiles = shadowJar.outputs.files.map { file -> - File(file.parentFile, file.name.removeSuffix(".jar").removeSuffix("-all") + "-all.jar.md5") - } - - outFiles.forEach { file -> - outputs.files(file) - } - - doLast { - for (file in outFiles) { - file - .also { it.createNewFile() } - .writeText(file.inputStream().md5().toUHexString().trim(Char::isWhitespace)) - } - } - - tasks.getByName("publish").dependsOn(this) - tasks.getByName("bintrayUpload").dependsOn(this) - } - } - if (Bintray.isBintrayAvailable(project)) { bintray { val keyProps = Properties() @@ -173,11 +145,6 @@ inline fun Project.setupPublishing( publications { register("mavenJava", MavenPublication::class) { from(components["java"]) - afterEvaluate { - for (file in tasks.getByName("shadowJarMd5").outputs.files) { - artifact(provider { file }) - } - } groupId = rootProject.group.toString() this.artifactId = artifactId From c712824048264ac0f6665d638c0b77f59923bad2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 24 Sep 2020 14:01:58 +0800 Subject: [PATCH 11/11] Update docs --- docs/ConfiguringProjects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ConfiguringProjects.md b/docs/ConfiguringProjects.md index 6cd90981c..3fa2db93f 100644 --- a/docs/ConfiguringProjects.md +++ b/docs/ConfiguringProjects.md @@ -40,7 +40,7 @@ Mirai 鼓励插件开发者将自己的作品开源,并为此提供了模板 ### 使用 Gradle 插件配置项目 -`VERSION` 可在 +`VERSION`: [选择版本](#选择版本) 若使用 `build.gradle.kts`: ```kotlin