mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-05 23:50:08 +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)。
|
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`
|
### `publishPlugin`
|
||||||
|
|
||||||
配置好 Bintray 参数,使用 `./gradlew publishPlugin` 可自动发布并上传插件到 Bintray。
|
配置好 Bintray 参数,使用 `./gradlew publishPlugin` 可自动发布并上传插件到 Bintray。
|
||||||
@ -57,9 +74,9 @@ mirai {
|
|||||||
|
|
||||||
*2021/3/21 更新:* 由于 Bintray JCenter 即将关闭,随着论坛的发展,mirai 正在策划插件中心服务。待插件中心完成后将会提供更好的插件分发平台。
|
*2021/3/21 更新:* 由于 Bintray JCenter 即将关闭,随着论坛的发展,mirai 正在策划插件中心服务。待插件中心完成后将会提供更好的插件分发平台。
|
||||||
|
|
||||||
#### 排除依赖
|
#### 排除依赖 (过时)
|
||||||
|
|
||||||
如果要在打包 JAR(`buildPlugin`)时排除一些依赖,请使用如下配置:
|
如果要在打包 JAR(`buildPluginLegacy`)时排除一些依赖,请使用如下配置:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
mirai {
|
mirai {
|
||||||
|
@ -12,6 +12,8 @@ package net.mamoe.mirai.console.gradle
|
|||||||
import org.gradle.testkit.runner.GradleRunner
|
import org.gradle.testkit.runner.GradleRunner
|
||||||
import org.gradle.testkit.runner.internal.PluginUnderTestMetadataReading
|
import org.gradle.testkit.runner.internal.PluginUnderTestMetadataReading
|
||||||
import org.junit.jupiter.api.BeforeEach
|
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 org.junit.jupiter.api.io.TempDir
|
||||||
import java.io.File
|
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
|
package net.mamoe.mirai.console.gradle
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
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() {
|
class TestBuildPlugin : AbstractTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `can build plugin`() {
|
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()
|
gradleRunner()
|
||||||
.withArguments("buildPlugin", "--stacktrace")
|
.withArguments("buildPlugin", "dependencies", "--stacktrace", "--info")
|
||||||
.build()
|
.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
|
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||||
|
|
||||||
public class MiraiConsoleGradlePlugin : Plugin<Project> {
|
public class MiraiConsoleGradlePlugin : Plugin<Project> {
|
||||||
|
internal companion object {
|
||||||
|
const val MIRAI_SHADOW_CONF_NAME: String = "shadowLink"
|
||||||
|
}
|
||||||
|
|
||||||
private fun KotlinSourceSet.configureSourceSet(project: Project, target: KotlinTarget) {
|
private fun KotlinSourceSet.configureSourceSet(project: Project, target: KotlinTarget) {
|
||||||
try {
|
try {
|
||||||
@Suppress("DEPRECATION") // user may use 1.4
|
@Suppress("DEPRECATION") // user may use 1.4
|
||||||
@ -113,12 +117,19 @@ public class MiraiConsoleGradlePlugin : Plugin<Project> {
|
|||||||
fun registerBuildPluginTask(target: KotlinTarget, isSingleTarget: Boolean) {
|
fun registerBuildPluginTask(target: KotlinTarget, isSingleTarget: Boolean) {
|
||||||
tasks.create(
|
tasks.create(
|
||||||
"buildPlugin".wrapNameWithPlatform(target, isSingleTarget),
|
"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,
|
BuildMiraiPluginTask::class.java,
|
||||||
target
|
target
|
||||||
).apply shadow@{
|
).apply shadow@{
|
||||||
group = "mirai"
|
group = "mirai"
|
||||||
|
|
||||||
archiveExtension.set("mirai.jar")
|
archiveExtension.set("legacy.mirai.jar")
|
||||||
|
|
||||||
val compilations = target.compilations.filter { it.name == MAIN_COMPILATION_NAME }
|
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) {
|
override fun apply(target: Project): Unit = with(target) {
|
||||||
extensions.create("mirai", MiraiConsoleExtension::class.java)
|
extensions.create("mirai", MiraiConsoleExtension::class.java)
|
||||||
|
|
||||||
@ -162,6 +177,8 @@ public class MiraiConsoleGradlePlugin : Plugin<Project> {
|
|||||||
plugins.apply(ShadowPlugin::class.java)
|
plugins.apply(ShadowPlugin::class.java)
|
||||||
plugins.apply(BintrayPlugin::class.java)
|
plugins.apply(BintrayPlugin::class.java)
|
||||||
|
|
||||||
|
project.setupConfigurations()
|
||||||
|
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
configureCompileTarget()
|
configureCompileTarget()
|
||||||
kotlinTargets.forEach { configureTarget(it) }
|
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