Use runtime classpath at root to workaround Dagger/Hilt API vs Impl issue.

This CL adds a new Hilt option called 'enableClasspathAggregation' that will configure the compile classpath of the project (for app modules and tests) to use the runtime classpath. This means that transitive dependencies will be available during compilation which in turn will allow Dagger to traverse the classes along the dependency tree and will allow for Hilt's aggregating classes to be discovered.

The classpath configuration is done by resolving the runtime configuration with an artifact view and adding it to the 'CompileOnly' config. This solution is inefficient and will cause build performance impact, but it is a starting point that can be further optimized by using a smarter transform that can extract the necessary classes required by Dagger and Hilt.

Fixes: https://github.com/google/dagger/issues/1991
RELNOTES=Use runtime classpath at root to workaround Dagger/Hilt API vs Impl issue.
PiperOrigin-RevId: 350239023
diff --git a/java/dagger/hilt/android/plugin/build.gradle b/java/dagger/hilt/android/plugin/build.gradle
index cbeb142..8520c77 100644
--- a/java/dagger/hilt/android/plugin/build.gradle
+++ b/java/dagger/hilt/android/plugin/build.gradle
@@ -43,6 +43,7 @@
 dependencies {
   implementation gradleApi()
   compileOnly 'com.android.tools.build:gradle:4.2.0-beta01'
+  // TODO(user): Make compileOnly to avoid dep for non-Kotlin projects.
   implementation 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.20'
   implementation 'org.javassist:javassist:3.26.0-GA'
   implementation 'org.ow2.asm:asm:9.0'
@@ -61,6 +62,12 @@
   it.pluginClasspath.from(configurations.additionalTestPlugin)
 }
 
+compileKotlin {
+  kotlinOptions {
+    jvmTarget = "1.8"
+  }
+}
+
 // Create sources Jar from main kotlin sources
 tasks.register("sourcesJar", Jar).configure {
   group = JavaBasePlugin.DOCUMENTATION_GROUP
diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt
index 1466521..7a33836 100644
--- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt
+++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt
@@ -19,6 +19,20 @@
  * Configuration options for the Hilt Gradle Plugin
  */
 interface HiltExtension {
+
+  /**
+   * If set to `true`, Hilt will adjust the compile classpath such that it includes transitive
+   * dependencies, ignoring `api` or `implementation` boundaries during compilation. You should
+   * enable this option if your project has multiple level of transitive dependencies that contain
+   * injected classes or entry points.
+   *
+   * Enabling this option also requires android.lintOptions.checkReleaseBuilds to be set to 'false'
+   * if the Android Gradle Plugin version being used is less than 7.0.
+   *
+   * See https://github.com/google/dagger/issues/1991 for more context.
+   */
+  var enableExperimentalClasspathAggregation: Boolean
+
   /**
    * If set to `true`, Hilt will register a transform task that will rewrite `@AndroidEntryPoint`
    * annotated classes before the host-side JVM tests run. You should enable this option if you are
@@ -31,5 +45,6 @@
 }
 
 internal open class HiltExtensionImpl : HiltExtension {
+  override var enableExperimentalClasspathAggregation: Boolean = false
   override var enableTransformForLocalTests: Boolean = false
 }
diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt
index cfc604a..1c8f4af 100644
--- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt
+++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt
@@ -20,13 +20,21 @@
 import com.android.build.api.extension.AndroidComponentsExtension
 import com.android.build.api.instrumentation.FramesComputationMode
 import com.android.build.api.instrumentation.InstrumentationScope
+import com.android.build.gradle.AppExtension
 import com.android.build.gradle.BaseExtension
+import com.android.build.gradle.LibraryExtension
+import com.android.build.gradle.TestExtension
 import com.android.build.gradle.TestedExtension
 import com.android.build.gradle.api.AndroidBasePlugin
+import com.android.build.gradle.api.BaseVariant
+import com.android.build.gradle.api.TestVariant
+import com.android.build.gradle.api.UnitTestVariant
+import dagger.hilt.android.plugin.util.CopyTransform
 import dagger.hilt.android.plugin.util.SimpleAGPVersion
 import java.io.File
 import org.gradle.api.Plugin
 import org.gradle.api.Project
+import org.gradle.api.attributes.Attribute
 
 /**
  * A Gradle plugin that checks if the project is an Android project and if so, registers a
@@ -57,6 +65,7 @@
     val hiltExtension = project.extensions.create(
       HiltExtension::class.java, "hilt", HiltExtensionImpl::class.java
     )
+    configureCompileClasspath(project, hiltExtension)
     if (SimpleAGPVersion.ANDROID_GRADLE_PLUGIN_VERSION < SimpleAGPVersion(4, 2)) {
       // Configures bytecode transform using older APIs pre AGP 4.2
       configureTransform(project, hiltExtension)
@@ -67,6 +76,125 @@
     configureProcessorFlags(project)
   }
 
+  private fun configureCompileClasspath(project: Project, hiltExtension: HiltExtension) {
+    val androidExtension = project.extensions.findByType(BaseExtension::class.java)
+      ?: throw error("Android BaseExtension not found.")
+    when (androidExtension) {
+      is AppExtension -> {
+        // For an app project we configure the app variant and both androidTest and test variants,
+        // Hilt components are generated in all of them.
+        androidExtension.applicationVariants.all {
+          configureVariantCompileClasspath(project, hiltExtension, androidExtension, it)
+        }
+        androidExtension.testVariants.all {
+          configureVariantCompileClasspath(project, hiltExtension, androidExtension, it)
+        }
+        androidExtension.unitTestVariants.all {
+          configureVariantCompileClasspath(project, hiltExtension, androidExtension, it)
+        }
+      }
+      is LibraryExtension -> {
+        // For a library project, only the androidTest and test variant are configured since
+        // Hilt components are not generated in a library.
+        androidExtension.testVariants.all {
+          configureVariantCompileClasspath(project, hiltExtension, androidExtension, it)
+        }
+        androidExtension.unitTestVariants.all {
+          configureVariantCompileClasspath(project, hiltExtension, androidExtension, it)
+        }
+      }
+      is TestExtension -> {
+        androidExtension.applicationVariants.all {
+          configureVariantCompileClasspath(project, hiltExtension, androidExtension, it)
+        }
+      }
+      else -> error(
+        "Hilt plugin is unable to configure the compile classpath for project with extension " +
+          "'$androidExtension'"
+      )
+    }
+
+    project.dependencies.apply {
+      registerTransform(CopyTransform::class.java) { spec ->
+        // Java/Kotlin library projects offer an artifact of type 'jar'.
+        spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, "jar")
+        // Android library projects (with or without Kotlin) offer an artifact of type
+        // 'android-classes', which AGP can offer as a jar.
+        spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, "android-classes-jar")
+        spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, DAGGER_ARTIFACT_TYPE_VALUE)
+      }
+    }
+  }
+
+  private fun configureVariantCompileClasspath(
+    project: Project,
+    hiltExtension: HiltExtension,
+    androidExtension: BaseExtension,
+    variant: BaseVariant
+  ) {
+    if (!hiltExtension.enableExperimentalClasspathAggregation) {
+      // Option is not enabled, don't configure compile classpath. Note that the option can't be
+      // checked earlier (before iterating over the variants) since it would have been too early for
+      // the value to be populated from the build file.
+      return
+    }
+
+    if (androidExtension.lintOptions.isCheckReleaseBuilds &&
+      SimpleAGPVersion.ANDROID_GRADLE_PLUGIN_VERSION < SimpleAGPVersion(7, 0)
+    ) {
+      // Sadly we have to ask users to disable lint when enableExperimentalClasspathAggregation is
+      // set to true and they are not in AGP 7.0+ since Lint will cause issues during the
+      // configuration phase. See b/158753935 and b/160392650
+      error(
+        "Invalid Hilt plugin configuration: When 'enableExperimentalClasspathAggregation' is " +
+          "enabled 'android.lintOptions.checkReleaseBuilds' has to be set to false unless " +
+          "com.android.tools.build:gradle:7.0.0+ is used."
+      )
+    }
+
+    if (
+      listOf(
+          "android.injected.build.model.only", // Sent by AS 1.0 only
+          "android.injected.build.model.only.advanced", // Sent by AS 1.1+
+          "android.injected.build.model.only.versioned", // Sent by AS 2.4+
+          "android.injected.build.model.feature.full.dependencies", // Sent by AS 2.4+
+          "android.injected.build.model.v2", // Sent by AS 4.2+
+        ).any { project.properties.containsKey(it) }
+    ) {
+      // Do not configure compile classpath when AndroidStudio is building the model (syncing)
+      // otherwise it will cause a freeze.
+      return
+    }
+
+    val runtimeConfiguration = if (variant is TestVariant) {
+      // For Android test variants, the tested runtime classpath is used since the test app has
+      // tested dependencies removed.
+      variant.testedVariant.runtimeConfiguration
+    } else {
+      variant.runtimeConfiguration
+    }
+    val artifactView = runtimeConfiguration.incoming.artifactView { view ->
+      view.attributes.attribute(ARTIFACT_TYPE_ATTRIBUTE, DAGGER_ARTIFACT_TYPE_VALUE)
+    }
+
+    // CompileOnly config names don't follow the usual convention:
+    // <Variant Name>   -> <Config Name>
+    // debug            -> debugCompileOnly
+    // debugAndroidTest -> androidTestDebugCompileOnly
+    // debugUnitTest    -> testDebugCompileOnly
+    // release          -> releaseCompileOnly
+    // releaseUnitTest  -> testReleaseCompileOnly
+    val compileOnlyConfigName = when (variant) {
+      is TestVariant ->
+        "androidTest${variant.name.substringBeforeLast("AndroidTest").capitalize()}CompileOnly"
+      is UnitTestVariant ->
+        "test${variant.name.substringBeforeLast("UnitTest").capitalize()}CompileOnly"
+      else ->
+        "${variant.name}CompileOnly"
+    }
+    project.dependencies.add(compileOnlyConfigName, artifactView.files)
+  }
+
   @Suppress("UnstableApiUsage")
   private fun configureTransformASM(project: Project, hiltExtension: HiltExtension) {
     var warnAboutLocalTestsFlag = false
@@ -145,6 +273,9 @@
   }
 
   companion object {
+    val ARTIFACT_TYPE_ATTRIBUTE = Attribute.of("artifactType", String::class.java)
+    const val DAGGER_ARTIFACT_TYPE_VALUE = "jar-for-dagger"
+
     const val LIBRARY_GROUP = "com.google.dagger"
     val PROCESSOR_OPTIONS = listOf(
       "dagger.fastInit" to "enabled",
diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/CopyTransform.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/CopyTransform.kt
new file mode 100644
index 0000000..39a2d3a
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/CopyTransform.kt
@@ -0,0 +1,29 @@
+package dagger.hilt.android.plugin.util
+
+import org.gradle.api.artifacts.transform.CacheableTransform
+import org.gradle.api.artifacts.transform.InputArtifact
+import org.gradle.api.artifacts.transform.TransformAction
+import org.gradle.api.artifacts.transform.TransformOutputs
+import org.gradle.api.artifacts.transform.TransformParameters
+import org.gradle.api.file.FileSystemLocation
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.Classpath
+
+// A transform that registers the input jar file as an output and thus changing from one artifact
+// type to another.
+// TODO: Improve to only copy classes that need to be visible by Hilt & Dagger.
+@CacheableTransform
+abstract class CopyTransform : TransformAction<TransformParameters.None> {
+  @get:Classpath
+  @get:InputArtifact
+  abstract val inputArtifactProvider: Provider<FileSystemLocation>
+
+  override fun transform(outputs: TransformOutputs) {
+    val input = inputArtifactProvider.get().asFile
+    when {
+      input.isDirectory -> outputs.dir(input)
+      input.isFile -> outputs.file(input)
+      else -> error("File/directory does not exist: ${input.absolutePath}")
+    }
+  }
+}
diff --git a/java/dagger/hilt/android/plugin/src/test/data/android-libraryA/build.gradle b/java/dagger/hilt/android/plugin/src/test/data/android-libraryA/build.gradle
new file mode 100644
index 0000000..e2d2a7b
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/data/android-libraryA/build.gradle
@@ -0,0 +1,28 @@
+plugins {
+    id 'com.android.library'
+    id 'dagger.hilt.android.plugin'
+}
+
+android {
+    compileSdkVersion 30
+    buildToolsVersion "30.0.2"
+
+    defaultConfig {
+        minSdkVersion 21
+        targetSdkVersion 30
+        versionCode 1
+        versionName "1.0"
+    }
+    compileOptions {
+        sourceCompatibility 1.8
+        targetCompatibility 1.8
+    }
+}
+
+dependencies {
+    implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'
+    annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+
+    implementation project(':libraryB')
+    implementation project(':libraryC')
+}
\ No newline at end of file
diff --git a/java/dagger/hilt/android/plugin/src/test/data/android-libraryA/src/main/AndroidManifest.xml b/java/dagger/hilt/android/plugin/src/test/data/android-libraryA/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e704d4a
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/data/android-libraryA/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="liba">
+</manifest>
\ No newline at end of file
diff --git a/java/dagger/hilt/android/plugin/src/test/data/android-libraryA/src/main/java/liba/LibraryA.java b/java/dagger/hilt/android/plugin/src/test/data/android-libraryA/src/main/java/liba/LibraryA.java
new file mode 100644
index 0000000..95e9356
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/data/android-libraryA/src/main/java/liba/LibraryA.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package liba;
+
+import javax.inject.Inject;
+import libb.LibraryB;
+import libc.LibraryC;
+
+/** Test LibA */
+public class LibraryA {
+  @Inject
+  public LibraryA(LibraryB b, LibraryC c) {}
+}
diff --git a/java/dagger/hilt/android/plugin/src/test/data/android-libraryC/build.gradle b/java/dagger/hilt/android/plugin/src/test/data/android-libraryC/build.gradle
new file mode 100644
index 0000000..3031528
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/data/android-libraryC/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+    id 'com.android.library'
+    id 'dagger.hilt.android.plugin'
+}
+
+android {
+    compileSdkVersion 30
+    buildToolsVersion "30.0.2"
+
+    defaultConfig {
+        minSdkVersion 21
+        targetSdkVersion 30
+        versionCode 1
+        versionName "1.0"
+    }
+    compileOptions {
+        sourceCompatibility 1.8
+        targetCompatibility 1.8
+    }
+}
+
+dependencies {
+    implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'
+    annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+}
\ No newline at end of file
diff --git a/java/dagger/hilt/android/plugin/src/test/data/android-libraryC/src/main/AndroidManifest.xml b/java/dagger/hilt/android/plugin/src/test/data/android-libraryC/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a4967e3
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/data/android-libraryC/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="libc">
+</manifest>
\ No newline at end of file
diff --git a/java/dagger/hilt/android/plugin/src/test/data/android-libraryC/src/main/java/libc/LibraryC.java b/java/dagger/hilt/android/plugin/src/test/data/android-libraryC/src/main/java/libc/LibraryC.java
new file mode 100644
index 0000000..f7397fc
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/data/android-libraryC/src/main/java/libc/LibraryC.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libc;
+
+import javax.inject.Inject;
+
+/** Test LibC */
+public class LibraryC {
+  @Inject
+  public LibraryC() {}
+}
diff --git a/java/dagger/hilt/android/plugin/src/test/data/java-libraryA/build.gradle b/java/dagger/hilt/android/plugin/src/test/data/java-libraryA/build.gradle
new file mode 100644
index 0000000..dde8858
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/data/java-libraryA/build.gradle
@@ -0,0 +1,15 @@
+plugins {
+    id 'java-library'
+}
+
+java {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+    implementation 'com.google.dagger:hilt-core:LOCAL-SNAPSHOT'
+    annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+
+    implementation project(':libraryB')
+}
\ No newline at end of file
diff --git a/java/dagger/hilt/android/plugin/src/test/data/java-libraryA/src/main/java/liba/LibraryA.java b/java/dagger/hilt/android/plugin/src/test/data/java-libraryA/src/main/java/liba/LibraryA.java
new file mode 100644
index 0000000..afc0da2
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/data/java-libraryA/src/main/java/liba/LibraryA.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package liba;
+
+import javax.inject.Inject;
+import libb.LibraryBProvided;
+
+/** Test LibA */
+public class LibraryA {
+  @Inject
+  public LibraryA(LibraryBProvided b) {}
+}
diff --git a/java/dagger/hilt/android/plugin/src/test/data/java-libraryB/build.gradle b/java/dagger/hilt/android/plugin/src/test/data/java-libraryB/build.gradle
new file mode 100644
index 0000000..55780cc
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/data/java-libraryB/build.gradle
@@ -0,0 +1,13 @@
+plugins {
+    id 'java-library'
+}
+
+java {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+    implementation 'com.google.dagger:hilt-core:LOCAL-SNAPSHOT'
+    annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+}
\ No newline at end of file
diff --git a/java/dagger/hilt/android/plugin/src/test/data/java-libraryB/src/main/java/libb/LibraryB.java b/java/dagger/hilt/android/plugin/src/test/data/java-libraryB/src/main/java/libb/LibraryB.java
new file mode 100644
index 0000000..285d5ba
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/data/java-libraryB/src/main/java/libb/LibraryB.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libb;
+
+import javax.inject.Inject;
+
+/** Test LibB */
+public class LibraryB {
+  @Inject
+  public LibraryB() {}
+}
diff --git a/java/dagger/hilt/android/plugin/src/test/data/java-libraryB/src/main/java/libb/LibraryBModule.java b/java/dagger/hilt/android/plugin/src/test/data/java-libraryB/src/main/java/libb/LibraryBModule.java
new file mode 100644
index 0000000..d1c08f0
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/data/java-libraryB/src/main/java/libb/LibraryBModule.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libb;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+
+/** Test LibB Module */
+@Module
+@InstallIn(SingletonComponent.class)
+public final class LibraryBModule {
+  @Provides
+  public static LibraryBProvided provideB() {
+    return new LibraryBProvided();
+  }
+}
diff --git a/java/dagger/hilt/android/plugin/src/test/data/java-libraryB/src/main/java/libb/LibraryBProvided.java b/java/dagger/hilt/android/plugin/src/test/data/java-libraryB/src/main/java/libb/LibraryBProvided.java
new file mode 100644
index 0000000..b472bfb
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/data/java-libraryB/src/main/java/libb/LibraryBProvided.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libb;
+
+/** Test LibB Provided */
+public class LibraryBProvided {}
diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/CompileClasspathTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/CompileClasspathTest.kt
new file mode 100644
index 0000000..6141250
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/kotlin/CompileClasspathTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.File
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class CompileClasspathTest {
+  @get:Rule
+  val testProjectDir = TemporaryFolder()
+
+  lateinit var gradleRunner: GradleTestRunner
+
+  @Before
+  fun setup() {
+    gradleRunner = GradleTestRunner(testProjectDir)
+    gradleRunner.addAndroidOption(
+      "lintOptions.checkReleaseBuilds = false"
+    )
+    gradleRunner.addHiltOption(
+      "enableExperimentalClasspathAggregation = true"
+    )
+    gradleRunner.addDependencies(
+      "implementation 'androidx.appcompat:appcompat:1.1.0'",
+      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
+      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'",
+      "implementation project(':libraryA')",
+    )
+    gradleRunner.addSrc(
+      srcPath = "minimal/MyApp.java",
+      srcContent =
+        """
+        package minimal;
+
+        import android.app.Application;
+        import liba.LibraryA;
+
+        @dagger.hilt.android.HiltAndroidApp
+        public class MyApp extends Application {
+          @javax.inject.Inject
+          LibraryA libraryA;
+        }
+        """.trimIndent()
+    )
+    gradleRunner.setAppClassName(".MyApp")
+  }
+
+  // Verifies that library B and library C injected classes are available in the root classpath.
+  @Test
+  fun test_injectClasses() {
+    File("src/test/data/android-libraryA")
+      .copyRecursively(File(testProjectDir.root, "libraryA"))
+    File("src/test/data/java-libraryB")
+      .copyRecursively(File(testProjectDir.root, "libraryB"))
+    File("src/test/data/android-libraryC")
+      .copyRecursively(File(testProjectDir.root, "libraryC"))
+
+    testProjectDir.newFile("settings.gradle").apply {
+      writeText(
+        """
+        include ':libraryA'
+        include ':libraryB'
+        include ':libraryC'
+        """.trimIndent()
+      )
+    }
+
+    val result = gradleRunner.build()
+    val assembleTask = result.getTask(":assembleDebug")
+    assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
+  }
+
+  // Verifies that library B Hilt module is available in the root classpath.
+  @Test
+  fun test_injectClassesFromModules() {
+    File("src/test/data/java-libraryA")
+      .copyRecursively(File(testProjectDir.root, "libraryA"))
+    File("src/test/data/java-libraryB")
+      .copyRecursively(File(testProjectDir.root, "libraryB"))
+
+    testProjectDir.newFile("settings.gradle").apply {
+      writeText(
+        """
+        include ':libraryA'
+        include ':libraryB'
+        """.trimIndent()
+      )
+    }
+
+    val result = gradleRunner.build()
+    val assembleTask = result.getTask(":assembleDebug")
+    assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
+  }
+}
diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/GradleTransformTestRunner.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/GradleTestRunner.kt
similarity index 82%
rename from java/dagger/hilt/android/plugin/src/test/kotlin/GradleTransformTestRunner.kt
rename to java/dagger/hilt/android/plugin/src/test/kotlin/GradleTestRunner.kt
index 4ba3bcb..2c48a02 100644
--- a/java/dagger/hilt/android/plugin/src/test/kotlin/GradleTransformTestRunner.kt
+++ b/java/dagger/hilt/android/plugin/src/test/kotlin/GradleTestRunner.kt
@@ -20,12 +20,14 @@
 import org.junit.rules.TemporaryFolder
 
 /**
- * Testing utility class that sets up a simple Android project that applies the transform plugin.
+ * Testing utility class that sets up a simple Android project that applies the Hilt plugin.
  */
-class GradleTransformTestRunner(val tempFolder: TemporaryFolder) {
+class GradleTestRunner(val tempFolder: TemporaryFolder) {
   private val dependencies = mutableListOf<String>()
   private val activities = mutableListOf<String>()
-
+  private val additionalAndroidOptions = mutableListOf<String>()
+  private val hiltOptions = mutableListOf<String>()
+  private var appClassName: String? = null
   private var buildFile: File? = null
   private var gradlePropertiesFile: File? = null
   private var manifestFile: File? = null
@@ -41,8 +43,18 @@
   }
 
   // Adds an <activity> tag in the project's Android Manifest, e.g. "<activity name=".Foo"/>
-  fun addActivities(vararg deps: String) {
-    activities.addAll(deps)
+  fun addActivities(vararg activityElements: String) {
+    activities.addAll(activityElements)
+  }
+
+  // Adds 'android' options to the project's build.gradle, e.g. "lintOptions.checkReleaseBuilds = false"
+  fun addAndroidOption(vararg options: String) {
+    additionalAndroidOptions.addAll(options)
+  }
+
+  // Adds 'hilt' options to the project's build.gradle, e.g. "enableExperimentalClasspathAggregation = true"
+  fun addHiltOption(vararg options: String) {
+    hiltOptions.addAll(options)
   }
 
   // Adds a source package to the project. The package path is relative to 'src/main/java'.
@@ -62,6 +74,10 @@
     return tempFolder.newFile("/src/main/res/$resPath").apply { writeText(resContent) }
   }
 
+  fun setAppClassName(name: String) {
+    appClassName = name
+  }
+
   // Executes a Gradle builds and expects it to succeed.
   fun build(): Result {
     setupFiles()
@@ -114,17 +130,24 @@
               sourceCompatibility 1.8
               targetCompatibility 1.8
           }
+          ${additionalAndroidOptions.joinToString(separator = "\n")}
         }
 
-        repositories {
-          mavenLocal()
-          google()
-          jcenter()
+        allprojects {
+          repositories {
+            mavenLocal()
+            google()
+            jcenter()
+          }
         }
 
         dependencies {
           ${dependencies.joinToString(separator = "\n")}
         }
+
+        hilt {
+          ${hiltOptions.joinToString(separator = "\n")}
+        }
         """.trimIndent()
       )
     }
@@ -149,6 +172,7 @@
         <?xml version="1.0" encoding="utf-8"?>
         <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="minimal">
             <application
+                android:name="${appClassName ?: "android.app.Application"}"
                 android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
                 ${activities.joinToString(separator = "\n")}
             </application>
diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/HiltGradlePluginTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/HiltGradlePluginTest.kt
index 89a16c4..f256985 100644
--- a/java/dagger/hilt/android/plugin/src/test/kotlin/HiltGradlePluginTest.kt
+++ b/java/dagger/hilt/android/plugin/src/test/kotlin/HiltGradlePluginTest.kt
@@ -15,12 +15,6 @@
  */
 
 import com.google.common.truth.Truth.assertThat
-import java.io.DataInputStream
-import java.io.FileInputStream
-import javassist.bytecode.ByteArray
-import javassist.bytecode.ClassFile
-import org.gradle.testkit.runner.TaskOutcome
-import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -36,185 +30,21 @@
   @get:Rule
   val testProjectDir = TemporaryFolder()
 
-  lateinit var gradleTransformRunner: GradleTransformTestRunner
+  lateinit var gradleRunner: GradleTestRunner
 
   @Before
   fun setup() {
-    gradleTransformRunner = GradleTransformTestRunner(testProjectDir)
-    gradleTransformRunner.addSrc(
-      srcPath = "minimal/MainActivity.java",
-      srcContent =
-        """
-        package minimal;
-
-        import android.os.Bundle;
-        import androidx.appcompat.app.AppCompatActivity;
-
-        @dagger.hilt.android.AndroidEntryPoint
-        public class MainActivity extends AppCompatActivity {
-          @Override
-          public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-          }
-        }
-        """.trimIndent()
-    )
-  }
-
-  // Simple functional test to verify transformation.
-  @Test
-  fun testAssemble() {
-    gradleTransformRunner.addDependencies(
-      "implementation 'androidx.appcompat:appcompat:1.1.0'",
-      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
-      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
-    )
-    gradleTransformRunner.addActivities(
-      "<activity android:name=\".MainActivity\"/>"
-    )
-
-    val result = gradleTransformRunner.build()
-    val assembleTask = result.getTask(":assembleDebug")
-    assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
-
-    val transformedClass = result.getTransformedFile("minimal/MainActivity.class")
-    FileInputStream(transformedClass).use { fileInput ->
-      ClassFile(DataInputStream(fileInput)).let { classFile ->
-        // Verify superclass is updated
-        assertEquals("minimal.Hilt_MainActivity", classFile.superclass)
-        // Verify super call is also updated
-        val constPool = classFile.constPool
-        classFile.methods.first { it.name == "onCreate" }.let { methodInfo ->
-          // bytecode of MainActivity.onCreate() is:
-          // 0 - aload_0
-          // 1 - aload_1
-          // 2 - invokespecial
-          // 5 - return
-          val invokeIndex = 2
-          val methodRef = ByteArray.readU16bit(methodInfo.codeAttribute.code, invokeIndex + 1)
-          val classRef = constPool.getMethodrefClassName(methodRef)
-          assertEquals("minimal.Hilt_MainActivity", classRef)
-        }
-      }
-    }
-  }
-
-  // Verify correct transformation is done on nested classes.
-  @Test
-  fun testAssemble_nestedClass() {
-    gradleTransformRunner.addDependencies(
-      "implementation 'androidx.appcompat:appcompat:1.1.0'",
-      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
-      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
-    )
-
-    gradleTransformRunner.addSrc(
-      srcPath = "minimal/TopClass.java",
-      srcContent =
-        """
-        package minimal;
-
-        import androidx.appcompat.app.AppCompatActivity;
-
-        public class TopClass {
-            @dagger.hilt.android.AndroidEntryPoint
-            public static class NestedActivity extends AppCompatActivity { }
-        }
-        """.trimIndent()
-    )
-
-    val result = gradleTransformRunner.build()
-    val assembleTask = result.getTask(":assembleDebug")
-    assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
-
-    val transformedClass = result.getTransformedFile("minimal/TopClass\$NestedActivity.class")
-    FileInputStream(transformedClass).use { fileInput ->
-      ClassFile(DataInputStream(fileInput)).let { classFile ->
-        assertEquals("minimal.Hilt_TopClass_NestedActivity", classFile.superclass)
-      }
-    }
-  }
-
-  // Verify transformation ignores abstract methods.
-  @Test
-  fun testAssemble_abstractMethod() {
-    gradleTransformRunner.addDependencies(
-      "implementation 'androidx.appcompat:appcompat:1.1.0'",
-      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
-      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
-    )
-
-    gradleTransformRunner.addSrc(
-      srcPath = "minimal/AbstractActivity.java",
-      srcContent =
-        """
-        package minimal;
-
-        import androidx.appcompat.app.AppCompatActivity;
-
-        @dagger.hilt.android.AndroidEntryPoint
-        public abstract class AbstractActivity extends AppCompatActivity {
-            public abstract void method();
-        }
-        """.trimIndent()
-    )
-
-    val result = gradleTransformRunner.build()
-    val assembleTask = result.getTask(":assembleDebug")
-    assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
-
-    val transformedClass = result.getTransformedFile("minimal/AbstractActivity.class")
-    FileInputStream(transformedClass).use { fileInput ->
-      ClassFile(DataInputStream(fileInput)).let { classFile ->
-        assertEquals("minimal.Hilt_AbstractActivity", classFile.superclass)
-      }
-    }
-  }
-
-  // Verify transformation ignores native methods.
-  @Test
-  fun testAssemble_nativeMethod() {
-    gradleTransformRunner.addDependencies(
-      "implementation 'androidx.appcompat:appcompat:1.1.0'",
-      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
-      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
-    )
-
-    gradleTransformRunner.addSrc(
-      srcPath = "minimal/SimpleActivity.java",
-      srcContent =
-        """
-        package minimal;
-
-        import androidx.appcompat.app.AppCompatActivity;
-
-        @dagger.hilt.android.AndroidEntryPoint
-        public class SimpleActivity extends AppCompatActivity {
-            public native void method();
-        }
-        """.trimIndent()
-    )
-
-    val result = gradleTransformRunner.build()
-    val assembleTask = result.getTask(":assembleDebug")
-    assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
-
-    val transformedClass = result.getTransformedFile("minimal/SimpleActivity.class")
-    FileInputStream(transformedClass).use { fileInput ->
-      ClassFile(DataInputStream(fileInput)).let { classFile ->
-        assertEquals("minimal.Hilt_SimpleActivity", classFile.superclass)
-      }
-    }
+    gradleRunner = GradleTestRunner(testProjectDir)
   }
 
   // Verify plugin configuration fails when runtime dependency is missing but plugin is applied.
   @Test
-  fun testAssemble_missingLibraryDep() {
-    gradleTransformRunner.addDependencies(
+  fun test_missingLibraryDep() {
+    gradleRunner.addDependencies(
       "implementation 'androidx.appcompat:appcompat:1.1.0'"
     )
 
-    val result = gradleTransformRunner.buildAndFail()
+    val result = gradleRunner.buildAndFail()
     assertThat(result.getOutput()).contains(
       "The Hilt Android Gradle plugin is applied but no " +
         "com.google.dagger:hilt-android dependency was found."
@@ -223,125 +53,16 @@
 
   // Verify plugin configuration fails when compiler dependency is missing but plugin is applied.
   @Test
-  fun testAssemble_missingCompilerDep() {
-    gradleTransformRunner.addDependencies(
+  fun test_missingCompilerDep() {
+    gradleRunner.addDependencies(
       "implementation 'androidx.appcompat:appcompat:1.1.0'",
       "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'"
     )
 
-    val result = gradleTransformRunner.buildAndFail()
+    val result = gradleRunner.buildAndFail()
     assertThat(result.getOutput()).contains(
       "The Hilt Android Gradle plugin is applied but no " +
         "com.google.dagger:hilt-compiler dependency was found."
     )
   }
-
-  // Verifies the transformation is applied incrementally when a class to be transformed is updated.
-  @Test
-  fun testTransform_incrementalClass() {
-    gradleTransformRunner.addDependencies(
-      "implementation 'androidx.appcompat:appcompat:1.1.0'",
-      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
-      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
-    )
-
-    val srcFile = gradleTransformRunner.addSrc(
-      srcPath = "minimal/OtherActivity.java",
-      srcContent =
-        """
-        package minimal;
-
-        import androidx.appcompat.app.AppCompatActivity;
-
-        @dagger.hilt.android.AndroidEntryPoint
-        public class OtherActivity extends AppCompatActivity {
-
-        }
-        """.trimIndent()
-    )
-
-    gradleTransformRunner.build().let {
-      val assembleTask = it.getTask(TRANSFORM_TASK_NAME)
-      assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
-    }
-
-    gradleTransformRunner.build().let {
-      val assembleTask = it.getTask(TRANSFORM_TASK_NAME)
-      assertEquals(TaskOutcome.UP_TO_DATE, assembleTask.outcome)
-    }
-
-    srcFile.delete()
-    gradleTransformRunner.addSrc(
-      srcPath = "minimal/OtherActivity.java",
-      srcContent =
-        """
-        package minimal;
-
-        import androidx.fragment.app.FragmentActivity;
-
-        @dagger.hilt.android.AndroidEntryPoint
-        public class OtherActivity extends FragmentActivity {
-
-        }
-        """.trimIndent()
-    )
-
-    val result = gradleTransformRunner.build()
-    val assembleTask = result.getTask(TRANSFORM_TASK_NAME)
-    assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
-
-    val transformedClass = result.getTransformedFile("minimal/OtherActivity.class")
-    FileInputStream(transformedClass).use { fileInput ->
-      ClassFile(DataInputStream(fileInput)).let { classFile ->
-        assertEquals("minimal.Hilt_OtherActivity", classFile.superclass)
-      }
-    }
-  }
-
-  // Verifies the transformation is applied incrementally when a new class is added to an existing
-  // directory.
-  @Test
-  fun testTransform_incrementalDir() {
-    gradleTransformRunner.addDependencies(
-      "implementation 'androidx.appcompat:appcompat:1.1.0'",
-      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
-      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
-    )
-
-    gradleTransformRunner.addSrcPackage("ui/")
-
-    gradleTransformRunner.build().let {
-      val assembleTask = it.getTask(TRANSFORM_TASK_NAME)
-      assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
-    }
-
-    gradleTransformRunner.build().let {
-      val assembleTask = it.getTask(TRANSFORM_TASK_NAME)
-      assertEquals(TaskOutcome.UP_TO_DATE, assembleTask.outcome)
-    }
-
-    gradleTransformRunner.addSrc(
-      srcPath = "ui/OtherActivity.java",
-      srcContent =
-        """
-        package ui;
-
-        import androidx.appcompat.app.AppCompatActivity;
-
-        @dagger.hilt.android.AndroidEntryPoint
-        public class OtherActivity extends AppCompatActivity {
-
-        }
-        """.trimIndent()
-    )
-
-    val result = gradleTransformRunner.build()
-    val assembleTask = result.getTask(TRANSFORM_TASK_NAME)
-    assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
-  }
-
-  companion object {
-    const val TRANSFORM_TASK_NAME =
-      ":transformDebugClassesWithAsm"
-  }
 }
diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/TransformTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/TransformTest.kt
new file mode 100644
index 0000000..0b84002
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/src/test/kotlin/TransformTest.kt
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.DataInputStream
+import java.io.FileInputStream
+import javassist.bytecode.ByteArray
+import javassist.bytecode.ClassFile
+import junit.framework.Assert.assertEquals
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class TransformTest {
+
+  @get:Rule
+  val testProjectDir = TemporaryFolder()
+
+  lateinit var gradleRunner: GradleTestRunner
+
+  @Before
+  fun setup() {
+    gradleRunner = GradleTestRunner(testProjectDir)
+    gradleRunner.addSrc(
+      srcPath = "minimal/MainActivity.java",
+      srcContent =
+        """
+        package minimal;
+
+        import android.os.Bundle;
+        import androidx.appcompat.app.AppCompatActivity;
+
+        @dagger.hilt.android.AndroidEntryPoint
+        public class MainActivity extends AppCompatActivity {
+          @Override
+          public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+          }
+        }
+        """.trimIndent()
+    )
+  }
+
+  // Simple functional test to verify transformation.
+  @Test
+  fun testAssemble() {
+    gradleRunner.addDependencies(
+      "implementation 'androidx.appcompat:appcompat:1.1.0'",
+      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
+      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
+    )
+    gradleRunner.addActivities(
+      "<activity android:name=\".MainActivity\"/>"
+    )
+
+    val result = gradleRunner.build()
+    val assembleTask = result.getTask(":assembleDebug")
+    Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
+
+    val transformedClass = result.getTransformedFile("minimal/MainActivity.class")
+    FileInputStream(transformedClass).use { fileInput ->
+      ClassFile(DataInputStream(fileInput)).let { classFile ->
+        // Verify superclass is updated
+        Assert.assertEquals("minimal.Hilt_MainActivity", classFile.superclass)
+        // Verify super call is also updated
+        val constPool = classFile.constPool
+        classFile.methods.first { it.name == "onCreate" }.let { methodInfo ->
+          // bytecode of MainActivity.onCreate() is:
+          // 0 - aload_0
+          // 1 - aload_1
+          // 2 - invokespecial
+          // 5 - return
+          val invokeIndex = 2
+          val methodRef = ByteArray.readU16bit(methodInfo.codeAttribute.code, invokeIndex + 1)
+          val classRef = constPool.getMethodrefClassName(methodRef)
+          Assert.assertEquals("minimal.Hilt_MainActivity", classRef)
+        }
+      }
+    }
+  }
+
+  // Verify correct transformation is done on nested classes.
+  @Test
+  fun testAssemble_nestedClass() {
+    gradleRunner.addDependencies(
+      "implementation 'androidx.appcompat:appcompat:1.1.0'",
+      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
+      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
+    )
+
+    gradleRunner.addSrc(
+      srcPath = "minimal/TopClass.java",
+      srcContent =
+        """
+        package minimal;
+
+        import androidx.appcompat.app.AppCompatActivity;
+
+        public class TopClass {
+            @dagger.hilt.android.AndroidEntryPoint
+            public static class NestedActivity extends AppCompatActivity { }
+        }
+        """.trimIndent()
+    )
+
+    val result = gradleRunner.build()
+    val assembleTask = result.getTask(":assembleDebug")
+    Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
+
+    val transformedClass = result.getTransformedFile("minimal/TopClass\$NestedActivity.class")
+    FileInputStream(transformedClass).use { fileInput ->
+      ClassFile(DataInputStream(fileInput)).let { classFile ->
+        Assert.assertEquals("minimal.Hilt_TopClass_NestedActivity", classFile.superclass)
+      }
+    }
+  }
+
+  // Verify transformation ignores abstract methods.
+  @Test
+  fun testAssemble_abstractMethod() {
+    gradleRunner.addDependencies(
+      "implementation 'androidx.appcompat:appcompat:1.1.0'",
+      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
+      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
+    )
+
+    gradleRunner.addSrc(
+      srcPath = "minimal/AbstractActivity.java",
+      srcContent =
+        """
+        package minimal;
+
+        import androidx.appcompat.app.AppCompatActivity;
+
+        @dagger.hilt.android.AndroidEntryPoint
+        public abstract class AbstractActivity extends AppCompatActivity {
+            public abstract void method();
+        }
+        """.trimIndent()
+    )
+
+    val result = gradleRunner.build()
+    val assembleTask = result.getTask(":assembleDebug")
+    Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
+
+    val transformedClass = result.getTransformedFile("minimal/AbstractActivity.class")
+    FileInputStream(transformedClass).use { fileInput ->
+      ClassFile(DataInputStream(fileInput)).let { classFile ->
+        Assert.assertEquals("minimal.Hilt_AbstractActivity", classFile.superclass)
+      }
+    }
+  }
+
+  // Verify transformation ignores native methods.
+  @Test
+  fun testAssemble_nativeMethod() {
+    gradleRunner.addDependencies(
+      "implementation 'androidx.appcompat:appcompat:1.1.0'",
+      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
+      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
+    )
+
+    gradleRunner.addSrc(
+      srcPath = "minimal/SimpleActivity.java",
+      srcContent =
+        """
+        package minimal;
+
+        import androidx.appcompat.app.AppCompatActivity;
+
+        @dagger.hilt.android.AndroidEntryPoint
+        public class SimpleActivity extends AppCompatActivity {
+            public native void method();
+        }
+        """.trimIndent()
+    )
+
+    val result = gradleRunner.build()
+    val assembleTask = result.getTask(":assembleDebug")
+    Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
+
+    val transformedClass = result.getTransformedFile("minimal/SimpleActivity.class")
+    FileInputStream(transformedClass).use { fileInput ->
+      ClassFile(DataInputStream(fileInput)).let { classFile ->
+        Assert.assertEquals("minimal.Hilt_SimpleActivity", classFile.superclass)
+      }
+    }
+  }
+
+  // Verifies the transformation is applied incrementally when a class to be transformed is updated.
+  @Test
+  fun testTransform_incrementalClass() {
+    gradleRunner.addDependencies(
+      "implementation 'androidx.appcompat:appcompat:1.1.0'",
+      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
+      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
+    )
+
+    val srcFile = gradleRunner.addSrc(
+      srcPath = "minimal/OtherActivity.java",
+      srcContent =
+        """
+        package minimal;
+
+        import androidx.appcompat.app.AppCompatActivity;
+
+        @dagger.hilt.android.AndroidEntryPoint
+        public class OtherActivity extends AppCompatActivity {
+
+        }
+        """.trimIndent()
+    )
+
+    gradleRunner.build().let {
+      val assembleTask = it.getTask(TRANSFORM_TASK_NAME)
+      Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
+    }
+
+    gradleRunner.build().let {
+      val assembleTask = it.getTask(TRANSFORM_TASK_NAME)
+      Assert.assertEquals(TaskOutcome.UP_TO_DATE, assembleTask.outcome)
+    }
+
+    srcFile.delete()
+    gradleRunner.addSrc(
+      srcPath = "minimal/OtherActivity.java",
+      srcContent =
+        """
+        package minimal;
+
+        import androidx.fragment.app.FragmentActivity;
+
+        @dagger.hilt.android.AndroidEntryPoint
+        public class OtherActivity extends FragmentActivity {
+
+        }
+        """.trimIndent()
+    )
+
+    val result = gradleRunner.build()
+    val assembleTask = result.getTask(TRANSFORM_TASK_NAME)
+    Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
+
+    val transformedClass = result.getTransformedFile("minimal/OtherActivity.class")
+    FileInputStream(transformedClass).use { fileInput ->
+      ClassFile(DataInputStream(fileInput)).let { classFile ->
+        Assert.assertEquals("minimal.Hilt_OtherActivity", classFile.superclass)
+      }
+    }
+  }
+
+  // Verifies the transformation is applied incrementally when a new class is added to an existing
+  // directory.
+  @Test
+  fun testTransform_incrementalDir() {
+    gradleRunner.addDependencies(
+      "implementation 'androidx.appcompat:appcompat:1.1.0'",
+      "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
+      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
+    )
+
+    gradleRunner.addSrcPackage("ui/")
+
+    gradleRunner.build().let {
+      val assembleTask = it.getTask(TRANSFORM_TASK_NAME)
+      assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
+    }
+
+    gradleRunner.build().let {
+      val assembleTask = it.getTask(TRANSFORM_TASK_NAME)
+      assertEquals(TaskOutcome.UP_TO_DATE, assembleTask.outcome)
+    }
+
+    gradleRunner.addSrc(
+      srcPath = "ui/OtherActivity.java",
+      srcContent =
+        """
+        package ui;
+
+        import androidx.appcompat.app.AppCompatActivity;
+
+        @dagger.hilt.android.AndroidEntryPoint
+        public class OtherActivity extends AppCompatActivity {
+
+        }
+        """.trimIndent()
+    )
+
+    val result = gradleRunner.build()
+    val assembleTask = result.getTask(TRANSFORM_TASK_NAME)
+    assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
+  }
+
+  companion object {
+    const val TRANSFORM_TASK_NAME =
+      ":transformDebugClassesWithAsm"
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simple/app/build.gradle b/javatests/artifacts/hilt-android/simple/app/build.gradle
index 0f8a05a..98b79e1 100644
--- a/javatests/artifacts/hilt-android/simple/app/build.gradle
+++ b/javatests/artifacts/hilt-android/simple/app/build.gradle
@@ -36,6 +36,9 @@
     testOptions {
         unitTests.includeAndroidResources = true
     }
+    lintOptions {
+        checkReleaseBuilds = false
+    }
     sourceSets {
         String sharedTestDir = 'src/sharedTest/java'
         test {
@@ -48,6 +51,7 @@
 }
 
 hilt {
+    enableExperimentalClasspathAggregation = true
     enableTransformForLocalTests = true
 }
 
diff --git a/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/TransitiveDependenciesTest.java b/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/TransitiveDependenciesTest.java
new file mode 100644
index 0000000..a4f900d
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/TransitiveDependenciesTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.simple;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import dagger.hilt.android.simple.feature.FeatureEntryPoints;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests that entry points in deep dependencies with `implementation` boundaries are available at
+ * runtime.
+ */
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+public class TransitiveDependenciesTest {
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void testEntryPoint() {
+    FeatureEntryPoints.invokeEntryPoints(
+        InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext());
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simple/deep-android-lib/build.gradle b/javatests/artifacts/hilt-android/simple/deep-android-lib/build.gradle
new file mode 100644
index 0000000..e5ed046
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simple/deep-android-lib/build.gradle
@@ -0,0 +1,26 @@
+plugins {
+    id 'com.android.library'
+    id 'dagger.hilt.android.plugin'
+}
+
+android {
+    compileSdkVersion 30
+    buildToolsVersion "30.0.2"
+
+    defaultConfig {
+        minSdkVersion 15
+        targetSdkVersion 30
+        versionCode 1
+        versionName "1.0"
+    }
+
+    compileOptions {
+        sourceCompatibility 1.8
+        targetCompatibility 1.8
+    }
+}
+
+dependencies {
+    implementation "com.google.dagger:hilt-android:$dagger_version"
+    annotationProcessor "com.google.dagger:hilt-compiler:$dagger_version"
+}
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simple/deep-android-lib/src/main/AndroidManifest.xml b/javatests/artifacts/hilt-android/simple/deep-android-lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..49a304e
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simple/deep-android-lib/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Dagger Authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dagger.hilt.android.simple.deep">
+
+</manifest>
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simple/deep-android-lib/src/main/java/dagger/hilt/android/simple/deep/DeepAndroidLib.java b/javatests/artifacts/hilt-android/simple/deep-android-lib/src/main/java/dagger/hilt/android/simple/deep/DeepAndroidLib.java
new file mode 100644
index 0000000..57fd803
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simple/deep-android-lib/src/main/java/dagger/hilt/android/simple/deep/DeepAndroidLib.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.simple.deep;
+
+import android.content.Context;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.EntryPointAccessors;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+
+public class DeepAndroidLib {
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface LibEntryPoint {
+    DeepAndroidLib getDeepAndroidInstance();
+  }
+
+  @Inject
+  public DeepAndroidLib() {}
+
+  public static DeepAndroidLib getInstance(Context context) {
+    return EntryPointAccessors.fromApplication(context, LibEntryPoint.class)
+        .getDeepAndroidInstance();
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simple/deep-lib/build.gradle b/javatests/artifacts/hilt-android/simple/deep-lib/build.gradle
new file mode 100644
index 0000000..22f815c
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simple/deep-lib/build.gradle
@@ -0,0 +1,13 @@
+plugins {
+    id 'java-library'
+}
+
+java {
+    sourceCompatibility = JavaVersion.VERSION_1_7
+    targetCompatibility = JavaVersion.VERSION_1_7
+}
+
+dependencies {
+    implementation "com.google.dagger:hilt-core:$dagger_version"
+    annotationProcessor "com.google.dagger:hilt-compiler:$dagger_version"
+}
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simple/deep-lib/src/main/java/dagger/hilt/android/deep/DeepLib.java b/javatests/artifacts/hilt-android/simple/deep-lib/src/main/java/dagger/hilt/android/deep/DeepLib.java
new file mode 100644
index 0000000..419e539
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simple/deep-lib/src/main/java/dagger/hilt/android/deep/DeepLib.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.deep;
+
+import dagger.hilt.EntryPoint;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+
+public class DeepLib {
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface LibEntryPoint {
+    DeepLib getDeepInstance();
+  }
+
+  @Inject
+  public DeepLib() {}
+
+  public static DeepLib getInstance(Object componentManager) {
+    return EntryPoints.get(componentManager, LibEntryPoint.class).getDeepInstance();
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simple/feature/build.gradle b/javatests/artifacts/hilt-android/simple/feature/build.gradle
index 8cf3dd5..0e772a8 100644
--- a/javatests/artifacts/hilt-android/simple/feature/build.gradle
+++ b/javatests/artifacts/hilt-android/simple/feature/build.gradle
@@ -48,6 +48,9 @@
     // by the app need to expose @kotlin.Metadata
     api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
 
+    implementation project(":deep-android-lib")
+    implementation project(":deep-lib")
+
     implementation 'androidx.appcompat:appcompat:1.2.0'
     implementation "com.google.dagger:hilt-android:$dagger_version"
     kapt "com.google.dagger:hilt-compiler:$dagger_version"
diff --git a/javatests/artifacts/hilt-android/simple/feature/src/main/java/dagger/hilt/android/simple/feature/FeatureEntryPoints.java b/javatests/artifacts/hilt-android/simple/feature/src/main/java/dagger/hilt/android/simple/feature/FeatureEntryPoints.java
new file mode 100644
index 0000000..c1ca22f
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simple/feature/src/main/java/dagger/hilt/android/simple/feature/FeatureEntryPoints.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.simple.feature;
+
+import android.content.Context;
+import dagger.hilt.android.deep.DeepLib;
+import dagger.hilt.android.simple.deep.DeepAndroidLib;
+
+public final class FeatureEntryPoints {
+
+  public static void invokeEntryPoints(Context context) {
+    DeepAndroidLib.getInstance(context);
+    DeepLib.getInstance(context);
+  }
+
+  private FeatureEntryPoints() {}
+}
diff --git a/javatests/artifacts/hilt-android/simple/settings.gradle b/javatests/artifacts/hilt-android/simple/settings.gradle
index 9a84d22..bad895d 100644
--- a/javatests/artifacts/hilt-android/simple/settings.gradle
+++ b/javatests/artifacts/hilt-android/simple/settings.gradle
@@ -2,3 +2,5 @@
 include ':app'
 include ':feature'
 include ':lib'
+include ':deep-android-lib'
+include ':deep-lib'
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/android-library/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/android-library/build.gradle
new file mode 100644
index 0000000..383c4be
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simpleKotlin/android-library/build.gradle
@@ -0,0 +1,31 @@
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+    id 'kotlin-kapt'
+}
+
+android {
+    compileSdkVersion 30
+    buildToolsVersion "30.0.2"
+
+    defaultConfig {
+        minSdkVersion 15
+        targetSdkVersion 30
+        versionCode 1
+        versionName "1.0"
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+    implementation project(':deep-android-lib')
+    implementation project(':deep-kotlin-lib')
+    implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'
+    kapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+}
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/android-library/src/main/AndroidManifest.xml b/javatests/artifacts/hilt-android/simpleKotlin/android-library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c60ba73
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simpleKotlin/android-library/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dagger.hilt.android.simpleKotlin.lib">
+
+</manifest>
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/android-library/src/main/java/dagger/hilt/android/simpleKotlin/lib/AndroidLibraryEntryPoints.kt b/javatests/artifacts/hilt-android/simpleKotlin/android-library/src/main/java/dagger/hilt/android/simpleKotlin/lib/AndroidLibraryEntryPoints.kt
new file mode 100644
index 0000000..cb10c8b
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simpleKotlin/android-library/src/main/java/dagger/hilt/android/simpleKotlin/lib/AndroidLibraryEntryPoints.kt
@@ -0,0 +1,12 @@
+package dagger.hilt.android.simpleKotlin.lib
+
+import android.content.Context
+import dagger.hilt.android.simpleKotlin.deep.DeepAndroidLib
+import dagger.hilt.android.simpleKotlin.deep.DeepLib
+
+object AndroidLibraryEntryPoints {
+  fun invokeEntryPoints(context: Context) {
+    DeepAndroidLib.getInstance(context)
+    DeepLib.getInstance(context)
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle
index 8dcf6a8..93442b4 100644
--- a/javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle
+++ b/javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle
@@ -47,9 +47,13 @@
     testOptions {
         unitTests.includeAndroidResources = true
     }
+    lintOptions {
+        checkReleaseBuilds = false
+    }
 }
 
 hilt {
+    enableExperimentalClasspathAggregation = true
     enableTransformForLocalTests = true
 }
 
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/app/src/androidTest/java/dagger/hilt/android/simpleKotlin/SimpleEmulatorTestRunner.kt b/javatests/artifacts/hilt-android/simpleKotlin/app/src/androidTest/java/dagger/hilt/android/simpleKotlin/SimpleEmulatorTestRunner.kt
new file mode 100644
index 0000000..d790265
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simpleKotlin/app/src/androidTest/java/dagger/hilt/android/simpleKotlin/SimpleEmulatorTestRunner.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dagger.hilt.android.simpleKotlin
+
+import android.app.Application
+import android.content.Context
+import androidx.test.runner.AndroidJUnitRunner
+import dagger.hilt.android.testing.HiltTestApplication
+
+/** A custom runner to setup the emulator application class for tests.  */
+class SimpleEmulatorTestRunner : AndroidJUnitRunner() {
+  override fun newApplication(cl: ClassLoader, className: String, context: Context): Application {
+    return super.newApplication(cl, HiltTestApplication::class.java.name, context)
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/app/src/sharedTest/java/dagger/hilt/android/simpleKotlin/TransitiveDependenciesTest.kt b/javatests/artifacts/hilt-android/simpleKotlin/app/src/sharedTest/java/dagger/hilt/android/simpleKotlin/TransitiveDependenciesTest.kt
new file mode 100644
index 0000000..10e6515
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simpleKotlin/app/src/sharedTest/java/dagger/hilt/android/simpleKotlin/TransitiveDependenciesTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dagger.hilt.android.simpleKotlin
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import dagger.hilt.android.simpleKotlin.lib.AndroidLibraryEntryPoints
+import dagger.hilt.android.simpleKotlin.lib.KotlinLibraryEntryPoints
+import dagger.hilt.android.testing.HiltAndroidRule
+import dagger.hilt.android.testing.HiltAndroidTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests that entry points in deep dependencies with `implementation` boundaries are available at
+ * runtime.
+ */
+@HiltAndroidTest
+@RunWith(AndroidJUnit4::class)
+class TransitiveDependenciesTest {
+  @get:Rule
+  val rule = HiltAndroidRule(this)
+
+  @Test
+  fun testEntryPointFromAndroidLib() {
+    AndroidLibraryEntryPoints.invokeEntryPoints(
+      InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
+    )
+  }
+
+  @Test
+  fun testEntryPointFromKotlinLib() {
+    KotlinLibraryEntryPoints.invokeEntryPoints(
+      InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
+    )
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/build.gradle
new file mode 100644
index 0000000..1774e8f
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/build.gradle
@@ -0,0 +1,29 @@
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+    id 'kotlin-kapt'
+}
+
+android {
+    compileSdkVersion 30
+    buildToolsVersion "30.0.2"
+
+    defaultConfig {
+        minSdkVersion 15
+        targetSdkVersion 30
+        versionCode 1
+        versionName "1.0"
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+    implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'
+    kapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+}
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/src/main/AndroidManifest.xml b/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ae2a5e3
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dagger.hilt.android.simpleKotlin.deep">
+
+</manifest>
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/src/main/java/dagger/hilt/android/simpleKotlin/deep/DeepAndroidLib.kt b/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/src/main/java/dagger/hilt/android/simpleKotlin/deep/DeepAndroidLib.kt
new file mode 100644
index 0000000..58d5e41
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/src/main/java/dagger/hilt/android/simpleKotlin/deep/DeepAndroidLib.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.simpleKotlin.deep
+
+import android.content.Context
+import dagger.hilt.EntryPoint
+import dagger.hilt.InstallIn
+import dagger.hilt.android.EntryPointAccessors
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Inject
+
+class DeepAndroidLib @Inject constructor() {
+  @EntryPoint
+  @InstallIn(SingletonComponent::class)
+  internal interface LibEntryPoint {
+    fun getDeepAndroidInstance(): DeepAndroidLib
+  }
+
+  companion object {
+    fun getInstance(context: Context) =
+      EntryPointAccessors.fromApplication(context, LibEntryPoint::class.java)
+        .getDeepAndroidInstance()
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/deep-kotlin-lib/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/deep-kotlin-lib/build.gradle
new file mode 100644
index 0000000..306c171
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simpleKotlin/deep-kotlin-lib/build.gradle
@@ -0,0 +1,16 @@
+plugins {
+    id 'java-library'
+    id 'kotlin'
+    id 'kotlin-kapt'
+}
+
+java {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+    implementation "com.google.dagger:hilt-core:LOCAL-SNAPSHOT"
+    kapt "com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT"
+}
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/deep-kotlin-lib/src/main/java/dagger/hilt/android/simpleKotlin/deep/DeepLib.kt b/javatests/artifacts/hilt-android/simpleKotlin/deep-kotlin-lib/src/main/java/dagger/hilt/android/simpleKotlin/deep/DeepLib.kt
new file mode 100644
index 0000000..0d2bc61
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simpleKotlin/deep-kotlin-lib/src/main/java/dagger/hilt/android/simpleKotlin/deep/DeepLib.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dagger.hilt.android.simpleKotlin.deep
+
+import dagger.hilt.EntryPoint
+import dagger.hilt.EntryPoints
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Inject
+
+class DeepLib @Inject constructor() {
+  @EntryPoint
+  @InstallIn(SingletonComponent::class)
+  internal interface LibEntryPoint {
+    fun getDeepInstance(): DeepLib
+  }
+
+  companion object {
+    fun getInstance(componentManager: Any) =
+      EntryPoints.get(componentManager, LibEntryPoint::class.java)
+        .getDeepInstance()
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/kotlin-library/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/kotlin-library/build.gradle
new file mode 100644
index 0000000..7c8462f
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simpleKotlin/kotlin-library/build.gradle
@@ -0,0 +1,17 @@
+plugins {
+    id 'java-library'
+    id 'kotlin'
+    id 'kotlin-kapt'
+}
+
+java {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+    implementation project(':deep-kotlin-lib')
+    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+    implementation "com.google.dagger:hilt-core:LOCAL-SNAPSHOT"
+    kapt "com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT"
+}
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/kotlin-library/src/main/java/dagger/hilt/android/simpleKotlin/lib/KotlinLibraryEntryPoints.kt b/javatests/artifacts/hilt-android/simpleKotlin/kotlin-library/src/main/java/dagger/hilt/android/simpleKotlin/lib/KotlinLibraryEntryPoints.kt
new file mode 100644
index 0000000..8989ebb
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simpleKotlin/kotlin-library/src/main/java/dagger/hilt/android/simpleKotlin/lib/KotlinLibraryEntryPoints.kt
@@ -0,0 +1,9 @@
+package dagger.hilt.android.simpleKotlin.lib
+
+import dagger.hilt.android.simpleKotlin.deep.DeepLib
+
+object KotlinLibraryEntryPoints {
+  fun invokeEntryPoints(component: Any) {
+    DeepLib.getInstance(component)
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/settings.gradle b/javatests/artifacts/hilt-android/simpleKotlin/settings.gradle
index e42f9d1..cea0913 100644
--- a/javatests/artifacts/hilt-android/simpleKotlin/settings.gradle
+++ b/javatests/artifacts/hilt-android/simpleKotlin/settings.gradle
@@ -1,2 +1,6 @@
 rootProject.name='Simple Kotlin Hilt Android'
 include ':app'
+include ':android-library'
+include ':kotlin-library'
+include ':deep-android-lib'
+include ':deep-kotlin-lib'