Xavier F. Gouchet, Senior Software Engineer
@xgouchet
Rock the Gradle,
Rule the world
Slide 2
Slide 2 text
No content
Slide 3
Slide 3 text
INTRODUCTION
0
A (brief) Gradle introduction
Slide 4
Slide 4 text
WHAT IS GRADLE ?
General Purpose
Language Agnostic
Feature Agnostic
Dependency
Management System
High performance
Build Management
Task Dependency Graph
Slide 5
Slide 5 text
THE GRADLE ALGORITHM
INIT
Launches the JVM
Analyse the working directory
Creates the Project object(s)
Compiles the buildSrc module(s)
CONFIG EXEC
Executes all the build.gradle scripts
Creates & configures tasks
Resolve dependencies
Execute the relevant tasks
Works like any module in your project
Compiled and tested before any gradle task
Groovy, Java, Kotlin, …
Any public class / method becomes available in gradle
scripts
How does it work?
Slide 12
Slide 12 text
Better dependency management
Helper classes / methods
Plugin configuration
Custom plugin
(Locally versioned with the project)
What can we use it for?
Slide 13
Slide 13 text
⚠
“A single change in buildSrc
causes the whole project to
become out-of-date.”
Slide 14
Slide 14 text
SCRIPTS
2
“Don’t repeat yourself”
— Uncle Bob Martin
Slide 15
Slide 15 text
DEPENDENCIES
Write Once
Use Everywhere
https://unsplash.com/@eliabevces
UTILITIES
Expand your toolbox
https://unsplash.com/@toddquackenbush
Slide 31
Slide 31 text
android {
defaultConfig {
versionCode 31401
versionName "3.14.1"
}
}
Version Code & Version Name
app/build.gradle
Slide 32
Slide 32 text
data class Version(
val major: Int,
val minor: Int,
val hotfix: Int
) {
val name = "$major.$minor.$hotfix"
val code = (major × 10000) + (minor × 100) + hotfix
}
Version Code & Version Name
buildSrc/src/main/kotlin/Version.kt
Slide 33
Slide 33 text
data class Version(
val major: Int,
val minor: Int,
val hotfix: Int
) {
val name = "$major.$minor.$hotfix"
val code = (major × 10000) + (minor × 100) + hotfix
}
Version Code & Version Name
buildSrc/src/main/kotlin/Version.kt
Slide 34
Slide 34 text
data class Version(
val major: Int,
val minor: Int,
val hotfix: Int
) {
val name = "$major.$minor.$hotfix"
val code = (major × 10000) + (minor × 100) + hotfix
}
Version Code & Version Name
buildSrc/src/main/kotlin/Version.kt
Slide 35
Slide 35 text
object App {
val Version = Version(3, 14, 1)
}
Version Code & Version Name
buildSrc/src/main/kotlin/App.kt
Slide 36
Slide 36 text
import App
android {
defaultConfig {
versionCode App.Version.code
versionName App.Version.name
}
}
Version Code & Version Name
app/build.gradle
Slide 37
Slide 37 text
PLUGINS
3
Now you’re thinking with Plugins
Slide 38
Slide 38 text
Variants
Graph
What a Plugin can do…
Tasks Extensions
Per-module configurations
Configuration
Project
Task
Dependencies
Slide 39
Slide 39 text
A concrete example
Sharing localisation strings between platforms
Slide 40
Slide 40 text
TASK
The Core Feature
https://unsplash.com/@wanxi
Slide 41
Slide 41 text
A Custom Task
open class GetStringsTask : DefaultTask() {
var languages : Array = arrayOf()
var root = ""
var baseUrl = "http://127.0.0.1"
@TaskAction fun performTask() {
for (l in languages) {
Downloader.download("$baseUrl/$l/strings.xml",
"$root/res/values-$l/strings.xml")
}
}
}
buildSrc/src/main/kotlin/GetStringsTask.kt
Slide 42
Slide 42 text
A Custom Task
open class GetStringsTask : DefaultTask() {
var languages : Array = arrayOf()
var root = ""
var baseUrl = "http://127.0.0.1"
@TaskAction fun performTask() {
for (l in languages) {
Downloader.download("$baseUrl/$l/strings.xml",
"$root/res/values-$l/strings.xml")
}
}
}
buildSrc/src/main/kotlin/GetStringsTask.kt
Slide 43
Slide 43 text
A Custom Task
open class GetStringsTask : DefaultTask() {
var languages : Array = arrayOf()
var root = ""
var baseUrl = "http://127.0.0.1"
@TaskAction fun performTask() {
for (l in languages) {
Downloader.download("$baseUrl/$l/strings.xml",
"$root/res/values-$l/strings.xml")
}
}
}
buildSrc/src/main/kotlin/GetStringsTask.kt
Slide 44
Slide 44 text
A Custom Task
open class GetStringsTask : DefaultTask() {
var languages : Array = arrayOf()
var root = ""
var baseUrl = "http://127.0.0.1"
@TaskAction fun performTask() {
for (l in languages) {
Downloader.download("$baseUrl/$l/strings.xml",
"$root/res/values-$l/strings.xml")
}
}
}
buildSrc/src/main/kotlin/GetStringsTask.kt
Slide 45
Slide 45 text
A Custom Task
open class GetStringsTask : DefaultTask() {
var languages : Array = arrayOf()
var root = ""
var baseUrl = "http://127.0.0.1"
@TaskAction fun performTask() {
for (l in languages) {
Downloader.download("$baseUrl/$l/strings.xml",
"$root/res/values-$l/strings.xml")
}
}
}
buildSrc/src/main/kotlin/GetStringsTask.kt
EXTENSION
Adding some
flexibility
https://unsplash.com/@glenncarstenspeters
Slide 48
Slide 48 text
A Custom Extension
open class GetStringsExt(
var languages: Array = arrayOf(),
var baseUrl: String = "http://127.0.0.1"
)
buildSrc/src/main/kotlin/GetStringsExt.kt
Slide 49
Slide 49 text
A Custom Extension
open class GetStringsTask : DefaultTask() {
var root = ""
var extension = GetStringsExt()
@TaskAction fun performTask() {
for (l in extension.languages) {
val url = "${extension.baseUrl}/$l/strings.xml"
val path = "$root/res/values-$l/strings.xml"
Downloader.download(url, path)
}
}
}
buildSrc/src/main/kotlin/GetStringsTask.kt
Slide 50
Slide 50 text
A Custom Extension
open class GetStringsTask : DefaultTask() {
var root = ""
var extension = GetStringsExt()
@TaskAction fun performTask() {
for (l in extension.languages) {
val url = "${extension.baseUrl}/$l/strings.xml"
val path = "$root/res/values-$l/strings.xml"
Downloader.download(url, path)
}
}
}
buildSrc/src/main/kotlin/GetStringsTask.kt
Slide 51
Slide 51 text
A Custom Extension
open class GetStringsTask : DefaultTask() {
var root = ""
var extension = GetStringsExt()
@TaskAction fun performTask() {
for (l in extension.languages) {
val url = "${extension.baseUrl}/$l/strings.xml"
val path = "$root/res/values-$l/strings.xml"
Downloader.download(url, path)
}
}
}
buildSrc/src/main/kotlin/GetStringsTask.kt
Slide 52
Slide 52 text
A Custom Extension
open class GetStringsTask : DefaultTask() {
var root = ""
var extension = GetStringsExt()
@TaskAction fun performTask() {
for (l in extension.languages) {
val url = "${extension.baseUrl}/$l/strings.xml"
val path = "$root/res/values-$l/strings.xml"
Downloader.download(url, path)
}
}
}
buildSrc/src/main/kotlin/GetStringsTask.kt
Slide 53
Slide 53 text
A Custom Extension… and a Plugin
class GetStringsPlugin : Plugin {
override fun apply(project: Project) {
val ext = project.extensions
.create("getStrings", GetStringsExt::class.java)
val task = project.tasks
.create("getStrings", GetStringsTask::class.java)
task.apply {
root = "${project.projectDir.path}/src/main"
extension = ext
}
}
}
buildSrc/src/main/kotlin/GetStringsPlugin.kt
Slide 54
Slide 54 text
A Custom Extension… and a Plugin
class GetStringsPlugin : Plugin {
override fun apply(project: Project) {
val ext = project.extensions
.create("getStrings", GetStringsExt::class.java)
val task = project.tasks
.create("getStrings", GetStringsTask::class.java)
task.apply {
root = "${project.projectDir.path}/src/main"
extension = ext
}
}
}
buildSrc/src/main/kotlin/GetStringsPlugin.kt
Slide 55
Slide 55 text
A Custom Extension… and a Plugin
class GetStringsPlugin : Plugin {
override fun apply(project: Project) {
val ext = project.extensions
.create("getStrings", GetStringsExt::class.java)
val task = project.tasks
.create("getStrings", GetStringsTask::class.java)
task.apply {
root = "${project.projectDir.path}/src/main"
extension = ext
}
}
}
buildSrc/src/main/kotlin/GetStringsPlugin.kt
Slide 56
Slide 56 text
A Custom Extension… and a Plugin
class GetStringsPlugin : Plugin {
override fun apply(project: Project) {
val ext = project.extensions
.create("getStrings", GetStringsExt::class.java)
val task = project.tasks
.create("getStrings", GetStringsTask::class.java)
task.apply {
root = "${project.projectDir.path}/src/main"
extension = ext
}
}
}
buildSrc/src/main/kotlin/GetStringsPlugin.kt
Slide 57
Slide 57 text
A Custom Extension… and a Plugin
class GetStringsPlugin : Plugin {
override fun apply(project: Project) {
val ext = project.extensions
.create("getStrings", GetStringsExt::class.java)
val task = project.tasks
.create("getStrings", GetStringsTask::class.java)
task.apply {
root = "${project.projectDir.path}/src/main"
extension = ext
}
}
}
buildSrc/src/main/kotlin/GetStringsPlugin.kt
Slide 58
Slide 58 text
A Custom Extension… and a Plugin
class GetStringsPlugin : Plugin {
override fun apply(project: Project) {
val ext = project.extensions
.create("getStrings", GetStringsExt::class.java)
val task = project.tasks
.create("getStrings", GetStringsTask::class.java)
task.apply {
root = "${project.projectDir.path}/src/main"
extension = ext
}
}
}
buildSrc/src/main/kotlin/GetStringsPlugin.kt
Slide 59
Slide 59 text
A Custom Extension… and a Plugin
import GetStringsPlugin
apply plugin: GetStringsPlugin
getStrings {
languages = ["en", "fr"]
baseUrl = "https://example.org/locales"
// no need to specify the root path anymore
}
app/build.gradle
Slide 60
Slide 60 text
The extension makes it easy to configure
The plugin can fill in properties automatically
The plugin can generate tasks per variant
The plugin can manipulate the graph
Why use a Plugin+Extension
Slide 61
Slide 61 text
GOING FURTHER
4
“Don't stop me now (cause I’m having a good time!)”
— Queen
Slide 62
Slide 62 text
TASK GRAPH
Where the
Magic happen
https://unsplash.com/@bill_oxford
Slide 63
Slide 63 text
Ordering Tasks
class GetStringsPlugin : Plugin {
override fun apply(project: Project) {
// …
project.afterEvaluate { p ->
p.tasks
.withType(GenerateResValues::class.java) {
it.dependsOn(task)
}
}
}
}
buildSrc/src/main/kotlin/GetStringsPlugin.kt
Slide 64
Slide 64 text
Ordering Tasks
class GetStringsPlugin : Plugin {
override fun apply(project: Project) {
// …
project.afterEvaluate { p ->
p.tasks
.withType(GenerateResValues::class.java) {
it.dependsOn(task)
}
}
}
}
buildSrc/src/main/kotlin/GetStringsPlugin.kt
Slide 65
Slide 65 text
Ordering Tasks
class GetStringsPlugin : Plugin {
override fun apply(project: Project) {
// …
project.afterEvaluate { p ->
p.tasks
.withType(GenerateResValues::class.java) {
it.dependsOn(task)
}
}
}
}
buildSrc/src/main/kotlin/GetStringsPlugin.kt
Slide 66
Slide 66 text
SIDENOTE ON TASK ORDER
STRICT
A.dependsOn(B)
C.finalizedBy(B)
LOOSE
D.mustRunAfter(B)
E.shouldRunAfter(B)
Slide 67
Slide 67 text
SIDENOTE ON TASK ORDER
LOOSE
D.mustRunAfter(B)
E.shouldRunAfter(B)
$ gradle D
> Task :app:D
BUILD SUCCESSFUL in 4s
1 task executed
Slide 68
Slide 68 text
SIDENOTE ON TASK ORDER
LOOSE
D.mustRunAfter(B)
E.shouldRunAfter(B)
$ gradle D B
> Task :app:B
> Task :app:D
BUILD SUCCESSFUL in 8s
2 tasks executed
Slide 69
Slide 69 text
SIDENOTE ON TASK ORDER
LOOSE
D.mustRunAfter(B)
E.shouldRunAfter(B)
$ gradle E
> Task :app:E
BUILD SUCCESSFUL in 15s
1 task executed
Slide 70
Slide 70 text
SIDENOTE ON TASK ORDER
LOOSE
D.mustRunAfter(B)
E.shouldRunAfter(B)
$ gradle E B
> Task :app:B
> Task :app:E
BUILD SUCCESSFUL in 16s
2 tasks executed
Slide 71
Slide 71 text
SIDENOTE ON TASK ORDER
LOOSE
D.mustRunAfter(B)
E.shouldRunAfter(B)
B.dependsOn(A)
A.dependsOn(E)
$ gradle E B
> Task :app:E
> Task :app:A
> Task :app:B
BUILD SUCCESSFUL in 23s
3 tasks executed
Slide 72
Slide 72 text
PLUGIN INTEGRATION
Help the developer
use your Plugin
https://unsplash.com/@momentsbygabriel
Slide 73
Slide 73 text
Aliasing the Plugin
apply plugin: "java-gradle-plugin"
gradlePlugin {
plugins {
getStrings {
id = "getStrings" // the alias
implementationClass = "GetStringsPlugin"
}
}
}
buildSrc/build.gradle
Slide 74
Slide 74 text
Aliasing the Plugin
import GetStringsPlugin
apply plugin: GetStringsPlugin
app/build.gradle
Slide 75
Slide 75 text
Aliasing the Plugin
apply plugin: "getStrings"
app/build.gradle
Slide 76
Slide 76 text
Task’s group & description
class GetStringsPlugin : Plugin {
init {
group = "mobileEra"
description = "Downloads strings.xml from server."
}
// …
}
buildSrc/src/main/kotlin/GetStringsPlugin.kt
Slide 77
Slide 77 text
Android tasks
-------------
androidDependencies - Displays the Android dependencies.
sourceSets - Prints out all the source sets in the project.
MobileEra tasks
---------------
getStrings - Downloads strings.xml from a server.
Verification tasks
------------------
lint - Runs lint on all variants.
Task’s group & description
$ gradlew :app:tasks
Slide 78
Slide 78 text
Android tasks
-------------
androidDependencies - Displays the Android dependencies.
sourceSets - Prints out all the source sets in the project.
MobileEra tasks
---------------
getStrings - Downloads strings.xml from a server.
Verification tasks
------------------
lint - Runs lint on all variants.
Task’s group & description
$ gradlew :app:tasks
Slide 79
Slide 79 text
GRADLE CACHE
Avoid
unnecessary work
https://unsplash.com/@jasonrobertsphotography
Slide 80
Slide 80 text
buildSrc/src/main/kotlin/GetStringsTask.kt
Defining Inputs & Outputs
open class GetStringsTask : DefaultTask() {
@Input fun getLanguagesInputs(): List {
return extension.languages.toList()
}
@Input fun getBaseUrlInput(): String {
return extension.baseUrl
}
}
Slide 81
Slide 81 text
buildSrc/src/main/kotlin/GetStringsTask.kt
Defining Inputs & Outputs
open class GetStringsTask : DefaultTask() {
@OutputFiles
fun getTaskOutputs(): List {
return extension.languages.map { l ->
File("$root/res/values-$l/strings.xml")
}
}
}
Slide 82
Slide 82 text
It will never
update until we change
the config ?
Slide 83
Slide 83 text
buildSrc/src/main/kotlin/GetStringsTask.kt
Defining Inputs & Outputs
open class GetStringsTask : DefaultTask() {
@Input
fun getDateInput() : String {
val formatter = DateTimeFormatter.ISO_DATE
return LocalDateTime.now().format(formatter)
}
}
Slide 84
Slide 84 text
INCREMENTAL TASKS
Avoid
unnecessary work
https://unsplash.com/@pluyar
Slide 85
Slide 85 text
buildSrc/src/main/kotlin/GetStringsTask.kt
Incremental Task
open class GetStringsTask : DefaultTask() {
@TaskAction
fun incrementAction(inputs: IncrementalTaskInputs) {
if (inputs.isIncremental) {
inputs.outOfDate { println("Out of date: $it") }
inputs.removed { println("Removed: $it") }
} else {
// …
}
}
}
Slide 86
Slide 86 text
buildSrc/src/main/kotlin/GetStringsTask.kt
Incremental Task
open class GetStringsTask : DefaultTask() {
@TaskAction
fun incrementAction(inputs: IncrementalTaskInputs) {
if (inputs.isIncremental) {
inputs.outOfDate { println("Out of date: $it") }
inputs.removed { println("Removed: $it") }
} else {
// …
}
}
}
Slide 87
Slide 87 text
buildSrc/src/main/kotlin/GetStringsTask.kt
Incremental Task
open class GetStringsTask : DefaultTask() {
@TaskAction
fun incrementAction(inputs: IncrementalTaskInputs) {
if (inputs.isIncremental) {
inputs.outOfDate { println("Out of date: $it") }
inputs.removed { println("Removed: $it") }
} else {
// …
}
}
}
Slide 88
Slide 88 text
buildSrc/src/main/kotlin/GetStringsTask.kt
Incremental Task
open class GetStringsTask : DefaultTask() {
@TaskAction
fun incrementAction(inputs: IncrementalTaskInputs) {
if (inputs.isIncremental) {
inputs.outOfDate { println("Out of date: $it") }
inputs.removed { println("Removed: $it") }
} else {
// …
}
}
}
Slide 89
Slide 89 text
buildSrc/src/main/kotlin/GetStringsTask.kt
Incremental Task
open class GetStringsTask : DefaultTask() {
@TaskAction
fun incrementAction(inputs: IncrementalTaskInputs) {
if (inputs.isIncremental) {
inputs.outOfDate { println("Out of date: $it") }
inputs.removed { println("Removed: $it") }
} else {
// …
}
}
}
Slide 90
Slide 90 text
buildSrc/src/main/kotlin/GetStringsTask.kt
Incremental Task
open class GetStringsTask : DefaultTask() {
@TaskAction
fun incrementAction(inputs: IncrementalTaskInputs) {
if (inputs.isIncremental) {
inputs.outOfDate { println("Out of date: $it") }
inputs.removed { println("Removed: $it") }
} else {
// …
}
}
}
Slide 91
Slide 91 text
TESTING YOUR PLUGIN
#TestMatters
Caspar Benson / Getty Images
Slide 92
Slide 92 text
PLUGIN
Only test the action of
the Plugin itself
INTEGRATION
Dummy project to test
real life scenarios
Testing a Gradle Plugin
buildSrc is tested on each build!
TASK
Delegate to other
classes as much as
possible
Slide 93
Slide 93 text
CREATING A DSL
Simplify the
configuration
https://unsplash.com/@ratushny
Slide 94
Slide 94 text
app/build.gradle
Creating a DSL
getStrings {
baseUrl = "https://example.org/locales"
variants {
debug { languages = ["en"] }
free { languages = ["en", "fr"] }
fullRelease { languages = ["en", "fr", "es"] }
}
}
Slide 95
Slide 95 text
DSL can go as deep as you need
Slide 96
Slide 96 text
GENERATING STUFF
Delegate the
tedious tasks
https://unsplash.com/@lennykuhne
⚠
“Make sure your generator task is
executed at the right moment.”
Slide 99
Slide 99 text
WRAPPING UP
5
You can enter here the subtitle if you need it
Slide 100
Slide 100 text
buildSrc is always built and tested
Take away
A single change in buildSrc invalidates all tasks
Publish your plugins
Slide 101
Slide 101 text
Your build scripts are still code.
Keep them as clean, maintainable
and understandable
as your production code.
Slide 102
Slide 102 text
Get the slides at
https://speakerdeck.com/xgouchet/rock-the-gradle-mobileera
Check sample project at
https://github.com/xgouchet/RockTheGradle/
Slide 103
Slide 103 text
CREDITS: This presentation template was created by Slidesgo, including
icons by Flaticon, and infographics & images by Freepik.
Do you have any questions?
@xgouchet
@datadoghq
THANK YOU!