Slide 1

Slide 1 text

Writing backwards compatible Gradle plugins Simon Schiller ● droidcon Berlin 2022 simonxschiller simonschiller simonschiller.io

Slide 2

Slide 2 text

Gradle plugins ● Fundamental building blocks of Gradle ● Used to customize and extend build logic ● Plugins typically interact with other plugins

Slide 3

Slide 3 text

Writing Gradle plugins class SamplePlugin : Plugin { override fun apply(project: Project) { // Plugin logic } }

Slide 4

Slide 4 text

Writing Gradle plugins class SamplePlugin : Plugin { override fun apply(project: Project) { // Plugin logic } } # Located under META-INF/gradle-plugins/com.droidcon.sample.properties implementation-class=com.droidcon.sample.SamplePlugin

Slide 5

Slide 5 text

Interacting with other plugins // To interact with AGP we need a dependency on AGP dependencies { implementation("com.android.tools.build:gradle:7.0.0") }

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Backwards compatibility

Slide 8

Slide 8 text

Example - Manifest printing plugin plugins { id("com.droidcon.sample") version "1.0.0" id("com.android.application") version "7.0.0" }

Slide 9

Slide 9 text

Example - Manifest printing plugin plugins { id("com.droidcon.sample") version "1.0.0" id("com.android.application") version "7.0.0" } > ./gradlew printReleaseManifest > > > package="com.droidcon.sample" > > > > >

Slide 10

Slide 10 text

Backwards compatibility com.droidcon.sample 󰳕 AGP 7.0.0

Slide 11

Slide 11 text

Backwards compatibility com.droidcon.sample 󰳕 AGP 7.0.0 󰞵 AGP 4.2.0 󰠁 AGP 4.1.0

Slide 12

Slide 12 text

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 } } }

Slide 13

Slide 13 text

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 } } }

Slide 14

Slide 14 text

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 } } }

Slide 15

Slide 15 text

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 > > >

Slide 16

Slide 16 text

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) } } }

Slide 17

Slide 17 text

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) } } }

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

What is happening? project com.droidcon.sample com.android.application 4.2.0

Slide 20

Slide 20 text

What is happening? project com.droidcon.sample com.android.application com.android.application 7.0.0 4.2.0

Slide 21

Slide 21 text

What is happening? project com.droidcon.sample com.android.application com.android.application 7.0.0 4.2.0

Slide 22

Slide 22 text

What is happening? project com.droidcon.sample com.android.application com.android.application 7.0.0 4.2.0

Slide 23

Slide 23 text

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.android.tools.build gradle 7.0.0

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

How to resolve it // In the plugin code project.plugins.withId("com.android.application") { // Code interacting with the Android Gradle plugin }

Slide 27

Slide 27 text

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) } } }

Slide 28

Slide 28 text

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 > > >

Slide 29

Slide 29 text

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 > > > > ./gradlew printReleaseManifest > > FAILURE: Build failed with an exception > > * Exception is: > java.lang.NoClassDefFoundError: ...

Slide 30

Slide 30 text

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()

Slide 31

Slide 31 text

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 }

Slide 32

Slide 32 text

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 }

Slide 33

Slide 33 text

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 }

Slide 34

Slide 34 text

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 }

Slide 35

Slide 35 text

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 }

Slide 36

Slide 36 text

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 }

Slide 37

Slide 37 text

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 }

Slide 38

Slide 38 text

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 }

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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")

Slide 43

Slide 43 text

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)

Slide 44

Slide 44 text

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)

Slide 45

Slide 45 text

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)

Slide 46

Slide 46 text

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)

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Different code for different versions val callback = Action> { 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) }

Slide 49

Slide 49 text

Different code for different versions val callback = Action> { 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) }

Slide 50

Slide 50 text

Different code for different versions val callback = Action> { 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) }

Slide 51

Slide 51 text

Different code for different versions val callback = Action> { 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) }

Slide 52

Slide 52 text

Different code for different versions val callback = Action> { 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) }

Slide 53

Slide 53 text

Different code for different versions val callback = Action> { 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) }

Slide 54

Slide 54 text

Different code for different versions val callback = Action> { 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) }

Slide 55

Slide 55 text

Different code for different versions val callback = Action> { 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")

Slide 56

Slide 56 text

Different code for different versions val callback = Action> { 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")

Slide 57

Slide 57 text

1. Reflection

Slide 58

Slide 58 text

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 { 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) })

Slide 59

Slide 59 text

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 { 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) })

Slide 60

Slide 60 text

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 { 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) })

Slide 61

Slide 61 text

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 { 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) })

Slide 62

Slide 62 text

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 { 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) })

Slide 63

Slide 63 text

2. Groovy

Slide 64

Slide 64 text

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) { ... } }

Slide 65

Slide 65 text

// 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

Slide 66

Slide 66 text

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) { ... } }

Slide 67

Slide 67 text

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) { ... } }

Slide 68

Slide 68 text

// 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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

3. Compile against multiple versions

Slide 74

Slide 74 text

3. Compile against multiple versions val callback = Action> { 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")

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

3. Compile against multiple versions // Compatibility code in src/compat4Dot1/kotlin/Compat4Dot1.kt fun handle4Dot1(project: Project, callback: Action>) { 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>) { ... } // Compatibility code in src/compat7Dot0/kotlin/Compat7Dot0.kt fun handle7Dot0(project: Project, callback: Action>) { ... }

Slide 82

Slide 82 text

3. Compile against multiple versions // Compatibility code in src/compat4Dot1/kotlin/Compat4Dot1.kt fun handle4Dot1(project: Project, callback: Action>) { 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>) { ... } // Compatibility code in src/compat7Dot0/kotlin/Compat7Dot0.kt fun handle7Dot0(project: Project, callback: Action>) { ... }

Slide 83

Slide 83 text

3. Compile against multiple versions // Compatibility code in src/compat4Dot1/kotlin/Compat4Dot1.kt fun handle4Dot1(project: Project, callback: Action>) { 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>) { ... } // Compatibility code in src/compat7Dot0/kotlin/Compat7Dot0.kt fun handle7Dot0(project: Project, callback: Action>) { ... }

Slide 84

Slide 84 text

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") ...

Slide 85

Slide 85 text

// 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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

3. Compile against multiple versions ... compileOnly(compat4Dot1.output) compileOnly(compat4Dot2.output) compileOnly(compat7Dot0.output) } tasks.withType { 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

Slide 88

Slide 88 text

... compileOnly(compat4Dot1.output) compileOnly(compat4Dot2.output) compileOnly(compat7Dot0.output) } tasks.withType { 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

Slide 89

Slide 89 text

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) }

Slide 90

Slide 90 text

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) } } }

Slide 91

Slide 91 text

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 > > >

Slide 92

Slide 92 text

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 > > > > ./gradlew printReleaseManifest > > >

Slide 93

Slide 93 text

Non-plugin dependencies

Slide 94

Slide 94 text

Non-plugin dependencies your plugin dependency 1.2.0

Slide 95

Slide 95 text

Non-plugin dependencies your plugin dependency 1.2.0 1.1.0 dependency another plugin

Slide 96

Slide 96 text

Non-plugin dependencies your plugin dependency 1.2.0 1.1.0 dependency another plugin project

Slide 97

Slide 97 text

Non-plugin dependencies your plugin dependency 1.2.0 1.1.0 dependency another plugin project

Slide 98

Slide 98 text

Embedding dependencies Declaring dependencies your plugin dependency

Slide 99

Slide 99 text

Embedding dependencies Declaring dependencies Embedding dependencies your plugin dependency your plugin dependency

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

Testing

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

Testing against multiple versions See https://docs.gradle.org/current/userguide/testing_gradle_plugins.html for more details

Slide 107

Slide 107 text

What did we talk about? ● Backwards compatibility

Slide 108

Slide 108 text

What did we talk about? ● Backwards compatibility ● Why it can be a problem

Slide 109

Slide 109 text

What did we talk about? ● Backwards compatibility ● Why it can be a problem ● Determining the AGP version ○ Fields from AGP ○ Gradle APIs

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

Writing backwards compatible Gradle plugins Simon Schiller ● droidcon Berlin 2022 simonxschiller simonschiller simonschiller.io