[build] Rewrite shadow relocation; fix dependency graph issues with Android, and improve build performance:

- Generate relocated JARs with classifier `relocated`, instead of replacing the output of `:jar` task.

- Create `JvmRelocated` publications to publish relocated artifacts.

- Patch Kotlin Metadata and Maven Pom for the added publication.

- Updated deps test to be more strict
This commit is contained in:
Him188
2023-04-29 15:02:05 +01:00
parent 40d4957837
commit 178ca6c1b5
19 changed files with 683 additions and 359 deletions

View File

@@ -0,0 +1,39 @@
<!--
~ Copyright 2019-2023 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
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Publish deps test artifacts" type="GradleRunConfiguration" factoryName="Gradle"
folderName="Publishing Tests">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="mirai.build.project.version" value="2.99.0-deps-test"/>
</map>
</option>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value="--stacktrace"/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value=":mirai-deps-test:publishMiraiArtifactsToMavenLocal"/>
</list>
</option>
<option name="vmOptions"/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<ForceTestExec>false</ForceTestExec>
<method v="2"/>
</configuration>
</component>

View File

@@ -1,3 +1,12 @@
<!--
~ Copyright 2019-2023 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
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Publish local artifacts" type="GradleRunConfiguration" factoryName="Gradle" folderName="Build">
<ExternalSystemSettings>
@@ -9,7 +18,7 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="scriptParameters" value="--stacktrace"/>
<option name="taskDescriptions">
<list />
</option>
@@ -23,6 +32,7 @@
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<ForceTestExec>false</ForceTestExec>
<method v="2" />
</configuration>
</component>

View File

@@ -11,6 +11,7 @@
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.base.DokkaBaseConfiguration
import shadow.configureMppShadow
import java.time.LocalDateTime
buildscript {
@@ -79,9 +80,6 @@ allprojects {
substituteDependenciesUsingExpectedVersion()
}
}
afterEvaluate {
configureShadowDependenciesForPublishing()
}
subprojects {
afterEvaluate {

View File

@@ -7,8 +7,6 @@
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("UNUSED_VARIABLE")
import com.google.gradle.osdetector.OsDetector
import org.gradle.api.Project
import org.gradle.api.attributes.Attribute
@@ -28,14 +26,14 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
import java.io.File
import java.util.*
val MIRAI_PLATFORM_ATTRIBUTE = Attribute.of(
val MIRAI_PLATFORM_ATTRIBUTE: Attribute<String> = Attribute.of(
"net.mamoe.mirai.platform", String::class.java
)
/**
* Flags a target as an HMPP intermediate target
*/
val MIRAI_PLATFORM_INTERMEDIATE = Attribute.of(
val MIRAI_PLATFORM_INTERMEDIATE: Attribute<Boolean> = Attribute.of(
"net.mamoe.mirai.platform.intermediate", Boolean::class.javaObjectType
)

View File

@@ -0,0 +1,169 @@
/*
* Copyright 2019-2023 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
*/
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.tasks.GenerateModuleMetadata
import shadow.relocationFilters
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.security.MessageDigest
fun Project.configurePatchKotlinModuleMetadataTask(
relocatedPublicationName: String,
relocateDependencies: Task,
originalPublicationName: String
) {
// We will modify Kotlin metadata, so do generate metadata before relocation
val generateMetadataTask =
tasks.getByName("generateMetadataFileFor${originalPublicationName.titlecase()}Publication") as GenerateModuleMetadata
publications.getByName(relocatedPublicationName) {
this as MavenPublication
this.artifact(generateMetadataTask.outputFile) {
classifier = null
extension = "module"
}
}
generateMetadataTask.dependsOn(relocateDependencies)
val patchMetadataTask =
tasks.create("patchMetadataFileFor${relocatedPublicationName.capitalize()}RelocatedPublication") {
group = "mirai"
generateMetadataTask.finalizedBy(this)
dependsOn(generateMetadataTask)
dependsOn(relocateDependencies)
// remove dependencies in Kotlin module metadata
doLast {
// mirai-core-jvm-2.13.0.module
val file = generateMetadataTask.outputFile.asFile.get()
val metadata = Gson().fromJson(
file.readText(),
JsonElement::class.java
).asJsonObject
val metadataVersion = metadata["formatVersion"]?.asString
check(metadataVersion == "1.1") {
"Unsupported Kotlin metadata version. version=$metadataVersion, file=${file.absolutePath}"
}
for (variant in metadata["variants"]!!.asJsonArray) {
patchKotlinMetadataVariant(variant, relocateDependencies.outputs.files.singleFile)
}
file.writeText(GsonBuilder().setPrettyPrinting().create().toJson(metadata))
}
}
// Set "publishKotlinMultiplatformPublicationTo*" and "publish${targetName.capitalize()}PublicationTo*" dependsOn patchMetadataTask
if (project.kotlinMpp != null) {
tasks.filter { it.name.startsWith("publishKotlinMultiplatformPublicationTo") }.let { publishTasks ->
if (publishTasks.isEmpty()) {
throw GradleException("[Shadow Relocation] Cannot find publishKotlinMultiplatformPublicationTo for project '${project.path}'.")
}
publishTasks.forEach { it.dependsOn(patchMetadataTask) }
}
tasks.filter { it.name.startsWith("publish${relocatedPublicationName.capitalize()}PublicationTo") }
.let { publishTasks ->
if (publishTasks.isEmpty()) {
throw GradleException("[Shadow Relocation] Cannot find publish${relocatedPublicationName.capitalize()}PublicationTo for project '${project.path}'.")
}
publishTasks.forEach { it.dependsOn(patchMetadataTask) }
}
}
}
private fun Project.patchKotlinMetadataVariant(variant: JsonElement, relocatedJar: File) {
val dependencies = variant.asJsonObject["dependencies"]!!.asJsonArray
dependencies.removeAll { dependency ->
val dep = dependency.asJsonObject
val groupId = dep["group"]!!.asString
val artifactId = dep["module"]!!.asString
relocationFilters.any { filter ->
filter.matchesDependency(
groupId = groupId,
artifactId = artifactId
)
}.also {
println("[Shadow Relocation] Filtering out $groupId:$artifactId from Kotlin module")
}
}
/*
"files": [
{
"name": "mirai-core-jvm-2.99.0-local.jar",
"url": "mirai-core-jvm-2.99.0-local.jar",
"size": 14742378,
"sha512": "7ab4afc88384a58687467ba13c6aefeda20fa53fd7759dc2bc78b2d46a6285f94ba6ccae426d192e7745f773401b3cb42a853e5445dc23bdcb1b5295e78ff71c",
"sha256": "772f593bfb85a80794693d4d9dfe2f77c222cfe9ca7e0d571abaa320e7aa82d3",
"sha1": "cb7937269d29b574725d6f28668847fd672de7cf",
"md5": "3fca635ba5e55b7dd56c552e4ca01f7e"
}
]
*/
val files = variant.asJsonObject["files"].asJsonArray
val filesList = files.toList()
files.removeAll { true }
for (publishedFile0 in filesList) {
val publishedFile = publishedFile0.asJsonObject
val name = publishedFile["name"].asJsonPrimitive.asString
if (name.endsWith(".jar")) {
logPublishing { "Patching Kotlin Metadata: file $name" }
for (algorithm in ALGORITHMS) {
publishedFile.add(algorithm, JsonPrimitive(relocatedJar.digest(algorithm)))
}
publishedFile.add("size", JsonPrimitive(relocatedJar.length()))
} else {
error("Unexpected file '$name' while patching Kotlin metadata")
}
files.add(publishedFile)
}
}
private val ALGORITHMS = listOf("md5", "sha1", "sha256", "sha512")
fun File.digest(algorithm: String): String {
val arr = inputStream().buffered().use { it.digest(algorithm) }
return arr.toUHexString("").lowercase()
}
fun InputStream.digest(algorithm: String): ByteArray {
val digest = MessageDigest.getInstance(algorithm)
digest.reset()
use { input ->
object : OutputStream() {
override fun write(b: Int) {
digest.update(b.toByte())
}
override fun write(b: ByteArray, off: Int, len: Int) {
digest.update(b, off, len)
}
}.use { output ->
input.copyTo(output)
}
}
return digest.digest()
}

View File

@@ -15,9 +15,11 @@ import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.register
import shadow.RelocationConfig
import shadow.relocationFilters
inline fun logPublishing(@Suppress("UNUSED_PARAMETER") message: () -> String) {
// println("[Publishing] Configuring $message")
inline fun Project.logPublishing(message: () -> String) {
logger.debug("[Publishing] Configuring {}", message())
}
fun Project.configureMppPublishing() {
@@ -45,7 +47,9 @@ fun Project.configureMppPublishing() {
logPublishing { "Publications: ${publications.joinToString { it.name }}" }
val (nonJvmPublications, jvmPublications) = publications.filterIsInstance<MavenPublication>()
.partition { publication -> tasks.findByName("relocate${publication.name.titlecase()}Dependencies") == null }
.partition { publication ->
tasks.findByName(RelocationConfig.taskNameForRelocateDependencies(publication.name)) == null
}
for (publication in nonJvmPublications) {
configureMultiplatformPublication(publication, stubJavadoc, publication.name)
@@ -76,7 +80,7 @@ fun Project.configureMppPublishing() {
configureMultiplatformPublication(publication, stubJavadoc, publication.name)
publication.apply {
artifacts.filter { it.classifier.isNullOrEmpty() && it.extension == "jar" }.forEach {
it.builtBy(tasks.findByName("relocate${publication.name.titlecase()}Dependencies"))
it.builtBy(tasks.findByName(RelocationConfig.taskNameForRelocateDependencies(publication.name)))
}
}
}
@@ -108,6 +112,12 @@ private fun Project.configureMultiplatformPublication(
publication.artifactId = "${project.name}-metadata"
}
"jvm" -> {
publication.artifactId = "${project.name}-$moduleName"
useRelocatedPublication(publication, moduleName)
}
else -> {
// "jvm", "native", "js", "common"
publication.artifactId = "${project.name}-$moduleName"
@@ -115,6 +125,133 @@ private fun Project.configureMultiplatformPublication(
}
}
/**
* Creates a new publication and disables [publication].
*/
private fun Project.useRelocatedPublication(
publication: MavenPublication,
moduleName: String
) {
val relocatedPublicationName = RelocationConfig.relocatedPublicationName(publication.name)
registerRelocatedPublication(relocatedPublicationName, publication, moduleName)
logPublishing { "Registered relocated publication `$relocatedPublicationName` for module $moduleName, for project ${project.path}" }
// Add task dependencies
addTaskDependenciesForRelocatedPublication(moduleName, relocatedPublicationName)
val relocateDependencies = tasks.getByName(RelocationConfig.taskNameForRelocateDependencies(moduleName))
configurePatchKotlinModuleMetadataTask(relocatedPublicationName, relocateDependencies, publication.name)
}
private fun Project.registerRelocatedPublication(
relocatedPublicationName: String,
publication: MavenPublication,
moduleName: String
) {
// copy POM XML, since POM contains transitive dependencies
var patched = false
lateinit var oldXmlProvider: XmlProvider
publication.pom.withXml { oldXmlProvider = this }
publications.register(relocatedPublicationName, MavenPublication::class.java) {
this.artifactId = publication.artifactId
this.groupId = publication.groupId
this.version = publication.version
this.artifacts.addAll(publication.artifacts.filterNot { it.classifier == null && it.extension == "jar" })
project.tasks.findByName(RelocationConfig.taskNameForRelocateDependencies(moduleName))
?.let { relocateDependencies ->
this.artifact(relocateDependencies) {
this.classifier = null
this.extension = "jar"
}
}
pom.withXml {
val newXml = this
for (newChild in newXml.asNode().childrenNodes()) {
newXml.asNode().remove(newChild)
}
// Note: `withXml` is lazy, it is evaluated only when `generatePomFileFor...`
for (oldChild in oldXmlProvider.asNode().childrenNodes()) {
newXml.asNode().append(oldChild)
}
removeDependenciesInMavenPom(this)
patched = true
}
}
tasks.matching { it.name.startsWith("publish${relocatedPublicationName.titlecase()}PublicationTo") }.all {
dependsOn("generatePomFileFor${relocatedPublicationName.titlecase()}Publication")
}
tasks.matching { it.name == "generatePomFileFor${relocatedPublicationName.titlecase()}Publication" }.all {
dependsOn(tasks.getByName("generatePomFileFor${publication.name.titlecase()}Publication"))
doLast {
check(patched) { "POM is not patched" }
}
}
}
private fun Project.addTaskDependenciesForRelocatedPublication(moduleName: String, relocatedPublicationName: String) {
val originalTaskNamePrefix = "publish${moduleName.titlecase()}PublicationTo"
val relocatedTaskName = "publish${relocatedPublicationName.titlecase()}PublicationTo"
tasks.configureEach {
if (!name.startsWith(originalTaskNamePrefix)) return@configureEach
val originalTask = this
this.enabled = false
this.description = "${this.description} ([mirai] disabled in favor of $relocatedTaskName)"
val relocatedTasks = project.tasks.filter { it.name.startsWith(relocatedTaskName) }.toTypedArray()
check(relocatedTasks.isNotEmpty()) { "relocatedTasks is empty" }
relocatedTasks.forEach { publishRelocatedPublication ->
publishRelocatedPublication.dependsOn(*this.dependsOn.toTypedArray())
logger.info(
"[Publishing] $publishRelocatedPublication now dependsOn tasks: " +
this.dependsOn.joinToString()
)
}
project.tasks.filter { it.dependsOn.contains(originalTask) }
.forEach { it.dependsOn(*relocatedTasks) }
}
}
// Remove relocated dependencies in Maven pom
private fun Project.removeDependenciesInMavenPom(xmlProvider: XmlProvider) {
xmlProvider.run {
val node = asNode().getSingleChild("dependencies")
val dependencies = node.childrenNodes()
logger.info("[Shadow Relocation] deps: {}", dependencies)
logger.info(
"[Shadow Relocation] All filter notations: {}",
relocationFilters.flatMap { it.notations.notations() }.joinToString("\n")
)
dependencies.forEach { dep ->
val groupId = dep.getSingleChild("groupId").value().toString().removeSurrounding("[", "]")
val artifactId = dep.getSingleChild("artifactId").value().toString().removeSurrounding("[", "]")
logger.info("[Shadow Relocation] Checking $groupId:$artifactId")
if (
relocationFilters.any { filter ->
filter.matchesDependency(groupId = groupId, artifactId = artifactId)
}
) {
logger.info("[Shadow Relocation] Filtering out '$groupId:$artifactId' from pom for project '${project.path}'")
check(node.remove(dep)) { "Failed to remove dependency node" }
}
}
}
}
val publishPlatformArtifactsInRootModule: Project.(MavenPublication) -> Unit = { platformPublication ->
lateinit var platformPomBuilder: XmlProvider
platformPublication.pom.withXml { platformPomBuilder = this }

View File

@@ -1,292 +0,0 @@
/*
* Copyright 2019-2023 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
*/
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.execution.TaskExecutionGraph
import org.gradle.api.publish.tasks.GenerateModuleMetadata
import org.gradle.api.tasks.bundling.Jar
import org.gradle.kotlin.dsl.DependencyHandlerScope
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.get
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
/**
* @see RelocationNotes
*/
fun Project.configureMppShadow() {
val kotlin = kotlinMpp ?: return
configure(kotlin.targets.filter {
it.platformType == org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm
&& (it.attributes.getAttribute(MIRAI_PLATFORM_INTERMEDIATE) != true)
}) {
configureRelocationForMppTarget(project)
registerRegularShadowTask(this, mapTaskNameForMultipleTargets = true)
}
}
/**
* 配置 `publish` 和 `shadow` 相关依赖. 对于在本次构建的请求的任务及其直接或间接依赖, 以以下顺序执行:
*
* 1. 执行全部 `jar` 任务
* 2. 执行全部 `relocate` 任务
* 3. 执行全部 `publish` 任务
*
* 这是必要的因为 relocate 任务会覆盖 jar 任务的输出, 而在多模块并行编译时, Kotlin 编译器会依赖 jar 任务的输出. 如果在编译同时修改 JAR 文件, 就会导致 `ZipException`.
*
* 这也会让 publish 集中执行, Maven Central 不容易出问题.
*/
fun Project.configureShadowDependenciesForPublishing() {
check(this.rootProject === this) {
"configureShadowDependenciesForPublishing can only be used on root project."
}
val jarTaskNames = arrayOf("jvmJar", "jvmBaseJar")
gradle.projectsEvaluated {
// Tasks requested to run in this build
val allTasks = rootProject.allprojects.asSequence().flatMap { it.tasks }
val publishTasks = allTasks.filter { it.name.contains("publish", ignoreCase = true) }
val relocateTasks = allTasks.filter { it.name.contains("relocate", ignoreCase = true) }
val jarTasks = allTasks.filter {
it.name in jarTaskNames
}
val compileKotlinTasks = allTasks.filter { it.name.contains("compileKotlin", ignoreCase = true) }
val compileTestKotlinTasks = allTasks.filter { it.name.contains("compileTestKotlin", ignoreCase = true) }
relocateTasks.dependsOn(compileKotlinTasks.toList())
relocateTasks.dependsOn(compileTestKotlinTasks.toList())
relocateTasks.dependsOn(jarTasks.toList())
publishTasks.dependsOn(relocateTasks.toList())
}
}
val TaskExecutionGraph.hierarchicalTasks: Sequence<Task>
get() = sequence {
suspend fun SequenceScope<Task>.addTask(task: Task) {
yield(task)
for (dependency in getDependencies(task)) {
addTask(dependency)
}
}
for (task in allTasks) {
addTask(task)
}
}
/**
* Relocate some dependencies for `.jar`
* @see RelocationNotes
*/
private fun KotlinTarget.configureRelocationForMppTarget(project: Project) = project.run {
val configuration = project.configurations.findByName(SHADOW_RELOCATION_CONFIGURATION_NAME)
// e.g. relocateJvmDependencies
// do not change task name. see `configureShadowDependenciesForPublishing`
val relocateDependencies = tasks.create("relocate${targetName.titlecase()}Dependencies", ShadowJar::class) {
group = "mirai"
description = "Relocate dependencies to internal package"
destinationDirectory.set(buildDir.resolve("libs")) // build/libs
archiveBaseName.set("${project.name}-${targetName.lowercase()}") // e.g. "mirai-core-api-jvm"
dependsOn(compilations["main"].compileTaskProvider) // e.g. compileKotlinJvm
from(compilations["main"].output) // Add compilation result of mirai sourcecode, not including dependencies
configuration?.let {
from(it) // Include runtime dependencies
}
// Relocate packages
afterEvaluate {
val relocationFilters = project.relocationFilters
relocationFilters.forEach { relocation ->
relocation.packages.forEach { aPackage ->
relocate(aPackage, "$RELOCATION_ROOT_PACKAGE.$aPackage")
}
}
}
}
// We will modify Kotlin metadata, so do generate metadata before relocation
val generateMetadataTask =
tasks.getByName("generateMetadataFileFor${targetName.capitalize()}Publication") as GenerateModuleMetadata
generateMetadataTask.dependsOn(relocateDependencies)
val patchMetadataTask = tasks.create("patchMetadataFileFor${targetName.capitalize()}RelocatedPublication") {
dependsOn(generateMetadataTask)
dependsOn(relocateDependencies)
// remove dependencies in Kotlin module metadata
doLast {
// mirai-core-jvm-2.13.0.module
val file = generateMetadataTask.outputFile.asFile.get()
val metadata = Gson().fromJson(
file.readText(),
com.google.gson.JsonElement::class.java
).asJsonObject
val metadataVersion = metadata["formatVersion"]?.asString
check(metadataVersion == "1.1") {
"Unsupported Kotlin metadata version. version=$metadataVersion, file=${file.absolutePath}"
}
for (variant in metadata["variants"]!!.asJsonArray) {
val dependencies = variant.asJsonObject["dependencies"]!!.asJsonArray
dependencies.removeAll { dependency ->
val dep = dependency.asJsonObject
val groupId = dep["group"]!!.asString
val artifactId = dep["module"]!!.asString
relocationFilters.any { filter ->
filter.matchesDependency(
groupId = groupId,
artifactId = artifactId
)
}.also {
println("[Shadow Relocation] Filtering out $groupId:$artifactId from Kotlin module")
}
}
}
file.writeText(GsonBuilder().setPrettyPrinting().create().toJson(metadata))
}
}
// Set "publishKotlinMultiplatformPublicationTo*" and "publish${targetName.capitalize()}PublicationTo*" dependsOn patchMetadataTask
if (project.kotlinMpp != null) {
tasks.filter { it.name.startsWith("publishKotlinMultiplatformPublicationTo") }.let { publishTasks ->
if (publishTasks.isEmpty()) {
throw GradleException("[Shadow Relocation] Cannot find publishKotlinMultiplatformPublicationTo for project '${project.path}'.")
}
publishTasks.forEach { it.dependsOn(patchMetadataTask) }
}
tasks.filter { it.name.startsWith("publish${targetName.capitalize()}PublicationTo") }.let { publishTasks ->
if (publishTasks.isEmpty()) {
throw GradleException("[Shadow Relocation] Cannot find publish${targetName.capitalize()}PublicationTo for project '${project.path}'.")
}
publishTasks.forEach { it.dependsOn(patchMetadataTask) }
}
}
afterEvaluate {
// Remove relocated dependencies in Maven pom
mavenPublication {
pom.withXml {
val node = this.asNode().getSingleChild("dependencies")
val dependencies = node.childrenNodes()
logger.trace("[Shadow Relocation] deps: $dependencies")
dependencies.forEach { dep ->
val groupId = dep.getSingleChild("groupId").value().toString()
val artifactId = dep.getSingleChild("artifactId").value().toString()
logger.trace("[Shadow Relocation] Checking $groupId:$artifactId")
if (
relocationFilters.any { filter ->
filter.matchesDependency(groupId = groupId, artifactId = artifactId)
}
) {
logger.info("[Shadow Relocation] Filtering out '$groupId:$artifactId' from pom for project '${project.path}'")
check(node.remove(dep)) { "Failed to remove dependency node" }
}
}
}
}
}
}
private fun Sequence<Task>.dependsOn(
task: Task,
) {
return forEach { it.dependsOn(task) }
}
private fun Sequence<Task>.dependsOn(
tasks: Iterable<Task>,
) {
return forEach { it.dependsOn(tasks) }
}
/**
* 添加 `implementation` 和 `shadow`
*/
fun DependencyHandlerScope.shadowImplementation(dependencyNotation: Any) {
"implementation"(dependencyNotation)
"shadow"(dependencyNotation)
}
fun Project.registerRegularShadowTaskForJvmProject(
configurations: List<Configuration> = listOfNotNull(
project.configurations.findByName("runtimeClasspath"),
project.configurations.findByName("${kotlinJvm!!.target.name}RuntimeClasspath"),
project.configurations.findByName("runtime")
)
): ShadowJar {
return project.registerRegularShadowTask(kotlinJvm!!.target, mapTaskNameForMultipleTargets = false, configurations)
}
fun Project.registerRegularShadowTask(
target: KotlinTarget,
mapTaskNameForMultipleTargets: Boolean,
configurations: List<Configuration> = listOfNotNull(
project.configurations.findByName("runtimeClasspath"),
project.configurations.findByName("${target.targetName}RuntimeClasspath"),
project.configurations.findByName("runtime")
),
): ShadowJar {
return tasks.create(
if (mapTaskNameForMultipleTargets) "shadow${target.targetName.capitalize()}Jar" else "shadowJar",
ShadowJar::class
) {
group = "mirai"
archiveClassifier.set("all")
(tasks.findByName("jar") as? Jar)?.let {
manifest.inheritFrom(it.manifest)
}
val compilation = target.compilations["main"]
dependsOn(compilation.compileTaskProvider)
from(compilation.output)
// components.findByName("java")?.let { from(it) }
project.sourceSets.findByName("main")?.output?.let { from(it) } // for JVM projects
this.configurations = configurations
// Relocate packages
afterEvaluate {
val relocationFilters = project.relocationFilters
relocationFilters.forEach { relocation ->
relocation.packages.forEach { aPackage ->
relocate(aPackage, "$RELOCATION_ROOT_PACKAGE.$aPackage")
}
}
}
exclude { file ->
file.name.endsWith(".sf", ignoreCase = true)
}
exclude("META-INF/INDEX.LIST", "META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "module-info.class")
}
}
fun Project.configureRelocatedShadowJarForJvmProject(kotlin: KotlinJvmProjectExtension): ShadowJar {
return registerRegularShadowTask(kotlin.target, mapTaskNameForMultipleTargets = false)
}
const val RELOCATION_ROOT_PACKAGE = "net.mamoe.mirai.internal.deps"

View File

@@ -120,6 +120,13 @@ class RelocatedDependency(
* Kotlin packages. e.g. `io.ktor`
*/
vararg val packages: String,
/**
* Exclude them, so no transitive dependencies exposed to Maven and Kotlin JVM consumers
*/
val notationsToExcludeInPom: RelocatableDependency = MultiplatformDependency.jvm(
notation.substringBefore(":"),
notation.substringAfter(":").substringBeforeLast(":")
),
/**
* Additional exclusions apart from everything from `org.jetbrains.kotlin` and `org.jetbrains.kotlinx`.
*/
@@ -142,14 +149,49 @@ fun KotlinDependencyHandler.implementationKotlinxIo(module: String) {
}
}
class DependencyNotation(
val groupId: String,
val artifactId: String,
) {
fun toMap(): Map<String, String> {
return mapOf("group" to groupId, "module" to artifactId)
}
override fun toString(): String {
return "$groupId:$artifactId"
}
}
sealed interface RelocatableDependency {
fun notations(): Sequence<DependencyNotation>
}
class SinglePlatformDependency(
val groupId: String,
val artifactId: String
) : RelocatableDependency {
override fun notations(): Sequence<DependencyNotation> {
return sequenceOf(DependencyNotation(groupId, artifactId))
}
}
class CompositeDependency(
private val dependencies: List<RelocatableDependency>
) : RelocatableDependency {
constructor(vararg dependencies: RelocatableDependency) : this(dependencies.toList())
override fun notations(): Sequence<DependencyNotation> = dependencies.asSequence().flatMap { it.notations() }
}
class MultiplatformDependency private constructor(
private val groupId: String,
private val baseArtifactId: String,
vararg val targets: String,
) {
fun notations(): Sequence<Map<String, String>> {
return sequenceOf(mapOf("group" to groupId, "module" to baseArtifactId))
.plus(targets.asSequence().map { mapOf("group" to groupId, "module" to "$baseArtifactId.$it") })
) : RelocatableDependency {
override fun notations(): Sequence<DependencyNotation> {
return sequenceOf(DependencyNotation(groupId, baseArtifactId))
.plus(targets.asSequence().map { DependencyNotation(groupId, "$baseArtifactId-$it") })
}
companion object {
@@ -161,7 +203,7 @@ class MultiplatformDependency private constructor(
fun ModuleDependency.exclude(multiplatformDependency: MultiplatformDependency) {
multiplatformDependency.notations().forEach {
exclude(it)
exclude(it.toMap())
}
}
@@ -191,7 +233,10 @@ object ExcludeProperties {
}
val `ktor-io` = ktor("io", Versions.ktor)
val `ktor-io_relocated` = RelocatedDependency(`ktor-io`, "io.ktor.utils.io") {
val `ktor-io_relocated` = RelocatedDependency(
`ktor-io`, "io.ktor.utils.io",
notationsToExcludeInPom = MultiplatformDependency.jvm("io.ktor", "ktor-io")
) {
exclude(ExcludeProperties.`everything from slf4j`)
exclude(ExcludeProperties.`slf4j-api`)
}
@@ -202,7 +247,16 @@ val `ktor-serialization` = ktor("serialization", Versions.ktor)
val `ktor-websocket-serialization` = ktor("websocket-serialization", Versions.ktor)
val `ktor-client-core` = ktor("client-core", Versions.ktor)
val `ktor-client-core_relocated` = RelocatedDependency(`ktor-client-core`, "io.ktor") {
val `ktor-client-core_relocated` = RelocatedDependency(
`ktor-client-core`, "io.ktor",
notationsToExcludeInPom = CompositeDependency(
MultiplatformDependency.jvm("io.ktor", "ktor-io"),
MultiplatformDependency.jvm("io.ktor", "ktor-client-core"),
MultiplatformDependency.jvm("io.ktor", "ktor-client-okhttp"),
MultiplatformDependency.jvm("io.ktor", "ktor-http"),
MultiplatformDependency.jvm("io.ktor", "ktor-utils"),
)
) {
exclude(ExcludeProperties.`ktor-io`)
exclude(ExcludeProperties.`everything from slf4j`)
}
@@ -213,7 +267,21 @@ val `ktor-client-curl` = ktor("client-curl", Versions.ktor)
val `ktor-client-darwin` = ktor("client-darwin", Versions.ktor)
val `ktor-client-okhttp` = ktor("client-okhttp", Versions.ktor)
val `ktor-client-okhttp_relocated` =
RelocatedDependency(ktor("client-okhttp", Versions.ktor), "io.ktor", "okhttp", "okio") {
RelocatedDependency(
ktor("client-okhttp", Versions.ktor), "io.ktor", "okhttp", "okio",
notationsToExcludeInPom = CompositeDependency(
MultiplatformDependency.jvm("io.ktor", "ktor-io"),
MultiplatformDependency.jvm("io.ktor", "ktor-client-core"),
MultiplatformDependency.jvm("io.ktor", "ktor-client-okhttp"),
MultiplatformDependency.jvm("io.ktor", "ktor-http"),
MultiplatformDependency.jvm("io.ktor", "ktor-serialization"),
MultiplatformDependency.jvm("io.ktor", "ktor-utils"),
MultiplatformDependency.jvm("io.ktor", "ktor-websockets"),
MultiplatformDependency.jvm("io.ktor", "ktor-websockets-serialization"),
MultiplatformDependency.jvm("com.squareup.okhttp3", "okhttp3"),
MultiplatformDependency.jvm("com.squareup.okio", "okio"),
)
) {
exclude(ExcludeProperties.`ktor-io`)
exclude(ExcludeProperties.`everything from slf4j`)
}

View File

@@ -7,6 +7,11 @@
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package shadow
import ExcludeProperties
import RelocatableDependency
import RelocatedDependency
import org.gradle.api.Action
import org.gradle.api.DomainObjectCollection
import org.gradle.api.Project
@@ -66,7 +71,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
*
* 如果你都使用 [relocateImplementation], 就会导致在 Android 平台发生 'Duplicated Class' 问题. 如果你都使用 [relocateCompileOnly] 则会在 clinit 阶段遇到 [NoClassDefFoundError]
*
* ## relocation 发生的时机晚于编译
* ## relocation 发生的时机晚于编译 (Jar)
*
* mirai-core-utils relocate ktor-io, 然后 mirai-core `build.gradle.kts` 使用了 `implementation(project(":mirai-core-utils"))`.
* mirai-core 编译时, 编译器仍然会使用 relocate 之前的 `io.ktor`. 为了在 mirai-core 将对 `io.ktor` 的调用转为对 `net.mamoe.mirai.internal.deps.io.ktor` 的调用, 需要配置 relocation.
@@ -74,6 +79,13 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
*
* 所以你需要为所有依赖了 mirai-core-utils 的模块都分别配置 [relocateCompileOnly].
*
* ## relocation 仅在发布 (e.g. `publishToMavenLocal`) 时自动使用
*
* 其他任何时候, 比如在 mirai-console 编译时, mirai-console 依赖的是未 relocate JAR. 使用 `jar` 任务打包的也是未 relocate .
*
* 若需要 relocated JAR, 使用 `relocateJvmDependencies`. 其中 `Jvm` 可换为其他启动了 relocate Kotlin target .
* 可在 IDEA Gradle 视图中找到 mirai 文件夹, 查看可用的 task 列表.
*
* ### "在运行时包含" 是如何实现的?
*
* relocate 的类会被直接当做是当前模块的类打包进 JAR.
@@ -89,7 +101,7 @@ object RelocationNotes
/**
* 添加一个通常的 [compileOnly][KotlinDependencyHandler.compileOnly] 依赖, 并按 [relocatedDependency] 定义的配置 relocate.
*
* 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用**都不会** relocate [RELOCATION_ROOT_PACKAGE].
* 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用**都不会** relocate [RelocationConfig.RELOCATION_ROOT_PACKAGE].
* 运行时 (runtime) **不会**包含被 relocate 的依赖及其所有间接依赖.
*
* @see RelocationNotes
@@ -102,7 +114,9 @@ fun KotlinDependencyHandler.relocateCompileOnly(
}
project.relocationFilters.add(
RelocationFilter(
dependency.groupNotNull, dependency.name, relocatedDependency.packages.toList(), includeInRuntime = false,
relocatedDependency.notationsToExcludeInPom,
relocatedDependency.packages.toList(),
includeInRuntime = false,
)
)
// Don't add to runtime
@@ -112,7 +126,7 @@ fun KotlinDependencyHandler.relocateCompileOnly(
/**
* 添加一个通常的 [compileOnly][KotlinDependencyHandler.compileOnly] 依赖, 并按 [relocatedDependency] 定义的配置 relocate.
*
* 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用**都不会** relocate [RELOCATION_ROOT_PACKAGE].
* 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用**都不会** relocate [RelocationConfig.RELOCATION_ROOT_PACKAGE].
* 运行时 (runtime) **不会**包含被 relocate 的依赖及其所有间接依赖.
*
* @see RelocationNotes
@@ -127,7 +141,9 @@ fun DependencyHandler.relocateCompileOnly(
})
project.relocationFilters.add(
RelocationFilter(
dependency.groupNotNull, dependency.name, relocatedDependency.packages.toList(), includeInRuntime = false,
relocatedDependency.notationsToExcludeInPom,
relocatedDependency.packages.toList(),
includeInRuntime = false,
)
)
// Don't add to runtime
@@ -137,7 +153,7 @@ fun DependencyHandler.relocateCompileOnly(
/**
* 添加一个通常的 [implementation][KotlinDependencyHandler.implementation] 依赖, 并按 [relocatedDependency] 定义的配置 relocate.
*
* 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用**都会** relocate [RELOCATION_ROOT_PACKAGE].
* 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用**都会** relocate [RelocationConfig.RELOCATION_ROOT_PACKAGE].
* 运行时 (runtime) ****包含被 relocate 的依赖及其所有间接依赖.
*
* @see RelocationNotes
@@ -151,13 +167,14 @@ fun KotlinDependencyHandler.relocateImplementation(
}
project.relocationFilters.add(
RelocationFilter(
dependency.groupNotNull, dependency.name, relocatedDependency.packages.toList(), includeInRuntime = true,
relocatedDependency.notationsToExcludeInPom, relocatedDependency.packages.toList(), includeInRuntime = true,
)
)
project.configurations.maybeCreate(SHADOW_RELOCATION_CONFIGURATION_NAME)
val configurationName = RelocationConfig.SHADOW_RELOCATION_CONFIGURATION_NAME
project.configurations.maybeCreate(configurationName)
addDependencyTo(
project.dependencies,
SHADOW_RELOCATION_CONFIGURATION_NAME,
configurationName,
relocatedDependency.notation,
Action<ExternalModuleDependency> {
relocatedDependency.exclusionAction(this)
@@ -171,7 +188,7 @@ fun KotlinDependencyHandler.relocateImplementation(
/**
* 添加一个通常的 [implementation][KotlinDependencyHandler.implementation] 依赖, 并按 [relocatedDependency] 定义的配置 relocate.
*
* 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用都会被 relocate [RELOCATION_ROOT_PACKAGE].
* 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用都会被 relocate [RelocationConfig.RELOCATION_ROOT_PACKAGE].
* 运行时 (runtime) 将会包含被 relocate 的依赖及其所有间接依赖.
*
* @see RelocationNotes
@@ -187,13 +204,14 @@ fun DependencyHandler.relocateImplementation(
})
project.relocationFilters.add(
RelocationFilter(
dependency.groupNotNull, dependency.name, relocatedDependency.packages.toList(), includeInRuntime = true,
relocatedDependency.notationsToExcludeInPom, relocatedDependency.packages.toList(), includeInRuntime = true,
)
)
project.configurations.maybeCreate(SHADOW_RELOCATION_CONFIGURATION_NAME)
val configurationName = RelocationConfig.SHADOW_RELOCATION_CONFIGURATION_NAME
project.configurations.maybeCreate(configurationName)
addDependencyTo(
project.dependencies,
SHADOW_RELOCATION_CONFIGURATION_NAME,
configurationName,
relocatedDependency.notation,
Action<ExternalModuleDependency> {
relocatedDependency.exclusionAction(this)
@@ -204,8 +222,7 @@ fun DependencyHandler.relocateImplementation(
return dependency
}
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION") // compiler bug
private val ExternalModuleDependency.groupNotNull get() = group!!
private val ExternalModuleDependency.groupNotNull: String get() = group.toString()
private fun ExternalModuleDependency.intrinsicExclusions() {
exclude(ExcludeProperties.`everything from kotlin`)
@@ -213,14 +230,10 @@ private fun ExternalModuleDependency.intrinsicExclusions() {
}
const val SHADOW_RELOCATION_CONFIGURATION_NAME = "shadowRelocation"
data class RelocationFilter(
val groupId: String,
val artifactId: String? = null,
val packages: List<String> = listOf(groupId),
val filesFilter: String = groupId.replace(".", "/"),
val notations: RelocatableDependency,
val packages: List<String>,
// val filesFilter: String = groupId.replace(".", "/"),
/**
* Pack relocated dependency into the fat jar. If set to `false`, dependencies will be removed.
* This is to avoid duplicated classes. See #2291.
@@ -229,10 +242,9 @@ data class RelocationFilter(
) {
fun matchesDependency(groupId: String?, artifactId: String?): Boolean {
if (this.groupId == groupId) return true
if (this.artifactId != null && this.artifactId == artifactId) return true
return false
return notations.notations().any {
it.groupId == groupId && it.artifactId == artifactId
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2019-2023 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 shadow
import titlecase
object RelocationConfig {
const val RELOCATION_ROOT_PACKAGE = "net.mamoe.mirai.internal.deps"
const val SHADOW_RELOCATION_CONFIGURATION_NAME = "shadowRelocation"
fun taskNameForRelocateDependencies(
targetName: String
) = "relocate${targetName.titlecase()}Dependencies"
fun relocatedPublicationName(originalPublicationName: String): String = originalPublicationName + "Relocated"
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2019-2023 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 shadow
import MIRAI_PLATFORM_INTERMEDIATE
import capitalize
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import kotlinJvm
import kotlinMpp
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.tasks.bundling.Jar
import org.gradle.kotlin.dsl.DependencyHandlerScope
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.get
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import sourceSets
/**
* @see RelocationNotes
*/
fun Project.configureMppShadow() {
val kotlin = kotlinMpp ?: return
configure(kotlin.targets.filter {
it.platformType == KotlinPlatformType.jvm
&& (it.attributes.getAttribute(MIRAI_PLATFORM_INTERMEDIATE) != true)
}) {
configureRelocationForMppTarget(project)
registerRegularShadowTask(this, mapTaskNameForMultipleTargets = true)
}
}
/**
* Relocate some dependencies for `.jar`
* @see RelocationNotes
*/
private fun KotlinTarget.configureRelocationForMppTarget(project: Project) = project.run {
val configuration = project.configurations.findByName(RelocationConfig.SHADOW_RELOCATION_CONFIGURATION_NAME)
// e.g. relocateJvmDependencies
// do not change task name. see `configureShadowDependenciesForPublishing`
val relocateDependenciesName = RelocationConfig.taskNameForRelocateDependencies(targetName)
tasks.create(relocateDependenciesName, ShadowJar::class) {
group = "mirai"
description = "Relocate dependencies to internal package"
destinationDirectory.set(buildDir.resolve("libs")) // build/libs
archiveBaseName.set("${project.name}-${targetName.lowercase()}-relocated") // e.g. "mirai-core-api-jvm"
dependsOn(compilations["main"].compileTaskProvider) // e.g. compileKotlinJvm
from(compilations["main"].output) // Add the compilation result of mirai sourcecode, not including dependencies
configuration?.let {
from(it) // Include runtime dependencies
}
// Relocate packages
afterEvaluate {
val relocationFilters = project.relocationFilters
relocationFilters.forEach { relocation ->
relocation.packages.forEach { aPackage ->
relocate(aPackage, "${RelocationConfig.RELOCATION_ROOT_PACKAGE}.$aPackage")
}
}
}
}
}
/**
* 添加 `implementation` 和 `shadow`
*/
fun DependencyHandlerScope.shadowImplementation(dependencyNotation: Any) {
"implementation"(dependencyNotation)
"shadow"(dependencyNotation)
}
fun Project.registerRegularShadowTaskForJvmProject(
configurations: List<Configuration> = listOfNotNull(
project.configurations.findByName("runtimeClasspath"),
project.configurations.findByName("${kotlinJvm!!.target.name}RuntimeClasspath"),
project.configurations.findByName("runtime")
)
): ShadowJar {
return project.registerRegularShadowTask(kotlinJvm!!.target, mapTaskNameForMultipleTargets = false, configurations)
}
fun Project.registerRegularShadowTask(
target: KotlinTarget,
mapTaskNameForMultipleTargets: Boolean,
configurations: List<Configuration> = listOfNotNull(
project.configurations.findByName("runtimeClasspath"),
project.configurations.findByName("${target.targetName}RuntimeClasspath"),
project.configurations.findByName("runtime")
),
): ShadowJar {
return tasks.create(
if (mapTaskNameForMultipleTargets) "shadow${target.targetName.capitalize()}Jar" else "shadowJar",
ShadowJar::class
) {
group = "mirai"
archiveClassifier.set("all")
(tasks.findByName("jar") as? Jar)?.let {
manifest.inheritFrom(it.manifest)
}
val compilation = target.compilations["main"]
dependsOn(compilation.compileTaskProvider)
from(compilation.output)
// components.findByName("java")?.let { from(it) }
project.sourceSets.findByName("main")?.output?.let { from(it) } // for JVM projects
this.configurations = configurations
// Relocate packages
afterEvaluate {
val relocationFilters = project.relocationFilters
relocationFilters.forEach { relocation ->
relocation.packages.forEach { aPackage ->
relocate(aPackage, "${RelocationConfig.RELOCATION_ROOT_PACKAGE}.$aPackage")
}
}
}
exclude { file ->
file.name.endsWith(".sf", ignoreCase = true)
}
exclude("META-INF/INDEX.LIST", "META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "module-info.class")
}
}
fun Project.configureRelocatedShadowJarForJvmProject(kotlin: KotlinJvmProjectExtension): ShadowJar {
return registerRegularShadowTask(kotlin.target, mapTaskNameForMultipleTargets = false)
}

View File

@@ -7,6 +7,9 @@
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
import shadow.registerRegularShadowTaskForJvmProject
import shadow.shadowImplementation
plugins {
kotlin("jvm")
kotlin("plugin.serialization")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
* Copyright 2019-2023 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.
@@ -9,6 +9,9 @@
@file:Suppress("UnusedImport")
import shadow.configureRelocatedShadowJarForJvmProject
import shadow.relocateImplementation
plugins {
kotlin("jvm")
kotlin("plugin.serialization")
@@ -34,7 +37,7 @@ dependencies {
val shadow = configureRelocatedShadowJarForJvmProject(kotlin)
if (System.getenv("MIRAI_IS_SNAPSHOTS_PUBLISHING")?.toBoolean() != true) {
// Do not publish -all jars to snapshot server since they are too large.
// Do not publish `-all` jars to snapshot server since they are too large.
configurePublishing("mirai-core-all", addShadowJar = false)

View File

@@ -9,6 +9,7 @@
@file:Suppress("UNUSED_VARIABLE")
import BinaryCompatibilityConfigurator.configureBinaryValidators
import shadow.relocateCompileOnly
plugins {
id("com.android.library")

View File

@@ -9,6 +9,8 @@
@file:Suppress("UNUSED_VARIABLE")
import shadow.relocateImplementation
plugins {
id("com.android.library")
kotlin("multiplatform")
@@ -98,7 +100,7 @@ if (tasks.findByName("androidMainClasses") != null) {
tasks.getByName("androidBaseTest").dependsOn("checkAndroidApiLevel")
}
//configureMppPublishing()
configureMppPublishing()
//mavenCentralPublish {
// artifactId = "mirai-core-utils"

View File

@@ -12,6 +12,8 @@
import BinaryCompatibilityConfigurator.configureBinaryValidators
import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractNativeLibrary
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import shadow.relocateCompileOnly
import shadow.relocateImplementation
plugins {
id("com.android.library")
@@ -49,6 +51,9 @@ kotlin {
implementation(`kotlinx-serialization-protobuf`)
implementation(`kotlinx-atomicfu`)
// runtime from mirai-core-utils
relocateCompileOnly(`ktor-io_relocated`)
// relocateImplementation(`ktor-http_relocated`)
// relocateImplementation(`ktor-serialization_relocated`)
// relocateImplementation(`ktor-websocket-serialization_relocated`)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
* Copyright 2019-2023 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.
@@ -48,16 +48,10 @@ val publishMiraiArtifactsToMavenLocal by tasks.registering {
// Always print this very important message
logger.warn("[publishMiraiArtifactsToMavenLocal] Project version is '${project.version}'.")
}
}
doLast {
// delete shadowed Jars, since Kotlin can't compile modules that depend on them.
rootProject.subprojects
.asSequence()
.flatMap { proj -> proj.tasks.filter { task -> task.name.contains("relocate") } }
.flatMap { it.outputs.files }
.filter { it.isFile && it.name.endsWith(".jar") }
.forEach { it.delete() }
}
tasks.getByName("test") {
mustRunAfter(publishMiraiArtifactsToMavenLocal)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
* Copyright 2019-2023 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.
@@ -33,6 +33,9 @@ abstract class AbstractTest {
@JvmStatic
fun isMiraiLocalAvailable(): Boolean {
val commandLine =
"""./gradlew publishMiraiArtifactsToMavenLocal -Dmirai.build.project.version=$miraiLocalVersion"""
return if (mavenLocalDir.resolve("net/mamoe/mirai-core/$miraiLocalVersion").exists()) {
println(
"""
@@ -41,7 +44,8 @@ abstract class AbstractTest {
- added/removed a dependency for mirai-core series modules
- changed version of any of the dependencies for mirai-core series modules
You can update by running `./gradlew publishMiraiLocalArtifacts`.
You can update by running the following command:
$commandLine
""".trimIndent()
)
true
@@ -58,7 +62,9 @@ abstract class AbstractTest {
Note that you can ignore this test if you did not change project (dependency) structure.
And you don't need to worry if you does not run this test — this test is always executed on the CI when you make a PR.
You can run `./gradlew publishMiraiLocalArtifacts` to publish local artifacts.
You can run the following command to publish local artifacts:
$commandLine
Then you can run this test again. (By your original way or ./gradlew :mirai-deps-test:test)
""".trimIndent()
System.err.println(

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
* Copyright 2019-2023 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.
@@ -117,8 +117,8 @@ class CoreShadowRelocationTest : AbstractTest() {
"""
dependencies {
implementation("net.mamoe:mirai-core:$miraiLocalVersion") {
exclude("net.mamoe", "mirai-core-api")
exclude("net.mamoe", "mirai-core-utils")
exclude("net.mamoe", "mirai-core-api-jvm")
exclude("net.mamoe", "mirai-core-utils-jvm")
}
}
""".trimIndent()
@@ -130,6 +130,7 @@ class CoreShadowRelocationTest : AbstractTest() {
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
fun `test mirai-core-api without transitive mirai-core-utils`() {
val fragment = buildTestCases {
-`mirai-core-utils`
-both(`ktor-io`)
-both(`ktor-client-core`)
-both(`ktor-client-okhttp`)
@@ -143,7 +144,7 @@ class CoreShadowRelocationTest : AbstractTest() {
"""
dependencies {
implementation("net.mamoe:mirai-core-api:$miraiLocalVersion") {
exclude("net.mamoe", "mirai-core-utils")
exclude("net.mamoe", "mirai-core-utils-jvm")
}
}
""".trimIndent()
@@ -233,6 +234,7 @@ class CoreShadowRelocationTest : AbstractTest() {
val relocated: (FunctionTestCase.() -> FunctionTestCase)? = null,
)
val `mirai-core-utils` = ClassTestCase("mirai-core-utils Symbol", "net.mamoe.mirai.utils.Symbol")
val `ktor-io` = ClassTestCase("ktor-io ByteBufferChannel", ByteBufferChannel)
val `ktor-client-core` = ClassTestCase("ktor-client-core HttpClient", HttpClient)
val `ktor-client-okhttp` = ClassTestCase("ktor-client-core OkHttp", KtorOkHttp)
@@ -302,7 +304,7 @@ class CoreShadowRelocationTest : AbstractTest() {
result.append(
"""
@Test
fun `no relocated ${name}`() {
fun `no ${name}`() {
assertThrows<ClassNotFoundException> { Class.forName("$qualifiedClassName") }
}
""".trimIndent()