Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Writing backwards compatible Gradle plugins - droidcon Berlin 2022

Writing backwards compatible Gradle plugins - droidcon Berlin 2022

Slides accompanying my talk about "Writing backwards compatible Gradle plugins" held at droidcon Berlin 2022

Simon Schiller

July 06, 2022
Tweet

Other Decks in Programming

Transcript

  1. Writing backwards compatible Gradle plugins Simon Schiller • droidcon Berlin

    2022 simonxschiller simonschiller simonschiller.io
  2. Gradle plugins • Fundamental building blocks of Gradle • Used

    to customize and extend build logic • Plugins typically interact with other plugins
  3. Writing Gradle plugins class SamplePlugin : Plugin<Project> { override fun

    apply(project: Project) { // Plugin logic } } # Located under META-INF/gradle-plugins/com.droidcon.sample.properties implementation-class=com.droidcon.sample.SamplePlugin
  4. Interacting with other plugins // To interact with AGP we

    need a dependency on AGP dependencies { implementation("com.android.tools.build:gradle:7.0.0") }
  5. Resources around writing Gradle plugins • Using Gradle Plugins •

    Developing Custom Gradle Plugins • Extend the Android Gradle plugin • Gradle and AGP Build APIs - How to write a plugin
  6. Example - Manifest printing plugin plugins { id("com.droidcon.sample") version "1.0.0"

    id("com.android.application") version "7.0.0" } > ./gradlew printReleaseManifest > > <?xml version="1.0" encoding="utf-8"?> > <manifest xmlns:android="http://schemas.android.com/apk/res/android" > package="com.droidcon.sample" > > > <application android:hasCode="false" /> > > </manifest>
  7. Backwards compatibility id("com.android.application") version "7.0.0" id("com.droidcon.sample") version "1.0.0" android {

    buildTypes { named("release").configure { isMinifyEnabled = true } } } > ./gradlew printReleaseManifest > > <?xml version="1.0" encoding="utf-8"?> > <manifest xmlns:android="...">
  8. Backwards compatibility id("com.android.application") version "7.0.0" id("com.droidcon.sample") version "1.0.0" android {

    buildTypes { named("release").configure { isMinifyEnabled = true } } } id("com.android.application") version "4.2.0" id("com.droidcon.sample") version "1.0.0" android { buildTypes { named("release").configure { minifyEnabled(true) } } }
  9. Backwards compatibility id("com.android.application") version "7.0.0" id("com.droidcon.sample") version "1.0.0" android {

    buildTypes { named("release").configure { isMinifyEnabled = true } } } id("com.android.application") version "4.2.0" id("com.droidcon.sample") version "1.0.0" android { buildTypes { named("release").configure { minifyEnabled(true) } } }
  10. Backwards compatibility id("com.android.application") version "7.0.0" id("com.droidcon.sample") version "1.0.0" android {

    buildTypes { named("release").configure { isMinifyEnabled = true } } } id("com.android.application") version "4.2.0" id("com.droidcon.sample") version "1.0.0" android { buildTypes { named("release").configure { minifyEnabled(true) } } } > ./gradlew printReleaseManifest > > Script compilation error: > minifyEnabled(true) > ^ Unresolved reference: minifyEnabled
  11. What is happening? // In our com.droidcon.sample plugin dependencies {

    implementation("com.android.tools.build:gradle:7.0.0") } project com.droidcon.sample com.android.application com.android.application 7.0.0 4.2.0 <!-- com.droidcon.sample pom.xml --> <dependency> <groupId>com.android.tools.build</groupId> <artifactId>gradle</artifactId> <version>7.0.0</version> </dependency>
  12. How to resolve it // In our com.droidcon.sample plugin dependencies

    { compileOnly("com.android.tools.build:gradle:7.0.0") } project com.droidcon.sample com.android.application 4.2.0 <!-- com.droidcon.sample pom.xml --> <dependencies> </dependencies>
  13. How to resolve it // In our com.droidcon.sample plugin dependencies

    { compileOnly("com.android.tools.build:gradle:7.0.0") } project com.droidcon.sample com.android.application 4.2.0
  14. Does it work now? id("com.android.application") version "7.0.0" id("com.droidcon.sample") version "1.0.0"

    android { buildTypes { named("release").configure { isMinifyEnabled = true } } } id("com.android.application") version "4.2.0" id("com.droidcon.sample") version "1.0.0" android { buildTypes { named("release").configure { minifyEnabled(true) } } }
  15. Does it work now? id("com.android.application") version "7.0.0" id("com.droidcon.sample") version "1.0.0"

    android { buildTypes { named("release").configure { isMinifyEnabled = true } } } id("com.android.application") version "4.2.0" id("com.droidcon.sample") version "1.0.0" android { buildTypes { named("release").configure { minifyEnabled(true) } } } > ./gradlew printReleaseManifest > > <?xml version="1.0" encoding="utf-8"?> > <manifest xmlns:android="...">
  16. Does it work now? id("com.android.application") version "7.0.0" id("com.droidcon.sample") version "1.0.0"

    android { buildTypes { named("release").configure { isMinifyEnabled = true } } } id("com.android.application") version "4.2.0" id("com.droidcon.sample") version "1.0.0" android { buildTypes { named("release").configure { minifyEnabled(true) } } } > ./gradlew printReleaseManifest > > <?xml version="1.0" encoding="utf-8"?> > <manifest xmlns:android="..."> > ./gradlew printReleaseManifest > > FAILURE: Build failed with an exception > > * Exception is: > java.lang.NoClassDefFoundError: ...
  17. The problem Version A sample.Foo.foo() sample.Bar.bar() sample.Baz.baz() Version B sample.Foo.foo()

    sample.Bar.change() sample.Baz.baz() Version C sample.Foo.change() sample.Bar.change() sample.Baz.baz()
  18. Locating the merged manifest - AGP 4.1.0 import com.android.build.api.artifact.ArtifactType import

    com.android.build.api.dsl.ApplicationExtension val extension = project.extensions.getByType(ApplicationExtension::class.java) extension.onVariantProperties { variant -> val manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) // Do something with the manifest }
  19. Locating the merged manifest - AGP 4.1.0 import com.android.build.api.artifact.ArtifactType import

    com.android.build.api.dsl.ApplicationExtension val extension = project.extensions.getByType(ApplicationExtension::class.java) extension.onVariantProperties { variant -> val manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) // Do something with the manifest }
  20. Locating the merged manifest - AGP 4.1.0 import com.android.build.api.artifact.ArtifactType import

    com.android.build.api.dsl.ApplicationExtension val extension = project.extensions.getByType(ApplicationExtension::class.java) extension.onVariantProperties { variant -> val manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) // Do something with the manifest }
  21. Locating the merged manifest - AGP 4.1.0 import com.android.build.api.artifact.ArtifactType import

    com.android.build.api.dsl.ApplicationExtension val extension = project.extensions.getByType(ApplicationExtension::class.java) extension.onVariantProperties { variant -> val manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) // Do something with the manifest }
  22. Locating the merged manifest - AGP 4.2.0 import com.android.build.api.artifact.ArtifactType import

    com.android.build.api.extension.AndroidComponentsExtension val extension = project.extensions.getByType(AndroidComponentsExtension::class.java) extension.onVariants { variant -> val manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) // Do something with the manifest }
  23. Locating the merged manifest - AGP 4.2.0 import com.android.build.api.artifact.ArtifactType import

    com.android.build.api.extension.AndroidComponentsExtension val extension = project.extensions.getByType(AndroidComponentsExtension::class.java) extension.onVariants { variant -> val manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) // Do something with the manifest }
  24. Locating the merged manifest - AGP 7.0.0 import com.android.build.api.artifact.SingleArtifact import

    com.android.build.api.variant.AndroidComponentsExtension val extension = project.extensions.getByType(AndroidComponentsExtension::class.java) extension.onVariants { variant -> val manifest = variant.artifacts.get(SingleArtifact.MERGED_MANIFEST) // Do something with the manifest }
  25. Locating the merged manifest - AGP 7.0.0 import com.android.build.api.artifact.SingleArtifact import

    com.android.build.api.variant.AndroidComponentsExtension val extension = project.extensions.getByType(AndroidComponentsExtension::class.java) extension.onVariants { variant -> val manifest = variant.artifacts.get(SingleArtifact.MERGED_MANIFEST) // Do something with the manifest }
  26. Which version are we running against? Run code specific to

    A Run code specific to B Run code specific to C How to resolve it A B C
  27. Which version are we running against? Run code specific to

    A Run code specific to B Run code specific to C How to resolve it A B C
  28. 1. Android Gradle plugin APIs import com.android.Version val version =

    Version.ANDROID_GRADLE_PLUGIN_VERSION // 7.0.0 return VersionNumber.parse(version)
  29. 1. Android Gradle plugin APIs import com.android.Version val version =

    Version.ANDROID_GRADLE_PLUGIN_VERSION // 7.0.0 return VersionNumber.parse(version) // In your build.gradle.kts compileOnly("com.android.tools:common:30.0.0")
  30. 2. Gradle APIs // Get all resolved Gradle classpath dependencies

    val buildscript = project.rootProject.buildscript val config = buildscript.configurations.getByName("classpath") val artifacts = config.resolvedConfiguration.resolvedArtifacts // Find the Android Gradle plugin dependency val androidGradlePlugin = artifacts.singleOrNull { it.moduleVersion.id.toString().startsWith("com.android.tools.build:gradle:") } ?: error("Could not determine Android Gradle plugin version") // Extract the resolved Android Gradle plugin version val version = androidGradlePlugin.moduleVersion.id.version // 7.0.0 return VersionNumber.parse(version)
  31. 2. Gradle APIs // Get all resolved Gradle classpath dependencies

    val buildscript = project.rootProject.buildscript val config = buildscript.configurations.getByName("classpath") val artifacts = config.resolvedConfiguration.resolvedArtifacts // Find the Android Gradle plugin dependency val androidGradlePlugin = artifacts.singleOrNull { it.moduleVersion.id.toString().startsWith("com.android.tools.build:gradle:") } ?: error("Could not determine Android Gradle plugin version") // Extract the resolved Android Gradle plugin version val version = androidGradlePlugin.moduleVersion.id.version // 7.0.0 return VersionNumber.parse(version)
  32. 2. Gradle APIs // Get all resolved Gradle classpath dependencies

    val buildscript = project.rootProject.buildscript val config = buildscript.configurations.getByName("classpath") val artifacts = config.resolvedConfiguration.resolvedArtifacts // Find the Android Gradle plugin dependency val androidGradlePlugin = artifacts.singleOrNull { it.moduleVersion.id.toString().startsWith("com.android.tools.build:gradle:") } ?: error("Could not determine Android Gradle plugin version") // Extract the resolved Android Gradle plugin version val version = androidGradlePlugin.moduleVersion.id.version // 7.0.0 return VersionNumber.parse(version)
  33. 2. Gradle APIs // Get all resolved Gradle classpath dependencies

    val buildscript = project.rootProject.buildscript val config = buildscript.configurations.getByName("classpath") val artifacts = config.resolvedConfiguration.resolvedArtifacts // Find the Android Gradle plugin dependency val androidGradlePlugin = artifacts.singleOrNull { it.moduleVersion.id.toString().startsWith("com.android.tools.build:gradle:") } ?: error("Could not determine Android Gradle plugin version") // Extract the resolved Android Gradle plugin version val version = androidGradlePlugin.moduleVersion.id.version // 7.0.0 return VersionNumber.parse(version)
  34. Which version are we running against? Run code specific to

    A Run code specific to B Run code specific to C How to resolve it A B C
  35. Different code for different versions val callback = Action<Provider<RegularFile>> {

    manifest -> // Do something with the manifest } // Call the correct code depending on the available version val version = getAndroidGradlePluginVersion(project) when { version < VersionNumber.parse("4.1.0") -> error("Unsupported AGP version") version < VersionNumber.parse("4.2.0") -> handle4Dot1(project, callback) version < VersionNumber.parse("7.0.0") -> handle4Dot2(project, callback) else -> handle7Dot0(project, callback) }
  36. Different code for different versions val callback = Action<Provider<RegularFile>> {

    manifest -> // Do something with the manifest } // Call the correct code depending on the available version val version = getAndroidGradlePluginVersion(project) when { version < VersionNumber.parse("4.1.0") -> error("Unsupported AGP version") version < VersionNumber.parse("4.2.0") -> handle4Dot1(project, callback) version < VersionNumber.parse("7.0.0") -> handle4Dot2(project, callback) else -> handle7Dot0(project, callback) }
  37. Different code for different versions val callback = Action<Provider<RegularFile>> {

    manifest -> // Do something with the manifest } // Call the correct code depending on the available version val version = getAndroidGradlePluginVersion(project) when { version < VersionNumber.parse("4.1.0") -> error("Unsupported AGP version") version < VersionNumber.parse("4.2.0") -> handle4Dot1(project, callback) version < VersionNumber.parse("7.0.0") -> handle4Dot2(project, callback) else -> handle7Dot0(project, callback) }
  38. Different code for different versions val callback = Action<Provider<RegularFile>> {

    manifest -> // Do something with the manifest } // Call the correct code depending on the available version val version = getAndroidGradlePluginVersion(project) when { version < VersionNumber.parse("4.1.0") -> error("Unsupported AGP version") version < VersionNumber.parse("4.2.0") -> handle4Dot1(project, callback) version < VersionNumber.parse("7.0.0") -> handle4Dot2(project, callback) else -> handle7Dot0(project, callback) }
  39. Different code for different versions val callback = Action<Provider<RegularFile>> {

    manifest -> // Do something with the manifest } // Call the correct code depending on the available version val version = getAndroidGradlePluginVersion(project) when { version < VersionNumber.parse("4.1.0") -> error("Unsupported AGP version") version < VersionNumber.parse("4.2.0") -> handle4Dot1(project, callback) version < VersionNumber.parse("7.0.0") -> handle4Dot2(project, callback) else -> handle7Dot0(project, callback) }
  40. Different code for different versions val callback = Action<Provider<RegularFile>> {

    manifest -> // Do something with the manifest } // Call the correct code depending on the available version val version = getAndroidGradlePluginVersion(project) when { version < VersionNumber.parse("4.1.0") -> error("Unsupported AGP version") version < VersionNumber.parse("4.2.0") -> handle4Dot1(project, callback) version < VersionNumber.parse("7.0.0") -> handle4Dot2(project, callback) else -> handle7Dot0(project, callback) }
  41. Different code for different versions val callback = Action<Provider<RegularFile>> {

    manifest -> // Do something with the manifest } // Call the correct code depending on the available version val version = getAndroidGradlePluginVersion(project) when { version < VersionNumber.parse("4.1.0") -> error("Unsupported AGP version") version < VersionNumber.parse("4.2.0") -> handle4Dot1(project, callback) version < VersionNumber.parse("7.0.0") -> handle4Dot2(project, callback) else -> handle7Dot0(project, callback) }
  42. Different code for different versions val callback = Action<Provider<RegularFile>> {

    manifest -> // Do something with the manifest } // Call the correct code depending on the available version val version = getAndroidGradlePluginVersion(project) when { version < VersionNumber.parse("4.1.0") -> error("Unsupported AGP version") version < VersionNumber.parse("4.2.0") -> handle4Dot1(project, callback) version < VersionNumber.parse("7.0.0") -> handle4Dot2(project, callback) else -> handle7Dot0(project, callback) } compileOnly("com.android.tools.build:gradle:4.1.0")
  43. Different code for different versions val callback = Action<Provider<RegularFile>> {

    manifest -> // Do something with the manifest } // Call the correct code depending on the available version val version = getAndroidGradlePluginVersion(project) when { version < VersionNumber.parse("4.1.0") -> error("Unsupported AGP version") version < VersionNumber.parse("4.2.0") -> handle4Dot1(project, callback) version < VersionNumber.parse("7.0.0") -> handle4Dot2(project, callback) else -> handle7Dot0(project, callback) } compileOnly("com.android.tools.build:gradle:4.2.0")
  44. 1. Reflection // Compatibility code for Android Gradle plugin 4.1.0

    val android = project.extensions.getByName("android") val method = android::class.java.getMethod("onVariantProperties", Action::class.java) method.invoke(android, Action<Any> { variant -> val className = "com.android.build.api.artifact.ArtifactType" val enumClass = Class.forName("$className\$MERGED_MANIFEST") val enumInstance = enumClass.getField("INSTANCE").get(null) val artifacts = variant::class.java.getMethod("getArtifacts").invoke(variant) val getMethod = artifacts::class.java.getMethod("get", Class.forName(className)) val manifest = getMethod.invoke(artifacts, enumInstance) callback.execute(manifest) })
  45. 1. Reflection // Compatibility code for Android Gradle plugin 4.1.0

    val android = project.extensions.getByName("android") val method = android::class.java.getMethod("onVariantProperties", Action::class.java) method.invoke(android, Action<Any> { variant -> val className = "com.android.build.api.artifact.ArtifactType" val enumClass = Class.forName("$className\$MERGED_MANIFEST") val enumInstance = enumClass.getField("INSTANCE").get(null) val artifacts = variant::class.java.getMethod("getArtifacts").invoke(variant) val getMethod = artifacts::class.java.getMethod("get", Class.forName(className)) val manifest = getMethod.invoke(artifacts, enumInstance) callback.execute(manifest) })
  46. 1. Reflection // Compatibility code for Android Gradle plugin 4.1.0

    val android = project.extensions.getByName("android") val method = android::class.java.getMethod("onVariantProperties", Action::class.java) method.invoke(android, Action<Any> { variant -> val className = "com.android.build.api.artifact.ArtifactType" val enumClass = Class.forName("$className\$MERGED_MANIFEST") val enumInstance = enumClass.getField("INSTANCE").get(null) val artifacts = variant::class.java.getMethod("getArtifacts").invoke(variant) val getMethod = artifacts::class.java.getMethod("get", Class.forName(className)) val manifest = getMethod.invoke(artifacts, enumInstance) callback.execute(manifest) })
  47. 1. Reflection // Compatibility code for Android Gradle plugin 4.1.0

    val android = project.extensions.getByName("android") val method = android::class.java.getMethod("onVariantProperties", Action::class.java) method.invoke(android, Action<Any> { variant -> val className = "com.android.build.api.artifact.ArtifactType" val enumClass = Class.forName("$className\$MERGED_MANIFEST") val enumInstance = enumClass.getField("INSTANCE").get(null) val artifacts = variant::class.java.getMethod("getArtifacts").invoke(variant) val getMethod = artifacts::class.java.getMethod("get", Class.forName(className)) val manifest = getMethod.invoke(artifacts, enumInstance) callback.execute(manifest) })
  48. 1. Reflection // Compatibility code for Android Gradle plugin 4.1.0

    val android = project.extensions.getByName("android") val method = android::class.java.getMethod("onVariantProperties", Action::class.java) method.invoke(android, Action<Any> { variant -> val className = "com.android.build.api.artifact.ArtifactType" val enumClass = Class.forName("$className\$MERGED_MANIFEST") val enumInstance = enumClass.getField("INSTANCE").get(null) val artifacts = variant::class.java.getMethod("getArtifacts").invoke(variant) val getMethod = artifacts::class.java.getMethod("get", Class.forName(className)) val manifest = getMethod.invoke(artifacts, enumInstance) callback.execute(manifest) })
  49. 2. Groovy // Compatibility code in src/main/groovy/Compat.groovy class Compat {

    static void handle4Dot1(Project project, Action callback) { project.android.onVariantProperties { variant -> def manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) callback.execute(manifest) } } static void handle4Dot2(Project project, Action callback) { ... } static void handle7Dot0(Project project, Action callback) { ... } }
  50. // Compatibility code in src/main/groovy/Compat.groovy class Compat { static void

    handle4Dot1(Project project, Action callback) { project.android.onVariantProperties { variant -> def manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) callback.execute(manifest) } } static void handle4Dot2(Project project, Action callback) { ... } static void handle7Dot0(Project project, Action callback) { ... } } 2. Groovy
  51. 2. Groovy // Compatibility code in src/main/groovy/Compat.groovy class Compat {

    static void handle4Dot1(Project project, Action callback) { project.android.onVariantProperties { variant -> def manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) callback.execute(manifest) } } static void handle4Dot2(Project project, Action callback) { ... } static void handle7Dot0(Project project, Action callback) { ... } }
  52. 2. Groovy // Compatibility code in src/main/groovy/Compat.groovy class Compat {

    static void handle4Dot1(Project project, Action callback) { project.android.onVariantProperties { variant -> def manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) callback.execute(manifest) } } static void handle4Dot2(Project project, Action callback) { ... } static void handle7Dot0(Project project, Action callback) { ... } }
  53. // Compatibility code in src/main/groovy/Compat.groovy class Compat { static void

    handle4Dot1(Project project, Action callback) { project.android.onVariantProperties { variant -> def manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) callback.execute(manifest) } } static void handle4Dot2(Project project, Action callback) { ... } static void handle7Dot0(Project project, Action callback) { ... } } 2. Groovy
  54. 2. Groovy // In your build.gradle.kts plugins { id("org.gradle.groovy") }

    // Include dependencies in the Groovy classpath tasks.withType<GroovyCompile> { classpath = sourceSets.main.get().compileClasspath } // Include compiled Groovy classes in the Kotlin classpath tasks.withType<KotlinCompile> { classpath += files(sourceSets.main.get().groovy.classesDirectory) }
  55. 2. Groovy // In your build.gradle.kts plugins { id("org.gradle.groovy") }

    // Include dependencies in the Groovy classpath tasks.withType<GroovyCompile> { classpath = sourceSets.main.get().compileClasspath } // Include compiled Groovy classes in the Kotlin classpath tasks.withType<KotlinCompile> { classpath += files(sourceSets.main.get().groovy.classesDirectory) } groovy
  56. // In your build.gradle.kts plugins { id("org.gradle.groovy") } // Include

    dependencies in the Groovy classpath tasks.withType<GroovyCompile> { classpath = sourceSets.main.get().compileClasspath } // Include compiled Groovy classes in the Kotlin classpath tasks.withType<KotlinCompile> { classpath += files(sourceSets.main.get().groovy.classesDirectory) } 2. Groovy groovy dependencies
  57. // In your build.gradle.kts plugins { id("org.gradle.groovy") } // Include

    dependencies in the Groovy classpath tasks.withType<GroovyCompile> { classpath = sourceSets.main.get().compileClasspath } // Include compiled Groovy classes in the Kotlin classpath tasks.withType<KotlinCompile> { classpath += files(sourceSets.main.get().groovy.classesDirectory) } 2. Groovy kotlin groovy dependencies
  58. 3. Compile against multiple versions val callback = Action<Provider<RegularFile>> {

    manifest -> // Do something with the manifest } // Call the correct code depending on the available version val version = getAndroidGradlePluginVersion(project) when { version < VersionNumber.parse("4.1.0") -> error("Unsupported AGP version") version < VersionNumber.parse("4.2.0") -> handle4Dot1(project, callback) version < VersionNumber.parse("7.0.0") -> handle4Dot2(project, callback) else -> handle7Dot0(project, callback) } compileOnly("com.android.tools.build:gradle:4.1.0")
  59. 3. Compile against multiple versions src/ │ ├─ main/ │

    ├─ AndroidManifest.xml │ └─ kotlin/.../ │ ├─ debug/ │ ├─ AndroidManifest.xml │ └─ kotlin/.../ │ └─ release/ ├─ AndroidManifest.xml └─ kotlin/.../
  60. 3. Compile against multiple versions src/ │ ├─ main/ │

    ├─ AndroidManifest.xml │ └─ kotlin/.../ │ ├─ debug/ │ ├─ AndroidManifest.xml │ └─ kotlin/.../ │ └─ release/ ├─ AndroidManifest.xml └─ kotlin/.../
  61. 3. Compile against multiple versions src/ │ ├─ main/ │

    ├─ AndroidManifest.xml │ └─ kotlin/.../ │ ├─ debug/ │ ├─ AndroidManifest.xml │ └─ kotlin/.../ │ └─ release/ ├─ AndroidManifest.xml └─ kotlin/.../
  62. 3. Compile against multiple versions src/ │ ├─ main/ │

    ├─ AndroidManifest.xml │ └─ kotlin/.../ │ ├─ debug/ │ ├─ AndroidManifest.xml │ └─ kotlin/.../ │ └─ release/ ├─ AndroidManifest.xml └─ kotlin/.../ src/ │ ├─ main/kotlin/.../ │ └─ SamplePlugin.kt │ ├─ compat4Dot1/kotlin/.../ │ └─ Compat4Dot1.kt │ ├─ compat4Dot2/kotlin/.../ │ └─ Compat4Dot2.kt │ └─ compat7Dot0/kotlin/.../ └─ Compat7Dot0.kt
  63. 3. Compile against multiple versions src/ │ ├─ main/ │

    ├─ AndroidManifest.xml │ └─ kotlin/.../ │ ├─ debug/ │ ├─ AndroidManifest.xml │ └─ kotlin/.../ │ └─ release/ ├─ AndroidManifest.xml └─ kotlin/.../ src/ │ ├─ main/kotlin/.../ │ └─ SamplePlugin.kt │ ├─ compat4Dot1/kotlin/.../ │ └─ Compat4Dot1.kt │ ├─ compat4Dot2/kotlin/.../ │ └─ Compat4Dot2.kt │ └─ compat7Dot0/kotlin/.../ └─ Compat7Dot0.kt
  64. 3. Compile against multiple versions src/ │ ├─ main/ │

    ├─ AndroidManifest.xml │ └─ kotlin/.../ │ ├─ debug/ │ ├─ AndroidManifest.xml │ └─ kotlin/.../ │ └─ release/ ├─ AndroidManifest.xml └─ kotlin/.../ src/ │ ├─ main/kotlin/.../ │ └─ SamplePlugin.kt │ ├─ compat4Dot1/kotlin/.../ │ └─ Compat4Dot1.kt │ ├─ compat4Dot2/kotlin/.../ │ └─ Compat4Dot2.kt │ └─ compat7Dot0/kotlin/.../ └─ Compat7Dot0.kt
  65. 3. Compile against multiple versions // Compatibility code in src/compat4Dot1/kotlin/Compat4Dot1.kt

    fun handle4Dot1(project: Project, callback: Action<Provider<RegularFile>>) { val extension = project.extensions.getByType(ApplicationExtension::class.java) extension.onVariantProperties { variant -> val manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) callback.execute(manifest) } } // Compatibility code in src/compat4Dot2/kotlin/Compat4Dot2.kt fun handle4Dot2(project: Project, callback: Action<Provider<RegularFile>>) { ... } // Compatibility code in src/compat7Dot0/kotlin/Compat7Dot0.kt fun handle7Dot0(project: Project, callback: Action<Provider<RegularFile>>) { ... }
  66. 3. Compile against multiple versions // Compatibility code in src/compat4Dot1/kotlin/Compat4Dot1.kt

    fun handle4Dot1(project: Project, callback: Action<Provider<RegularFile>>) { val extension = project.extensions.getByType(ApplicationExtension::class.java) extension.onVariantProperties { variant -> val manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) callback.execute(manifest) } } // Compatibility code in src/compat4Dot2/kotlin/Compat4Dot2.kt fun handle4Dot2(project: Project, callback: Action<Provider<RegularFile>>) { ... } // Compatibility code in src/compat7Dot0/kotlin/Compat7Dot0.kt fun handle7Dot0(project: Project, callback: Action<Provider<RegularFile>>) { ... }
  67. 3. Compile against multiple versions // Compatibility code in src/compat4Dot1/kotlin/Compat4Dot1.kt

    fun handle4Dot1(project: Project, callback: Action<Provider<RegularFile>>) { val extension = project.extensions.getByType(ApplicationExtension::class.java) extension.onVariantProperties { variant -> val manifest = variant.artifacts.get(ArtifactType.MERGED_MANIFEST) callback.execute(manifest) } } // Compatibility code in src/compat4Dot2/kotlin/Compat4Dot2.kt fun handle4Dot2(project: Project, callback: Action<Provider<RegularFile>>) { ... } // Compatibility code in src/compat7Dot0/kotlin/Compat7Dot0.kt fun handle7Dot0(project: Project, callback: Action<Provider<RegularFile>>) { ... }
  68. 3. Compile against multiple versions // In your build.gradle.kts val

    compat4Dot1 by sourceSets.creating val compat4Dot2 by sourceSets.creating val compat7Dot0 by sourceSets.creating dependencies { compat4Dot1.compileOnlyConfigurationName("com.android.tools.build:gradle:4.1.0") compat4Dot2.compileOnlyConfigurationName("com.android.tools.build:gradle:4.2.0") compat7Dot0.compileOnlyConfigurationName("com.android.tools.build:gradle:7.0.0") ...
  69. // In your build.gradle.kts val compat4Dot1 by sourceSets.creating val compat4Dot2

    by sourceSets.creating val compat7Dot0 by sourceSets.creating dependencies { compat4Dot1.compileOnlyConfigurationName("com.android.tools.build:gradle:4.1.0") compat4Dot2.compileOnlyConfigurationName("com.android.tools.build:gradle:4.2.0") compat7Dot0.compileOnlyConfigurationName("com.android.tools.build:gradle:7.0.0") ... 3. Compile against multiple versions compat4Dot2 compat4Dot1 compat7Dot0
  70. 3. Compile against multiple versions // In your build.gradle.kts val

    compat4Dot1 by sourceSets.creating val compat4Dot2 by sourceSets.creating val compat7Dot0 by sourceSets.creating dependencies { compat4Dot1.compileOnlyConfigurationName("com.android.tools.build:gradle:4.1.0") compat4Dot2.compileOnlyConfigurationName("com.android.tools.build:gradle:4.2.0") compat7Dot0.compileOnlyConfigurationName("com.android.tools.build:gradle:7.0.0") ... compat4Dot2 compat4Dot1 compat7Dot0 AGP 4.2.0 AGP 4.1.0 AGP 7.0.0
  71. 3. Compile against multiple versions ... compileOnly(compat4Dot1.output) compileOnly(compat4Dot2.output) compileOnly(compat7Dot0.output) }

    tasks.withType<Jar> { from(compat4Dot1.output) from(compat4Dot2.output) from(compat7Dot0.output) } compat4Dot2 compat4Dot1 compat7Dot0 AGP 4.2.0 AGP 4.1.0 AGP 7.0.0 main
  72. ... compileOnly(compat4Dot1.output) compileOnly(compat4Dot2.output) compileOnly(compat7Dot0.output) } tasks.withType<Jar> { from(compat4Dot1.output) from(compat4Dot2.output) from(compat7Dot0.output)

    } com.droidcon.sample 3. Compile against multiple versions compat4Dot2 compat4Dot1 compat7Dot0 AGP 4.2.0 AGP 4.1.0 AGP 7.0.0 main
  73. 3. Compile against multiple versions // Main code in src/main/kotlin/SamplePlugin.kt

    val version = getAndroidGradlePluginVersion(project) when { version < VersionNumber.parse("4.1.0") -> error("Unsupported AGP version") version < VersionNumber.parse("4.2.0") -> handle4Dot1(project, callback) version < VersionNumber.parse("7.0.0") -> handle4Dot2(project, callback) else -> handle7Dot0(project, callback) }
  74. Does it work now? id("com.android.application") version "7.0.0" id("com.droidcon.sample") version "1.0.0"

    android { buildTypes { named("release").configure { isMinifyEnabled = true } } } id("com.android.application") version "4.2.0" id("com.droidcon.sample") version "1.0.0" android { buildTypes { named("release").configure { minifyEnabled(true) } } }
  75. Does it work now? id("com.android.application") version "7.0.0" id("com.droidcon.sample") version "1.0.0"

    android { buildTypes { named("release").configure { isMinifyEnabled = true } } } id("com.android.application") version "4.2.0" id("com.droidcon.sample") version "1.0.0" android { buildTypes { named("release").configure { minifyEnabled(true) } } } > ./gradlew printReleaseManifest > > <?xml version="1.0" encoding="utf-8"?> > <manifest xmlns:android="...">
  76. Does it work now? id("com.android.application") version "7.0.0" id("com.droidcon.sample") version "1.0.0"

    android { buildTypes { named("release").configure { isMinifyEnabled = true } } } id("com.android.application") version "4.2.0" id("com.droidcon.sample") version "1.0.0" android { buildTypes { named("release").configure { minifyEnabled(true) } } } > ./gradlew printReleaseManifest > > <?xml version="1.0" encoding="utf-8"?> > <manifest xmlns:android="..."> > ./gradlew printReleaseManifest > > <?xml version="1.0" encoding="utf-8"?> > <manifest xmlns:android="...">
  77. Embedding dependencies Declaring dependencies Embedding dependencies Also repackage classes: foo.Dependency

    -> plugin.foo.Dependency foo.bar.Other -> plugin.foo.bar.Other your plugin dependency your plugin dependency
  78. Embedding dependencies Declaring dependencies Embedding dependencies Also repackage classes: foo.Dependency

    -> plugin.foo.Dependency foo.bar.Other -> plugin.foo.bar.Other your plugin dependency See https://github.com/johnrengelman/shadow or https://github.com/GradleUp/gr8 for more details your plugin dependency
  79. Testing against multiple versions # Example CI config for GitHub

    Actions jobs: verify: strategy: matrix: android-gradle-plugin: [ 4.1.0, 4.2.0, 7.0.0 ] steps: # ... - name: 'Run checks' env: ANDROID_GRADLE_PLUGIN_VERSION: ${{ matrix.android-gradle-plugin }} run: ./gradlew check --stacktrace # ... See https://docs.gradle.org/current/userguide/testing_gradle_plugins.html for more details
  80. Testing against multiple versions # Example CI config for GitHub

    Actions jobs: verify: strategy: matrix: android-gradle-plugin: [ 4.1.0, 4.2.0, 7.0.0 ] steps: # ... - name: 'Run checks' env: ANDROID_GRADLE_PLUGIN_VERSION: ${{ matrix.android-gradle-plugin }} run: ./gradlew check --stacktrace # ... See https://docs.gradle.org/current/userguide/testing_gradle_plugins.html for more details
  81. Testing against multiple versions # Example CI config for GitHub

    Actions jobs: verify: strategy: matrix: android-gradle-plugin: [ 4.1.0, 4.2.0, 7.0.0 ] steps: # ... - name: 'Run checks' env: ANDROID_GRADLE_PLUGIN_VERSION: ${{ matrix.android-gradle-plugin }} run: ./gradlew check --stacktrace # ... See https://docs.gradle.org/current/userguide/testing_gradle_plugins.html for more details
  82. What did we talk about? • Backwards compatibility • Why

    it can be a problem • Determining the AGP version ◦ Fields from AGP ◦ Gradle APIs
  83. What did we talk about? • Backwards compatibility • Why

    it can be a problem • Determining the AGP version ◦ Fields from AGP ◦ Gradle APIs • Writing compatible code ◦ Reflection ◦ Groovy ◦ Compiling against multiple versions
  84. What did we talk about? • Backwards compatibility • Why

    it can be a problem • Determining the AGP version ◦ Fields from AGP ◦ Gradle APIs • Writing compatible code ◦ Reflection ◦ Groovy ◦ Compiling against multiple versions • Non-plugin dependencies
  85. What did we talk about? • Backwards compatibility • Why

    it can be a problem • Determining the AGP version ◦ Fields from AGP ◦ Gradle APIs • Writing compatible code ◦ Reflection ◦ Groovy ◦ Compiling against multiple versions • Non-plugin dependencies • Testing
  86. What did we talk about? • Backwards compatibility • Why

    it can be a problem • Determining the AGP version ◦ Fields from AGP ◦ Gradle APIs • Writing compatible code ◦ Reflection ◦ Groovy ◦ Compiling against multiple versions • Non-plugin dependencies • Testing See https://github.com/simonschiller/manifestprinter for a sample project
  87. Writing backwards compatible Gradle plugins Simon Schiller • droidcon Berlin

    2022 simonxschiller simonschiller simonschiller.io