mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-01 10:12:51 +08:00
gradle plugin
This commit is contained in:
parent
d32913aa9f
commit
5df5ecc444
@ -40,6 +40,23 @@ mirai { // this: MiraiConsoleExtension
|
||||
|
||||
DSL 详见 [MiraiConsoleExtension](src/MiraiConsoleExtension.kt)。
|
||||
|
||||
### 打包依赖
|
||||
|
||||
Mirai Console Gradle 在打包 JAR(`buildPlugin`) 时不会携带任何外部依赖,
|
||||
而是会保存一份依赖列表,在加载插件时下载,
|
||||
如果您使用了不可在 `Maven Central` 搜索到的依赖, 请使用以下配置告知 mirai-console-gradle
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
implementation "org.example:test:1.0.0"
|
||||
|
||||
// 无需版本号
|
||||
shadowLink "org.example:test"
|
||||
// build.gradle.kts
|
||||
"shadowLink"("org.example:test")
|
||||
}
|
||||
```
|
||||
|
||||
### `publishPlugin`
|
||||
|
||||
配置好 Bintray 参数,使用 `./gradlew publishPlugin` 可自动发布并上传插件到 Bintray。
|
||||
@ -57,9 +74,9 @@ mirai {
|
||||
|
||||
*2021/3/21 更新:* 由于 Bintray JCenter 即将关闭,随着论坛的发展,mirai 正在策划插件中心服务。待插件中心完成后将会提供更好的插件分发平台。
|
||||
|
||||
#### 排除依赖
|
||||
#### 排除依赖 (过时)
|
||||
|
||||
如果要在打包 JAR(`buildPlugin`)时排除一些依赖,请使用如下配置:
|
||||
如果要在打包 JAR(`buildPluginLegacy`)时排除一些依赖,请使用如下配置:
|
||||
|
||||
```kotlin
|
||||
mirai {
|
||||
|
@ -12,6 +12,8 @@ package net.mamoe.mirai.console.gradle
|
||||
import org.gradle.testkit.runner.GradleRunner
|
||||
import org.gradle.testkit.runner.internal.PluginUnderTestMetadataReading
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback
|
||||
import org.junit.jupiter.api.extension.RegisterExtension
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.io.File
|
||||
|
||||
@ -91,4 +93,16 @@ abstract class AbstractTest {
|
||||
// """
|
||||
|
||||
}
|
||||
|
||||
@JvmField
|
||||
@RegisterExtension
|
||||
internal val after: AfterEachCallback = AfterEachCallback { context ->
|
||||
if (context.executionException.isPresent) {
|
||||
val inst = context.requiredTestInstance as AbstractTest
|
||||
println("====================== build.gradle ===========================")
|
||||
println(inst.tempDir.resolve("build.gradle").readText())
|
||||
println("==================== settings.gradle ==========================")
|
||||
println(inst.tempDir.resolve("settings.gradle").readText())
|
||||
}
|
||||
}
|
||||
}
|
@ -10,14 +10,49 @@
|
||||
package net.mamoe.mirai.console.gradle
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class TestBuildPlugin : AbstractTest() {
|
||||
|
||||
@Test
|
||||
fun `can build plugin`() {
|
||||
tempDir.resolve("build.gradle").appendText(
|
||||
"""
|
||||
dependencies {
|
||||
api "com.zaxxer:SparseBitSet:1.2"
|
||||
implementation "com.google.code.gson:gson:2.8.9"
|
||||
api "org.slf4j:slf4j-simple:1.7.32"
|
||||
shadowLink "org.slf4j:slf4j-simple"
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
gradleRunner()
|
||||
.withArguments("buildPlugin", "--stacktrace")
|
||||
.withArguments("buildPlugin", "dependencies", "--stacktrace", "--info")
|
||||
.build()
|
||||
val jar = tempDir.resolve("build/libs").listFiles()!!.first { it.name.endsWith(".mirai.jar") }
|
||||
ZipFile(jar).use { zipFile ->
|
||||
|
||||
assertNotNull(zipFile.getEntry("org/slf4j/impl/SimpleLogger.class"))
|
||||
|
||||
val dpPrivate = zipFile.getInputStream(
|
||||
zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-private.txt")
|
||||
).use { it.readBytes().decodeToString() }
|
||||
val dpShared = zipFile.getInputStream(
|
||||
zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-shared.txt")
|
||||
).use { it.readBytes().decodeToString() }
|
||||
|
||||
assertTrue { dpShared.contains("com.zaxxer:SparseBitSet:1.2") }
|
||||
assertFalse { dpShared.contains("com.google.code.gson:gson") }
|
||||
assertFalse { dpShared.contains("org.slf4j:slf4j-simple") }
|
||||
|
||||
assertTrue { dpPrivate.contains("com.zaxxer:SparseBitSet:1.2") }
|
||||
assertTrue { dpPrivate.contains("com.google.code.gson:gson:2.8.9") }
|
||||
assertFalse { dpPrivate.contains("org.slf4j:slf4j-simple") }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.gradle
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.artifacts.ExternalModuleDependency
|
||||
import org.gradle.api.artifacts.ResolvedArtifact
|
||||
import org.gradle.api.artifacts.ResolvedDependency
|
||||
import org.gradle.api.attributes.AttributeContainer
|
||||
import org.gradle.api.capabilities.Capability
|
||||
import org.gradle.api.file.DuplicatesStrategy
|
||||
import org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration
|
||||
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ArtifactVisitor
|
||||
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ResolvableArtifact
|
||||
import org.gradle.api.internal.file.FileCollectionInternal
|
||||
import org.gradle.api.internal.file.FileCollectionStructureVisitor
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.api.tasks.TaskContainer
|
||||
import org.gradle.internal.DisplayName
|
||||
import org.gradle.internal.component.external.model.ModuleComponentArtifactIdentifier
|
||||
import org.gradle.jvm.tasks.Jar
|
||||
import org.gradle.kotlin.dsl.create
|
||||
import org.gradle.kotlin.dsl.get
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("RedundantLambdaArrow", "RemoveExplicitTypeArguments")
|
||||
public open class BuildMiraiPluginV2 : Jar() {
|
||||
|
||||
// @get:Internal
|
||||
private lateinit var metadataTask: GenMetadataTask
|
||||
|
||||
internal open class GenMetadataTask
|
||||
@Inject internal constructor(
|
||||
@JvmField internal val orgTask: BuildMiraiPluginV2,
|
||||
) : DefaultTask() {
|
||||
companion object {
|
||||
val miraiDependencies = mutableSetOf(
|
||||
"net.mamoe:mirai-core-api",
|
||||
"net.mamoe:mirai-core-api-jvm",
|
||||
"net.mamoe:mirai-core-api-android",
|
||||
"net.mamoe:mirai-core",
|
||||
"net.mamoe:mirai-core-jvm",
|
||||
"net.mamoe:mirai-core-android",
|
||||
"net.mamoe:mirai-core-utils",
|
||||
"net.mamoe:mirai-core-utils-jvm",
|
||||
"net.mamoe:mirai-core-utils-android",
|
||||
"net.mamoe:mirai-console",
|
||||
"net.mamoe:mirai-console-terminal",
|
||||
)
|
||||
}
|
||||
@TaskAction
|
||||
internal fun run() {
|
||||
val runtime = mutableSetOf<String>()
|
||||
val api = mutableSetOf<String>()
|
||||
val linkedDependencies = mutableSetOf<String>()
|
||||
val linkToApi = mutableSetOf<String>()
|
||||
val shadowedFiles = mutableSetOf<File>()
|
||||
val shadowedDependencies = mutableSetOf<String>()
|
||||
|
||||
project.configurations.findByName(MiraiConsoleGradlePlugin.MIRAI_SHADOW_CONF_NAME)?.allDependencies?.forEach { dep ->
|
||||
if (dep is ExternalModuleDependency) {
|
||||
val artId = "${dep.group}:${dep.name}"
|
||||
shadowedDependencies.add(artId)
|
||||
}
|
||||
}
|
||||
project.configurations.findByName("apiElements")?.allDependencies?.forEach { dep ->
|
||||
if (dep is ExternalModuleDependency) {
|
||||
val artId = "${dep.group}:${dep.name}"
|
||||
linkedDependencies.add(artId)
|
||||
linkToApi.add(artId)
|
||||
}
|
||||
}
|
||||
project.configurations.findByName("implementation")?.allDependencies?.forEach { dep ->
|
||||
if (dep is ExternalModuleDependency) {
|
||||
linkedDependencies.add("${dep.group}:${dep.name}")
|
||||
}
|
||||
}
|
||||
linkedDependencies.removeAll(shadowedDependencies)
|
||||
linkToApi.removeAll(shadowedDependencies)
|
||||
linkedDependencies.addAll(miraiDependencies)
|
||||
|
||||
fun ResolvedDependency.depId(): String = "$moduleGroup:$moduleName"
|
||||
|
||||
val runtimeClasspath = project.configurations["runtimeClasspath"].resolvedConfiguration
|
||||
fun markAsResolved(resolvedDependency: ResolvedDependency) {
|
||||
val depId = resolvedDependency.depId()
|
||||
linkedDependencies.add(depId)
|
||||
resolvedDependency.children.forEach { markAsResolved(it) }
|
||||
}
|
||||
|
||||
fun linkDependencyTo(resolvedDependency: ResolvedDependency, dependencies: MutableCollection<String>) {
|
||||
dependencies.add(resolvedDependency.module.toString())
|
||||
resolvedDependency.children.forEach { linkDependencyTo(it, dependencies) }
|
||||
}
|
||||
|
||||
fun resolveDependency(resolvedDependency: ResolvedDependency) {
|
||||
val depId = resolvedDependency.depId()
|
||||
if (depId in linkedDependencies) {
|
||||
markAsResolved(resolvedDependency)
|
||||
linkDependencyTo(resolvedDependency, runtime)
|
||||
if (depId in linkToApi) {
|
||||
linkDependencyTo(resolvedDependency, api)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
runtimeClasspath.firstLevelModuleDependencies.forEach { resolveDependency(it) }
|
||||
|
||||
logger.info { "linkedDependencies: $linkedDependencies" }
|
||||
logger.info { "linkToAPi : $linkToApi" }
|
||||
logger.info { "api : $api" }
|
||||
logger.info { "runtime : $runtime" }
|
||||
|
||||
val lenientConfiguration = runtimeClasspath.lenientConfiguration
|
||||
if (lenientConfiguration is DefaultLenientConfiguration) {
|
||||
val resolvedArtifacts = mutableSetOf<ResolvedArtifact>()
|
||||
lenientConfiguration.select().visitArtifacts(object : ArtifactVisitor {
|
||||
override fun prepareForVisit(source: FileCollectionInternal.Source): FileCollectionStructureVisitor.VisitType {
|
||||
return FileCollectionStructureVisitor.VisitType.Visit
|
||||
}
|
||||
|
||||
override fun visitArtifact(
|
||||
variantName: DisplayName,
|
||||
variantAttributes: AttributeContainer,
|
||||
capabilities: MutableList<out Capability>,
|
||||
artifact: ResolvableArtifact
|
||||
) {
|
||||
resolvedArtifacts.add(artifact.toPublicView())
|
||||
}
|
||||
|
||||
override fun requireArtifactFiles(): Boolean = false
|
||||
override fun visitFailure(failure: Throwable) {}
|
||||
}, false)
|
||||
resolvedArtifacts
|
||||
} else {
|
||||
runtimeClasspath.resolvedArtifacts
|
||||
}.forEach { artifact ->
|
||||
val artId = artifact.id
|
||||
if (artId is ModuleComponentArtifactIdentifier) {
|
||||
val cid = artId.componentIdentifier
|
||||
if ("${cid.group}:${cid.module}" in linkedDependencies) {
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
logger.info { " `- $artId - ${artId.javaClass}" }
|
||||
shadowedFiles.add(artifact.file)
|
||||
}
|
||||
|
||||
shadowedFiles.forEach { file ->
|
||||
if (file.isDirectory) {
|
||||
orgTask.from(file)
|
||||
} else if (file.extension == "jar") {
|
||||
orgTask.from(project.zipTree(file))
|
||||
} else {
|
||||
orgTask.from(file)
|
||||
}
|
||||
}
|
||||
|
||||
temporaryDir.also {
|
||||
it.mkdirs()
|
||||
}.let { tmpDir ->
|
||||
tmpDir.resolve("api.txt").writeText(api.sorted().joinToString("\n"))
|
||||
tmpDir.resolve("runtime.txt").writeText(runtime.sorted().joinToString("\n"))
|
||||
orgTask.from(tmpDir.resolve("api.txt")) { copy ->
|
||||
copy.into("META-INF/mirai-console-plugin")
|
||||
copy.rename { "dependencies-shared.txt" }
|
||||
}
|
||||
orgTask.from(tmpDir.resolve("runtime.txt")) { copy ->
|
||||
copy.into("META-INF/mirai-console-plugin")
|
||||
copy.rename { "dependencies-private.txt" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun registerMetadataTask(tasks: TaskContainer, metadataTaskName: String) {
|
||||
metadataTask = tasks.create<GenMetadataTask>(metadataTaskName, this)
|
||||
}
|
||||
|
||||
internal fun init(target: KotlinTarget) {
|
||||
dependsOn(metadataTask)
|
||||
archiveExtension.set("mirai.jar")
|
||||
duplicatesStrategy = DuplicatesStrategy.WARN
|
||||
|
||||
val compilations = target.compilations.filter { it.name == KotlinCompilation.MAIN_COMPILATION_NAME }
|
||||
compilations.forEach {
|
||||
dependsOn(it.compileKotlinTask)
|
||||
from(it.output.allOutputs)
|
||||
metadataTask.dependsOn(it.compileKotlinTask)
|
||||
}
|
||||
exclude { elm ->
|
||||
elm.path.startsWith("META-INF/") && elm.name.endsWith(".sf", ignoreCase = true)
|
||||
}
|
||||
}
|
||||
}
|
@ -30,6 +30,10 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||
|
||||
public class MiraiConsoleGradlePlugin : Plugin<Project> {
|
||||
internal companion object {
|
||||
const val MIRAI_SHADOW_CONF_NAME: String = "shadowLink"
|
||||
}
|
||||
|
||||
private fun KotlinSourceSet.configureSourceSet(project: Project, target: KotlinTarget) {
|
||||
try {
|
||||
@Suppress("DEPRECATION") // user may use 1.4
|
||||
@ -113,12 +117,19 @@ public class MiraiConsoleGradlePlugin : Plugin<Project> {
|
||||
fun registerBuildPluginTask(target: KotlinTarget, isSingleTarget: Boolean) {
|
||||
tasks.create(
|
||||
"buildPlugin".wrapNameWithPlatform(target, isSingleTarget),
|
||||
BuildMiraiPluginV2::class.java
|
||||
).also { buildPluginV2 ->
|
||||
buildPluginV2.registerMetadataTask(tasks, "miraiPrepareMetadata".wrapNameWithPlatform(target, isSingleTarget))
|
||||
buildPluginV2.init(target)
|
||||
}
|
||||
tasks.create(
|
||||
"buildPluginLegacy".wrapNameWithPlatform(target, isSingleTarget),
|
||||
BuildMiraiPluginTask::class.java,
|
||||
target
|
||||
).apply shadow@{
|
||||
group = "mirai"
|
||||
|
||||
archiveExtension.set("mirai.jar")
|
||||
archiveExtension.set("legacy.mirai.jar")
|
||||
|
||||
val compilations = target.compilations.filter { it.name == MAIN_COMPILATION_NAME }
|
||||
|
||||
@ -153,6 +164,10 @@ public class MiraiConsoleGradlePlugin : Plugin<Project> {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.setupConfigurations() {
|
||||
configurations.create(MIRAI_SHADOW_CONF_NAME).isCanBeResolved = false
|
||||
}
|
||||
|
||||
override fun apply(target: Project): Unit = with(target) {
|
||||
extensions.create("mirai", MiraiConsoleExtension::class.java)
|
||||
|
||||
@ -162,6 +177,8 @@ public class MiraiConsoleGradlePlugin : Plugin<Project> {
|
||||
plugins.apply(ShadowPlugin::class.java)
|
||||
plugins.apply(BintrayPlugin::class.java)
|
||||
|
||||
project.setupConfigurations()
|
||||
|
||||
afterEvaluate {
|
||||
configureCompileTarget()
|
||||
kotlinTargets.forEach { configureTarget(it) }
|
||||
|
16
mirai-console/tools/gradle-plugin/src/main/kotlin/dsl.kt
Normal file
16
mirai-console/tools/gradle-plugin/src/main/kotlin/dsl.kt
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.gradle
|
||||
|
||||
import org.gradle.api.logging.Logger
|
||||
|
||||
internal inline fun Logger.info(msg: () -> String) {
|
||||
if (isInfoEnabled) info(msg())
|
||||
}
|
Loading…
Reference in New Issue
Block a user