Skip to content

Commit

Permalink
Generate proguard rules on-demand (#236)
Browse files Browse the repository at this point in the history
* Add ProguardConfig

* Wire in proguard config to externally generated files

* Remove generated file bits

* Add tests

* Nix unused import
  • Loading branch information
ZacSweers committed Jan 15, 2020
1 parent 688eb8e commit b16971a
Show file tree
Hide file tree
Showing 5 changed files with 440 additions and 33 deletions.
@@ -1,35 +1,3 @@
# Annotations are for embedding static analysis information.
-dontwarn org.jetbrains.annotations.**
-dontwarn com.google.errorprone.annotations.**

# Retain generated TypeAdapters if annotated type is retained.
-if @com.ryanharter.auto.value.gson.GenerateTypeAdapter class *
-keep class <1>_GsonTypeAdapter {
<init>(...);
<fields>;
}
-if @com.ryanharter.auto.value.gson.GenerateTypeAdapter class **$*
-keep class <1>_<2>_GsonTypeAdapter {
<init>(...);
<fields>;
}
-if @com.ryanharter.auto.value.gson.GenerateTypeAdapter class **$*$*
-keep class <1>_<2>_<3>_GsonTypeAdapter {
<init>(...);
<fields>;
}
-if @com.ryanharter.auto.value.gson.GenerateTypeAdapter class **$*$*$*
-keep class <1>_<2>_<3>_<4>_GsonTypeAdapter {
<init>(...);
<fields>;
}
-if @com.ryanharter.auto.value.gson.GenerateTypeAdapter class **$*$*$*$*
-keep class <1>_<2>_<3>_<4>_<5>_GsonTypeAdapter {
<init>(...);
<fields>;
}
-if @com.ryanharter.auto.value.gson.GenerateTypeAdapter class **$*$*$*$*$*
-keep class <1>_<2>_<3>_<4>_<5>_<6>_GsonTypeAdapter {
<init>(...);
<fields>;
}
1 change: 1 addition & 0 deletions auto-value-gson/build.gradle
Expand Up @@ -39,6 +39,7 @@ targetCompatibility = JavaVersion.VERSION_1_8
dependencies {
annotationProcessor "net.ltgt.gradle.incap:incap-processor:0.2"
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc5'
annotationProcessor 'com.google.auto.value:auto-value:1.7'
compileOnly "net.ltgt.gradle.incap:incap:0.2"
compileOnly 'com.google.auto.service:auto-service:1.0-rc5'

Expand Down
Expand Up @@ -51,6 +51,7 @@
import java.util.stream.Stream;

import javax.annotation.Nullable;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.SupportedOptions;
Expand Down Expand Up @@ -269,14 +270,15 @@ public String generateClass(Context context, String className, String classToExt
TypeSpec typeAdapter = createTypeAdapter(type, classNameClass, autoValueClass, adapterClassName,
finalSuperClass, properties, params, context.builder().orElse(null), context.processingEnvironment());

Filer filer = context.processingEnvironment().getFiler();
if (generateExternalAdapter) {
try {
TypeSpec.Builder builder = typeAdapter.toBuilder();
generatedAnnotationSpec.ifPresent(builder::addAnnotation);
JavaFile.builder(context.packageName(), builder.build())
.skipJavaLangImports(true)
.build()
.writeTo(context.processingEnvironment().getFiler());
.writeTo(filer);
} catch (IOException e) {
context.processingEnvironment().getMessager()
.printMessage(Diagnostic.Kind.ERROR,
Expand All @@ -285,6 +287,31 @@ public String generateClass(Context context, String className, String classToExt
type,
e.getMessage()));
}

ClassName proguardTarget = ClassName.get(context.autoValueClass());
List<String> adapterConstructorParams = Lists.newArrayList();
typeAdapter.methodSpecs.stream().filter(MethodSpec::isConstructor).findFirst()
.ifPresent(c -> {
for (ParameterSpec p : c.parameters) {
adapterConstructorParams.add(proguardNameOf(p.type));
}
});

ProguardConfig proguardConfig = ProguardConfig.create(
proguardTarget,
adapterClassName,
adapterConstructorParams
);
try {
proguardConfig.writeTo(filer, context.autoValueClass());
} catch (IOException e) {
context.processingEnvironment().getMessager()
.printMessage(Diagnostic.Kind.ERROR,
String.format(
"Failed to write proguard file for element \"%s\" with reason \"%s\"",
context.autoValueClass(),
e.getMessage()));
}
return null;
} else {
TypeSpec.Builder subclass = TypeSpec.classBuilder(classNameClass)
Expand Down Expand Up @@ -926,4 +953,33 @@ private static void buildParameterizedTypeArguments(CodeBlock.Builder block, Typ
block.add("$T.class", typeArg);
}
}

@Nullable
private static ClassName rawType(TypeName typeName) {
if (typeName instanceof ClassName) {
return (ClassName) typeName;
} else if (typeName instanceof ArrayTypeName) {
return rawType(((ArrayTypeName) typeName).componentType);
} else if (typeName instanceof ParameterizedTypeName) {
return ((ParameterizedTypeName) typeName).rawType;
} else if (typeName instanceof WildcardTypeName) {
return rawType(((WildcardTypeName) typeName).upperBounds.get(0));
} else {
return null;
}
}

private static String proguardNameOf(TypeName typeName) {
if (typeName instanceof ClassName) {
return ((ClassName) typeName).canonicalName();
} else if (typeName instanceof ArrayTypeName) {
return proguardNameOf(((ArrayTypeName) typeName).componentType) + "[]";
} else if (typeName instanceof ParameterizedTypeName) {
return ((ParameterizedTypeName) typeName).rawType.canonicalName();
} else if (typeName instanceof TypeVariableName) {
return "java.lang.Object";
} else {
throw new UnsupportedOperationException("Unrecognized TypeName type: " + typeName);
}
}
}
@@ -0,0 +1,85 @@
package com.ryanharter.auto.value.gson;

import com.google.auto.value.AutoValue;
import com.squareup.javapoet.ClassName;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import javax.annotation.processing.Filer;
import javax.lang.model.element.Element;

import static javax.tools.StandardLocation.CLASS_OUTPUT;

/**
* Represents a proguard configuration for a given spec. This covers two main areas:
* <ul>
* <li>Keeping the target class name to GenerateTypeAdapter.FACTORY's reflective lookup of the adapter.</li>
* <li>Keeping the generated adapter class name + public constructor for reflective lookup.</li>
* </ul>
* <p>
* 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.
* <p>
* To keep this processor as an {@code ISOLATING} incremental processor, we generate one file per
* target class with a deterministic name (see {@link #outputFile}) with an appropriate originating
* element.
*/
@AutoValue
abstract class ProguardConfig {
abstract ClassName targetClass();
abstract ClassName adapterName();
abstract List<String> adapterConstructorParams();
abstract String outputFile();

static ProguardConfig create(
ClassName targetClass,
ClassName adapterName,
List<String> adapterConstructorParams) {
String outputFile = "META-INF/proguard/avg-" + targetClass.canonicalName() + ".pro";
return new AutoValue_ProguardConfig(targetClass,
adapterName,
adapterConstructorParams,
outputFile);
}

/** Writes this to {@code filer}. */
final void writeTo(Filer filer, Element... originatingElements) throws IOException {
try (Writer writer = filer.createResource(CLASS_OUTPUT, "", outputFile(), originatingElements)
.openWriter()) {
writeTo(writer);
}
}

private void writeTo(Appendable out) throws IOException {
//
// -if class {the target class}
// -keepnames class {the target class}
// -if class {the target class}
// -keep class {the generated adapter} {
// <init>(...);
// }
//
String targetName = targetClass().canonicalName();
String adapterCanonicalName = adapterName().canonicalName();
// Keep the class name for GenerateTypeAdapter.FACTORY's reflective lookup based on it
out.append("-if class ")
.append(targetName)
.append("\n");
out.append("-keepnames class ")
.append(targetName)
.append("\n");
out.append("-if class ")
.append(targetName)
.append("\n");
out.append("-keep class ")
.append(adapterCanonicalName)
.append(" {\n");

// Keep the constructor for GenerateTypeAdapter.FACTORY's reflective lookup
String constructorArgs = String.join(",", adapterConstructorParams());
out.append(" <init>(")
.append(constructorArgs)
.append(");\n");
out.append("}\n");
}
}

0 comments on commit b16971a

Please sign in to comment.