[AGP DSL] Automatically support += in Groovy
Groovy `foo += bar` maps to something like setFoo(getFoo().plus(bar))
Bug: 140406102
Test: Expanded DslDecoratorUnitTest.
Change-Id: I68269f74b2542d874ef1d1a8371292626565df61
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dsl/decorator/DslDecorator.kt b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dsl/decorator/DslDecorator.kt
index 17cbffc..1a31587 100644
--- a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dsl/decorator/DslDecorator.kt
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dsl/decorator/DslDecorator.kt
@@ -18,6 +18,7 @@
import com.android.build.gradle.internal.dsl.AgpDslLockedException
import com.android.build.gradle.internal.dsl.Lockable
+import com.android.utils.usLocaleCapitalize
import com.android.utils.usLocaleDecapitalize
import com.google.common.annotations.VisibleForTesting
import com.google.common.base.Stopwatch
@@ -148,9 +149,7 @@
for (field in abstractProperties) {
createFieldBackedGetters(classWriter, generatedClass, field)
- if (field.settersToGenerate.isNotEmpty()) {
- createFieldBackedSetters(classWriter, generatedClass, field, lockable)
- }
+ createFieldBackedSetters(classWriter, generatedClass, field, lockable)
}
if (lockable) {
@@ -332,14 +331,21 @@
property: ManagedProperty,
lockable: Boolean
) {
- for (setter in property.settersToGenerate) {
- createFieldBackedSetter(
- classWriter,
- generatedClass,
- property,
- lockable,
- setter
- )
+ when (property.supportedPropertyType) {
+ is SupportedPropertyType.Val -> {
+ createGroovyMutatingSetter(classWriter, generatedClass, property)
+ }
+ is SupportedPropertyType.Var -> {
+ for (setter in property.settersToGenerate) {
+ createFieldBackedSetter(
+ classWriter,
+ generatedClass,
+ property,
+ lockable,
+ setter
+ )
+ }
+ }
}
}
@@ -353,7 +359,6 @@
val type = property.supportedPropertyType
check(type.implementationType == setter.argumentTypes[0]) {
"Currently only setters that use the same type are supported."
- // TODO(b/140406102): Support setters for groovy +=
}
// Mark bridge methods as synthetic.
val access = if(type.type == setter.argumentTypes[0]) property.access else property.access.or(Opcodes.ACC_SYNTHETIC)
@@ -385,6 +390,52 @@
}
}
+ /**
+ * To allow groovy field += bar, we need to generate a set(...) method.
+ *
+ * Mutability is not controlled here, it depends on the underlying lockable collection type.
+ */
+ private fun createGroovyMutatingSetter(
+ classWriter: ClassWriter,
+ generatedClass: Type,
+ property: ManagedProperty
+ ) {
+ val extraSetter = Method(
+ "set${property.name.usLocaleCapitalize()}",
+ Type.VOID_TYPE,
+ arrayOf(property.supportedPropertyType.type)
+ )
+ GeneratorAdapter(Opcodes.ACC_PUBLIC.or(Opcodes.ACC_SYNTHETIC), extraSetter, null, null, classWriter).apply {
+ // val newList = ArrayList(argument) // Take a copy so e.g. field = field doesn't clear the field!
+ // __backingField.clear()
+ // __backingField.addAll(newList)
+ newInstance(ARRAY_LIST)
+ dup()
+ loadArg(0)
+ checkCast(COLLECTION)
+ invokeConstructor(
+ ARRAY_LIST,
+ ARRAY_LIST_COPY_CONSTRUCTOR
+ )
+ loadThis()
+ getField(generatedClass, property.backingFieldName,
+ property.supportedPropertyType.implementationType
+ )
+ dup()
+ invokeVirtual(
+ property.supportedPropertyType.implementationType,
+ CLEAR
+ )
+ swap()
+ invokeVirtual(
+ property.supportedPropertyType.implementationType,
+ ADD_ALL
+ )
+ returnValue()
+ endMethod()
+ }
+ }
+
private fun createFieldBackedGetters(
classWriter: ClassWriter,
generatedClass: Type,
@@ -425,6 +476,12 @@
private val LOCK_METHOD = Method("lock", Type.VOID_TYPE, arrayOf())
private val LOCKED_EXCEPTION = Type.getType(AgpDslLockedException::class.java)
+ private val COLLECTION = Type.getType(Collection::class.java)
+ private val ARRAY_LIST = Type.getType(ArrayList::class.java)
+ private val ARRAY_LIST_COPY_CONSTRUCTOR = Method("<init>", Type.VOID_TYPE, arrayOf(COLLECTION))
+ private val CLEAR = Method("clear", Type.VOID_TYPE, arrayOf())
+ private val ADD_ALL = Method("addAll", Type.BOOLEAN_TYPE, arrayOf(COLLECTION))
+
// Use reflection to avoid needing to compile against java11 APIs yet.
private val privateLookupInMethod by lazy(LazyThreadSafetyMode.PUBLICATION) {
MethodHandles::class.java.getDeclaredMethod(
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/dsl/decorator/DslDecoratorUnitTest.kt b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/dsl/decorator/DslDecoratorUnitTest.kt
index 554dab6..97ca0a2 100644
--- a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/dsl/decorator/DslDecoratorUnitTest.kt
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/dsl/decorator/DslDecoratorUnitTest.kt
@@ -20,6 +20,7 @@
import com.android.build.gradle.internal.dsl.Lockable
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import groovy.util.Eval
import org.gradle.api.provider.Property
import org.junit.Test
import java.lang.reflect.Modifier
@@ -317,4 +318,22 @@
.getDeclaredConstructor().newInstance()
assertThat(decorated.foo).isEqualTo("hello")
}
+
+ interface WithList {
+ val list: MutableList<String>
+ }
+
+ @Test
+ fun `check groovy setter generation`() {
+ val decorated = DslDecorator(listOf(SupportedPropertyType.Val.List))
+ .decorate(WithList::class)
+ val withList = decorated.getDeclaredConstructor().newInstance()
+ Eval.me("withList", withList, "withList.list += ['one', 'two']")
+ assertThat(withList.list).containsExactly("one", "two").inOrder()
+ Eval.me("withList", withList, "withList.list += 'three'")
+ assertThat(withList.list).containsExactly("one", "two", "three").inOrder()
+ // Check self-assignment preserves values
+ Eval.me("withList", withList, "withList.list = withList.list")
+ assertThat(withList.list).containsExactly("one", "two", "three").inOrder()
+ }
}