From debb7d31603b54a2fcf1a51eb0a3c2762772491f Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Tue, 14 Jan 2020 14:59:10 -0500 Subject: [PATCH] Generate proguard rules for code gen on demand (#1067) * Extract target constructor signature into TargetConstructor.kt We'll need this to know what the runtime types are for proguard rules * Add ProguardConfig * Wire in proguard config * Write proguard configs out with adapters * Add full tests * Now remove the rules! * Ignore on inline classes for now * Pass adapter constructor params correctly --- kotlin/codegen/pom.xml | 5 + .../codegen/JsonClassCodegenProcessor.kt | 15 +- .../kotlin/codegen/api/AdapterGenerator.kt | 81 ++++++- .../moshi/kotlin/codegen/api/ProguardRules.kt | 104 +++++++++ .../kotlin/codegen/api/TargetConstructor.kt | 3 +- .../squareup/moshi/kotlin/codegen/metadata.kt | 57 +++-- .../codegen/JsonClassCodegenProcessorTest.kt | 205 ++++++++++++++++++ .../resources/META-INF/proguard/moshi.pro | 45 ---- pom.xml | 1 + 9 files changed, 438 insertions(+), 78 deletions(-) create mode 100644 kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/ProguardRules.kt diff --git a/kotlin/codegen/pom.xml b/kotlin/codegen/pom.xml index 1a148b1..c34755a 100644 --- a/kotlin/codegen/pom.xml +++ b/kotlin/codegen/pom.xml @@ -47,6 +47,11 @@ kotlinpoet-classinspector-elements ${kotlinpoet.version} + + org.ow2.asm + asm + ${asm.version} + net.ltgt.gradle.incap incap diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt index bfc2f72..114d21c 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt @@ -108,9 +108,9 @@ class JsonClassCodegenProcessor : AbstractProcessor() { val jsonClass = type.getAnnotation(annotation) if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) { val generator = adapterGenerator(type, cachedClassInspector) ?: continue - generator - .generateFile { - it.toBuilder() + val preparedAdapter = generator + .prepare { spec -> + spec.toBuilder() .apply { generatedType?.asClassName()?.let { generatedClassName -> addAnnotation( @@ -125,14 +125,19 @@ class JsonClassCodegenProcessor : AbstractProcessor() { .addOriginatingElement(type) .build() } - .writeTo(filer) + + preparedAdapter.spec.writeTo(filer) + preparedAdapter.proguardConfig?.writeTo(filer, type) } } return false } - private fun adapterGenerator(element: TypeElement, cachedClassInspector: MoshiCachedClassInspector): AdapterGenerator? { + private fun adapterGenerator( + element: TypeElement, + cachedClassInspector: MoshiCachedClassInspector + ): AdapterGenerator? { val type = targetType(messager, elements, types, element, cachedClassInspector) ?: return null val properties = mutableMapOf() diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt index ac09a75..00214ac 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt @@ -45,6 +45,7 @@ import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterProperty import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.PropertyOnly import java.lang.reflect.Constructor import java.lang.reflect.Type +import org.objectweb.asm.Type as AsmType private val MOSHI_UTIL = Util::class.asClassName() private const val TO_STRING_PREFIX = "GeneratedJsonAdapter(" @@ -52,7 +53,7 @@ private const val TO_STRING_SIZE_BASE = TO_STRING_PREFIX.length + 1 // 1 is the /** Generates a JSON adapter for a target type. */ internal class AdapterGenerator( - target: TargetType, + private val target: TargetType, private val propertyList: List ) { @@ -60,6 +61,8 @@ internal class AdapterGenerator( private val INT_TYPE_BLOCK = CodeBlock.of("%T::class.javaPrimitiveType", INT) private val DEFAULT_CONSTRUCTOR_MARKER_TYPE_BLOCK = CodeBlock.of( "%T.DEFAULT_CONSTRUCTOR_MARKER", Util::class) + private val CN_MOSHI = Moshi::class.asClassName() + private val CN_TYPE = Type::class.asClassName() private val COMMON_SUPPRESS = arrayOf( // https://github.com/square/moshi/issues/1023 @@ -98,10 +101,10 @@ internal class AdapterGenerator( private val moshiParam = ParameterSpec.builder( nameAllocator.newName("moshi"), - Moshi::class).build() + CN_MOSHI).build() private val typesParam = ParameterSpec.builder( nameAllocator.newName("types"), - ARRAY.parameterizedBy(Type::class.asTypeName())) + ARRAY.parameterizedBy(CN_TYPE)) .build() private val readerParam = ParameterSpec.builder( nameAllocator.newName("reader"), @@ -140,15 +143,59 @@ internal class AdapterGenerator( .initializer("null") .build() - fun generateFile(typeHook: (TypeSpec) -> TypeSpec = { it }): FileSpec { + fun prepare(typeHook: (TypeSpec) -> TypeSpec = { it }): PreparedAdapter { for (property in nonTransientProperties) { property.allocateNames(nameAllocator) } + val generatedAdapter = generateType().let(typeHook) val result = FileSpec.builder(className.packageName, adapterName) result.addComment("Code generated by moshi-kotlin-codegen. Do not edit.") - result.addType(generateType().let(typeHook)) - return result.build() + result.addType(generatedAdapter) + return PreparedAdapter(result.build(), generatedAdapter.createProguardRule()) + } + + private fun TypeSpec.createProguardRule(): ProguardConfig { + val adapterProperties = propertySpecs + .asSequence() + .filter { prop -> + prop.type.rawType() == JsonAdapter::class.asClassName() + } + .filter { prop -> prop.annotations.isNotEmpty() } + .mapTo(mutableSetOf()) { prop -> + QualifierAdapterProperty( + name = prop.name, + qualifiers = prop.annotations.mapTo(mutableSetOf()) { it.className } + ) + } + + val adapterConstructorParams = when (requireNotNull(primaryConstructor).parameters.size) { + 1 -> listOf(CN_MOSHI.canonicalName) + 2 -> listOf(CN_MOSHI.canonicalName, "${CN_TYPE.canonicalName}[]") + // Should never happen + else -> error("Unexpected number of arguments on primary constructor: $primaryConstructor") + } + + var hasDefaultProperties = false + var parameterTypes = emptyList() + target.constructor.signature?.let { constructorSignature -> + if (constructorSignature.startsWith("constructor-impl")) { + // Inline class, we don't support this yet. + // This is a static method with signature like 'constructor-impl(I)I' + return@let + } + hasDefaultProperties = propertyList.any { it.hasDefault } + parameterTypes = AsmType.getArgumentTypes(constructorSignature.removePrefix("")) + .map { it.toCanonicalString() } + } + return ProguardConfig( + targetClass = className, + adapterName = adapterName, + adapterConstructorParams = adapterConstructorParams, + targetConstructorHasDefaults = hasDefaultProperties, + targetConstructorParams = parameterTypes, + qualifierProperties = adapterProperties + ) } private fun generateType(): TypeSpec { @@ -518,6 +565,28 @@ internal class AdapterGenerator( } } +/** Represents a prepared adapter with its [spec] and optional associated [proguardConfig]. */ +internal data class PreparedAdapter(val spec: FileSpec, val proguardConfig: ProguardConfig?) + +private fun AsmType.toCanonicalString(): String { + return when (this) { + AsmType.VOID_TYPE -> "void" + AsmType.BOOLEAN_TYPE -> "boolean" + AsmType.CHAR_TYPE -> "char" + AsmType.BYTE_TYPE -> "byte" + AsmType.SHORT_TYPE -> "short" + AsmType.INT_TYPE -> "int" + AsmType.FLOAT_TYPE -> "float" + AsmType.LONG_TYPE -> "long" + AsmType.DOUBLE_TYPE -> "double" + else -> when (sort) { + AsmType.ARRAY -> "${elementType.toCanonicalString()}[]" + // Object type + else -> className + } + } +} + private interface PropertyComponent { val property: PropertyGenerator val type: TypeName diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/ProguardRules.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/ProguardRules.kt new file mode 100644 index 0000000..4ec51d5 --- /dev/null +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/ProguardRules.kt @@ -0,0 +1,104 @@ +package com.squareup.moshi.kotlin.codegen.api + +import com.squareup.kotlinpoet.ClassName +import javax.annotation.processing.Filer +import javax.lang.model.element.Element +import javax.tools.StandardLocation + +/** + * Represents a proguard configuration for a given spec. This covers three main areas: + * - Keeping the target class name to Moshi's reflective lookup of the adapter. + * - Keeping the generated adapter class name + public constructor for reflective lookup. + * - Keeping any used JsonQualifier annotations and the properties they are attached to. + * - If the target class has default parameter values, also keeping the associated synthetic + * constructor as well as the DefaultConstructorMarker type Kotlin adds to it. + * + * Each rule is intended to be as specific and targeted as possible to reduce footprint, and each is + * conditioned on usage of the original target type. + * + * To keep this processor as an ISOLATING incremental processor, we generate one file per target + * class with a deterministic name (see [outputFile]) with an appropriate originating element. + */ +internal data class ProguardConfig( + val targetClass: ClassName, + val adapterName: String, + val adapterConstructorParams: List, + val targetConstructorHasDefaults: Boolean, + val targetConstructorParams: List, + val qualifierProperties: Set +) { + private val outputFile = "META-INF/proguard/moshi-${targetClass.canonicalName}.pro" + + /** Writes this to `filer`. */ + fun writeTo(filer: Filer, vararg originatingElements: Element) { + filer.createResource(StandardLocation.CLASS_OUTPUT, "", outputFile, *originatingElements) + .openWriter() + .use(::writeTo) + } + + private fun writeTo(out: Appendable): Unit = out.run { + // + // -if class {the target class} + // -keepnames class {the target class} + // -if class {the target class} + // -keep class {the generated adapter} { + // (...); + // private final {adapter fields} + // } + // + val targetName = targetClass.canonicalName + val adapterCanonicalName = ClassName(targetClass.packageName, adapterName) + // Keep the class name for Moshi's reflective lookup based on it + appendln("-if class $targetName") + appendln("-keepnames class $targetName") + + appendln("-if class $targetName") + appendln("-keep class $adapterCanonicalName {") + // Keep the constructor for Moshi's reflective lookup + val constructorArgs = adapterConstructorParams.joinToString(",") + appendln(" public ($constructorArgs)") + // Keep any qualifier properties + for (qualifierProperty in qualifierProperties) { + appendln(" private com.squareup.moshi.JsonAdapter ${qualifierProperty.name}") + } + appendln("}") + + qualifierProperties.asSequence() + .flatMap { it.qualifiers.asSequence() } + .map(ClassName::canonicalName) + .sorted() + .forEach { qualifier -> + appendln("-if class $targetName") + appendln("-keep @interface $qualifier") + } + + if (targetConstructorHasDefaults) { + // If the target class has default parameter values, keep its synthetic constructor + // + // -keepnames class kotlin.jvm.internal.DefaultConstructorMarker + // -keepclassmembers @com.squareup.moshi.JsonClass @kotlin.Metadata class * { + // synthetic (...); + // } + // + appendln("-if class $targetName") + appendln("-keepnames class kotlin.jvm.internal.DefaultConstructorMarker") + appendln("-if class $targetName") + appendln("-keepclassmembers class ${targetClass.canonicalName} {") + val allParams = targetConstructorParams.toMutableList() + val maskCount = (targetConstructorParams.size % 32) + 1 + repeat(maskCount) { + allParams += "int" + } + allParams += "kotlin.jvm.internal.DefaultConstructorMarker" + val params = allParams.joinToString(",") + appendln(" public synthetic ($params)") + appendln("}") + } + } +} + +/** + * Represents a qualified property with its [name] in the adapter fields and list of [qualifiers] + * associated with it. + */ +internal data class QualifierAdapterProperty(val name: String, val qualifiers: Set) diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetConstructor.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetConstructor.kt index 742cb4b..94c2358 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetConstructor.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetConstructor.kt @@ -20,7 +20,8 @@ import com.squareup.kotlinpoet.KModifier /** A constructor in user code that should be called by generated code. */ internal data class TargetConstructor( val parameters: LinkedHashMap, - val visibility: KModifier + val visibility: KModifier, + val signature: String? ) { init { visibility.checkIsVisibility() diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt index b020492..87a2201 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt @@ -22,6 +22,7 @@ import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.asClassName import com.squareup.kotlinpoet.asTypeName +import com.squareup.kotlinpoet.metadata.ImmutableKmConstructor import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview import com.squareup.kotlinpoet.metadata.isAbstract import com.squareup.kotlinpoet.metadata.isClass @@ -54,7 +55,7 @@ import javax.lang.model.element.ElementKind import javax.lang.model.element.TypeElement import javax.lang.model.util.Elements import javax.lang.model.util.Types -import javax.tools.Diagnostic +import javax.tools.Diagnostic.Kind.ERROR private val JSON_QUALIFIER = JsonQualifier::class.java private val JSON = Json::class.asClassName() @@ -71,7 +72,12 @@ private fun Collection.visibility(): KModifier { } @KotlinPoetMetadataPreview -internal fun primaryConstructor(kotlinApi: TypeSpec, elements: Elements): TargetConstructor? { +internal fun primaryConstructor( + targetElement: TypeElement, + kotlinApi: TypeSpec, + elements: Elements, + messager: Messager +): TargetConstructor? { val primaryConstructor = kotlinApi.primaryConstructor ?: return null val parameters = LinkedHashMap() @@ -87,7 +93,14 @@ internal fun primaryConstructor(kotlinApi: TypeSpec, elements: Elements): Target ) } - return TargetConstructor(parameters, primaryConstructor.modifiers.visibility()) + val kmConstructorSignature = primaryConstructor.tag()?.signature?.toString() + ?: run { + messager.printMessage(ERROR, "No KmConstructor found for primary constructor.", + targetElement) + null + } + return TargetConstructor(parameters, primaryConstructor.modifiers.visibility(), + kmConstructorSignature) } /** Returns a target type for `element`, or null if it cannot be used with code gen. */ @@ -101,7 +114,7 @@ internal fun targetType(messager: Messager, val typeMetadata = element.getAnnotation(Metadata::class.java) if (typeMetadata == null) { messager.printMessage( - Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", + ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element) return null } @@ -110,7 +123,7 @@ internal fun targetType(messager: Messager, cachedClassInspector.toImmutableKmClass(typeMetadata) } catch (e: UnsupportedOperationException) { messager.printMessage( - Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Class type", + ERROR, "@JsonClass can't be applied to $element: must be a Class type", element) return null } @@ -118,44 +131,44 @@ internal fun targetType(messager: Messager, when { kmClass.isEnum -> { messager.printMessage( - Diagnostic.Kind.ERROR, + ERROR, "@JsonClass with 'generateAdapter = \"true\"' can't be applied to $element: code gen for enums is not supported or necessary", element) return null } !kmClass.isClass -> { messager.printMessage( - Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", + ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element) return null } kmClass.isInner -> { messager.printMessage( - Diagnostic.Kind.ERROR, + ERROR, "@JsonClass can't be applied to $element: must not be an inner class", element) return null } kmClass.isSealed -> { messager.printMessage( - Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be sealed", + ERROR, "@JsonClass can't be applied to $element: must not be sealed", element) return null } kmClass.isAbstract -> { messager.printMessage( - Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be abstract", + ERROR, "@JsonClass can't be applied to $element: must not be abstract", element) return null } kmClass.isLocal -> { messager.printMessage( - Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be local", + ERROR, "@JsonClass can't be applied to $element: must not be local", element) return null } !kmClass.isPublic && !kmClass.isInternal -> { messager.printMessage( - Diagnostic.Kind.ERROR, + ERROR, "@JsonClass can't be applied to $element: must be internal or public", element) return null @@ -166,14 +179,14 @@ internal fun targetType(messager: Messager, val typeVariables = kotlinApi.typeVariables val appliedType = AppliedType.get(element) - val constructor = primaryConstructor(kotlinApi, elements) + val constructor = primaryConstructor(element, kotlinApi, elements, messager) if (constructor == null) { - messager.printMessage(Diagnostic.Kind.ERROR, "No primary constructor found on $element", + messager.printMessage(ERROR, "No primary constructor found on $element", element) return null } if (constructor.visibility != KModifier.INTERNAL && constructor.visibility != KModifier.PUBLIC) { - messager.printMessage(Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: " + + messager.printMessage(ERROR, "@JsonClass can't be applied to $element: " + "primary constructor is not internal or public", element) return null } @@ -186,7 +199,7 @@ internal fun targetType(messager: Messager, } .onEach { supertype -> if (supertype.element.getAnnotation(Metadata::class.java) == null) { - messager.printMessage(Diagnostic.Kind.ERROR, + messager.printMessage(ERROR, "@JsonClass can't be applied to $element: supertype $supertype is not a Kotlin type", element) return null @@ -275,7 +288,7 @@ internal fun TargetProperty.generator( if (isTransient) { if (!hasDefault) { messager.printMessage( - Diagnostic.Kind.ERROR, "No default value for transient property $name", + ERROR, "No default value for transient property $name", sourceElement) return null } @@ -283,7 +296,7 @@ internal fun TargetProperty.generator( } if (!isVisible) { - messager.printMessage(Diagnostic.Kind.ERROR, "property $name is not visible", + messager.printMessage(ERROR, "property $name is not visible", sourceElement) return null } @@ -300,13 +313,13 @@ internal fun TargetProperty.generator( ?: continue annotationElement.getAnnotation(Retention::class.java)?.let { if (it.value != RetentionPolicy.RUNTIME) { - messager.printMessage(Diagnostic.Kind.ERROR, + messager.printMessage(ERROR, "JsonQualifier @${jsonQualifier.className.simpleName} must have RUNTIME retention") } } annotationElement.getAnnotation(Target::class.java)?.let { if (ElementType.FIELD !in it.value) { - messager.printMessage(Diagnostic.Kind.ERROR, + messager.printMessage(ERROR, "JsonQualifier @${jsonQualifier.className.simpleName} must support FIELD target") } } @@ -335,7 +348,9 @@ private fun List?.jsonName(): String? { val mirror = requireNotNull(annotation.tag()) { "Could not get the annotation mirror from the annotation spec" } - mirror.elementValues.entries.single { it.key.simpleName.contentEquals("name") }.value.value as String + mirror.elementValues.entries.single { + it.key.simpleName.contentEquals("name") + }.value.value as String } } diff --git a/kotlin/codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessorTest.kt b/kotlin/codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessorTest.kt index 5d425cc..450ee96 100644 --- a/kotlin/codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessorTest.kt +++ b/kotlin/codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessorTest.kt @@ -398,6 +398,211 @@ class JsonClassCodegenProcessorTest { ) } + @Test + fun `Processor should generate comprehensive proguard rules`() { + val result = compile(kotlin("source.kt", + """ + package testPackage + import com.squareup.moshi.JsonClass + import com.squareup.moshi.JsonQualifier + + typealias FirstName = String + typealias LastName = String + + @JsonClass(generateAdapter = true) + data class Aliases(val firstName: FirstName, val lastName: LastName, val hairColor: String) + + @JsonClass(generateAdapter = true) + data class Simple(val firstName: String) + + @JsonClass(generateAdapter = true) + data class Generic(val firstName: T, val lastName: String) + + @JsonQualifier + annotation class MyQualifier + + @JsonClass(generateAdapter = true) + data class UsingQualifiers(val firstName: String, @MyQualifier val lastName: String) + + @JsonClass(generateAdapter = true) + data class MixedTypes(val firstName: String, val otherNames: MutableList) + + @JsonClass(generateAdapter = true) + data class DefaultParams(val firstName: String = "") + + @JsonClass(generateAdapter = true) + data class Complex(val firstName: FirstName = "", @MyQualifier val names: MutableList, val genericProp: T) + + @JsonClass(generateAdapter = true) + class MultipleMasks( + val arg0: Long = 0, + val arg1: Long = 1, + val arg2: Long = 2, + val arg3: Long = 3, + val arg4: Long = 4, + val arg5: Long = 5, + val arg6: Long = 6, + val arg7: Long = 7, + val arg8: Long = 8, + val arg9: Long = 9, + val arg10: Long = 10, + val arg11: Long, + val arg12: Long = 12, + val arg13: Long = 13, + val arg14: Long = 14, + val arg15: Long = 15, + val arg16: Long = 16, + val arg17: Long = 17, + val arg18: Long = 18, + val arg19: Long = 19, + @Suppress("UNUSED_PARAMETER") arg20: Long = 20, + val arg21: Long = 21, + val arg22: Long = 22, + val arg23: Long = 23, + val arg24: Long = 24, + val arg25: Long = 25, + val arg26: Long = 26, + val arg27: Long = 27, + val arg28: Long = 28, + val arg29: Long = 29, + val arg30: Long = 30, + val arg31: Long = 31, + val arg32: Long = 32, + val arg33: Long = 33, + val arg34: Long = 34, + val arg35: Long = 35, + val arg36: Long = 36, + val arg37: Long = 37, + val arg38: Long = 38, + @Transient val arg39: Long = 39, + val arg40: Long = 40, + val arg41: Long = 41, + val arg42: Long = 42, + val arg43: Long = 43, + val arg44: Long = 44, + val arg45: Long = 45, + val arg46: Long = 46, + val arg47: Long = 47, + val arg48: Long = 48, + val arg49: Long = 49, + val arg50: Long = 50, + val arg51: Long = 51, + val arg52: Long = 52, + @Transient val arg53: Long = 53, + val arg54: Long = 54, + val arg55: Long = 55, + val arg56: Long = 56, + val arg57: Long = 57, + val arg58: Long = 58, + val arg59: Long = 59, + val arg60: Long = 60, + val arg61: Long = 61, + val arg62: Long = 62, + val arg63: Long = 63, + val arg64: Long = 64, + val arg65: Long = 65 + ) + """ + )) + assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) + + assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.Aliases.pro" }).hasContent(""" + -if class testPackage.Aliases + -keepnames class testPackage.Aliases + -if class testPackage.Aliases + -keep class testPackage.AliasesJsonAdapter { + public (com.squareup.moshi.Moshi) + } + """.trimIndent()) + + assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.Simple.pro" }).hasContent(""" + -if class testPackage.Simple + -keepnames class testPackage.Simple + -if class testPackage.Simple + -keep class testPackage.SimpleJsonAdapter { + public (com.squareup.moshi.Moshi) + } + """.trimIndent()) + + assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.Generic.pro" }).hasContent(""" + -if class testPackage.Generic + -keepnames class testPackage.Generic + -if class testPackage.Generic + -keep class testPackage.GenericJsonAdapter { + public (com.squareup.moshi.Moshi,java.lang.reflect.Type[]) + } + """.trimIndent()) + + assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.UsingQualifiers.pro" }).hasContent(""" + -if class testPackage.UsingQualifiers + -keepnames class testPackage.UsingQualifiers + -if class testPackage.UsingQualifiers + -keep class testPackage.UsingQualifiersJsonAdapter { + public (com.squareup.moshi.Moshi) + private com.squareup.moshi.JsonAdapter stringAtMyQualifierAdapter + } + -if class testPackage.UsingQualifiers + -keep @interface testPackage.MyQualifier + """.trimIndent()) + + assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.MixedTypes.pro" }).hasContent(""" + -if class testPackage.MixedTypes + -keepnames class testPackage.MixedTypes + -if class testPackage.MixedTypes + -keep class testPackage.MixedTypesJsonAdapter { + public (com.squareup.moshi.Moshi) + } + """.trimIndent()) + + assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.DefaultParams.pro" }).hasContent(""" + -if class testPackage.DefaultParams + -keepnames class testPackage.DefaultParams + -if class testPackage.DefaultParams + -keep class testPackage.DefaultParamsJsonAdapter { + public (com.squareup.moshi.Moshi) + } + -if class testPackage.DefaultParams + -keepnames class kotlin.jvm.internal.DefaultConstructorMarker + -if class testPackage.DefaultParams + -keepclassmembers class testPackage.DefaultParams { + public synthetic (java.lang.String,int,int,kotlin.jvm.internal.DefaultConstructorMarker) + } + """.trimIndent()) + + assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.Complex.pro" }).hasContent(""" + -if class testPackage.Complex + -keepnames class testPackage.Complex + -if class testPackage.Complex + -keep class testPackage.ComplexJsonAdapter { + public (com.squareup.moshi.Moshi,java.lang.reflect.Type[]) + private com.squareup.moshi.JsonAdapter mutableListOfStringAtMyQualifierAdapter + } + -if class testPackage.Complex + -keep @interface testPackage.MyQualifier + -if class testPackage.Complex + -keepnames class kotlin.jvm.internal.DefaultConstructorMarker + -if class testPackage.Complex + -keepclassmembers class testPackage.Complex { + public synthetic (java.lang.String,java.util.List,java.lang.Object,int,int,int,int,kotlin.jvm.internal.DefaultConstructorMarker) + } + """.trimIndent()) + + assertThat(result.generatedFiles.find { it.name == "moshi-testPackage.MultipleMasks.pro" }).hasContent(""" + -if class testPackage.MultipleMasks + -keepnames class testPackage.MultipleMasks + -if class testPackage.MultipleMasks + -keep class testPackage.MultipleMasksJsonAdapter { + public (com.squareup.moshi.Moshi) + } + -if class testPackage.MultipleMasks + -keepnames class kotlin.jvm.internal.DefaultConstructorMarker + -if class testPackage.MultipleMasks + -keepclassmembers class testPackage.MultipleMasks { + public synthetic (long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,long,int,int,int,kotlin.jvm.internal.DefaultConstructorMarker) + } + """.trimIndent()) + } + private fun prepareCompilation(vararg sourceFiles: SourceFile): KotlinCompilation { return KotlinCompilation() .apply { diff --git a/moshi/src/main/resources/META-INF/proguard/moshi.pro b/moshi/src/main/resources/META-INF/proguard/moshi.pro index a63c38a..c4cea38 100644 --- a/moshi/src/main/resources/META-INF/proguard/moshi.pro +++ b/moshi/src/main/resources/META-INF/proguard/moshi.pro @@ -14,48 +14,3 @@ ; **[] values(); } - -# The name of @JsonClass types is used to look up the generated adapter. --keepnames @com.squareup.moshi.JsonClass class * - -# Retain generated target class's synthetic defaults constructor and keep DefaultConstructorMarker's -# name. We will look this up reflectively to invoke the type's constructor. -# -# We can't _just_ keep the defaults constructor because Proguard/R8's spec doesn't allow wildcard -# matching preceding parameters. --keepnames class kotlin.jvm.internal.DefaultConstructorMarker --keepclassmembers @com.squareup.moshi.JsonClass @kotlin.Metadata class * { - synthetic (...); -} - -# Retain generated JsonAdapters if annotated type is retained. --if @com.squareup.moshi.JsonClass class * --keep class <1>JsonAdapter { - (...); - ; -} --if @com.squareup.moshi.JsonClass class **$* --keep class <1>_<2>JsonAdapter { - (...); - ; -} --if @com.squareup.moshi.JsonClass class **$*$* --keep class <1>_<2>_<3>JsonAdapter { - (...); - ; -} --if @com.squareup.moshi.JsonClass class **$*$*$* --keep class <1>_<2>_<3>_<4>JsonAdapter { - (...); - ; -} --if @com.squareup.moshi.JsonClass class **$*$*$*$* --keep class <1>_<2>_<3>_<4>_<5>JsonAdapter { - (...); - ; -} --if @com.squareup.moshi.JsonClass class **$*$*$*$*$* --keep class <1>_<2>_<3>_<4>_<5>_<6>JsonAdapter { - (...); - ; -} diff --git a/pom.xml b/pom.xml index 39ce45d..51d809f 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ 2.1.0 1.3.60 1.5.0 + 7.1 0.1.0 3.1.0