Skip to content
This repository was archived by the owner on Aug 20, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
105 changes: 105 additions & 0 deletions codegen/build.gradle
Original file line number Diff line number Diff line change
@@ -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')
49 changes: 49 additions & 0 deletions codegen/src/main/resources/META-INF/proguard/dagger-reflect.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# When using factories or builders, the enclosing class is used to determine the component type.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a mention of @Binds which I think may be a special case as it's not referenced by generated Dagger component code. Is it covered by one of these rules?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These rules are for the codegen artifact reflection, not the reflection in the reflect artifact.

I'm not convinced anyone will be using shrinking with the reflect artifact. I'd be curious what their use case for this was.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good point, sorry.

Re shrinking with reflect: we sometimes minifyEnabled=true on debug flavor so we can test shrinking-related issues. If dagger-reflect was set up it with if (idea) this quick-change-for-debugging would fail the build unexpectedly because of reflect.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, though, having dagger-reflect in your APK would already produce results that were not representative of what happens when shrinking in release. In release builds a large portion of Dagger-generated types are vertically merge and/or inlined. I've even written some non-trivial graphs that have been completely eliminated by shrinking leaving only a bunch of chained calls to constructors.

I'm not opposed to adding rules to the reflect artifact. They're probably pretty basic. But I think the concept of shrinking and using reflect are mutually exclusive in their goals so it's not a high priority.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I was referring to here is adding minifyEnabled=true to debug so that we can verify things not related to Dagger, or dagger-reflect. But at the moment if the debug build is set up with dagger-reflect, it prevents debugging other problems., because injection crashes. I see why it's not high priority though, as it shouldn't be hard to turn off dagger-reflect, it's just inconvenient.

-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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package dagger;

interface BuilderComponentNoAnnotation {
@Keep
interface Builder {
BuilderComponentNoAnnotation build();
}
Expand Down
43 changes: 29 additions & 14 deletions codegen/src/test/java/dagger/DaggerCodegenTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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());
}
}

Expand All @@ -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
Expand All @@ -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());
}
}

Expand All @@ -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");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dagger;

interface FactoryComponentNoAnnotation {
@Keep
interface Factory {
FactoryComponentNoAnnotation create();
}
Expand Down
8 changes: 8 additions & 0 deletions codegen/src/test/java/dagger/Keep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dagger;

import static java.lang.annotation.RetentionPolicy.CLASS;

import java.lang.annotation.Retention;

@Retention(CLASS)
public @interface Keep {}
18 changes: 18 additions & 0 deletions codegen/src/test/rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# @org.junit.Test
-keepattributes RuntimeVisibleAnnotations

# Test methods are invoked reflectively.
-keepclasseswithmembers class * {
@org.junit.Test <methods>;
}

# Test classes are constructed reflectively.
-keepclassmembers class **.*Test {
<init>(...);
}

# 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