[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()