mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-25 15:40:28 +08:00
Introduce version DSL
This commit is contained in:
parent
807df3bbcd
commit
fd23a03618
@ -60,6 +60,7 @@ import kotlin.coroutines.CoroutineContext
|
||||
/**
|
||||
* [MiraiConsole] 公开 API 与前端实现的连接桥.
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleImplementation,
|
||||
MiraiConsole {
|
||||
override val pluginCenter: PluginCenter get() = throw UnsupportedOperationException("PluginCenter is not supported yet")
|
||||
@ -219,11 +220,12 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
mainLogger.info { "mirai-console started successfully." }
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@DslMarker
|
||||
private annotation class MiraiIsCool
|
||||
private annotation class ILoveOmaeKumikoForever
|
||||
|
||||
@MiraiIsCool
|
||||
@ILoveOmaeKumikoForever
|
||||
private inline fun phase(block: () -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
|
@ -187,7 +187,15 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
private fun <D : PluginDescription> List<D>.sortByDependencies(): List<D> {
|
||||
val resolved = ArrayList<D>(this.size)
|
||||
|
||||
fun D.canBeLoad(): Boolean = this.dependencies.all { it.isOptional || it in resolved }
|
||||
fun D.canBeLoad(): Boolean = this.dependencies.all { dependency ->
|
||||
val target = resolved.findDependency(dependency)
|
||||
if (target == null) {
|
||||
dependency.isOptional
|
||||
} else {
|
||||
target.checkSatisfies(dependency, this@canBeLoad)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fun List<D>.consumeLoadable(): List<D> {
|
||||
val (canBeLoad, cannotBeLoad) = this.partition { it.canBeLoad() }
|
||||
@ -196,7 +204,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
}
|
||||
|
||||
fun Collection<PluginDependency>.filterIsMissing(): List<PluginDependency> =
|
||||
this.filterNot { it.isOptional || it in resolved }
|
||||
this.filterNot { it.isOptional || resolved.findDependency(it) != null }
|
||||
|
||||
fun List<D>.doSort() {
|
||||
if (this.isEmpty()) return
|
||||
@ -206,8 +214,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
check(resultPlugins.size < beforeSize) {
|
||||
throw PluginMissingDependencyException(resultPlugins.joinToString("\n") { badPlugin ->
|
||||
"Cannot load plugin ${badPlugin.name}, missing dependencies: ${
|
||||
badPlugin.dependencies.filterIsMissing()
|
||||
.joinToString()
|
||||
badPlugin.dependencies.filterIsMissing().joinToString()
|
||||
}"
|
||||
})
|
||||
}
|
||||
@ -235,5 +242,13 @@ internal fun PluginDescription.wrapWith(loader: PluginLoader<*, *>, plugin: Plug
|
||||
loader as PluginLoader<Plugin, PluginDescription>, this, plugin
|
||||
)
|
||||
|
||||
internal operator fun List<PluginDescription>.contains(dependency: PluginDependency): Boolean =
|
||||
any { it.id == dependency.id }
|
||||
internal fun List<PluginDescription>.findDependency(dependency: PluginDependency): PluginDescription? {
|
||||
return find { it.id.equals(dependency.id, ignoreCase = true) }
|
||||
}
|
||||
|
||||
internal fun PluginDescription.checkSatisfies(dependency: PluginDependency, plugin: PluginDescription) {
|
||||
val requirement = dependency.versionRequirement
|
||||
if (requirement != null && this.version !in requirement) {
|
||||
throw PluginLoadException("Plugin '${plugin.id}' ('${plugin.id}') requires '${dependency.id}' with version $requirement while the resolved is ${this.version}")
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,6 @@
|
||||
|
||||
package net.mamoe.mirai.console.plugin.description
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
|
||||
/**
|
||||
* 插件的一个依赖的信息.
|
||||
*
|
||||
@ -28,13 +26,14 @@ public data class PluginDependency @JvmOverloads constructor(
|
||||
*
|
||||
* 版本遵循 [语义化版本 2.0 规范](https://semver.org/lang/zh-CN/),
|
||||
*
|
||||
* 允许 [Apache Ivy 风格版本号表示](http://ant.apache.org/ivy/history/latest-milestone/settings/version-matchers.html)
|
||||
* ### 示例
|
||||
* `Requirement.buildIvy("[1.0, 2.0)")`
|
||||
*/
|
||||
public val version: Semver? = null,
|
||||
public val versionRequirement: VersionRequirement? = null,
|
||||
/**
|
||||
* 若为 `false`, 插件在找不到此依赖时也能正常加载.
|
||||
*/
|
||||
public val isOptional: Boolean = false
|
||||
public val isOptional: Boolean = false,
|
||||
) {
|
||||
init {
|
||||
kotlin.runCatching {
|
||||
@ -50,13 +49,4 @@ public data class PluginDependency @JvmOverloads constructor(
|
||||
public constructor(name: String, isOptional: Boolean = false) : this(
|
||||
name, null, isOptional
|
||||
)
|
||||
|
||||
/**
|
||||
* @see PluginDependency
|
||||
*/
|
||||
public constructor(name: String, version: String, isOptional: Boolean) : this(
|
||||
name,
|
||||
Semver(version, Semver.SemverType.IVY),
|
||||
isOptional
|
||||
)
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
package net.mamoe.mirai.console.plugin.description
|
||||
|
||||
import com.vdurmont.semver4j.Requirement
|
||||
import com.vdurmont.semver4j.Semver
|
||||
|
||||
public sealed class VersionRequirement {
|
||||
public abstract operator fun contains(version: Semver): Boolean
|
||||
public fun contains(version: String): Boolean = contains(Semver(version, Semver.SemverType.LOOSE))
|
||||
|
||||
public data class Exact(
|
||||
val version: Semver,
|
||||
) : VersionRequirement() {
|
||||
public constructor(version: String) : this(Semver(version, Semver.SemverType.LOOSE))
|
||||
|
||||
override fun contains(version: Semver): Boolean = this.version.isEqualTo(version)
|
||||
}
|
||||
|
||||
public data class MatchesNpmPattern(
|
||||
val pattern: String,
|
||||
) : VersionRequirement() {
|
||||
private val requirement = Requirement.buildNPM(pattern)
|
||||
override fun contains(version: Semver): Boolean = requirement.isSatisfiedBy(version)
|
||||
}
|
||||
|
||||
public data class MatchesIvyPattern(
|
||||
val pattern: String,
|
||||
) : VersionRequirement() {
|
||||
private val requirement = Requirement.buildIvy(pattern)
|
||||
override fun contains(version: Semver): Boolean = requirement.isSatisfiedBy(version)
|
||||
}
|
||||
|
||||
|
||||
public data class MatchesCocoapodsPattern(
|
||||
val pattern: String,
|
||||
) : VersionRequirement() {
|
||||
private val requirement = Requirement.buildCocoapods(pattern)
|
||||
override fun contains(version: Semver): Boolean = requirement.isSatisfiedBy(version)
|
||||
}
|
||||
|
||||
public abstract class Custom : VersionRequirement()
|
||||
|
||||
public data class InRange(
|
||||
val begin: Semver,
|
||||
val beginInclusive: Boolean,
|
||||
val end: Semver,
|
||||
val endInclusive: Boolean,
|
||||
) : VersionRequirement() {
|
||||
public constructor(
|
||||
begin: String,
|
||||
beginInclusive: Boolean,
|
||||
end: Semver,
|
||||
endInclusive: Boolean,
|
||||
) : this(Semver(begin, Semver.SemverType.LOOSE), beginInclusive, end, endInclusive)
|
||||
|
||||
public constructor(
|
||||
begin: String,
|
||||
beginInclusive: Boolean,
|
||||
end: String,
|
||||
endInclusive: Boolean,
|
||||
) : this(Semver(begin, Semver.SemverType.LOOSE),
|
||||
beginInclusive,
|
||||
Semver(end, Semver.SemverType.LOOSE),
|
||||
endInclusive)
|
||||
|
||||
public constructor(
|
||||
begin: Semver,
|
||||
beginInclusive: Boolean,
|
||||
end: String,
|
||||
endInclusive: Boolean,
|
||||
) : this(begin, beginInclusive, Semver(end, Semver.SemverType.LOOSE), endInclusive)
|
||||
|
||||
override fun contains(version: Semver): Boolean {
|
||||
return if (beginInclusive) {
|
||||
version.isGreaterThanOrEqualTo(begin)
|
||||
} else {
|
||||
version.isGreaterThan(begin)
|
||||
} && if (endInclusive) {
|
||||
version.isLowerThanOrEqualTo(begin)
|
||||
} else {
|
||||
version.isLowerThan(begin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
public class Builder {
|
||||
@ILoveKafuuChinoForever
|
||||
public fun exact(version: Semver): VersionRequirement = Exact(version)
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public fun exact(version: String): VersionRequirement = Exact(version)
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public fun custom(checker: (version: Semver) -> Boolean): VersionRequirement {
|
||||
return object : Custom() {
|
||||
override fun contains(version: Semver): Boolean = checker(version)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Semver.SemverType.NPM
|
||||
*/
|
||||
@ILoveKafuuChinoForever
|
||||
public fun npmPattern(versionPattern: String): VersionRequirement {
|
||||
return MatchesNpmPattern(versionPattern)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Semver.SemverType.IVY
|
||||
*/
|
||||
@ILoveKafuuChinoForever
|
||||
public fun ivyPattern(versionPattern: String): VersionRequirement {
|
||||
return MatchesIvyPattern(versionPattern)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Semver.SemverType.COCOAPODS
|
||||
*/
|
||||
@ILoveKafuuChinoForever
|
||||
public fun cocoapodsPattern(versionPattern: String): VersionRequirement {
|
||||
return MatchesCocoapodsPattern(versionPattern)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public fun range(
|
||||
begin: Semver,
|
||||
beginInclusive: Boolean,
|
||||
end: Semver,
|
||||
endInclusive: Boolean,
|
||||
): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive)
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public fun range(
|
||||
begin: String,
|
||||
beginInclusive: Boolean,
|
||||
end: Semver,
|
||||
endInclusive: Boolean,
|
||||
): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive)
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public fun range(
|
||||
begin: Semver,
|
||||
beginInclusive: Boolean,
|
||||
end: String,
|
||||
endInclusive: Boolean,
|
||||
): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive)
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public fun range(
|
||||
begin: String,
|
||||
beginInclusive: Boolean,
|
||||
end: String,
|
||||
endInclusive: Boolean,
|
||||
): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive)
|
||||
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public operator fun Semver.rangeTo(endInclusive: Semver): VersionRequirement {
|
||||
return InRange(this, true, endInclusive, true)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public operator fun Semver.rangeTo(endInclusive: String): VersionRequirement {
|
||||
return InRange(this, true, Semver(endInclusive, Semver.SemverType.LOOSE), true)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public operator fun String.rangeTo(endInclusive: String): VersionRequirement {
|
||||
return InRange(Semver(this, Semver.SemverType.LOOSE),
|
||||
true,
|
||||
Semver(endInclusive, Semver.SemverType.LOOSE),
|
||||
true)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public operator fun String.rangeTo(endInclusive: Semver): VersionRequirement {
|
||||
return InRange(Semver(this, Semver.SemverType.LOOSE), true, endInclusive, true)
|
||||
}
|
||||
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public infix fun Semver.until(endExclusive: Semver): VersionRequirement {
|
||||
return InRange(this, true, endExclusive, false)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public infix fun Semver.until(endExclusive: String): VersionRequirement {
|
||||
return InRange(this, true, Semver(endExclusive, Semver.SemverType.LOOSE), false)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public infix fun String.until(endExclusive: String): VersionRequirement {
|
||||
return InRange(Semver(this, Semver.SemverType.LOOSE),
|
||||
true,
|
||||
Semver(endExclusive, Semver.SemverType.LOOSE),
|
||||
false)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public infix fun String.until(endExclusive: Semver): VersionRequirement {
|
||||
return InRange(Semver(this, Semver.SemverType.LOOSE), true, endExclusive, false)
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@DslMarker
|
||||
private annotation class ILoveKafuuChinoForever
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ package net.mamoe.mirai.console.plugin.jvm
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDependency
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.plugin.description.VersionRequirement
|
||||
|
||||
/**
|
||||
* JVM 插件的描述. 通常作为 `plugin.yml`
|
||||
@ -122,16 +123,6 @@ public class JvmPluginDescriptionBuilder(
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun info(value: String): JvmPluginDescriptionBuilder = apply { this.info = value.trimIndent() }
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
version: String? = null,
|
||||
isOptional: Boolean = false,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
if (version == null) this.dependencies.add(PluginDependency(pluginId, version, isOptional))
|
||||
else this.dependencies.add(PluginDependency(pluginId, version, isOptional))
|
||||
}
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun setDependencies(
|
||||
value: Set<PluginDependency>,
|
||||
@ -148,18 +139,79 @@ public class JvmPluginDescriptionBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginDependency
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
version: Semver? = null,
|
||||
isOptional: Boolean = false,
|
||||
): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, version, isOptional)) }
|
||||
versionRequirement: VersionRequirement? = null,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
if (versionRequirement == null)
|
||||
this.dependencies.add(PluginDependency(pluginId, versionRequirement, isOptional))
|
||||
else this.dependencies.add(PluginDependency(pluginId, versionRequirement, isOptional))
|
||||
}
|
||||
|
||||
/**
|
||||
* isOptional = false
|
||||
*
|
||||
* @see PluginDependency
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
versionRequirement: VersionRequirement? = null,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
if (versionRequirement == null)
|
||||
this.dependencies.add(PluginDependency(pluginId, versionRequirement, false))
|
||||
else this.dependencies.add(PluginDependency(pluginId, versionRequirement, false))
|
||||
}
|
||||
|
||||
/**
|
||||
* 无版本要求
|
||||
*
|
||||
* @see PluginDependency
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
isOptional: Boolean = false,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
dependsOn(pluginId, isOptional, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:
|
||||
*
|
||||
* ```
|
||||
* 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 VersionRequirement.Builder
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
isOptional: Boolean = false,
|
||||
versionRequirement: VersionRequirement.Builder.() -> VersionRequirement,
|
||||
): JvmPluginDescriptionBuilder =
|
||||
apply {
|
||||
this.dependencies.add(PluginDependency(pluginId,
|
||||
VersionRequirement.Builder().run(versionRequirement),
|
||||
isOptional))
|
||||
}
|
||||
|
||||
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
public fun build(): JvmPluginDescription =
|
||||
SimpleJvmPluginDescription(name, version, id, author, info, dependencies)
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@DslMarker
|
||||
private annotation class ILoveKuriyamaMiraiForever // https://zh.moegirl.org.cn/zh-cn/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5
|
||||
|
Loading…
Reference in New Issue
Block a user