From 8ee343c94a7960bc64849d09031b5c0a786a1ff2 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 12 Sep 2025 17:02:37 +0800 Subject: [PATCH 1/3] Share Moshi dependencies --- build.gradle.kts | 2 ++ .../jengelman/gradle/plugins/shadow/PublishingTest.kt | 5 ++--- .../jengelman/gradle/plugins/shadow/internal/Moshi.kt | 6 ++++++ 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Moshi.kt diff --git a/build.gradle.kts b/build.gradle.kts index ebd8f4a07..c47fe6986 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -113,6 +113,8 @@ dependencies { implementation(libs.jdependency) implementation(libs.jdom2) implementation(libs.kotlin.metadata) + implementation(libs.moshi) + implementation(libs.moshi.kotlin) implementation(libs.plexus.utils) implementation(libs.plexus.xml) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt index f3a6c138f..b5ae7b5d0 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt @@ -10,6 +10,7 @@ import assertk.assertions.isEqualTo import assertk.assertions.single import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin.Companion.SHADOW_RUNTIME_ELEMENTS_CONFIGURATION_NAME import com.github.jengelman.gradle.plugins.shadow.internal.classPathAttributeKey +import com.github.jengelman.gradle.plugins.shadow.internal.moshi import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import com.github.jengelman.gradle.plugins.shadow.util.GradleModuleMetadata import com.github.jengelman.gradle.plugins.shadow.util.Issue @@ -20,8 +21,6 @@ import com.github.jengelman.gradle.plugins.shadow.util.containsOnly import com.github.jengelman.gradle.plugins.shadow.util.coordinate import com.github.jengelman.gradle.plugins.shadow.util.getMainAttr import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import java.nio.file.Path import kotlin.io.path.appendText import kotlin.io.path.exists @@ -663,7 +662,7 @@ class PublishingTest : BasePluginTest() { } private companion object { - val gmmAdapter: JsonAdapter = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() + val gmmAdapter: JsonAdapter = moshi .adapter(GradleModuleMetadata::class.java) val pomReader = MavenXpp3Reader() diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Moshi.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Moshi.kt new file mode 100644 index 000000000..06142cce8 --- /dev/null +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Moshi.kt @@ -0,0 +1,6 @@ +package com.github.jengelman.gradle.plugins.shadow.internal + +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory + +internal val moshi: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() From 2faa5c0f43de075b3c1f430894ddb20b4e9f7e10 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 12 Sep 2025 17:08:47 +0800 Subject: [PATCH 2/3] Generate `KotlinModuleMetadataAdapterFactory` --- .../gradle/plugins/shadow/internal/Moshi.kt | 243 +++++++++++++++++- 1 file changed, 242 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Moshi.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Moshi.kt index 06142cce8..429c039ce 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Moshi.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Moshi.kt @@ -1,6 +1,247 @@ +@file:OptIn(UnstableMetadataApi::class) + package com.github.jengelman.gradle.plugins.shadow.internal +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter import com.squareup.moshi.Moshi +import com.squareup.moshi.Types import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import java.lang.reflect.Type +import kotlin.metadata.ClassName +import kotlin.metadata.KmClass +import kotlin.metadata.jvm.JvmMetadataVersion +import kotlin.metadata.jvm.KmModule +import kotlin.metadata.jvm.KmPackageParts +import kotlin.metadata.jvm.KotlinModuleMetadata +import kotlin.metadata.jvm.UnstableMetadataApi + +internal val moshi: Moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .add(KotlinModuleMetadataAdapterFactory()) + .build() + +private class KotlinModuleMetadataAdapterFactory : JsonAdapter.Factory { + override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { + return when (type) { + KotlinModuleMetadata::class.java -> { + val kmModuleAdapter = moshi.adapter(KmModule::class.java) + val versionAdapter = moshi.adapter(JvmMetadataVersion::class.java) + KotlinModuleMetadataAdapter(kmModuleAdapter, versionAdapter) + } + JvmMetadataVersion::class.java -> JvmMetadataVersionAdapter() + KmModule::class.java -> { + val mapAdapter = moshi.adapter>( + Types.newParameterizedType( + Map::class.java, + String::class.java, + KmPackageParts::class.java, + ), + ) + val listKmClassAdapter = moshi.adapter>( + Types.newParameterizedType(List::class.java, KmClass::class.java), + ) + KmModuleAdapter(mapAdapter, listKmClassAdapter) + } + KmPackageParts::class.java -> { + val listStringAdapter = moshi.adapter>( + Types.newParameterizedType(List::class.java, String::class.java), + ) + val mapStringStringAdapter = moshi.adapter>( + Types.newParameterizedType( + Map::class.java, + String::class.java, + String::class.java, + ), + ) + KmPackagePartsAdapter(listStringAdapter, mapStringStringAdapter) + } + KmClass::class.java -> { + // This adapter is simplified. A full adapter would be very large. + val classNameAdapter = moshi.adapter(ClassName::class.java) + KmClassJsonAdapter(classNameAdapter) + } + else -> null + } + } +} + +private class KotlinModuleMetadataAdapter( + private val kmModuleAdapter: JsonAdapter, + private val versionAdapter: JsonAdapter, +) : JsonAdapter() { + override fun fromJson(reader: JsonReader): KotlinModuleMetadata { + reader.beginObject() + var kmModule: KmModule? = null + var version: JvmMetadataVersion? = null + while (reader.hasNext()) { + when (reader.nextName()) { + "kmModule" -> kmModule = kmModuleAdapter.fromJson(reader) + "version" -> version = versionAdapter.fromJson(reader) + else -> reader.skipValue() + } + } + reader.endObject() + return KotlinModuleMetadata( + kmModule ?: throw JsonDataException("Required property 'kmModule' is missing"), + version ?: throw JsonDataException("Required property 'version' is missing"), + ) + } + + override fun toJson(writer: JsonWriter, value: KotlinModuleMetadata?) { + if (value == null) { + writer.nullValue() + return + } + writer.beginObject() + writer.name("kmModule") + kmModuleAdapter.toJson(writer, value.kmModule) + writer.name("version") + versionAdapter.toJson(writer, value.version) + writer.endObject() + } +} + +private class JvmMetadataVersionAdapter : JsonAdapter() { + override fun fromJson(reader: JsonReader): JvmMetadataVersion { + reader.beginObject() + var major = -1 + var minor = -1 + var patch = -1 + while (reader.hasNext()) { + when (reader.nextName()) { + "major" -> major = reader.nextInt() + "minor" -> minor = reader.nextInt() + "patch" -> patch = reader.nextInt() + else -> reader.skipValue() + } + } + reader.endObject() + return JvmMetadataVersion(major, minor, patch) + } + + override fun toJson(writer: JsonWriter, value: JvmMetadataVersion?) { + if (value == null) { + writer.nullValue() + return + } + writer.beginObject() + writer.name("major").value(value.major) + writer.name("minor").value(value.minor) + writer.name("patch").value(value.patch) + writer.endObject() + } +} + +private class KmModuleAdapter( + private val packagePartsAdapter: JsonAdapter>, + private val optionalClassesAdapter: JsonAdapter>, +) : JsonAdapter() { + override fun fromJson(reader: JsonReader): KmModule { + val kmModule = KmModule() + reader.beginObject() + while (reader.hasNext()) { + when (reader.nextName()) { + "packageParts" -> { + packagePartsAdapter.fromJson(reader)?.let { + kmModule.packageParts.putAll(it) + } + } + "optionalAnnotationClasses" -> { + optionalClassesAdapter.fromJson(reader)?.let { + kmModule.optionalAnnotationClasses.addAll(it) + } + } + else -> reader.skipValue() + } + } + reader.endObject() + return kmModule + } + + override fun toJson(writer: JsonWriter, value: KmModule?) { + if (value == null) { + writer.nullValue() + return + } + writer.beginObject() + writer.name("packageParts") + packagePartsAdapter.toJson(writer, value.packageParts) + writer.name("optionalAnnotationClasses") + optionalClassesAdapter.toJson(writer, value.optionalAnnotationClasses) + writer.endObject() + } +} + +private class KmPackagePartsAdapter( + private val listStringAdapter: JsonAdapter>, + private val mapStringStringAdapter: JsonAdapter>, +) : JsonAdapter() { + override fun fromJson(reader: JsonReader): KmPackageParts { + var fileFacades: MutableList? = null + var multiFileClassParts: MutableMap? = null + reader.beginObject() + while (reader.hasNext()) { + when (reader.nextName()) { + "fileFacades" -> fileFacades = listStringAdapter.fromJson(reader)?.toMutableList() + "multiFileClassParts" -> + multiFileClassParts = + mapStringStringAdapter.fromJson(reader)?.toMutableMap() + else -> reader.skipValue() + } + } + reader.endObject() + return KmPackageParts( + fileFacades ?: mutableListOf(), + multiFileClassParts ?: mutableMapOf(), + ) + } + + override fun toJson(writer: JsonWriter, value: KmPackageParts?) { + if (value == null) { + writer.nullValue() + return + } + writer.beginObject() + writer.name("fileFacades") + listStringAdapter.toJson(writer, value.fileFacades) + writer.name("multiFileClassParts") + mapStringStringAdapter.toJson(writer, value.multiFileClassParts) + writer.endObject() + } +} + +// Simplified adapter for KmClass +private class KmClassJsonAdapter( + private val classNameAdapter: JsonAdapter, +) : JsonAdapter() { + override fun fromJson(reader: JsonReader): KmClass { + val kmClass = KmClass() + reader.beginObject() + while (reader.hasNext()) { + when (reader.nextName()) { + "name" -> kmClass.name = classNameAdapter.fromJson(reader) + ?: throw JsonDataException("Required property 'name' is missing for KmClass") + // Other properties would be deserialized here + else -> reader.skipValue() + } + } + reader.endObject() + return kmClass + } -internal val moshi: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() + override fun toJson(writer: JsonWriter, value: KmClass?) { + if (value == null) { + writer.nullValue() + return + } + writer.beginObject() + writer.name("name") + classNameAdapter.toJson(writer, value.name) + // Other properties would be serialized here. + // For brevity, we are only serializing the name. + writer.endObject() + } +} From 3689de47854929d0f5b72c53b388288ed1858772 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 12 Sep 2025 17:50:41 +0800 Subject: [PATCH 3/3] Test `moduleMetadataFileNotChanged` --- .../KotlinModuleMetadataAdapterTest.kt | 21 ++++++++++++++++++ .../META-INF/kotlin-stdlib.kotlin_module | Bin 0 -> 7998 bytes 2 files changed, 21 insertions(+) create mode 100644 src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/KotlinModuleMetadataAdapterTest.kt create mode 100644 src/test/resources/META-INF/kotlin-stdlib.kotlin_module diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/KotlinModuleMetadataAdapterTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/KotlinModuleMetadataAdapterTest.kt new file mode 100644 index 000000000..9c866f044 --- /dev/null +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/KotlinModuleMetadataAdapterTest.kt @@ -0,0 +1,21 @@ +package com.github.jengelman.gradle.plugins.shadow.internal + +import assertk.assertThat +import assertk.assertions.isEqualTo +import kotlin.io.path.readBytes +import kotlin.metadata.jvm.KotlinModuleMetadata +import kotlin.metadata.jvm.UnstableMetadataApi +import org.junit.jupiter.api.Test + +@OptIn(UnstableMetadataApi::class) +class KotlinModuleMetadataAdapterTest { + + @Test + fun moduleMetadataFileNotChanged() { + val expected = requireResourceAsPath("META-INF/kotlin-stdlib.kotlin_module").readBytes() + val adapter = moshi.adapter(KotlinModuleMetadata::class.java) + val json = adapter.toJson(KotlinModuleMetadata.read(expected)) + val actual = requireNotNull(adapter.fromJson(json)).write() + assertThat(actual).isEqualTo(expected) + } +} diff --git a/src/test/resources/META-INF/kotlin-stdlib.kotlin_module b/src/test/resources/META-INF/kotlin-stdlib.kotlin_module new file mode 100644 index 0000000000000000000000000000000000000000..aa2c0b61c78402e80bfcd2d02e2a3ba31239c80b GIT binary patch literal 7998 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 literal 0 HcmV?d00001