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
This commit is contained in:
Zac Sweers
2020-01-14 14:59:10 -05:00
committed by GitHub
parent f891c8187b
commit debb7d3160
9 changed files with 438 additions and 78 deletions

View File

@@ -47,6 +47,11 @@
<artifactId>kotlinpoet-classinspector-elements</artifactId> <artifactId>kotlinpoet-classinspector-elements</artifactId>
<version>${kotlinpoet.version}</version> <version>${kotlinpoet.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency> <dependency>
<groupId>net.ltgt.gradle.incap</groupId> <groupId>net.ltgt.gradle.incap</groupId>
<artifactId>incap</artifactId> <artifactId>incap</artifactId>

View File

@@ -108,9 +108,9 @@ class JsonClassCodegenProcessor : AbstractProcessor() {
val jsonClass = type.getAnnotation(annotation) val jsonClass = type.getAnnotation(annotation)
if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) { if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) {
val generator = adapterGenerator(type, cachedClassInspector) ?: continue val generator = adapterGenerator(type, cachedClassInspector) ?: continue
generator val preparedAdapter = generator
.generateFile { .prepare { spec ->
it.toBuilder() spec.toBuilder()
.apply { .apply {
generatedType?.asClassName()?.let { generatedClassName -> generatedType?.asClassName()?.let { generatedClassName ->
addAnnotation( addAnnotation(
@@ -125,14 +125,19 @@ class JsonClassCodegenProcessor : AbstractProcessor() {
.addOriginatingElement(type) .addOriginatingElement(type)
.build() .build()
} }
.writeTo(filer)
preparedAdapter.spec.writeTo(filer)
preparedAdapter.proguardConfig?.writeTo(filer, type)
} }
} }
return false 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 type = targetType(messager, elements, types, element, cachedClassInspector) ?: return null
val properties = mutableMapOf<String, PropertyGenerator>() val properties = mutableMapOf<String, PropertyGenerator>()

View File

@@ -45,6 +45,7 @@ import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterProperty
import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.PropertyOnly import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.PropertyOnly
import java.lang.reflect.Constructor import java.lang.reflect.Constructor
import java.lang.reflect.Type import java.lang.reflect.Type
import org.objectweb.asm.Type as AsmType
private val MOSHI_UTIL = Util::class.asClassName() private val MOSHI_UTIL = Util::class.asClassName()
private const val TO_STRING_PREFIX = "GeneratedJsonAdapter(" 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. */ /** Generates a JSON adapter for a target type. */
internal class AdapterGenerator( internal class AdapterGenerator(
target: TargetType, private val target: TargetType,
private val propertyList: List<PropertyGenerator> private val propertyList: List<PropertyGenerator>
) { ) {
@@ -60,6 +61,8 @@ internal class AdapterGenerator(
private val INT_TYPE_BLOCK = CodeBlock.of("%T::class.javaPrimitiveType", INT) private val INT_TYPE_BLOCK = CodeBlock.of("%T::class.javaPrimitiveType", INT)
private val DEFAULT_CONSTRUCTOR_MARKER_TYPE_BLOCK = CodeBlock.of( private val DEFAULT_CONSTRUCTOR_MARKER_TYPE_BLOCK = CodeBlock.of(
"%T.DEFAULT_CONSTRUCTOR_MARKER", Util::class) "%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( private val COMMON_SUPPRESS = arrayOf(
// https://github.com/square/moshi/issues/1023 // https://github.com/square/moshi/issues/1023
@@ -98,10 +101,10 @@ internal class AdapterGenerator(
private val moshiParam = ParameterSpec.builder( private val moshiParam = ParameterSpec.builder(
nameAllocator.newName("moshi"), nameAllocator.newName("moshi"),
Moshi::class).build() CN_MOSHI).build()
private val typesParam = ParameterSpec.builder( private val typesParam = ParameterSpec.builder(
nameAllocator.newName("types"), nameAllocator.newName("types"),
ARRAY.parameterizedBy(Type::class.asTypeName())) ARRAY.parameterizedBy(CN_TYPE))
.build() .build()
private val readerParam = ParameterSpec.builder( private val readerParam = ParameterSpec.builder(
nameAllocator.newName("reader"), nameAllocator.newName("reader"),
@@ -140,15 +143,59 @@ internal class AdapterGenerator(
.initializer("null") .initializer("null")
.build() .build()
fun generateFile(typeHook: (TypeSpec) -> TypeSpec = { it }): FileSpec { fun prepare(typeHook: (TypeSpec) -> TypeSpec = { it }): PreparedAdapter {
for (property in nonTransientProperties) { for (property in nonTransientProperties) {
property.allocateNames(nameAllocator) property.allocateNames(nameAllocator)
} }
val generatedAdapter = generateType().let(typeHook)
val result = FileSpec.builder(className.packageName, adapterName) val result = FileSpec.builder(className.packageName, adapterName)
result.addComment("Code generated by moshi-kotlin-codegen. Do not edit.") result.addComment("Code generated by moshi-kotlin-codegen. Do not edit.")
result.addType(generateType().let(typeHook)) result.addType(generatedAdapter)
return result.build() 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<String>()
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("<init>"))
.map { it.toCanonicalString() }
}
return ProguardConfig(
targetClass = className,
adapterName = adapterName,
adapterConstructorParams = adapterConstructorParams,
targetConstructorHasDefaults = hasDefaultProperties,
targetConstructorParams = parameterTypes,
qualifierProperties = adapterProperties
)
} }
private fun generateType(): TypeSpec { 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 { private interface PropertyComponent {
val property: PropertyGenerator val property: PropertyGenerator
val type: TypeName val type: TypeName

View File

@@ -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<String>,
val targetConstructorHasDefaults: Boolean,
val targetConstructorParams: List<String>,
val qualifierProperties: Set<QualifierAdapterProperty>
) {
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} {
// <init>(...);
// 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 <init>($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 <init>(...);
// }
//
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 <init>($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<ClassName>)

View File

@@ -20,7 +20,8 @@ import com.squareup.kotlinpoet.KModifier
/** A constructor in user code that should be called by generated code. */ /** A constructor in user code that should be called by generated code. */
internal data class TargetConstructor( internal data class TargetConstructor(
val parameters: LinkedHashMap<String, TargetParameter>, val parameters: LinkedHashMap<String, TargetParameter>,
val visibility: KModifier val visibility: KModifier,
val signature: String?
) { ) {
init { init {
visibility.checkIsVisibility() visibility.checkIsVisibility()

View File

@@ -22,6 +22,7 @@ import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.asTypeName import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.metadata.ImmutableKmConstructor
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.kotlinpoet.metadata.isAbstract import com.squareup.kotlinpoet.metadata.isAbstract
import com.squareup.kotlinpoet.metadata.isClass 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.element.TypeElement
import javax.lang.model.util.Elements import javax.lang.model.util.Elements
import javax.lang.model.util.Types 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_QUALIFIER = JsonQualifier::class.java
private val JSON = Json::class.asClassName() private val JSON = Json::class.asClassName()
@@ -71,7 +72,12 @@ private fun Collection<KModifier>.visibility(): KModifier {
} }
@KotlinPoetMetadataPreview @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 primaryConstructor = kotlinApi.primaryConstructor ?: return null
val parameters = LinkedHashMap<String, TargetParameter>() val parameters = LinkedHashMap<String, TargetParameter>()
@@ -87,7 +93,14 @@ internal fun primaryConstructor(kotlinApi: TypeSpec, elements: Elements): Target
) )
} }
return TargetConstructor(parameters, primaryConstructor.modifiers.visibility()) val kmConstructorSignature = primaryConstructor.tag<ImmutableKmConstructor>()?.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. */ /** 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) val typeMetadata = element.getAnnotation(Metadata::class.java)
if (typeMetadata == null) { if (typeMetadata == null) {
messager.printMessage( 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) element)
return null return null
} }
@@ -110,7 +123,7 @@ internal fun targetType(messager: Messager,
cachedClassInspector.toImmutableKmClass(typeMetadata) cachedClassInspector.toImmutableKmClass(typeMetadata)
} catch (e: UnsupportedOperationException) { } catch (e: UnsupportedOperationException) {
messager.printMessage( 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) element)
return null return null
} }
@@ -118,44 +131,44 @@ internal fun targetType(messager: Messager,
when { when {
kmClass.isEnum -> { kmClass.isEnum -> {
messager.printMessage( 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", "@JsonClass with 'generateAdapter = \"true\"' can't be applied to $element: code gen for enums is not supported or necessary",
element) element)
return null return null
} }
!kmClass.isClass -> { !kmClass.isClass -> {
messager.printMessage( 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) element)
return null return null
} }
kmClass.isInner -> { kmClass.isInner -> {
messager.printMessage( messager.printMessage(
Diagnostic.Kind.ERROR, ERROR,
"@JsonClass can't be applied to $element: must not be an inner class", element) "@JsonClass can't be applied to $element: must not be an inner class", element)
return null return null
} }
kmClass.isSealed -> { kmClass.isSealed -> {
messager.printMessage( 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) element)
return null return null
} }
kmClass.isAbstract -> { kmClass.isAbstract -> {
messager.printMessage( 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) element)
return null return null
} }
kmClass.isLocal -> { kmClass.isLocal -> {
messager.printMessage( 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) element)
return null return null
} }
!kmClass.isPublic && !kmClass.isInternal -> { !kmClass.isPublic && !kmClass.isInternal -> {
messager.printMessage( messager.printMessage(
Diagnostic.Kind.ERROR, ERROR,
"@JsonClass can't be applied to $element: must be internal or public", "@JsonClass can't be applied to $element: must be internal or public",
element) element)
return null return null
@@ -166,14 +179,14 @@ internal fun targetType(messager: Messager,
val typeVariables = kotlinApi.typeVariables val typeVariables = kotlinApi.typeVariables
val appliedType = AppliedType.get(element) val appliedType = AppliedType.get(element)
val constructor = primaryConstructor(kotlinApi, elements) val constructor = primaryConstructor(element, kotlinApi, elements, messager)
if (constructor == null) { if (constructor == null) {
messager.printMessage(Diagnostic.Kind.ERROR, "No primary constructor found on $element", messager.printMessage(ERROR, "No primary constructor found on $element",
element) element)
return null return null
} }
if (constructor.visibility != KModifier.INTERNAL && constructor.visibility != KModifier.PUBLIC) { 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) "primary constructor is not internal or public", element)
return null return null
} }
@@ -186,7 +199,7 @@ internal fun targetType(messager: Messager,
} }
.onEach { supertype -> .onEach { supertype ->
if (supertype.element.getAnnotation(Metadata::class.java) == null) { 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", "@JsonClass can't be applied to $element: supertype $supertype is not a Kotlin type",
element) element)
return null return null
@@ -275,7 +288,7 @@ internal fun TargetProperty.generator(
if (isTransient) { if (isTransient) {
if (!hasDefault) { if (!hasDefault) {
messager.printMessage( messager.printMessage(
Diagnostic.Kind.ERROR, "No default value for transient property $name", ERROR, "No default value for transient property $name",
sourceElement) sourceElement)
return null return null
} }
@@ -283,7 +296,7 @@ internal fun TargetProperty.generator(
} }
if (!isVisible) { if (!isVisible) {
messager.printMessage(Diagnostic.Kind.ERROR, "property $name is not visible", messager.printMessage(ERROR, "property $name is not visible",
sourceElement) sourceElement)
return null return null
} }
@@ -300,13 +313,13 @@ internal fun TargetProperty.generator(
?: continue ?: continue
annotationElement.getAnnotation(Retention::class.java)?.let { annotationElement.getAnnotation(Retention::class.java)?.let {
if (it.value != RetentionPolicy.RUNTIME) { if (it.value != RetentionPolicy.RUNTIME) {
messager.printMessage(Diagnostic.Kind.ERROR, messager.printMessage(ERROR,
"JsonQualifier @${jsonQualifier.className.simpleName} must have RUNTIME retention") "JsonQualifier @${jsonQualifier.className.simpleName} must have RUNTIME retention")
} }
} }
annotationElement.getAnnotation(Target::class.java)?.let { annotationElement.getAnnotation(Target::class.java)?.let {
if (ElementType.FIELD !in it.value) { if (ElementType.FIELD !in it.value) {
messager.printMessage(Diagnostic.Kind.ERROR, messager.printMessage(ERROR,
"JsonQualifier @${jsonQualifier.className.simpleName} must support FIELD target") "JsonQualifier @${jsonQualifier.className.simpleName} must support FIELD target")
} }
} }
@@ -335,7 +348,9 @@ private fun List<AnnotationSpec>?.jsonName(): String? {
val mirror = requireNotNull(annotation.tag<AnnotationMirror>()) { val mirror = requireNotNull(annotation.tag<AnnotationMirror>()) {
"Could not get the annotation mirror from the annotation spec" "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
} }
} }

View File

@@ -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<T>(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<String>)
@JsonClass(generateAdapter = true)
data class DefaultParams(val firstName: String = "")
@JsonClass(generateAdapter = true)
data class Complex<T>(val firstName: FirstName = "", @MyQualifier val names: MutableList<String>, 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 <init>(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 <init>(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 <init>(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 <init>(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 <init>(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 <init>(com.squareup.moshi.Moshi)
}
-if class testPackage.DefaultParams
-keepnames class kotlin.jvm.internal.DefaultConstructorMarker
-if class testPackage.DefaultParams
-keepclassmembers class testPackage.DefaultParams {
public synthetic <init>(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 <init>(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 <init>(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 <init>(com.squareup.moshi.Moshi)
}
-if class testPackage.MultipleMasks
-keepnames class kotlin.jvm.internal.DefaultConstructorMarker
-if class testPackage.MultipleMasks
-keepclassmembers class testPackage.MultipleMasks {
public synthetic <init>(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 { private fun prepareCompilation(vararg sourceFiles: SourceFile): KotlinCompilation {
return KotlinCompilation() return KotlinCompilation()
.apply { .apply {

View File

@@ -14,48 +14,3 @@
<fields>; <fields>;
**[] values(); **[] 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 <init>(...);
}
# Retain generated JsonAdapters if annotated type is retained.
-if @com.squareup.moshi.JsonClass class *
-keep class <1>JsonAdapter {
<init>(...);
<fields>;
}
-if @com.squareup.moshi.JsonClass class **$*
-keep class <1>_<2>JsonAdapter {
<init>(...);
<fields>;
}
-if @com.squareup.moshi.JsonClass class **$*$*
-keep class <1>_<2>_<3>JsonAdapter {
<init>(...);
<fields>;
}
-if @com.squareup.moshi.JsonClass class **$*$*$*
-keep class <1>_<2>_<3>_<4>JsonAdapter {
<init>(...);
<fields>;
}
-if @com.squareup.moshi.JsonClass class **$*$*$*$*
-keep class <1>_<2>_<3>_<4>_<5>JsonAdapter {
<init>(...);
<fields>;
}
-if @com.squareup.moshi.JsonClass class **$*$*$*$*$*
-keep class <1>_<2>_<3>_<4>_<5>_<6>JsonAdapter {
<init>(...);
<fields>;
}

View File

@@ -38,6 +38,7 @@
<okio2.version>2.1.0</okio2.version> <okio2.version>2.1.0</okio2.version>
<kotlin.version>1.3.60</kotlin.version> <kotlin.version>1.3.60</kotlin.version>
<kotlinpoet.version>1.5.0</kotlinpoet.version> <kotlinpoet.version>1.5.0</kotlinpoet.version>
<asm.version>7.1</asm.version>
<kotlinx-metadata.version>0.1.0</kotlinx-metadata.version> <kotlinx-metadata.version>0.1.0</kotlinx-metadata.version>
<maven-assembly.version>3.1.0</maven-assembly.version> <maven-assembly.version>3.1.0</maven-assembly.version>