mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-14 20:50:09 +08:00
[build] Rewrite shadow relocation
This commit is contained in:
parent
ca840f88be
commit
c0ccdbe9d3
@ -9,7 +9,6 @@
|
||||
|
||||
@file:Suppress("UnstableApiUsage", "UNUSED_VARIABLE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import org.jetbrains.dokka.base.DokkaBase
|
||||
import org.jetbrains.dokka.base.DokkaBaseConfiguration
|
||||
import java.time.LocalDateTime
|
||||
@ -86,13 +85,6 @@ allprojects {
|
||||
}
|
||||
configureJarManifest()
|
||||
substituteDependenciesUsingExpectedVersion()
|
||||
|
||||
if (System.getenv("MIRAI_IS_SNAPSHOTS_PUBLISHING") != null) {
|
||||
project.tasks.filterIsInstance<ShadowJar>().forEach { shadow ->
|
||||
shadow.enabled = false // they are too big
|
||||
}
|
||||
logger.info("Disabled all shadow tasks.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ kotlin {
|
||||
sourceSets.all {
|
||||
languageSettings.optIn("kotlin.Experimental")
|
||||
languageSettings.optIn("kotlin.RequiresOptIn")
|
||||
languageSettings.optIn("kotlin.ExperimentalStdlibApi")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import org.gradle.kotlin.dsl.get
|
||||
import org.gradle.kotlin.dsl.getting
|
||||
import org.gradle.kotlin.dsl.withType
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.kpm.external.project
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
|
||||
@ -29,6 +30,13 @@ val MIRAI_PLATFORM_ATTRIBUTE = Attribute.of(
|
||||
"net.mamoe.mirai.platform", String::class.java
|
||||
)
|
||||
|
||||
/**
|
||||
* Flags a target as an HMPP intermediate target
|
||||
*/
|
||||
val MIRAI_PLATFORM_INTERMEDIATE = Attribute.of(
|
||||
"net.mamoe.mirai.platform.intermediate", Boolean::class.javaObjectType
|
||||
)
|
||||
|
||||
val IDEA_ACTIVE = System.getProperty("idea.active") == "true" && System.getProperty("publication.test") != "true"
|
||||
|
||||
val OS_NAME = System.getProperty("os.name").toLowerCase()
|
||||
@ -150,6 +158,7 @@ fun Project.configureJvmTargetsHierarchical() {
|
||||
}
|
||||
attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.common) // magic
|
||||
attributes.attribute(MIRAI_PLATFORM_ATTRIBUTE, "jvmBase") // avoid resolution
|
||||
attributes.attribute(MIRAI_PLATFORM_INTERMEDIATE, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,12 +76,13 @@ inline fun Project.configurePublishing(
|
||||
addProjectComponents: Boolean = true,
|
||||
setupGpg: Boolean = true,
|
||||
skipPublicationSetup: Boolean = false,
|
||||
addShadowJar: Boolean = true
|
||||
) {
|
||||
configureRemoteRepos()
|
||||
|
||||
if (skipPublicationSetup) return
|
||||
|
||||
val shadowJar = if (!addProjectComponents) null else tasks.register<ShadowJar>("shadowJar") {
|
||||
val shadowJar = if (!addProjectComponents || !addShadowJar) null else tasks.register<ShadowJar>("shadowJar") {
|
||||
archiveClassifier.set("all")
|
||||
manifest.inheritFrom(tasks.getByName<Jar>("jar").manifest)
|
||||
from(project.sourceSets["main"].output)
|
||||
|
@ -23,6 +23,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
|
||||
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
|
||||
|
||||
@ -41,6 +42,20 @@ private fun Project.jvmVersion(): JavaVersion {
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.optInForAllTargets(qualifiedClassname: String) {
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinCompile::class) {
|
||||
kotlinOptions.freeCompilerArgs += "-Xopt-in=$qualifiedClassname"
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.enableLanguageFeatureForAllSourceSets(qualifiedClassname: String) {
|
||||
kotlinSourceSets!!.all {
|
||||
languageSettings {
|
||||
this.enableLanguageFeature(qualifiedClassname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.preConfigureJvmTarget() {
|
||||
val defaultVer = jvmVersion()
|
||||
|
||||
|
@ -7,13 +7,16 @@
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.DomainObjectCollection
|
||||
import org.gradle.api.NamedDomainObjectContainer
|
||||
import org.gradle.api.Project
|
||||
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
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@ -34,7 +37,7 @@ import java.io.File
|
||||
*
|
||||
* Relocation 是模块范围的. 可以为 group id `io.ktor` 下的所有模块执行 relocation, 也可以仅为某一个精确的模块比如 `io.ktor:ktor-client-core` 执行.
|
||||
*
|
||||
* 要增加一条 relocation 规则, 使用 [relocateAllFromGroupId] 或者 [relocateExactArtifact]. 不要现在就过去用, 你必须先读完本文全部.
|
||||
* 要增加一条 relocation 规则, 使用 [relocateAllFromGroupId] 或者 [addRelocationRuntime]. 不要现在就过去用, 你必须先读完本文全部.
|
||||
*
|
||||
* ## 间接依赖不会被处理
|
||||
*
|
||||
@ -81,61 +84,6 @@ import java.io.File
|
||||
*/
|
||||
object RelocationNotes
|
||||
|
||||
/**
|
||||
* 配置 Ktor 依赖.
|
||||
* @see RelocationNotes
|
||||
* @see relocateKtorForCore
|
||||
*/
|
||||
fun NamedDomainObjectContainer<KotlinSourceSet>.configureMultiplatformKtorDependencies(addDep: KotlinDependencyHandler.(Any) -> Dependency?) {
|
||||
getByName("commonMain").apply {
|
||||
dependencies {
|
||||
addDep(`ktor-io`)
|
||||
addDep(`ktor-client-core`)
|
||||
}
|
||||
}
|
||||
|
||||
findByName("jvmBaseMain")?.apply {
|
||||
dependencies {
|
||||
addDep(`ktor-client-okhttp`)
|
||||
}
|
||||
}
|
||||
|
||||
configure(WIN_TARGETS.map { getByName(it + "Main") }) {
|
||||
dependencies {
|
||||
addDep(`ktor-client-curl`)
|
||||
}
|
||||
}
|
||||
|
||||
configure(LINUX_TARGETS.map { getByName(it + "Main") }) {
|
||||
dependencies {
|
||||
addDep(`ktor-client-cio`)
|
||||
}
|
||||
}
|
||||
|
||||
findByName("darwinMain")?.apply {
|
||||
dependencies {
|
||||
addDep(`ktor-client-darwin`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> configure(list: Iterable<T>, function: T.() -> Unit) {
|
||||
list.forEach(function)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 使用之前阅读 [RelocationNotes]
|
||||
*/
|
||||
fun Project.relocateKtorForCore(includeInRuntime: Boolean) {
|
||||
// WARNING: You must also consider relocating transitive dependencies.
|
||||
// Otherwise, user will get NoClassDefFound error when using mirai as a classpath dependency. See #2263.
|
||||
|
||||
relocateAllFromGroupId("io.ktor", includeInRuntime)
|
||||
relocateAllFromGroupId("com.squareup.okhttp3", includeInRuntime)
|
||||
relocateAllFromGroupId("com.squareup.okio", includeInRuntime)
|
||||
}
|
||||
|
||||
/**
|
||||
* relocate 一个 [groupId] 下的所有模块.
|
||||
*
|
||||
@ -148,23 +96,133 @@ fun Project.relocateKtorForCore(includeInRuntime: Boolean) {
|
||||
*
|
||||
* @param groupId 例如 `io.ktor`
|
||||
* @param includeInRuntime 将 relocate 后的依赖本体包含在运行时 classpath.
|
||||
* @param packages 被 relocate 的模块的全部顶层包. 如 `com.squareup.okhttp3:okhttp` 的顶层包是 `okhttp3`
|
||||
*/
|
||||
fun Project.relocateAllFromGroupId(groupId: String, includeInRuntime: Boolean) {
|
||||
relocationFilters.add(RelocationFilter(groupId, includeInRuntime = includeInRuntime))
|
||||
fun Project.relocateAllFromGroupId(
|
||||
groupId: String,
|
||||
includeInRuntime: Boolean,
|
||||
packages: List<String> = listOf(groupId),
|
||||
) {
|
||||
relocationFilters.add(
|
||||
RelocationFilter(
|
||||
groupId,
|
||||
packages = packages,
|
||||
includeInRuntime = includeInRuntime
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun Project.relocateAllFromGroupId(
|
||||
groupId: String,
|
||||
includeInRuntime: Boolean,
|
||||
vararg packages: String,
|
||||
) = relocateAllFromGroupId(groupId, includeInRuntime, packages.toList())
|
||||
|
||||
|
||||
fun KotlinDependencyHandler.relocateCompileOnly(
|
||||
relocatedDependency: RelocatedDependency,
|
||||
action: ExternalModuleDependency.() -> Unit = {}
|
||||
): ExternalModuleDependency {
|
||||
val dependency = compileOnly(relocatedDependency.notation, action)
|
||||
project.relocationFilters.add(
|
||||
RelocationFilter(
|
||||
dependency.group!!, dependency.name, relocatedDependency.packages.toList(), includeInRuntime = false,
|
||||
)
|
||||
)
|
||||
// Don't add to runtime
|
||||
return dependency
|
||||
}
|
||||
|
||||
fun DependencyHandler.relocateCompileOnly(
|
||||
project: Project,
|
||||
relocatedDependency: RelocatedDependency,
|
||||
action: Action<ExternalModuleDependency> = Action {}
|
||||
): Dependency {
|
||||
val dependency = addDependencyTo(this, "compileOnly", relocatedDependency.notation, action)
|
||||
project.relocationFilters.add(
|
||||
RelocationFilter(
|
||||
dependency.group!!, dependency.name, relocatedDependency.packages.toList(), includeInRuntime = false,
|
||||
)
|
||||
)
|
||||
// Don't add to runtime
|
||||
return dependency
|
||||
}
|
||||
|
||||
/**
|
||||
* 精确地 relocate 一个依赖.
|
||||
* 添加一个通常的 [implementation][KotlinDependencyHandler.implementation] 依赖, 并按 [relocatedDependency] 定义的配置 relocate.
|
||||
*
|
||||
* 在发布版本时, 全部对 [RelocatedDependency.packages] 中的 API 的调用都会被 relocate 到 [RELOCATION_ROOT_PACKAGE].
|
||||
* 运行时 (runtime) 将会包含被 relocate 的依赖及其所有间接依赖.
|
||||
*
|
||||
* @see configureRelocationForTarget
|
||||
*/
|
||||
fun Project.relocateExactArtifact(groupId: String, artifactId: String, includeInRuntime: Boolean) {
|
||||
relocationFilters.add(RelocationFilter(groupId, artifactId, includeInRuntime = includeInRuntime))
|
||||
fun KotlinDependencyHandler.relocateImplementation(
|
||||
relocatedDependency: RelocatedDependency,
|
||||
action: ExternalModuleDependency.() -> Unit = {}
|
||||
): ExternalModuleDependency {
|
||||
val dependency = implementation(relocatedDependency.notation) {
|
||||
|
||||
}
|
||||
project.relocationFilters.add(
|
||||
RelocationFilter(
|
||||
dependency.group!!, 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)
|
||||
exclude(ExcludeProperties.`everything from kotlin`)
|
||||
exclude(ExcludeProperties.`everything from kotlinx`)
|
||||
action()
|
||||
}
|
||||
)
|
||||
return dependency
|
||||
}
|
||||
|
||||
fun DependencyHandler.relocateImplementation(
|
||||
project: Project,
|
||||
relocatedDependency: RelocatedDependency,
|
||||
action: Action<ExternalModuleDependency> = Action {}
|
||||
): ExternalModuleDependency {
|
||||
val dependency =
|
||||
addDependencyTo(this, "implementation", relocatedDependency.notation, Action<ExternalModuleDependency> {
|
||||
relocatedDependency.exclusionAction(this)
|
||||
exclude(ExcludeProperties.`everything from kotlin`)
|
||||
exclude(ExcludeProperties.`everything from kotlinx`)
|
||||
action.execute(this)
|
||||
})
|
||||
project.relocationFilters.add(
|
||||
RelocationFilter(
|
||||
dependency.group!!, 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)
|
||||
exclude(ExcludeProperties.`everything from kotlin`)
|
||||
exclude(ExcludeProperties.`everything from kotlinx`)
|
||||
action(this)
|
||||
}
|
||||
)
|
||||
return dependency
|
||||
}
|
||||
|
||||
|
||||
const val SHADOW_RELOCATION_CONFIGURATION_NAME = "shadowRelocation"
|
||||
|
||||
|
||||
data class RelocationFilter(
|
||||
val groupId: String,
|
||||
val artifactId: String? = null,
|
||||
val shadowFilter: String = groupId,
|
||||
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.
|
||||
|
@ -10,13 +10,13 @@
|
||||
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.publish.tasks.GenerateModuleMetadata
|
||||
import org.gradle.kotlin.dsl.create
|
||||
import org.gradle.kotlin.dsl.creating
|
||||
import org.gradle.kotlin.dsl.get
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||
|
||||
/**
|
||||
@ -27,13 +27,12 @@ fun Project.configureMppShadow() {
|
||||
|
||||
configure(kotlin.targets.filter {
|
||||
it.platformType == org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm
|
||||
&& it.attributes.getAttribute(MIRAI_PLATFORM_ATTRIBUTE) == null
|
||||
&& (it.attributes.getAttribute(MIRAI_PLATFORM_INTERMEDIATE) != true)
|
||||
}) {
|
||||
configureRelocationForTarget(project)
|
||||
}
|
||||
|
||||
// regular shadow file, with suffix `-all`
|
||||
configureRegularShadowJar(kotlin)
|
||||
registerRegularShadowTask(this, mapTaskNameForMultipleTargets = true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,119 +40,47 @@ fun Project.configureMppShadow() {
|
||||
* @see RelocationNotes
|
||||
*/
|
||||
private fun KotlinTarget.configureRelocationForTarget(project: Project) = project.run {
|
||||
val relocateDependencies =
|
||||
// e.g. relocateJvmDependencies
|
||||
tasks.create("relocate${targetName.titlecase()}Dependencies", ShadowJar::class) {
|
||||
group = "mirai"
|
||||
description = "Relocate dependencies to internal package"
|
||||
destinationDirectory.set(buildDir.resolve("libs"))
|
||||
// archiveClassifier.set("")
|
||||
archiveBaseName.set("${project.name}-${targetName.toLowerCase()}")
|
||||
val configuration = project.configurations.findByName(SHADOW_RELOCATION_CONFIGURATION_NAME)
|
||||
|
||||
dependsOn(compilations["main"].compileKotlinTask) // compileKotlinJvm
|
||||
// e.g. relocateJvmDependencies
|
||||
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.toLowerCase()}") // e.g. "mirai-core-api-jvm"
|
||||
|
||||
// Run after all *Jar tasks from all projects, since Kotlin compiler may depend on the .jar file, concurrently modifying the jar will cause Kotlin compiler to fail.
|
||||
// allprojects
|
||||
// .asSequence()
|
||||
// .flatMap { it.tasks }
|
||||
// .filter { it.name.contains("compileKotlin") }
|
||||
// .forEach { jar ->
|
||||
// mustRunAfter(jar)
|
||||
// }
|
||||
dependsOn(compilations["main"].compileKotlinTask) // e.g. compileKotlinJvm
|
||||
|
||||
from(compilations["main"].output)
|
||||
|
||||
// // change name to
|
||||
// doLast {
|
||||
// outputs.files.singleFile.renameTo(
|
||||
// outputs.files.singleFile.parentFile.resolve(
|
||||
// "${project.name}-${targetName.toLowerCase()}-${project.version}.jar"
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// Filter only those should be relocated
|
||||
|
||||
afterEvaluate {
|
||||
setRelocations()
|
||||
|
||||
var fileFiltered = relocationFilters.isEmpty()
|
||||
from(project.configurations.getByName("${targetName}RuntimeClasspath")
|
||||
.files
|
||||
.filter { file ->
|
||||
val matchingFilter = relocationFilters.find { filter ->
|
||||
// file.absolutePath example: /Users/xxx/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.7.0-RC/7f9f07fc65e534c15a820f61d846b9ffdba8f162/kotlin-stdlib-jdk8-1.7.0-RC.jar
|
||||
filter.matchesFile(file)
|
||||
}
|
||||
|
||||
if (matchingFilter != null) {
|
||||
fileFiltered = true
|
||||
println("Including file: ${file.absolutePath}")
|
||||
}
|
||||
|
||||
matchingFilter?.includeInRuntime == true
|
||||
}
|
||||
)
|
||||
check(fileFiltered) { "[Shadow Relocation] Expected at least one file filtered for target $targetName. Filters: $relocationFilters" }
|
||||
}
|
||||
from(compilations["main"].output) // Add compilation result of mirai sourcecode, not including dependencies
|
||||
configuration?.let {
|
||||
from(it) // Include runtime dependencies
|
||||
}
|
||||
|
||||
val allTasks = rootProject.allprojects.asSequence().flatMap { it.tasks }
|
||||
allTasks
|
||||
.filter {
|
||||
it.name.startsWith("publish${targetName.titlecase()}PublicationTo")
|
||||
}
|
||||
.onEach { it.dependsOn(relocateDependencies) }
|
||||
.count().let {
|
||||
check(it > 0) { "[Shadow Relocation] Expected at least one publication matched for target $targetName." }
|
||||
}
|
||||
|
||||
// Ensure all compilation has finished, otherwise Kotlin compiler will complain.
|
||||
allTasks
|
||||
.filter { it.name.endsWith("Jar") }
|
||||
.onEach { relocateDependencies.dependsOn(it) }
|
||||
.count().let {
|
||||
check(it > 0) { "[Shadow Relocation] Expected at least one task matched for target $targetName." }
|
||||
}
|
||||
|
||||
allTasks
|
||||
.filter { it.name.startsWith("compileKotlin") }
|
||||
.onEach { relocateDependencies.dependsOn(it) }
|
||||
.count().let {
|
||||
check(it > 0) { "[Shadow Relocation] Expected at least one task matched for target $targetName." }
|
||||
}
|
||||
|
||||
val metadataTask =
|
||||
tasks.getByName("generateMetadataFileFor${targetName.capitalize()}Publication") as GenerateModuleMetadata
|
||||
relocateDependencies.dependsOn(metadataTask)
|
||||
|
||||
afterEvaluate {
|
||||
// remove 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)
|
||||
}
|
||||
) {
|
||||
println("[Shadow Relocation] Filtering out $groupId:$artifactId from pom")
|
||||
check(node.remove(dep)) { "Failed to remove dependency node" }
|
||||
}
|
||||
// Relocate packages
|
||||
afterEvaluate {
|
||||
val relocationFilters = project.relocationFilters
|
||||
relocationFilters.forEach { relocation ->
|
||||
relocation.packages.forEach { aPackage ->
|
||||
relocate(aPackage, "$RELOCATION_ROOT_PACKAGE.$aPackage")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Relocate before packing Jar. Projects that depends on this project actually (explicitly or implicitly) depends on the Jar.
|
||||
tasks.getByName("${targetName}Jar").dependsOn(relocateDependencies)
|
||||
|
||||
// We will modify Kotlin metadata, so do generate metadata before relocation
|
||||
val generateMetadataTask =
|
||||
tasks.getByName("generateMetadataFileFor${targetName.capitalize()}Publication") as GenerateModuleMetadata
|
||||
|
||||
val patchMetadataTask = tasks.create("patchMetadataFileFor${targetName.capitalize()}RelocatedPublication") {
|
||||
dependsOn(generateMetadataTask)
|
||||
|
||||
// remove dependencies in Kotlin module metadata
|
||||
relocateDependencies.doLast {
|
||||
doLast {
|
||||
// mirai-core-jvm-2.13.0.module
|
||||
val file = metadataTask.outputFile.asFile.get()
|
||||
val file = generateMetadataTask.outputFile.asFile.get()
|
||||
val metadata = Gson().fromJson(
|
||||
file.readText(),
|
||||
com.google.gson.JsonElement::class.java
|
||||
@ -185,48 +112,109 @@ private fun KotlinTarget.configureRelocationForTarget(project: Project) = projec
|
||||
file.writeText(GsonBuilder().setPrettyPrinting().create().toJson(metadata))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.configureRegularShadowJar(kotlin: KotlinMultiplatformExtension) {
|
||||
if (project.configurations.findByName("jvmRuntimeClasspath") != null) {
|
||||
val shadowJvmJar by tasks.creating(ShadowJar::class) sd@{
|
||||
group = "mirai"
|
||||
archiveClassifier.set("-all")
|
||||
|
||||
val compilations =
|
||||
kotlin.targets.filter { it.platformType == KotlinPlatformType.jvm }
|
||||
.map { it.compilations["main"] }
|
||||
|
||||
compilations.forEach {
|
||||
dependsOn(it.compileKotlinTask)
|
||||
from(it.output)
|
||||
// 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) }
|
||||
}
|
||||
|
||||
setRelocations()
|
||||
|
||||
from(project.configurations.findByName("jvmRuntimeClasspath"))
|
||||
|
||||
this.exclude { file ->
|
||||
file.name.endsWith(".sf", ignoreCase = true)
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
this.manifest {
|
||||
this.attributes(
|
||||
"Manifest-Version" to 1,
|
||||
"Implementation-Vendor" to "Mamoe Technologies",
|
||||
"Implementation-Title" to this.name.toString(),
|
||||
"Implementation-Version" to this.version.toString()
|
||||
)
|
||||
}*/
|
||||
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 const val relocationRootPackage = "net.mamoe.mirai.internal.deps"
|
||||
private fun Sequence<Task>.dependsOn(
|
||||
relocateDependencies: ShadowJar,
|
||||
kotlinTarget: KotlinTarget,
|
||||
): Int {
|
||||
return onEach { relocateDependencies.dependsOn(it) }
|
||||
.count().also {
|
||||
check(it > 0) { "[Shadow Relocation] Expected at least one task task matched for target ${kotlinTarget.targetName}." }
|
||||
}
|
||||
}
|
||||
|
||||
private fun ShadowJar.setRelocations() {
|
||||
project.relocationFilters.forEach { relocation ->
|
||||
relocate(relocation.shadowFilter, "$relocationRootPackage.${relocation.groupId}")
|
||||
private fun Sequence<Task>.mustRunAfter(
|
||||
relocateDependencies: ShadowJar,
|
||||
kotlinTarget: KotlinTarget,
|
||||
): Int {
|
||||
return onEach { relocateDependencies.mustRunAfter(it) }
|
||||
.count().also {
|
||||
check(it > 0) { "[Shadow Relocation] Expected at least one task task matched for target ${kotlinTarget.targetName}." }
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.registerRegularShadowTask(target: KotlinTarget, mapTaskNameForMultipleTargets: Boolean): ShadowJar {
|
||||
return tasks.create(
|
||||
if (mapTaskNameForMultipleTargets) "shadow${target.targetName}Jar" else "shadowJar",
|
||||
ShadowJar::class
|
||||
) {
|
||||
group = "mirai"
|
||||
archiveClassifier.set("all")
|
||||
|
||||
val compilation = target.compilations["main"]
|
||||
dependsOn(compilation.compileKotlinTask)
|
||||
from(compilation.output)
|
||||
|
||||
// components.findByName("java")?.let { from(it) }
|
||||
project.sourceSets.findByName("main")?.output?.let { from(it) } // for JVM projects
|
||||
configurations =
|
||||
listOfNotNull(
|
||||
project.configurations.findByName("runtimeClasspath"),
|
||||
project.configurations.findByName("${target.targetName}RuntimeClasspath"),
|
||||
project.configurations.findByName("runtime")
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.configureRelocatedShadowJarForJvmProject(kotlin: KotlinJvmProjectExtension): ShadowJar {
|
||||
return registerRegularShadowTask(kotlin.target, mapTaskNameForMultipleTargets = false)
|
||||
}
|
||||
|
||||
const val RELOCATION_ROOT_PACKAGE = "net.mamoe.mirai.internal.deps"
|
@ -7,8 +7,12 @@
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("ObjectPropertyName", "ObjectPropertyName", "unused", "MemberVisibilityCanBePrivate")
|
||||
@file:Suppress(
|
||||
"ObjectPropertyName", "ObjectPropertyName", "unused", "MemberVisibilityCanBePrivate", "RemoveRedundantBackticks"
|
||||
)
|
||||
|
||||
import org.gradle.api.artifacts.ExternalModuleDependency
|
||||
import org.gradle.api.artifacts.ModuleDependency
|
||||
import org.gradle.api.attributes.Attribute
|
||||
import org.gradle.kotlin.dsl.exclude
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
|
||||
@ -19,7 +23,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
|
||||
object Versions {
|
||||
val project = System.getenv("mirai.build.project.version")?.takeIf { it.isNotBlank() }
|
||||
?: System.getProperty("mirai.build.project.version")?.takeIf { it.isNotBlank() }
|
||||
?: /*PROJECT_VERSION_START*/"2.14.0"/*PROJECT_VERSION_END*/
|
||||
?: /*PROJECT_VERSION_START*/"2.14.0-dev-shadow-1"/*PROJECT_VERSION_END*/
|
||||
|
||||
val core = project
|
||||
val console = project
|
||||
@ -40,6 +44,8 @@ object Versions {
|
||||
* 注意, 不要轻易升级 ktor 版本. 阅读 [RelocationNotes], 尤其是间接依赖部分.
|
||||
*/
|
||||
const val ktor = "2.1.0"
|
||||
const val okhttp = "4.9.3" // 需要跟 Ktor 依赖的相同, 用于 shadow 后携带到 runtime
|
||||
const val okio = "3.2.0" // 需要跟 OkHttp 依赖的相同, 用于 shadow 后携带到 runtime
|
||||
|
||||
const val binaryValidator = "0.4.0"
|
||||
|
||||
@ -93,7 +99,17 @@ val `kotlinx-serialization-json` = kotlinx("serialization-json", Versions.serial
|
||||
val `kotlinx-serialization-protobuf` = kotlinx("serialization-protobuf", Versions.serialization)
|
||||
const val `kotlinx-atomicfu` = "org.jetbrains.kotlinx:atomicfu:${Versions.atomicFU}"
|
||||
|
||||
val `ktor-io` = ktor("io", Versions.ktor)
|
||||
/**
|
||||
* @see relocateImplementation
|
||||
*/
|
||||
class RelocatedDependency(
|
||||
val notation: String,
|
||||
vararg val packages: String,
|
||||
/**
|
||||
* Additional exclusions apart from everything from `org.jetbrains.kotlin` and `org.jetbrains.kotlinx`.
|
||||
*/
|
||||
val exclusionAction: ExternalModuleDependency.() -> Unit = {},
|
||||
)
|
||||
|
||||
fun KotlinDependencyHandler.implementationKotlinxIo(module: String) {
|
||||
implementation(module) {
|
||||
@ -111,14 +127,83 @@ fun KotlinDependencyHandler.implementationKotlinxIo(module: String) {
|
||||
}
|
||||
}
|
||||
|
||||
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") })
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun jvm(groupId: String, baseArtifactId: String): MultiplatformDependency {
|
||||
return MultiplatformDependency(groupId, baseArtifactId, "common", "metadata", "jvm", "jdk8", "jdk7")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ModuleDependency.exclude(multiplatformDependency: MultiplatformDependency) {
|
||||
multiplatformDependency.notations().forEach {
|
||||
exclude(it)
|
||||
}
|
||||
}
|
||||
|
||||
object ExcludeProperties {
|
||||
val `everything from kotlin` = exclude(groupId = "org.jetbrains.kotlin", null)
|
||||
val `everything from kotlinx` = exclude(groupId = "org.jetbrains.kotlinx", null)
|
||||
val `kotlin-stdlib` = multiplatformJvm(groupId = "org.jetbrains.kotlin", "kotlin-stdlib")
|
||||
val `kotlinx-coroutines` = multiplatformJvm(groupId = "org.jetbrains.kotlinx", "kotlinx-coroutines")
|
||||
val `ktor-io` = multiplatformJvm(groupId = "io.ktor", "ktor-io")
|
||||
val `everything from slf4j` = exclude(groupId = "org.slf4j", null)
|
||||
|
||||
/**
|
||||
* @see org.gradle.kotlin.dsl.exclude
|
||||
*/
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun exclude(
|
||||
groupId: String?, artifactId: String?
|
||||
): Map<String, String> = buildMap {
|
||||
groupId?.let { put("group", groupId) }
|
||||
artifactId?.let { put("module", artifactId) }
|
||||
}
|
||||
|
||||
private fun multiplatformJvm(
|
||||
groupId: String, baseArtifactId: String
|
||||
): MultiplatformDependency = MultiplatformDependency.jvm(groupId, baseArtifactId)
|
||||
}
|
||||
|
||||
val `ktor-io` = ktor("io", Versions.ktor)
|
||||
val `ktor-io_relocated` = RelocatedDependency(`ktor-io`, "io.ktor.utils.io") {
|
||||
exclude(ExcludeProperties.`everything from slf4j`)
|
||||
}
|
||||
|
||||
val `ktor-http` = ktor("http", Versions.ktor)
|
||||
val `ktor-events` = ktor("events", Versions.ktor)
|
||||
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") {
|
||||
exclude(ExcludeProperties.`ktor-io`)
|
||||
exclude(ExcludeProperties.`everything from slf4j`)
|
||||
}
|
||||
|
||||
val `ktor-client-cio` = ktor("client-cio", Versions.ktor)
|
||||
val `ktor-client-mock` = ktor("client-mock", Versions.ktor)
|
||||
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") {
|
||||
exclude(ExcludeProperties.`ktor-io`)
|
||||
exclude(ExcludeProperties.`everything from slf4j`)
|
||||
}
|
||||
|
||||
const val `okhttp3` = "com.squareup.okhttp3:okhttp:${Versions.okhttp}"
|
||||
const val `okio` = "com.squareup.okio:okio-jvm:${Versions.okio}"
|
||||
|
||||
val `ktor-client-android` = ktor("client-android", Versions.ktor)
|
||||
val `ktor-client-logging` = ktor("client-logging", Versions.ktor)
|
||||
val `ktor-network` = ktor("network-jvm", Versions.ktor)
|
||||
@ -144,6 +229,7 @@ val ATTRIBUTE_MIRAI_TARGET_PLATFORM: Attribute<String> = Attribute.of("mirai.tar
|
||||
const val `kotlin-compiler` = "org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}"
|
||||
const val `kotlin-compiler_forIdea` = "org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompilerForIdeaPlugin}"
|
||||
|
||||
const val `kotlin-stdlib` = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinStdlib}"
|
||||
const val `kotlin-stdlib-jdk8` = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlinStdlib}"
|
||||
const val `kotlin-reflect` = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinStdlib}"
|
||||
const val `kotlin-test` = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlinStdlib}"
|
||||
|
@ -23,10 +23,34 @@ dependencies {
|
||||
api(project(":mirai-core"))
|
||||
api(project(":mirai-core-api"))
|
||||
api(project(":mirai-core-utils"))
|
||||
|
||||
relocateImplementation(project, `ktor-client-core_relocated`)
|
||||
relocateImplementation(project, `ktor-client-okhttp_relocated`)
|
||||
relocateImplementation(project, `ktor-io_relocated`)
|
||||
}
|
||||
|
||||
val shadow = configureRelocatedShadowJarForJvmProject(kotlin)
|
||||
|
||||
if (System.getenv("MIRAI_IS_SNAPSHOTS_PUBLISHING")?.toBoolean() != true) {
|
||||
configurePublishing("mirai-core-all")
|
||||
// Do not publish -all jars to snapshot server since they are too large.
|
||||
|
||||
configurePublishing("mirai-core-all", addShadowJar = false)
|
||||
|
||||
publications {
|
||||
getByName("mavenJava", MavenPublication::class) {
|
||||
artifact(shadow)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.getByName("publishMavenJavaPublicationToMavenLocal").dependsOn(shadow)
|
||||
tasks.findByName("publishMavenJavaPublicationToMavenCentralRepository")?.dependsOn(shadow)
|
||||
}
|
||||
|
||||
relocateKtorForCore(true)
|
||||
//
|
||||
//// WARNING: You must also consider relocating transitive dependencies.
|
||||
//// Otherwise, user will get NoClassDefFound error when using mirai as a classpath dependency. See #2263.
|
||||
//
|
||||
//val includeInRuntime = true
|
||||
//relocateAllFromGroupId("io.ktor", includeInRuntime, "io.ktor")
|
||||
//relocateAllFromGroupId("com.squareup.okhttp3", includeInRuntime, listOf("okhttp3"))
|
||||
//relocateAllFromGroupId("com.squareup.okio", includeInRuntime, listOf("okio"))
|
@ -43,7 +43,7 @@ kotlin {
|
||||
implementation(project(":mirai-core-utils"))
|
||||
implementation(project(":mirai-console-compiler-annotations"))
|
||||
implementation(`kotlinx-serialization-protobuf`)
|
||||
implementation(`ktor-io`)
|
||||
relocateCompileOnly(`ktor-io_relocated`) // runtime from mirai-core-utils
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +103,6 @@ if (tasks.findByName("androidMainClasses") != null) {
|
||||
|
||||
configureMppPublishing()
|
||||
configureBinaryValidators(setOf("jvm", "android").filterTargets())
|
||||
relocateKtorForCore(false)
|
||||
|
||||
//mavenCentralPublish {
|
||||
// artifactId = "mirai-core-api"
|
||||
|
@ -36,7 +36,10 @@ kotlin {
|
||||
api(`kotlinx-coroutines-core`)
|
||||
|
||||
implementation(`kotlinx-serialization-protobuf`)
|
||||
implementation(`ktor-io`)
|
||||
relocateImplementation(`ktor-io_relocated`) {
|
||||
exclude(ExcludeProperties.`kotlin-stdlib`)
|
||||
exclude(ExcludeProperties.`kotlinx-coroutines`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +57,6 @@ kotlin {
|
||||
}
|
||||
|
||||
findByName("androidMain")?.apply {
|
||||
//
|
||||
dependencies {
|
||||
compileOnly(`android-runtime`)
|
||||
// api1(`ktor-client-android`)
|
||||
@ -95,7 +97,6 @@ if (tasks.findByName("androidMainClasses") != null) {
|
||||
}
|
||||
|
||||
configureMppPublishing()
|
||||
relocateKtorForCore(true)
|
||||
|
||||
//mavenCentralPublish {
|
||||
// artifactId = "mirai-core-utils"
|
||||
|
@ -42,8 +42,10 @@ kotlin {
|
||||
|
||||
implementation(project(":mirai-core-utils"))
|
||||
implementation(`kotlinx-serialization-protobuf`)
|
||||
implementation(`ktor-io`)
|
||||
implementation(`ktor-client-core`)
|
||||
|
||||
// relocateImplementation(`ktor-http_relocated`)
|
||||
// relocateImplementation(`ktor-serialization_relocated`)
|
||||
// relocateImplementation(`ktor-websocket-serialization_relocated`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,7 +61,6 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(`log4j-api`)
|
||||
implementation(`netty-handler`)
|
||||
implementation(`ktor-client-okhttp`)
|
||||
api(`kotlinx-coroutines-jdk8`) // use -jvm modules for this magic target 'jvmBase'
|
||||
}
|
||||
}
|
||||
@ -105,6 +106,38 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Ktor
|
||||
|
||||
findByName("commonMain")?.apply {
|
||||
dependencies {
|
||||
relocateCompileOnly(`ktor-io_relocated`) // runtime from mirai-core-utils
|
||||
relocateImplementation(`ktor-client-core_relocated`)
|
||||
}
|
||||
}
|
||||
findByName("jvmBaseMain")?.apply {
|
||||
dependencies {
|
||||
relocateImplementation(`ktor-client-okhttp_relocated`)
|
||||
}
|
||||
}
|
||||
configure(WIN_TARGETS.map { getByName(it + "Main") }) {
|
||||
dependencies {
|
||||
implementation(`ktor-client-curl`)
|
||||
}
|
||||
}
|
||||
configure(LINUX_TARGETS.map { getByName(it + "Main") }) {
|
||||
dependencies {
|
||||
implementation(`ktor-client-cio`)
|
||||
}
|
||||
}
|
||||
findByName("darwinMain")?.apply {
|
||||
dependencies {
|
||||
implementation(`ktor-client-darwin`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Linkage
|
||||
NATIVE_TARGETS.forEach { targetName ->
|
||||
val defFile = projectDir.resolve("src/nativeMain/cinterop/OpenSSL.def")
|
||||
val target = targets.getByName(targetName) as KotlinNativeTarget
|
||||
@ -136,24 +169,6 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
configure(WIN_TARGETS.map { getByName(it + "Main") }) {
|
||||
dependencies {
|
||||
implementation(`ktor-client-curl`)
|
||||
}
|
||||
}
|
||||
|
||||
configure(LINUX_TARGETS.map { getByName(it + "Main") }) {
|
||||
dependencies {
|
||||
implementation(`ktor-client-cio`)
|
||||
}
|
||||
}
|
||||
|
||||
findByName("darwinMain")?.apply {
|
||||
dependencies {
|
||||
implementation(`ktor-client-darwin`)
|
||||
}
|
||||
}
|
||||
|
||||
disableCrossCompile()
|
||||
// val unixMain by getting {
|
||||
// dependencies {
|
||||
@ -202,7 +217,6 @@ if (tasks.findByName("androidMainClasses") != null) {
|
||||
|
||||
configureMppPublishing()
|
||||
configureBinaryValidators(setOf("jvm", "android").filterTargets())
|
||||
relocateKtorForCore(false)
|
||||
|
||||
//mavenCentralPublish {
|
||||
// artifactId = "mirai-core"
|
||||
|
@ -72,6 +72,13 @@ internal fun getMiraiImpl() = Mirai as MiraiImpl
|
||||
|
||||
internal expect fun createDefaultHttpClient(): HttpClient
|
||||
|
||||
// used by `net.mamoe.mirai.deps.test.CoreDependencyResolutionTest` in mirai-deps-test module. Do not change signature.
|
||||
@Suppress("unused")
|
||||
@TestOnly
|
||||
internal fun testHttpClient() {
|
||||
createDefaultHttpClient().close()
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
internal expect fun _MiraiImpl_static_init()
|
||||
|
||||
|
@ -95,6 +95,7 @@ tasks.register("publishMiraiLocalArtifacts", Exec::class) {
|
||||
"./gradlew",
|
||||
publishMiraiArtifactsToMavenLocal.name,
|
||||
"--no-daemon",
|
||||
"--stacktrace",
|
||||
"-Pkotlin.compiler.execution.strategy=in-process"
|
||||
)
|
||||
standardOutput = System.out
|
||||
|
@ -23,7 +23,7 @@ abstract class AbstractTest {
|
||||
const val miraiLocalVersion = "2.99.0-deps-test" // do Search Everywhere before changing this
|
||||
const val REASON_LOCAL_ARTIFACT_NOT_AVAILABLE = "local artifacts not available"
|
||||
|
||||
private val mavenLocalDir: File by lazy {
|
||||
val mavenLocalDir: File by lazy {
|
||||
org.gradle.api.internal.artifacts.mvnsettings.DefaultLocalMavenRepositoryLocator(
|
||||
org.gradle.api.internal.artifacts.mvnsettings.DefaultMavenSettingsProvider(DefaultMavenFileLocations())
|
||||
).localMavenRepository
|
||||
@ -165,9 +165,20 @@ abstract class AbstractTest {
|
||||
if (context.executionException.isPresent) {
|
||||
val inst = context.requiredTestInstance as AbstractTest
|
||||
println("====================== build.gradle ===========================")
|
||||
println(inst.tempDir.resolve("build.gradle").readText())
|
||||
println(inst.tempDir.resolveFirstExisting("build.gradle", "build.gradle.kts").readTextIfFound())
|
||||
println("==================== settings.gradle ==========================")
|
||||
println(inst.tempDir.resolve("settings.gradle").readText())
|
||||
println(inst.tempDir.resolveFirstExisting("settings.gradle", "settings.gradle.kts").readTextIfFound())
|
||||
}
|
||||
}
|
||||
|
||||
private fun File.resolveFirstExisting(vararg files: String): File? {
|
||||
return files.asSequence().map { resolve(it) }.firstOrNull { it.exists() }
|
||||
}
|
||||
|
||||
private fun File?.readTextIfFound(): String =
|
||||
when {
|
||||
this == null -> "(not found)"
|
||||
exists() -> readText()
|
||||
else -> "($name not found)"
|
||||
}
|
||||
}
|
@ -13,17 +13,20 @@ import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.condition.EnabledIf
|
||||
|
||||
class CoreDependencyResolutionTest : AbstractTest() {
|
||||
private val testCode = """
|
||||
package test
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPERIMENTAL_API_USAGE")
|
||||
fun main () {
|
||||
println(net.mamoe.mirai.BotFactory)
|
||||
println(net.mamoe.mirai.Mirai)
|
||||
println(net.mamoe.mirai.internal.testHttpClient())
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
@Test
|
||||
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
|
||||
fun `test resolve JVM root from Kotlin JVM`() {
|
||||
mainSrcDir.resolve("main.kt").writeText(
|
||||
"""
|
||||
package test
|
||||
fun main () {
|
||||
println(net.mamoe.mirai.BotFactory)
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
mainSrcDir.resolve("main.kt").writeText(testCode)
|
||||
buildFile.writeText(
|
||||
"""
|
||||
plugins {
|
||||
@ -36,6 +39,9 @@ class CoreDependencyResolutionTest : AbstractTest() {
|
||||
dependencies {
|
||||
implementation("net.mamoe:mirai-core:$miraiLocalVersion")
|
||||
}
|
||||
kotlin.sourceSets.all {
|
||||
languageSettings.optIn("net.mamoe.mirai.utils.TestOnly")
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
runGradle("build")
|
||||
@ -44,14 +50,7 @@ class CoreDependencyResolutionTest : AbstractTest() {
|
||||
@Test
|
||||
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
|
||||
fun `test resolve JVM from Kotlin JVM`() {
|
||||
mainSrcDir.resolve("main.kt").writeText(
|
||||
"""
|
||||
package test
|
||||
fun main () {
|
||||
println(net.mamoe.mirai.BotFactory)
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
mainSrcDir.resolve("main.kt").writeText(testCode)
|
||||
buildFile.writeText(
|
||||
"""
|
||||
plugins {
|
||||
@ -64,6 +63,9 @@ class CoreDependencyResolutionTest : AbstractTest() {
|
||||
dependencies {
|
||||
implementation("net.mamoe:mirai-core-jvm:$miraiLocalVersion")
|
||||
}
|
||||
kotlin.sourceSets.all {
|
||||
languageSettings.optIn("net.mamoe.mirai.utils.TestOnly")
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
runGradle("build")
|
||||
@ -72,14 +74,7 @@ class CoreDependencyResolutionTest : AbstractTest() {
|
||||
@Test
|
||||
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
|
||||
fun `test resolve JVM and Native from common`() {
|
||||
commonMainSrcDir.resolve("main.kt").writeText(
|
||||
"""
|
||||
package test
|
||||
fun main () {
|
||||
println(net.mamoe.mirai.BotFactory)
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
commonMainSrcDir.resolve("main.kt").writeText(testCode)
|
||||
buildFile.writeText(
|
||||
"""
|
||||
|import org.apache.tools.ant.taskdefs.condition.Os
|
||||
@ -111,6 +106,9 @@ class CoreDependencyResolutionTest : AbstractTest() {
|
||||
| }
|
||||
| }
|
||||
|}
|
||||
|kotlin.sourceSets.all {
|
||||
| languageSettings.optIn("net.mamoe.mirai.utils.TestOnly")
|
||||
|}
|
||||
""".trimMargin()
|
||||
)
|
||||
|
||||
@ -120,14 +118,7 @@ class CoreDependencyResolutionTest : AbstractTest() {
|
||||
@Test
|
||||
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
|
||||
fun `test resolve Native from common`() {
|
||||
nativeMainSrcDir.resolve("main.kt").writeText(
|
||||
"""
|
||||
package test
|
||||
fun main () {
|
||||
println(net.mamoe.mirai.BotFactory)
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
nativeMainSrcDir.resolve("main.kt").writeText(testCode)
|
||||
buildFile.writeText(
|
||||
"""
|
||||
|import org.apache.tools.ant.taskdefs.condition.Os
|
||||
@ -159,6 +150,9 @@ class CoreDependencyResolutionTest : AbstractTest() {
|
||||
| }
|
||||
| }
|
||||
|}
|
||||
|kotlin.sourceSets.all {
|
||||
| languageSettings.optIn("net.mamoe.mirai.utils.TestOnly")
|
||||
|}
|
||||
""".trimMargin()
|
||||
)
|
||||
|
||||
|
@ -11,31 +11,77 @@ package net.mamoe.mirai.deps.test
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.condition.EnabledIf
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* 为每个模块测试 relocated 的依赖是否存在于运行时
|
||||
*/
|
||||
class CoreShadowRelocationTest : AbstractTest() {
|
||||
companion object {
|
||||
private const val ByteBufferChannel = "io.ktor.utils.io.ByteBufferChannel"
|
||||
private const val HttpClient = "io.ktor.client.HttpClient"
|
||||
private const val KtorOkHttp = "io.ktor.client.engine.okhttp.OkHttp"
|
||||
private const val OkHttp = "okhttp3.OkHttp"
|
||||
private const val OkIO = "okio.ByteString"
|
||||
|
||||
private fun relocated(string: String): String {
|
||||
return "net.mamoe.mirai.internal.deps.$string"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
|
||||
fun `test OkHttp filtered out`() {
|
||||
testDir.resolve("test.kt").writeText(
|
||||
fun `test mirai-core-utils`() {
|
||||
val fragment = buildTestCases {
|
||||
+relocated(`ktor-io`)
|
||||
-both(`ktor-client-core`)
|
||||
-both(`ktor-client-okhttp`)
|
||||
-both(`okhttp3-okhttp`)
|
||||
-both(okio)
|
||||
}
|
||||
applyCodeFragment(fragment)
|
||||
buildFile.appendText(
|
||||
"""
|
||||
package test
|
||||
import org.junit.jupiter.api.*
|
||||
class MyTest {
|
||||
@Test
|
||||
fun `test base dependency`() {
|
||||
assertThrows<ClassNotFoundException> {
|
||||
Class.forName("io.ktor.client.engine.okhttp.OkHttp")
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun `test transitive dependency`() {
|
||||
assertThrows<ClassNotFoundException> {
|
||||
Class.forName("okhttp3.OkHttpClient")
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation("net.mamoe:mirai-core-utils:$miraiLocalVersion")
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
runGradle("check")
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
|
||||
fun `test mirai-core-api with transitive mirai-core-utils`() {
|
||||
val fragment = buildTestCases {
|
||||
+relocated(`ktor-io`)
|
||||
-both(`ktor-client-core`)
|
||||
-both(`ktor-client-okhttp`)
|
||||
-both(`okhttp3-okhttp`)
|
||||
-both(okio)
|
||||
}
|
||||
applyCodeFragment(fragment)
|
||||
buildFile.appendText(
|
||||
"""
|
||||
dependencies {
|
||||
implementation("net.mamoe:mirai-core-api:$miraiLocalVersion")
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
runGradle("check")
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
|
||||
fun `test mirai-core with transitive mirai-core-api and mirai-core-utils`() {
|
||||
val fragment = buildTestCases {
|
||||
+relocated(`ktor-io`)
|
||||
+relocated(`ktor-client-core`)
|
||||
+relocated(`ktor-client-okhttp`)
|
||||
+relocated(`okhttp3-okhttp`)
|
||||
+relocated(okio)
|
||||
}
|
||||
applyCodeFragment(fragment)
|
||||
buildFile.appendText(
|
||||
"""
|
||||
dependencies {
|
||||
@ -46,25 +92,20 @@ class CoreShadowRelocationTest : AbstractTest() {
|
||||
runGradle("check")
|
||||
}
|
||||
|
||||
|
||||
// ktor-io is shadowed into runtime in mirai-core-utils. So without mirai-core-utils,
|
||||
// we should expect no relocated ktor-io found, otherwise there will be duplicated classes on Android.
|
||||
// https://github.com/mamoe/mirai/issues/2291
|
||||
@Test
|
||||
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
|
||||
fun `no duplicated class when dependency shared across modules`() {
|
||||
testDir.resolve("test.kt").writeText(
|
||||
"""
|
||||
package test
|
||||
import org.junit.jupiter.api.*
|
||||
class MyTest {
|
||||
@Test
|
||||
fun `test base dependency`() {
|
||||
assertThrows<ClassNotFoundException> {
|
||||
Class.forName("net.mamoe.mirai.internal.deps.io.ktor.utils.io.ByteBufferChannel") // should only present in mirai-core-utils
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
fun `test mirai-core without transitive mirai-core-api and mirai-core-utils`() {
|
||||
val fragment = buildTestCases {
|
||||
-both(`ktor-io`)
|
||||
+relocated(`ktor-client-core`)
|
||||
+relocated(`ktor-client-okhttp`)
|
||||
+relocated(`okhttp3-okhttp`)
|
||||
+relocated(okio)
|
||||
}
|
||||
applyCodeFragment(fragment)
|
||||
buildFile.appendText(
|
||||
"""
|
||||
dependencies {
|
||||
@ -80,23 +121,21 @@ class CoreShadowRelocationTest : AbstractTest() {
|
||||
|
||||
@Test
|
||||
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
|
||||
fun `relocated ktor presents in mirai-core-utils`() {
|
||||
testDir.resolve("test.kt").writeText(
|
||||
"""
|
||||
package test
|
||||
import org.junit.jupiter.api.*
|
||||
class MyTest {
|
||||
@Test
|
||||
fun `test base dependency`() {
|
||||
Class.forName("net.mamoe.mirai.internal.deps.io.ktor.utils.io.ByteBufferChannel")
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
fun `test mirai-core-api without transitive mirai-core-utils`() {
|
||||
val fragment = buildTestCases {
|
||||
-both(`ktor-io`)
|
||||
-both(`ktor-client-core`)
|
||||
-both(`ktor-client-okhttp`)
|
||||
-both(`okhttp3-okhttp`)
|
||||
-both(okio)
|
||||
}
|
||||
applyCodeFragment(fragment)
|
||||
buildFile.appendText(
|
||||
"""
|
||||
dependencies {
|
||||
implementation("net.mamoe:mirai-core-utils:$miraiLocalVersion")
|
||||
implementation("net.mamoe:mirai-core-api:$miraiLocalVersion") {
|
||||
exclude("net.mamoe", "mirai-core-utils")
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
@ -105,26 +144,141 @@ class CoreShadowRelocationTest : AbstractTest() {
|
||||
|
||||
@Test
|
||||
@EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE)
|
||||
fun `relocated ktor presents transitively in mirai-core`() {
|
||||
testDir.resolve("test.kt").writeText(
|
||||
"""
|
||||
package test
|
||||
import org.junit.jupiter.api.*
|
||||
class MyTest {
|
||||
@Test
|
||||
fun `test base dependency`() {
|
||||
Class.forName("net.mamoe.mirai.internal.deps.io.ktor.utils.io.ByteBufferChannel")
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
fun `test mirai-core-all`() {
|
||||
val fragment = buildTestCases {
|
||||
+relocated(`ktor-io`)
|
||||
+relocated(`ktor-client-core`)
|
||||
+relocated(`ktor-client-okhttp`)
|
||||
+relocated(`okhttp3-okhttp`)
|
||||
+relocated(okio)
|
||||
}
|
||||
applyCodeFragment(fragment)
|
||||
|
||||
// mirai-core-all-2.99.0-deps-test-all.jar
|
||||
val miraiCoreAllJar =
|
||||
mavenLocalDir.resolve("net/mamoe/mirai-core-all/$miraiLocalVersion/mirai-core-all-$miraiLocalVersion-all.jar")
|
||||
assertTrue("'${miraiCoreAllJar.absolutePath}' does not exist") { miraiCoreAllJar.exists() }
|
||||
|
||||
buildFile.appendText(
|
||||
"""
|
||||
dependencies {
|
||||
implementation("net.mamoe:mirai-core:$miraiLocalVersion")
|
||||
implementation(fileTree("${miraiCoreAllJar.absolutePath}"))
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
runGradle("check")
|
||||
}
|
||||
|
||||
|
||||
@Suppress("PropertyName")
|
||||
private class TestBuilder {
|
||||
private val result = StringBuilder(
|
||||
"""
|
||||
package test
|
||||
import org.junit.jupiter.api.*
|
||||
class MyTest {
|
||||
""".trimIndent()
|
||||
).append("\n").append("\n")
|
||||
|
||||
class TestCase(
|
||||
val name: String,
|
||||
val qualifiedClassName: String,
|
||||
)
|
||||
|
||||
val `ktor-io` = TestCase("ktor-io ByteBufferChannel", ByteBufferChannel)
|
||||
val `ktor-client-core` = TestCase("ktor-client-core HttpClient", HttpClient)
|
||||
val `ktor-client-okhttp` = TestCase("ktor-client-core OkHttp", KtorOkHttp)
|
||||
val `okhttp3-okhttp` = TestCase("okhttp3 OkHttp", OkHttp)
|
||||
val okio = TestCase("okio ByteString", OkIO)
|
||||
|
||||
class Relocated(
|
||||
val testCase: TestCase
|
||||
)
|
||||
|
||||
class Both(
|
||||
val testCase: TestCase
|
||||
)
|
||||
|
||||
|
||||
private fun appendHas(name: String, qualifiedClassName: String) {
|
||||
result.append(
|
||||
"""
|
||||
@Test
|
||||
fun `has ${name}`() {
|
||||
Class.forName("$qualifiedClassName")
|
||||
}
|
||||
""".trimIndent()
|
||||
).append("\n")
|
||||
}
|
||||
|
||||
private fun appendNotFound(name: String, qualifiedClassName: String) {
|
||||
result.append(
|
||||
"""
|
||||
@Test
|
||||
fun `no relocated ${name}`() {
|
||||
assertThrows<ClassNotFoundException> { Class.forName("$qualifiedClassName") }
|
||||
}
|
||||
""".trimIndent()
|
||||
).append("\n")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asserts a class exists. Also asserts its relocated class does not exist.
|
||||
*/
|
||||
operator fun TestCase.unaryPlus() {
|
||||
appendHas(name, qualifiedClassName)
|
||||
appendNotFound("relocated $name", Companion.relocated(qualifiedClassName))
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a class does not exist.
|
||||
*/
|
||||
operator fun TestCase.unaryMinus() {
|
||||
appendNotFound(name, qualifiedClassName)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asserts a relocated class exists. Also asserts the original class does not exist.
|
||||
*/
|
||||
operator fun Relocated.unaryPlus() {
|
||||
this.testCase.run {
|
||||
appendHas("relocated $name", Companion.relocated(qualifiedClassName))
|
||||
appendNotFound(name, qualifiedClassName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a relocated class does not exist.
|
||||
*/
|
||||
operator fun Relocated.unaryMinus() {
|
||||
this.testCase.run {
|
||||
appendNotFound("relocated $name", Companion.relocated(qualifiedClassName))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts both the class and its relocated one do not exist.
|
||||
*/
|
||||
operator fun Both.unaryMinus() {
|
||||
-this.testCase
|
||||
-relocated(this.testCase)
|
||||
}
|
||||
|
||||
fun relocated(testCase: TestCase): Relocated = Relocated(testCase)
|
||||
fun both(testCase: TestCase) = Both(testCase)
|
||||
|
||||
fun build(): String = result.append("\n}\n").toString()
|
||||
}
|
||||
|
||||
private inline fun buildTestCases(action: TestBuilder.() -> Unit): String {
|
||||
return TestBuilder().apply(action).build()
|
||||
}
|
||||
|
||||
|
||||
private fun applyCodeFragment(fragment: String) {
|
||||
println("Applying code fragment: \n\n$fragment\n\n\n===========End of Fragment===========")
|
||||
testDir.resolve("test.kt").writeText(fragment)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user