Slide 1

Slide 1 text

Make Gradle work for you Create your own Tasks and Plugins By MWM | April 2023

Slide 2

Slide 2 text

Us 2 Frédéric Torcheux Android developer MWM @bowser_f Cécile Dreyfus Android developer MWM

Slide 3

Slide 3 text

What are we going to talk about ? We usually do the same things in our gradle files: - Android configuration - Dependencies - Repository / plugin configurations - … Our main issue as Android developers: - We are usually not comfortable with custom tasks - When they must evolve we don’t really know what we are doing 3

Slide 4

Slide 4 text

Our goal today - Give you the tools to be comfortable with writing your own Gradle Tasks and Plugins - Understand what we do on a daily basis with regular/common plugins 4

Slide 5

Slide 5 text

What is Gradle - Build automation tool - Open Source - Can be used with Kotlin or Groovy 5

Slide 6

Slide 6 text

What is a Task? “A Task represents some atomic piece of work which a build performs” ● compiling some classes ● creating a JAR ● generating Javadoc ● publishing some archives to a repository. 6

Slide 7

Slide 7 text

Simple Task tasks.register("helloAndroidMakers") { println("Printed at configuration step") doLast { println("Printed at Task execution") } } 7 app/build.gradle.kts

Slide 8

Slide 8 text

Where to put the Task code (1/3) In the build.gradle.kts of the Gradle module we want the Task. Pros ● Directly usable without doing anything else. Cons ● Not accessible outside the script. 8

Slide 9

Slide 9 text

Where to put the Task code (2/3) In the buildSrc of the Android Project. Pros ● Automatically accessible to all modules of the project. ● Task code separated from where it’s used. Cons ● A change in the code in buildSrc make the whole project to become out-of-date. 9

Slide 10

Slide 10 text

Where to put the Task code (3/3) In a standalone module. Pros ● Possible to release the generated JAR in a repository to use it in another project. Cons ● Longer setup ● Possibly overkill depending on your needs 10

Slide 11

Slide 11 text

Create a complex Task What do we want? 11

Slide 12

Slide 12 text

Let’s configure our “buildSrc” plugins { `kotlin-dsl` } repositories { // The org.jetbrains.kotlin.jvm plugin requires a repository // where to download the Kotlin compiler dependencies from. mavenCentral() } 12 buildSrc/build.gradle.kts

Slide 13

Slide 13 text

abstract class BundleReleaseFilesTask : DefaultTask() { @get:InputDirectory abstract val projectDir: DirectoryProperty @get:Input abstract val appVersion: Property @get:OutputDirectory abstract val outputDirectory: DirectoryProperty @TaskAction fun run() { // TODO } } 13 buildSrc/src/main/kotlin/BundleReleaseFilesTask.kt

Slide 14

Slide 14 text

abstract class BundleReleaseFilesTask : DefaultTask() { @get:InputDirectory abstract val projectDir: DirectoryProperty @get:Input abstract val appVersion: Property @get:OutputDirectory abstract val outputDirectory: DirectoryProperty @TaskAction fun run() { // TODO } } 14 buildSrc/src/main/kotlin/BundleReleaseFilesTask.kt

Slide 15

Slide 15 text

abstract class BundleReleaseFilesTask : DefaultTask() { @get:InputDirectory abstract val projectDir: DirectoryProperty @get:Input abstract val appVersion: Property @get:OutputDirectory abstract val outputDirectory: DirectoryProperty @TaskAction fun run() { // TODO } } 15 buildSrc/src/main/kotlin/BundleReleaseFilesTask.kt

Slide 16

Slide 16 text

abstract class BundleReleaseFilesTask : DefaultTask() { @get:InputDirectory abstract val projectDir: DirectoryProperty @get:Input abstract val appVersion: Property @get:OutputDirectory abstract val outputDirectory: DirectoryProperty @TaskAction fun run() { // TODO } } 16 buildSrc/src/main/kotlin/BundleReleaseFilesTask.kt

Slide 17

Slide 17 text

abstract class BundleReleaseFilesTask : DefaultTask() { @get:InputDirectory abstract val projectDir: DirectoryProperty @get:Input abstract val appVersion: Property @get:OutputDirectory abstract val outputDirectory: DirectoryProperty @TaskAction fun run() { // TODO } } 17 buildSrc/src/main/kotlin/BundleReleaseFilesTask.kt

Slide 18

Slide 18 text

abstract class BundleReleaseFilesTask : DefaultTask() { @get:InputDirectory abstract val projectDir: DirectoryProperty @get:Input abstract val appVersion: Property @get:OutputDirectory abstract val outputDirectory: DirectoryProperty @TaskAction fun run() { // TODO } } 18 buildSrc/src/main/kotlin/BundleReleaseFilesTask.kt

Slide 19

Slide 19 text

Incremental build > Task :app:validateSigningDebug UP-TO-DATE > Task :app:writeDebugAppMetadata UP-TO-DATE > Task :app:writeDebugSigningConfigVersions UP-TO-DATE > Task :app:packageDebug UP-TO-DATE > Task :app:createDebugApkListingFileRedirect UP-TO-DATE > Task :app:assembleDebug UP-TO-DATE 19

Slide 20

Slide 20 text

Register the Task in your app module tasks.register("bundleReleaseFiles") { projectDir.set(File(".")) appVersion.set("1.00.00") outputDirectory.set(File("build/outputs")) } 20 app/build.gradle.kt

Slide 21

Slide 21 text

Execute our Task ./gradlew bundleReleaseFiles > Task :app:bundleReleaseFiles BUILD SUCCESSFUL in 2s 21

Slide 22

Slide 22 text

Re-executing our Task ./gradlew bundleReleaseFiles > Task :app:bundleReleaseFiles UP-TO-DATE BUILD SUCCESSFUL in 421ms 22

Slide 23

Slide 23 text

Cool task ! Let’s reuse it elsewhere But… - We don’t want to copy paste the task everywhere - We may depend on other tasks for ours to be working properly 23

Slide 24

Slide 24 text

Enter plugins ! - A single/multiple tasks can be packaged in a plugin - A plugin encapsulates tasks and their dependencies (dependOn, …) 24

Slide 25

Slide 25 text

25 Create a plugin from a task

Slide 26

Slide 26 text

class BundleReleaseFilesPlugin : Plugin { override fun apply(target: Project) { // TODO } } 26 buildSrc/src/main/kotlin/BundleReleaseFilesPlugin.kt

Slide 27

Slide 27 text

class BundleReleaseFilesPlugin : Plugin { override fun apply(target: Project) { val bundleReleaseFilesTask = target.tasks.register( "bundleReleaseFiles", BundleReleaseFilesTask::class.java ) } } 27 buildSrc/src/main/kotlin/BundleReleaseFilesPlugin.kt

Slide 28

Slide 28 text

class BundleReleaseFilesPlugin : Plugin { override fun apply(target: Project) { val bundleReleaseFilesTask = target.tasks.register( "bundleReleaseFiles", BundleReleaseFilesTask::class.java ) { this.group = "my_plugin" this.description = "Bundle release files (APK, Bundle, mapping) in the same directory" } } } 28 buildSrc/src/main/kotlin/BundleReleaseFilesPlugin.kt

Slide 29

Slide 29 text

Extension: A DSL for the Plugin’s parameters interface BundleReleaseFilesPluginExtension { val appVersion: Property val outputDirectory: DirectoryProperty } 29 buildSrc/src/main/kotlin/BundleReleaseFilesPluginExtension.kt

Slide 30

Slide 30 text

class BundleReleaseFilesPlugin : Plugin { override fun apply(target: Project) { … val extension = target.extensions.create( "bundleReleaseFiles", BundleReleaseFilesPluginExtension::class.java ) } } 30 buildSrc/src/main/kotlin/BundleReleaseFilesPlugin.kt

Slide 31

Slide 31 text

class BundleReleaseFilesPlugin : Plugin { override fun apply(target: Project) { val extension = target.extensions.create( "bundleReleaseFiles", BundleReleaseFilesPluginExtension::class.java ) bundleReleaseFilesTask.configure { projectDir.set(target.projectDir) appVersion.set(extension.appVersion) outputDirectory.set(extension.outputDirectory) } } } 31 buildSrc/src/main/kotlin/BundleReleaseFilesPlugin.kt

Slide 32

Slide 32 text

How to expose the plugin plugins { id("java-gradle-plugin") } gradlePlugin { plugins { create("bundleReleaseFiles") { id = "com.mwm.bundle_release_files" implementationClass = "BundleReleaseFilesPlugin" } } } 32 buildSrc/build.gradle.kts

Slide 33

Slide 33 text

And finally… plugins { id("com.mwm.bundle_release_files") } bundleReleaseFiles { appVersion.set("1.00.00") outputDirectory.set(File("build/outputs")) } 33 app/build.gradle.kts

Slide 34

Slide 34 text

Plugin project structure - Best practice is to have the plugin in a dedicated module - This is still a Gradle module: - src/main/kotlin - tests - build.gradle.kts 34

Slide 35

Slide 35 text

Set the plugin available in the project repository // root settings.gradle.kts includeBuild("bundle_release_plugin") 35

Slide 36

Slide 36 text

Set the plugin available in the project repository // root settings.gradle.kts includeBuild("bundle_release_plugin") 36

Slide 37

Slide 37 text

Prepare for publication plugins { id("maven-publish") } publishing { publications { create("java") { from(components.getByName("java")) groupId = "com.mwm" artifactId = "bundle_release_files" version = "1.00.00" } } } 37 bundle_release_plugin/build.gradle.kts

Slide 38

Slide 38 text

Publish locally or to a remote repository ./gradlew publishToMavenLocal OR ./gradlew publish 38

Slide 39

Slide 39 text

Conclusion How to create a Task - extends DefaultTask - Property parameters - @get:Input / @get:output How to instantiate a Task in a module - tasks.register How to create a Plugin - implement Plugin - Java-gradle-plugin - Extension - Release a plugin with maven-publish How to apply a Plugin in a module - id(“my.plugin.id”) 39

Slide 40

Slide 40 text

To go further - Make our Task depend on the assembleRelease / bundleRelease Task. - Improve DX by providing default parameter values using Convention. - Improve our DSL to hide direct calls to Property.set 40

Slide 41

Slide 41 text

41

Slide 42

Slide 42 text

Questions? By MWM | April 2023 42 Come see us at our stand!