| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- /*
- * 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 org.gradle.api.Action
- import org.gradle.api.DomainObjectCollection
- import org.gradle.api.Project
- import org.gradle.api.artifacts.Configuration
- import org.gradle.api.artifacts.Dependency
- import org.gradle.api.artifacts.ExternalModuleDependency
- import org.gradle.api.artifacts.dsl.DependencyHandler
- import org.gradle.kotlin.dsl.accessors.runtime.addDependencyTo
- import org.gradle.kotlin.dsl.extra
- import org.gradle.kotlin.dsl.invoke
- import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
- /**
- * # 非常重要的提示 — 有关 relocation — 在看完全部本文前, 不要进行任何操作
- *
- * Mirai 会 relocate 一些内部使用的依赖库, 来允许调用方 (依赖 mirai 的模块).
- * 例如, mirai 使用 2.0, 但调用方可以使用 Ktor 1.0 而不会有 classpath 冲突.
- *
- * 这是通过 relocation 完成的. 在 mirai 项目构建中, Relocation 是指将一个模块的一些 class 的包名替换为另一个包名的过程.
- * 继续使用 Ktor 示例, `io.ktor.utils.io.core.ByteReadPacket` 将会被 relocate 为
- * `net.mamoe.mirai.internal.deps.io.ktor.utils.io.core.ByteReadPacket`, 即放到内部包内.
- *
- * ## 哪些模块被 relocate 了?
- *
- * 在 2.13.0 mirai 只 relocate `io.ktor`, `okhttp3`, `okio` 下的所有类.
- *
- * ## 如何配置 relocation?
- *
- * Relocation 的范围是通过 [Configuration] 指定的.
- *
- * 在通常的 `dependencies` 配置中, 使用 [relocateCompileOnly] 和 [relocateImplementation]
- * 可分别创建 `compileOnly` 或 `implementation` 的依赖, 并为其配置 relocation.
- *
- * ## 不能 relocate 参与 mirai 公开 API/ABI 的库
- *
- * 有些库的部分定义参与组成 mirai 的公开 API/ABI. 例如 kotlin-stdlib 提供 `String` 等类型, kotlinx-coroutines 提供 `CoroutineScope`等.
- * mirai 已经发布了使用这些类型的 API, 为了保证 ABI 兼容, 不能 relocate 它们 (你就需要在 [Configuration] `exclude`).
- * 要知道哪些 API 参与了 ABI, 执行 `./gradlew :mirai-core:dependencies` (把 `mirai-core` 换成你想了解的模块), 查看 `runtimeDependencies` 之类的.
- *
- * ## 考虑是否在运行时包含你的依赖 — 选择 [relocateCompileOnly] 和 [relocateImplementation]
- *
- * 根据 [Configuration] 配置不同, 被 relocate 的模块的间接依赖可能会或不会被处理.
- * 例如
- *
- * 所以 `io.ktor:ktor-client-okhttp` 依赖的 okhttp 不会被 relocate!
- * 因此你必须手动检查目标依赖的全部间接依赖并添加 relocation 配置. 不要轻易升级经过了 relocation 的依赖, 因为有可能他们的新版本会使用新的依赖!
- * 这个过程无法自动化, 因为你 relocate 的模块可能会依赖 kotlin-stlib 等你不会想要 relocation 的依赖. 为什么你不会想 relocate kotlin-stlib? 继续阅读
- *
- * relocate 依赖之后, 你的程序在运行时必须要有 relocate 之后的类, 比如 `net.mamoe.mirai.internal.deps.io.ktor.utils.io.core.ByteReadPacket`.
- *
- * [relocateCompileOnly] 会为你添加通常的 `compileOnly`, 然后配置 relocate, 但不会在打包 JAR 时包含被 relocate 的库. 而 [relocateImplementation] 则会包含.
- *
- * 在独立模块下很简单, 你只要一直使用 [relocateImplementation] 就行了.
- *
- * 但在有依赖关系的模块, 比如 mirai-core-api 依赖 mirai-core-utils, 而它们都需要使用 ktor-io, 就需要让_最早_依赖 ktor-io 的 mirai-core-utils 模块在运行时包含 ([relocateImplementation]), 而 mirai-core-api 不包含 ([relocateCompileOnly]).
- * 运行时 mirai-core-api 就会使用来自 mirai-core-utils 的 `net.mamoe.mirai.internal.deps.io.ktor.utils.io.core.ByteReadPacket`.
- *
- * 如果你都使用 [relocateImplementation], 就会导致在 Android 平台发生 'Duplicated Class' 问题. 如果你都使用 [relocateCompileOnly] 则会在 clinit 阶段遇到 [NoClassDefFoundError]
- *
- * ## relocation 发生的时机晚于编译
- *
- * 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.
- * 所以此时就不能让 mirai-core 也打包 relocation 后的 ktor 而在运行时携带, 否则因为用户依赖 mirai-core 时 Maven 会同时下载 mirai-core-utils, 用户 classpath 就会有两份被 relocate 的 Ktor, 导致冲突.
- *
- * 所以你需要为所有依赖了 mirai-core-utils 的模块都分别配置 [relocateCompileOnly].
- *
- * ### "在运行时包含" 是如何实现的?
- *
- * 被 relocate 的类会被直接当做是当前模块的类打包进 JAR.
- * 比如 ktor-io 的所有代码就会被变换包名后全部打包进 "mirai-core-utils.jar",
- * 而不是在 maven POM 中定义而以后从 Maven Central 等远程仓库下载.
- *
- * ----
- *
- * 如果你已经懂了以上内容, 你就可以修改 relocation 相关了. 修改之后一定在 mirai-deps-test 模块中添加测试.
- */
- object RelocationNotes
- /**
- * 添加一个通常的 [compileOnly][KotlinDependencyHandler.compileOnly] 依赖, 并按 [relocatedDependency] 定义的配置 relocate.
- *
- * 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用**都不会**被 relocate 到 [RELOCATION_ROOT_PACKAGE].
- * 运行时 (runtime) **不会**包含被 relocate 的依赖及其所有间接依赖.
- *
- * @see RelocationNotes
- */
- fun KotlinDependencyHandler.relocateCompileOnly(
- relocatedDependency: RelocatedDependency,
- ): ExternalModuleDependency {
- val dependency = compileOnly(relocatedDependency.notation) {
- }
- project.relocationFilters.add(
- RelocationFilter(
- dependency.groupNotNull, dependency.name, relocatedDependency.packages.toList(), includeInRuntime = false,
- )
- )
- // Don't add to runtime
- return dependency
- }
- /**
- * 添加一个通常的 [compileOnly][KotlinDependencyHandler.compileOnly] 依赖, 并按 [relocatedDependency] 定义的配置 relocate.
- *
- * 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用**都不会**被 relocate 到 [RELOCATION_ROOT_PACKAGE].
- * 运行时 (runtime) **不会**包含被 relocate 的依赖及其所有间接依赖.
- *
- * @see RelocationNotes
- */
- fun DependencyHandler.relocateCompileOnly(
- project: Project,
- relocatedDependency: RelocatedDependency,
- ): Dependency {
- val dependency =
- addDependencyTo(this, "compileOnly", relocatedDependency.notation, Action<ExternalModuleDependency> {
- })
- project.relocationFilters.add(
- RelocationFilter(
- dependency.groupNotNull, dependency.name, relocatedDependency.packages.toList(), includeInRuntime = false,
- )
- )
- // Don't add to runtime
- return dependency
- }
- /**
- * 添加一个通常的 [implementation][KotlinDependencyHandler.implementation] 依赖, 并按 [relocatedDependency] 定义的配置 relocate.
- *
- * 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用**都会**被 relocate 到 [RELOCATION_ROOT_PACKAGE].
- * 运行时 (runtime) 将**会**包含被 relocate 的依赖及其所有间接依赖.
- *
- * @see RelocationNotes
- */
- fun KotlinDependencyHandler.relocateImplementation(
- relocatedDependency: RelocatedDependency,
- action: ExternalModuleDependency.() -> Unit = {}
- ): ExternalModuleDependency {
- val dependency = implementation(relocatedDependency.notation) {
- }
- project.relocationFilters.add(
- RelocationFilter(
- dependency.groupNotNull, dependency.name, relocatedDependency.packages.toList(), includeInRuntime = true,
- )
- )
- project.configurations.maybeCreate(SHADOW_RELOCATION_CONFIGURATION_NAME)
- addDependencyTo(
- project.dependencies,
- SHADOW_RELOCATION_CONFIGURATION_NAME,
- relocatedDependency.notation,
- Action<ExternalModuleDependency> {
- relocatedDependency.exclusionAction(this)
- intrinsicExclusions()
- action()
- }
- )
- return dependency
- }
- /**
- * 添加一个通常的 [implementation][KotlinDependencyHandler.implementation] 依赖, 并按 [relocatedDependency] 定义的配置 relocate.
- *
- * 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用都会被 relocate 到 [RELOCATION_ROOT_PACKAGE].
- * 运行时 (runtime) 将会包含被 relocate 的依赖及其所有间接依赖.
- *
- * @see RelocationNotes
- */
- fun DependencyHandler.relocateImplementation(
- project: Project,
- relocatedDependency: RelocatedDependency,
- action: Action<ExternalModuleDependency> = Action {}
- ): ExternalModuleDependency {
- val dependency =
- addDependencyTo(this, "implementation", relocatedDependency.notation, Action<ExternalModuleDependency> {
- })
- project.relocationFilters.add(
- RelocationFilter(
- dependency.groupNotNull, dependency.name, relocatedDependency.packages.toList(), includeInRuntime = true,
- )
- )
- project.configurations.maybeCreate(SHADOW_RELOCATION_CONFIGURATION_NAME)
- addDependencyTo(
- project.dependencies,
- SHADOW_RELOCATION_CONFIGURATION_NAME,
- relocatedDependency.notation,
- Action<ExternalModuleDependency> {
- relocatedDependency.exclusionAction(this)
- intrinsicExclusions()
- action(this)
- }
- )
- return dependency
- }
- @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") // compiler bug
- private val ExternalModuleDependency.groupNotNull get() = group!!
- private fun ExternalModuleDependency.intrinsicExclusions() {
- exclude(ExcludeProperties.`everything from kotlin`)
- exclude(ExcludeProperties.`everything from kotlinx`)
- }
- 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(".", "/"),
- /**
- * Pack relocated dependency into the fat jar. If set to `false`, dependencies will be removed.
- * This is to avoid duplicated classes. See #2291.
- */ // #2291
- val includeInRuntime: Boolean,
- ) {
- fun matchesDependency(groupId: String?, artifactId: String?): Boolean {
- if (this.groupId == groupId) return true
- if (this.artifactId != null && this.artifactId == artifactId) return true
- return false
- }
- }
- val Project.relocationFilters: DomainObjectCollection<RelocationFilter>
- get() {
- if (project.extra.has("relocationFilters")) {
- @Suppress("UNCHECKED_CAST")
- return project.extra.get("relocationFilters") as DomainObjectCollection<RelocationFilter>
- } else {
- val container = project.objects.domainObjectSet(RelocationFilter::class.java)
- project.extra.set("relocationFilters", container)
- return container
- }
- }
|