diff --git a/.travis.yml b/.travis.yml index 637d39b0..f0b0d32c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,8 @@ language: android jdk: - oraclejdk8 - - openjdk11 + # TODO Java 11 is currently disabled because R8 and ProGuard (in codegen build) require rt.jar + #- openjdk11 before_install: # Install SDK license so Android Gradle plugin can install deps. diff --git a/codegen/build.gradle b/codegen/build.gradle index 0b3f6fbc..3af70f2b 100644 --- a/codegen/build.gradle +++ b/codegen/build.gradle @@ -1,13 +1,118 @@ +import org.gradle.internal.jvm.Jvm + apply plugin: 'java-library' sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 +configurations { + r8 + proGuard +} + +// TODO Remove when R8 is updated to 1.5 or newer which pulls kotlinx.metadata from Maven Central. +repositories { + maven { + url 'https://kotlin.bintray.com/kotlinx' + } +} + dependencies { testImplementation deps.junit testImplementation deps.truth testImplementation deps.dagger.runtime testAnnotationProcessor deps.dagger.compiler + + r8 'com.android.tools:r8:1.4.93' + proGuard 'net.sf.proguard:proguard-base:6.1.1' +} + +def r8Classes = new File(buildDir, 'classes/java/testR8') +task runR8(type: JavaExec, dependsOn: 'testClasses') { + classpath = configurations.r8 + main = 'com.android.tools.r8.R8' + + doFirst { + if (r8Classes.exists()) { + r8Classes.deleteDir() + } + r8Classes.mkdirs() + + def arguments = [ + '--release', + '--classfile', + '--output', r8Classes.absolutePath, + '--pg-conf', file('src/test/rules.pro').absolutePath, + '--pg-conf', file('src/main/resources/META-INF/proguard/dagger-reflect.pro').absolutePath, + '--lib', new File(Jvm.current().getJavaHome(), "jre/lib/rt.jar").absolutePath + ] + sourceSets.test.compileClasspath.each { + if (it.exists()) { + arguments.add('--lib') + arguments.add(it.absolutePath) + } + } + arguments.addAll(sourceSets.test.output.classesDirs.asFileTree) + + args = arguments + } +} + +task testR8(type: Test, dependsOn: 'runR8') { + description = 'Run tests after optimizing with R8' + group = 'verification' + + testClassesDirs = files(r8Classes) + classpath = files(r8Classes, sourceSets.test.compileClasspath) + + inputs.dir(r8Classes) +} +check.dependsOn('testR8') + +def proguardClasses = new File(buildDir, 'classes/java/testProGuard') +task runProGuard(type: JavaExec, dependsOn: 'testClasses') { + classpath = configurations.proGuard + main = 'proguard.ProGuard' + + doFirst { + if (proguardClasses.exists()) { + proguardClasses.deleteDir() + } + proguardClasses.mkdirs() + + def arguments = [ + '-include', file('src/test/rules.pro').absolutePath, + '-include', file('src/main/resources/META-INF/proguard/dagger-reflect.pro').absolutePath, + '-libraryjars', new File(Jvm.current().getJavaHome(), "jre/lib/rt.jar").absolutePath, + ] + sourceSets.test.compileClasspath.each { + if (it.exists()) { + arguments.add('-libraryjars') + arguments.add(it.absolutePath) + } + } + sourceSets.test.output.classesDirs.each { + if (it.exists()) { + arguments.add('-injars') + arguments.add(it.absolutePath) + } + } + arguments.add('-outjars') + arguments.add(proguardClasses.absolutePath) + + args = arguments + } +} + +task testProGuard(type: Test, dependsOn: 'runProGuard') { + description = 'Run tests after optimizing with ProGuard' + group = 'verification' + + testClassesDirs = files(proguardClasses) + classpath = files(proguardClasses, sourceSets.test.compileClasspath) + + inputs.dir(proguardClasses) } +check.dependsOn('testProGuard') apply from: rootProject.file('gradle/gradle-mvn-push.gradle') diff --git a/codegen/src/main/resources/META-INF/proguard/dagger-reflect.pro b/codegen/src/main/resources/META-INF/proguard/dagger-reflect.pro new file mode 100644 index 00000000..fac048ea --- /dev/null +++ b/codegen/src/main/resources/META-INF/proguard/dagger-reflect.pro @@ -0,0 +1,49 @@ +# When using factories or builders, the enclosing class is used to determine the component type. +-keepattributes InnerClasses +-keepattributes EnclosingMethod # Required by InnerClasses + +# JSR 330 and Dagger annotations like @Inject, @Provide, @Component, etc. are obviously needed. +-keepattributes RuntimeVisibleAnnotations + +# Annotation defaults are required for @MapKey, @Component, @Subcomponent, etc. +-keepattributes AnnotationDefault + +# Generic signatures are needed for properly parsing types of module methods, component methods, +# and injected fields/methods. +-keepattributes Signature + +# The names of component and subcomponent types must be kept so the Dagger-generated type can be +# resolved by prepending a prefix to the name. This should also prevent vertical class merging. +-keepnames @dagger.Component class * +-keepnames @dagger.Subcomponent class * + +# For each component type, keep the corresponding Dagger-generated type. We also keep the create +# method, in case there is no builder or factory. +-if @dagger.Component class **.* +-keep class <1>.Dagger<2> { + static ** create(); +} + +# For each component builder type that is kept, keep the name of that type which will ensure it +# remains nested inside its corresponding component type. +-if @dagger.Component$Builder class **.*$* +-keep class <1>.<2> + +# For each component builder type that is kept, keep the corresponding Dagger-generated component +# type and the factory method for the builder. +-if @dagger.Component$Builder class **.*$* +-keep class <1>.Dagger<2> { + static ** builder(); +} + +# For each component factory type that is kept, keep the name of that type which will ensure it +# remains nested inside its corresponding component type. +-if @dagger.Component$Factory class **.*$* +-keep class <1>.<2> + +# For each component factory type, keep the corresponding Dagger-generated component type and the +# factory method for the factory. +-if @dagger.Component$Factory class **.*$* +-keep class <1>.Dagger<2> { + static ** factory(); +} diff --git a/codegen/src/test/java/dagger/BuilderComponentNoAnnotation.java b/codegen/src/test/java/dagger/BuilderComponentNoAnnotation.java index f7a3d84e..17c73a5f 100644 --- a/codegen/src/test/java/dagger/BuilderComponentNoAnnotation.java +++ b/codegen/src/test/java/dagger/BuilderComponentNoAnnotation.java @@ -16,6 +16,7 @@ package dagger; interface BuilderComponentNoAnnotation { + @Keep interface Builder { BuilderComponentNoAnnotation build(); } diff --git a/codegen/src/test/java/dagger/DaggerCodegenTest.java b/codegen/src/test/java/dagger/DaggerCodegenTest.java index 6f2b3563..e76845ca 100644 --- a/codegen/src/test/java/dagger/DaggerCodegenTest.java +++ b/codegen/src/test/java/dagger/DaggerCodegenTest.java @@ -23,8 +23,8 @@ public final class DaggerCodegenTest { @Test public void create() { - JustComponent actual = Dagger.create(JustComponent.class); - assertThat(actual).isInstanceOf(JustComponent.class); + JustComponent component = Dagger.create(JustComponent.class); + assertThat(component).isInstanceOf(JustComponent.class); } @Test @@ -37,15 +37,20 @@ public void createNoAnnotation() { .hasMessageThat() .isEqualTo( "Unable to find generated component implementation " - + "dagger.DaggerJustComponentNoAnnotation for component " - + "dagger.JustComponentNoAnnotation"); + + "dagger.Dagger" + + JustComponentNoAnnotation.class.getSimpleName() + + " for component " + + "dagger." + + JustComponentNoAnnotation.class.getSimpleName()); } } @Test public void builder() { - BuilderComponent.Builder actual = Dagger.builder(BuilderComponent.Builder.class); - assertThat(actual).isInstanceOf(DaggerBuilderComponent.builder().getClass()); + BuilderComponent.Builder builder = Dagger.builder(BuilderComponent.Builder.class); + assertThat(builder).isInstanceOf(DaggerBuilderComponent.builder().getClass()); + BuilderComponent component = builder.build(); + assertThat(component).isInstanceOf(DaggerBuilderComponent.class); } @Test @@ -58,8 +63,11 @@ public void builderNoAnnotation() { .hasMessageThat() .isEqualTo( "Unable to find generated component implementation " - + "dagger.DaggerBuilderComponentNoAnnotation for component " - + "dagger.BuilderComponentNoAnnotation"); + + "dagger.Dagger" + + BuilderComponentNoAnnotation.class.getSimpleName() + + " for component " + + "dagger." + + BuilderComponentNoAnnotation.class.getSimpleName()); } } @@ -72,14 +80,17 @@ public void builderNotNested() { assertThat(e) .hasMessageThat() .isEqualTo( - "dagger.BuilderNotNested is not a nested type inside of a component interface"); + BuilderNotNested.class.getName() + + " is not a nested type inside of a component interface"); } } @Test public void factory() { - FactoryComponent.Factory actual = Dagger.factory(FactoryComponent.Factory.class); - assertThat(actual).isInstanceOf(DaggerFactoryComponent.factory().getClass()); + FactoryComponent.Factory factory = Dagger.factory(FactoryComponent.Factory.class); + assertThat(factory).isInstanceOf(DaggerFactoryComponent.factory().getClass()); + FactoryComponent component = factory.create(); + assertThat(component).isInstanceOf(DaggerFactoryComponent.class); } @Test @@ -92,8 +103,11 @@ public void factoryNoAnnotation() { .hasMessageThat() .isEqualTo( "Unable to find generated component implementation " - + "dagger.DaggerFactoryComponentNoAnnotation for component " - + "dagger.FactoryComponentNoAnnotation"); + + "dagger.Dagger" + + FactoryComponentNoAnnotation.class.getSimpleName() + + " for component " + + "dagger." + + FactoryComponentNoAnnotation.class.getSimpleName()); } } @@ -106,7 +120,8 @@ public void factoryNotNested() { assertThat(e) .hasMessageThat() .isEqualTo( - "dagger.FactoryNotNested is not a nested type inside of a component interface"); + FactoryNotNested.class.getName() + + " is not a nested type inside of a component interface"); } } } diff --git a/codegen/src/test/java/dagger/FactoryComponentNoAnnotation.java b/codegen/src/test/java/dagger/FactoryComponentNoAnnotation.java index 285b55e2..295ca672 100644 --- a/codegen/src/test/java/dagger/FactoryComponentNoAnnotation.java +++ b/codegen/src/test/java/dagger/FactoryComponentNoAnnotation.java @@ -1,6 +1,7 @@ package dagger; interface FactoryComponentNoAnnotation { + @Keep interface Factory { FactoryComponentNoAnnotation create(); } diff --git a/codegen/src/test/java/dagger/Keep.java b/codegen/src/test/java/dagger/Keep.java new file mode 100644 index 00000000..48abbd2a --- /dev/null +++ b/codegen/src/test/java/dagger/Keep.java @@ -0,0 +1,8 @@ +package dagger; + +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; + +@Retention(CLASS) +public @interface Keep {} diff --git a/codegen/src/test/rules.pro b/codegen/src/test/rules.pro new file mode 100644 index 00000000..12c09114 --- /dev/null +++ b/codegen/src/test/rules.pro @@ -0,0 +1,18 @@ +# @org.junit.Test +-keepattributes RuntimeVisibleAnnotations + +# Test methods are invoked reflectively. +-keepclasseswithmembers class * { + @org.junit.Test ; +} + +# Test classes are constructed reflectively. +-keepclassmembers class **.*Test { + (...); +} + +# Annotation to preserve conditions required for tests which otherwise aren't automatically kept. +-keep @dagger.Keep class * + +# For Mac OS and its case-insensitive (by default) filesystem. +-dontusemixedcaseclassnames