diff --git a/java/dagger/hilt/android/example/gradle/simple/app/build.gradle b/java/dagger/hilt/android/example/gradle/simple/app/build.gradle index 71e225e326a..b3687ee63c0 100644 --- a/java/dagger/hilt/android/example/gradle/simple/app/build.gradle +++ b/java/dagger/hilt/android/example/gradle/simple/app/build.gradle @@ -47,10 +47,6 @@ android { } } -hilt { - enableTransformForLocalTests = true -} - dependencies { implementation project(':feature') implementation 'androidx.appcompat:appcompat:1.2.0' diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/BroadcastReceiverTest.java b/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/BroadcastReceiverTest.java index 1557b755d31..7585bbb1d6c 100644 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/BroadcastReceiverTest.java +++ b/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/BroadcastReceiverTest.java @@ -93,6 +93,22 @@ public void verifyBaseReceiverIsNotDoubleInjected() throws InterruptedException assertThat(receiver.onReceiveCalled).isEqualTo(1); } + @Test + public void verifyComplexReceiverInjectedValue() throws InterruptedException { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + TestReceiverFour receiver = new TestReceiverFour(); + IntentFilter intentFilter = new IntentFilter("test-action"); + context.registerReceiver(receiver, intentFilter); + + Intent intent = new Intent(); + intent.setAction("test-action"); + context.sendBroadcast(intent); + + receiver.latch.await(2, TimeUnit.SECONDS); + assertThat(receiver.injectedValue).isNotEmpty(); + } + /** Test receiver */ @AndroidEntryPoint static class TestReceiverOne extends BroadcastReceiver { @@ -139,6 +155,35 @@ public void onReceive(Context context, Intent intent) { } } + /** Complex-ish test receiver */ + @AndroidEntryPoint + static class TestReceiverFour extends BroadcastReceiver { + + final CountDownLatch latch = new CountDownLatch(1); + + @Inject String injectedValue; + + @Override + public void onReceive(Context context, Intent intent) { + // Weird code, but it tests that the exception table and stack table frames are correctly + // updated in a transformation. + boolean var0; + if (context != null) { + var0 = false; + Object var1 = context.getClass(); + try { + throw new IllegalStateException(); + } catch (IllegalStateException ex) { + var0 = true; + } + } else { + BroadcastReceiver myself = this; + var0 = false; + } + latch.countDown(); + } + } + /** Base test receiver */ abstract static class BaseReceiverAbstractMethod extends BroadcastReceiver { diff --git a/java/dagger/hilt/android/example/gradle/simple/build.gradle b/java/dagger/hilt/android/example/gradle/simple/build.gradle index 46342ed1d92..a60d47f819b 100644 --- a/java/dagger/hilt/android/example/gradle/simple/build.gradle +++ b/java/dagger/hilt/android/example/gradle/simple/build.gradle @@ -17,7 +17,7 @@ buildscript { ext { kotlin_version = '1.3.61' - agp_version = System.getenv('AGP_VERSION') ?: "4.1.1" + agp_version = "4.2.0-beta01" } repositories { google() diff --git a/java/dagger/hilt/android/example/gradle/simple/feature/build.gradle b/java/dagger/hilt/android/example/gradle/simple/feature/build.gradle index 70fa1ea3055..462aefc1eea 100644 --- a/java/dagger/hilt/android/example/gradle/simple/feature/build.gradle +++ b/java/dagger/hilt/android/example/gradle/simple/feature/build.gradle @@ -39,10 +39,6 @@ kapt { correctErrorTypes true } -hilt { - enableTransformForLocalTests = true -} - dependencies { // This is api instead of implementation since Kotlin modules here consumed // by the app need to expose @kotlin.Metadata diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/build.gradle b/java/dagger/hilt/android/example/gradle/simpleKotlin/app/build.gradle index 5fc1923e34a..bff4797997c 100644 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/build.gradle +++ b/java/dagger/hilt/android/example/gradle/simpleKotlin/app/build.gradle @@ -39,10 +39,6 @@ android { } } -hilt { - enableTransformForLocalTests = true -} - dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/build.gradle b/java/dagger/hilt/android/example/gradle/simpleKotlin/build.gradle index 17f228968fa..c54652c91a4 100644 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/build.gradle +++ b/java/dagger/hilt/android/example/gradle/simpleKotlin/build.gradle @@ -17,7 +17,7 @@ buildscript { ext { kotlin_version = '1.3.61' - agp_version = System.getenv('AGP_VERSION') ?: "4.1.1" + agp_version = "4.2.0-beta01" } repositories { google() diff --git a/java/dagger/hilt/android/plugin/build.gradle b/java/dagger/hilt/android/plugin/build.gradle index 4445af00712..cbeb142d605 100644 --- a/java/dagger/hilt/android/plugin/build.gradle +++ b/java/dagger/hilt/android/plugin/build.gradle @@ -22,7 +22,7 @@ buildscript { } plugins { - id 'org.jetbrains.kotlin.jvm' version '1.3.61' + id 'org.jetbrains.kotlin.jvm' version '1.4.20' id 'java-gradle-plugin' id 'maven-publish' } @@ -32,15 +32,33 @@ repositories { jcenter() } +configurations { + additionalTestPlugin { + canBeConsumed = false + canBeResolved = true + extendsFrom implementation + } +} + dependencies { implementation gradleApi() - implementation 'com.android.tools.build:gradle:3.6.3' - implementation 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61' + compileOnly 'com.android.tools.build:gradle:4.2.0-beta01' + 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' testImplementation gradleTestKit() testImplementation 'junit:junit:4.12' testImplementation 'com.google.truth:truth:1.0.1' + additionalTestPlugin 'com.android.tools.build:gradle:4.2.0-beta01' +} + +// Configure the generating task of plugin-under-test-metadata.properties to +// include additional dependencies for the injected plugin classpath that +// are not present in the main runtime dependencies. This allows us to test +// the desired AGP version while keeping a compileOnly dep on the main source. +tasks.withType(PluginUnderTestMetadata.class).named("pluginUnderTestMetadata").configure { + it.pluginClasspath.from(configurations.additionalTestPlugin) } // Create sources Jar from main kotlin sources diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt new file mode 100644 index 00000000000..b3c345a1191 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt @@ -0,0 +1,197 @@ +package dagger.hilt.android.plugin + +import com.android.build.api.instrumentation.AsmClassVisitorFactory +import com.android.build.api.instrumentation.ClassContext +import com.android.build.api.instrumentation.ClassData +import com.android.build.api.instrumentation.InstrumentationParameters +import java.io.File +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.FieldVisitor +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes + +/** + * ASM Adapter that transforms @AndroidEntryPoint-annotated classes to extend the Hilt + * generated android class, including the @HiltAndroidApp application class. + */ +@Suppress("UnstableApiUsage") +class AndroidEntryPointClassVisitor( + private val apiVersion: Int, + nextClassVisitor: ClassVisitor, + private val additionalClasses: File +) : ClassVisitor(Opcodes.ASM8, nextClassVisitor) { + + interface AndroidEntryPointParams : InstrumentationParameters { + @get:Input + val additionalClassesDir: Property + } + + abstract class Factory : AsmClassVisitorFactory { + override fun createClassVisitor( + classContext: ClassContext, + nextClassVisitor: ClassVisitor + ): ClassVisitor { + return AndroidEntryPointClassVisitor( + apiVersion = instrumentationContext.apiVersion.get(), + nextClassVisitor = nextClassVisitor, + additionalClasses = parameters.get().additionalClassesDir.get() + ) + } + + /** + * Check if a class should be transformed. + * + * Only classes that are an Android entry point should be transformed. + */ + override fun isInstrumentable(classData: ClassData) = + classData.classAnnotations.any { ANDROID_ENTRY_POINT_ANNOTATIONS.contains(it) } + } + + // The name of the Hilt generated superclass in it internal form. + // e.g. "my/package/Hilt_MyActivity" + lateinit var newSuperclassName: String + + lateinit var oldSuperclassName: String + + override fun visit( + version: Int, + access: Int, + name: String, + signature: String?, + superName: String?, + interfaces: Array? + ) { + val packageName = name.substringBeforeLast('/') + val className = name.substringAfterLast('/') + newSuperclassName = + packageName + "/Hilt_" + className.replace("$", "_") + oldSuperclassName = superName ?: error { "Superclass of $name is null!" } + super.visit(version, access, name, signature, newSuperclassName, interfaces) + } + + override fun visitMethod( + access: Int, + name: String, + descriptor: String, + signature: String?, + exceptions: Array? + ): MethodVisitor { + val nextMethodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) + val invokeSpecialVisitor = InvokeSpecialAdapter(apiVersion, nextMethodVisitor) + if (name == ON_RECEIVE_METHOD_NAME && + descriptor == ON_RECEIVE_METHOD_DESCRIPTOR && + hasOnReceiveBytecodeInjectionMarker() + ) { + return OnReceiveAdapter(apiVersion, invokeSpecialVisitor) + } + return invokeSpecialVisitor + } + + /** + * Adapter for super calls (e.g. super.onCreate()) that rewrites the owner reference of the + * invokespecial instruction to use the new superclass. + * + * The invokespecial instruction is emitted for code that between other things also invokes a + * method of a superclass of the current class. The opcode invokespecial takes two operands, each + * of 8 bit, that together represent an address in the constant pool to a method reference. The + * method reference is computed at compile-time by looking the direct superclass declaration, but + * at runtime the code behaves like invokevirtual, where as the actual method invoked is looked up + * based on the class hierarchy. + * + * However, it has been observed that on APIs 19 to 22 the Android Runtime (ART) jumps over the + * direct superclass and into the method reference class, causing unexpected behaviours. + * Therefore, this method performs the additional transformation to rewrite direct super call + * invocations to use a method reference whose class in the pool is the new superclass. Note that + * this is not necessary for constructor calls since the Javassist library takes care of those. + * + * @see: https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html#jvms-6.5.invokespecial + * @see: https://source.android.com/devices/tech/dalvik/dalvik-bytecode + */ + inner class InvokeSpecialAdapter( + apiVersion: Int, + nextClassVisitor: MethodVisitor + ) : MethodVisitor(apiVersion, nextClassVisitor) { + override fun visitMethodInsn( + opcode: Int, + owner: String, + name: String, + descriptor: String, + isInterface: Boolean + ) { + if (opcode == Opcodes.INVOKESPECIAL && owner == oldSuperclassName) { + // Update the owner of all INVOKESPECIAL instructions, including those found in + // constructors. + super.visitMethodInsn(opcode, newSuperclassName, name, descriptor, isInterface) + } else { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + } + } + } + + /** + * Method adapter for a BroadcastReceiver's onReceive method to insert a super call since with + * its new superclass, onReceive will no longer be abstract (it is implemented by Hilt generated + * receiver). + */ + inner class OnReceiveAdapter( + apiVersion: Int, + nextClassVisitor: MethodVisitor + ) : MethodVisitor(apiVersion, nextClassVisitor) { + override fun visitCode() { + super.visitCode() + super.visitIntInsn(Opcodes.ALOAD, 0) // Load 'this' + super.visitIntInsn(Opcodes.ALOAD, 1) // Load method param 1 (Context) + super.visitIntInsn(Opcodes.ALOAD, 2) // Load method param 2 (Intent) + super.visitMethodInsn( + Opcodes.INVOKESPECIAL, + newSuperclassName, + ON_RECEIVE_METHOD_NAME, + ON_RECEIVE_METHOD_DESCRIPTOR, + false + ) + } + } + + /** + * Check if Hilt generated class is a BroadcastReceiver with the marker field which means + * a super.onReceive invocation has to be inserted in the implementation. + */ + private fun hasOnReceiveBytecodeInjectionMarker() = + findAdditionalClassFile(newSuperclassName).inputStream().use { + var hasMarker = false + ClassReader(it).accept( + object : ClassVisitor(apiVersion) { + override fun visitField( + access: Int, + name: String, + descriptor: String, + signature: String?, + value: Any? + ): FieldVisitor? { + if (name == "onReceiveBytecodeInjectionMarker") { + hasMarker = true + } + return null + } + }, + ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES + ) + return@use hasMarker + } + + private fun findAdditionalClassFile(className: String) = + File(additionalClasses, "$className.class") + + companion object { + val ANDROID_ENTRY_POINT_ANNOTATIONS = setOf( + "dagger.hilt.android.AndroidEntryPoint", + "dagger.hilt.android.HiltAndroidApp" + ) + const val ON_RECEIVE_METHOD_NAME = "onReceive" + const val ON_RECEIVE_METHOD_DESCRIPTOR = + "(Landroid/content/Context;Landroid/content/Intent;)V" + } +} 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 4f6f57831e0..14665212990 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 @@ -23,6 +23,9 @@ interface HiltExtension { * 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 * running Robolectric UI tests as part of your JUnit tests. + * + * This flag is not necessary if when com.android.tools.build:gradle:4.2.0+ is used and will be + * deprecated in a future version. */ var enableTransformForLocalTests: Boolean } 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 fc298515f29..cfc604a4579 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 @@ -16,9 +16,15 @@ package dagger.hilt.android.plugin +import com.android.build.api.component.Component +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.BaseExtension import com.android.build.gradle.TestedExtension import com.android.build.gradle.api.AndroidBasePlugin +import dagger.hilt.android.plugin.util.SimpleAGPVersion +import java.io.File import org.gradle.api.Plugin import org.gradle.api.Project @@ -48,14 +54,50 @@ class HiltGradlePlugin : Plugin { } private fun configureHilt(project: Project) { - val extension = project.extensions.create( + val hiltExtension = project.extensions.create( HiltExtension::class.java, "hilt", HiltExtensionImpl::class.java ) - configureTransform(project, extension) + if (SimpleAGPVersion.ANDROID_GRADLE_PLUGIN_VERSION < SimpleAGPVersion(4, 2)) { + // Configures bytecode transform using older APIs pre AGP 4.2 + configureTransform(project, hiltExtension) + } else { + // Configures bytecode transform using AGP 4.2 ASM pipeline. + configureTransformASM(project, hiltExtension) + } configureProcessorFlags(project) } - private fun configureTransform(project: Project, extension: HiltExtension) { + @Suppress("UnstableApiUsage") + private fun configureTransformASM(project: Project, hiltExtension: HiltExtension) { + var warnAboutLocalTestsFlag = false + fun registerTransform(androidComponent: Component) { + if (hiltExtension.enableTransformForLocalTests && !warnAboutLocalTestsFlag) { + project.logger.warn( + "The Hilt configuration option 'enableTransformForLocalTests' is no longer necessary " + + "when com.android.tools.build:gradle:4.2.0+ is used." + ) + warnAboutLocalTestsFlag = true + } + androidComponent.transformClassesWith( + classVisitorFactoryImplClass = AndroidEntryPointClassVisitor.Factory::class.java, + scope = InstrumentationScope.PROJECT + ) { params -> + val classesDir = + File(project.buildDir, "intermediates/javac/${androidComponent.name}/classes") + params.additionalClassesDir.set(classesDir) + } + androidComponent.setAsmFramesComputationMode( + FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS + ) + } + + val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) + androidComponents.onVariants { registerTransform(it) } + androidComponents.androidTest { registerTransform(it) } + androidComponents.unitTest { registerTransform(it) } + } + + private fun configureTransform(project: Project, hiltExtension: HiltExtension) { val androidExtension = project.extensions.findByType(BaseExtension::class.java) ?: throw error("Android BaseExtension not found.") androidExtension.registerTransform(AndroidEntryPointTransform()) @@ -66,7 +108,7 @@ class HiltGradlePlugin : Plugin { HiltTransformTestClassesTask.create( project = project, unitTestVariant = unitTestVariant, - extension = extension + extension = hiltExtension ) } } diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt new file mode 100644 index 00000000000..1580431243b --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt @@ -0,0 +1,41 @@ +package dagger.hilt.android.plugin.util + +import com.android.Version + +/** + * Simple Android Gradle Plugin version class since there is no public API one. b/175816217 + */ +internal data class SimpleAGPVersion( + val major: Int, + val minor: Int, +) : Comparable { + + override fun compareTo(other: SimpleAGPVersion): Int { + return compareValuesBy( + this, + other, + compareBy(SimpleAGPVersion::major).thenBy(SimpleAGPVersion::minor) + ) { it } + } + + companion object { + + val ANDROID_GRADLE_PLUGIN_VERSION by lazy { parse(Version.ANDROID_GRADLE_PLUGIN_VERSION) } + + fun parse(version: String?) = + tryParse(version) ?: error("Unable to parse AGP version: $version") + + private fun tryParse(version: String?): SimpleAGPVersion? { + if (version == null) { + return null + } + + val parts = version.split('.') + if (parts.size == 1) { + return SimpleAGPVersion(parts[0].toInt(), 0) + } + + return SimpleAGPVersion(parts[0].toInt(), parts[1].toInt()) + } + } +} diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/GradleTransformTestRunner.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/GradleTransformTestRunner.kt index f2f8ba06961..4ba3bcb6bb3 100644 --- a/java/dagger/hilt/android/plugin/src/test/kotlin/GradleTransformTestRunner.kt +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/GradleTransformTestRunner.kt @@ -91,7 +91,7 @@ class GradleTransformTestRunner(val tempFolder: TemporaryFolder) { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'com.android.tools.build:gradle:4.2.0-beta01' } } @@ -101,13 +101,13 @@ class GradleTransformTestRunner(val tempFolder: TemporaryFolder) { } android { - compileSdkVersion 29 - buildToolsVersion "29.0.2" + compileSdkVersion 30 + buildToolsVersion "30.0.2" defaultConfig { applicationId "plugin.test" minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 30 } compileOptions { @@ -177,15 +177,14 @@ class GradleTransformTestRunner(val tempFolder: TemporaryFolder) { fun getOutput() = buildResult.output // Finds a transformed file. The srcFilePath is relative to the app's package. - fun getTransformedFile(srcFilePath: String) = File( - projectRoot, - "build/intermediates/transforms/AndroidEntryPointTransform/debug" - ).listFiles()?.first { it.isDirectory }?.let { transformedDir -> - File(transformedDir, srcFilePath).also { + fun getTransformedFile(srcFilePath: String): File { + val parentDir = + File(projectRoot, "build/intermediates/asm_instrumented_project_classes/debug") + return File(parentDir, srcFilePath).also { if (!it.exists()) { error("Unable to find transformed class ${it.path}") } } - } ?: error("Unable to find transformed output directory.") + } } } 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 c0cf71ea9cc..89a16c467cd 100644 --- a/java/dagger/hilt/android/plugin/src/test/kotlin/HiltGradlePluginTest.kt +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/HiltGradlePluginTest.kt @@ -342,6 +342,6 @@ class HiltGradlePluginTest { companion object { const val TRANSFORM_TASK_NAME = - ":transformClassesWithAndroidEntryPointTransformForDebug" + ":transformDebugClassesWithAsm" } } diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt index 631a58fa0dd..a003ab5164e 100644 --- a/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt @@ -111,13 +111,13 @@ class IncrementalProcessorTest { } android { - compileSdkVersion 29 - buildToolsVersion "29.0.2" + compileSdkVersion 30 + buildToolsVersion "30.0.2" defaultConfig { applicationId "hilt.simple" minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 30 } compileOptions { diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/util/SimpleAGPVersionTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/util/SimpleAGPVersionTest.kt new file mode 100644 index 00000000000..75292b83c58 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/util/SimpleAGPVersionTest.kt @@ -0,0 +1,40 @@ +package util + +import com.google.common.truth.Truth.assertThat +import dagger.hilt.android.plugin.util.SimpleAGPVersion +import org.junit.Test + +class SimpleAGPVersionTest { + + @Test + fun parsing() { + assertThat(SimpleAGPVersion.parse("4.2")) + .isEqualTo(SimpleAGPVersion(4, 2)) + assertThat(SimpleAGPVersion.parse("4.2.1")) + .isEqualTo(SimpleAGPVersion(4, 2)) + assertThat(SimpleAGPVersion.parse("7.0.0-alpha01")) + .isEqualTo(SimpleAGPVersion(7, 0)) + } + + @Test + fun comparing() { + assertThat(SimpleAGPVersion(4, 2)) + .isEqualTo(SimpleAGPVersion(4, 2)) + assertThat(SimpleAGPVersion(4, 2)) + .isGreaterThan(SimpleAGPVersion(3, 4)) + assertThat(SimpleAGPVersion(4, 2)) + .isLessThan(SimpleAGPVersion(7, 0)) + + assertThat(SimpleAGPVersion.parse("4.2.1")) + .isEqualTo(SimpleAGPVersion.parse("4.2.2")) + assertThat(SimpleAGPVersion.parse("4.2.1")) + .isGreaterThan(SimpleAGPVersion.parse("3.4.1")) + assertThat(SimpleAGPVersion.parse("4.2.1")) + .isLessThan(SimpleAGPVersion.parse("7.0.1")) + + assertThat(SimpleAGPVersion.parse("4.2.1")) + .isLessThan(SimpleAGPVersion.parse("7.0.0-alpha01")) + assertThat(SimpleAGPVersion.parse("7.0.0-alpha01")) + .isEqualTo(SimpleAGPVersion.parse("7.0.0-alpha02")) + } +} diff --git a/javatests/artifacts/dagger-android/simple/build.gradle b/javatests/artifacts/dagger-android/simple/build.gradle index f8313339219..b3f69968544 100644 --- a/javatests/artifacts/dagger-android/simple/build.gradle +++ b/javatests/artifacts/dagger-android/simple/build.gradle @@ -15,12 +15,15 @@ */ buildscript { + ext { + agp_version = System.getenv('AGP_VERSION') ?: "4.2.0-beta01" + } repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath "com.android.tools.build:gradle:$agp_version" } } diff --git a/javatests/artifacts/hilt-android/simple/build.gradle b/javatests/artifacts/hilt-android/simple/build.gradle index 539137a429b..32913e31716 100644 --- a/javatests/artifacts/hilt-android/simple/build.gradle +++ b/javatests/artifacts/hilt-android/simple/build.gradle @@ -18,7 +18,7 @@ buildscript { ext { dagger_version = 'LOCAL-SNAPSHOT' kotlin_version = '1.3.61' - agp_version = System.getenv('AGP_VERSION') ?: "4.1.1" + agp_version = System.getenv('AGP_VERSION') ?: "4.2.0-beta01" } repositories { google() diff --git a/javatests/artifacts/hilt-android/simpleKotlin/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/build.gradle index 17f228968fa..04cf8ada22a 100644 --- a/javatests/artifacts/hilt-android/simpleKotlin/build.gradle +++ b/javatests/artifacts/hilt-android/simpleKotlin/build.gradle @@ -17,7 +17,7 @@ buildscript { ext { kotlin_version = '1.3.61' - agp_version = System.getenv('AGP_VERSION') ?: "4.1.1" + agp_version = System.getenv('AGP_VERSION') ?: "4.2.0-beta01" } repositories { google() diff --git a/util/run-local-tests.sh b/util/run-local-tests.sh index 9ea08d847f8..0a886c8c514 100755 --- a/util/run-local-tests.sh +++ b/util/run-local-tests.sh @@ -25,7 +25,9 @@ done # Run gradle tests with different versions of Android Gradle Plugin -readonly AGP_VERSIONS=("4.0.2" "3.6.4") +# At least latest stable and upcoming versions, this list can't be too long +# or else we timeout CI job. +readonly AGP_VERSIONS=("4.2.0-beta01" "4.1.0") readonly ANDROID_GRADLE_PROJECTS=( "java/dagger/example/gradle/android/simple" "javatests/artifacts/dagger-android/simple"