• Improve Gradle ecosystem
• Fix issues in open source plugin
affecting customers
Slide 6
Slide 6 text
• Meet with Google and Jetbrains
to help prioritize issues
impacting customers
Slide 7
Slide 7 text
No content
Slide 8
Slide 8 text
Android Development
Feature Development Tech Debt
(There are other things too)
Slide 9
Slide 9 text
Tech Debt
Refactoring Improving builds
Slide 10
Slide 10 text
Slow builds are Tech Debt
And it always pays off
Slide 11
Slide 11 text
Cost of Builds
60s waste * 50 builds / day * 50 devs
= 42 hours lost / day
Slide 12
Slide 12 text
Cost of Builds
60s waste * 50 builds / day * 50 devs
= 42 hours lost / day
not including lost focus
https://gradle.com/roi-calculator
Slide 13
Slide 13 text
Fast Builds Matter
Slide 14
Slide 14 text
Cost of Builds
60s waste * 50 builds / day * 50 devs
= 42 hours lost / day
hire 5 new people without paying
them! no recruiting
https://gradle.com/roi-calculator
Slide 15
Slide 15 text
Slow builds are Tech Debt
And it always pays off
And is easy to justify working on it
Slide 16
Slide 16 text
No content
Slide 17
Slide 17 text
What is build caching?
https://docs.gradle.org/current/userguide/build_cache.html
Slide 18
Slide 18 text
JavaCompile task
Source Files
Compiler Args
Dependencies
Class files
any task can be made cacheable
@CacheableTas
k
abstract class CompileLibraryResourcesTask : NewIncrementalTask() {
}
Slide 38
Slide 38 text
@CacheableTas
k
abstract class CompileLibraryResourcesTask : NewIncrementalTask()
{
@get:InputFile
s
abstract val mergedLibraryResourcesDir: DirectoryProperty
}
Slide 39
Slide 39 text
@CacheableTas
k
abstract class CompileLibraryResourcesTask : NewIncrementalTask()
{
@get:InputFile
s
abstract val mergedLibraryResourcesDir: DirectoryProperty
}
Slide 40
Slide 40 text
@CacheableTas
k
abstract class CompileLibraryResourcesTask : NewIncrementalTask()
{
@get:InputFile
s
abstract val mergedLibraryResourcesDir: DirectoryProperty
}
Slide 41
Slide 41 text
@CacheableTas
k
abstract class CompileLibraryResourcesTask : NewIncrementalTask()
{
@get:PathSensitive(PathSensitivity.ABSOLUTE
)
@get:InputFile
s
abstract val mergedLibraryResourcesDir: DirectoryProperty
}
Slide 42
Slide 42 text
@CacheableTas
k
abstract class CompileLibraryResourcesTask : NewIncrementalTask()
{
@get:PathSensitive(PathSensitivity.RELATIVE
)
@get:InputFile
s
abstract val mergedLibraryResourcesDir: DirectoryProperty
}
Slide 43
Slide 43 text
How does the cache fix plugin
workaround this?
https://issuetracker.google.com/issues/155218379
Slide 44
Slide 44 text
@AndroidIssue
(
introducedIn = "4.0.0"
,
fixedIn = ['7.0.0-alpha09']
,
link = "https://issuetracker.google.com/issues/155218379"
)
class CompileLibraryResourcesWorkaround implements Workaround
{
// the workaroun
d
}
Slide 45
Slide 45 text
@AndroidIssue
(
introducedIn = "4.0.0"
,
fixedIn = ['7.0.0-alpha09']
,
link = "https://issuetracker.google.com/issues/155218379"
)
class CompileLibraryResourcesWorkaround implements Workaround
{
// the workaroun
d
}
Slide 46
Slide 46 text
@AndroidIssue
(
introducedIn = "4.0.0"
,
fixedIn = ['7.0.0-alpha09']
,
link = "https://issuetracker.google.com/issues/155218379"
)
class CompileLibraryResourcesWorkaround implements Workaround
{
// the workaroun
d
}
Slide 47
Slide 47 text
@AndroidIssue
(
introducedIn = "4.0.0"
,
fixedIn = ['7.0.0-alpha09']
,
link = "https://issuetracker.google.com/issues/155218379"
)
class CompileLibraryResourcesWorkaround implements Workaround
{
// the workaroun
d
}
Slide 48
Slide 48 text
@AndroidIssue
(
introducedIn = "4.0.0"
,
fixedIn = ['7.0.0-alpha09']
,
link = "https://issuetracker.google.com/issues/155218379"
)
class CompileLibraryResourcesWorkaround implements Workaround
{
// the workaround
}
Slide 49
Slide 49 text
Three-card Monte
https://www.youtube.com/watch?v=QJv44-Ghj_Y
aka Find the Lady, das Kümmelblättchen, Jeu du Bonneteau, Gioco
delle tre carte, Bul Karayı Al Parayı
Slide 50
Slide 50 text
Con artists enticing people on Potsdamer Platz, Berlin, to play, and lose money in the game in 2018.
https://en.wikipedia.org/wiki/Three-card_Monte
Slide 51
Slide 51 text
No content
Slide 52
Slide 52 text
No content
Slide 53
Slide 53 text
Gradle Monte
🎩
Slide 54
Slide 54 text
Gradle Monte
🎩
don't try this at home
Slide 55
Slide 55 text
1.Add new workaround property with RELATIVE path sensitivity to existing task
2.Set workaround property with value of original property
3.Set original property to dummy (empty) value
4.Gradle performs input snapshotting on RELATIVE workaround property and
dummy original property
5.After input snapshotting, set original property back its original value
6.Task executes on original value of original property
Gradle Monte
🎩
tasks.withType(CompileLibraryResourcesTask).configureEach { Task task -
>
DirectoryProperty originalPropertyValue = objects.directoryProperty(
)
// Add a workaround input with the original property value and RELATIVE path sensitivit
y
task.inputs.dir(originalPropertyValue
)
.withPathSensitivity(PathSensitivity.RELATIVE
)
.withPropertyName("${propertyName}.workaround"
)
.optional(
)
}
Slide 61
Slide 61 text
tasks.withType(CompileLibraryResourcesTask).configureEach { Task task -
>
DirectoryProperty originalPropertyValue = objects.directoryProperty(
)
// Add a workaround input with the original property value and RELATIVE path sensitivit
y
task.inputs.dir(originalPropertyValue
)
.withPathSensitivity(PathSensitivity.RELATIVE
)
.withPropertyName("${propertyName}.workaround"
)
.optional(
)
}
Slide 62
Slide 62 text
tasks.withType(CompileLibraryResourcesTask).configureEach { Task task -
>
DirectoryProperty originalPropertyValue = objects.directoryProperty(
)
// Add a workaround input with the original property value and RELATIVE path sensitivit
y
task.inputs.dir(originalPropertyValue
)
.withPathSensitivity(PathSensitivity.RELATIVE
)
.withPropertyName("${propertyName}.workaround"
)
.optional(
)
gradle.taskGraph.beforeTask {
}
}
Slide 63
Slide 63 text
tasks.withType(CompileLibraryResourcesTask).configureEach { Task task -
>
DirectoryProperty originalPropertyValue = objects.directoryProperty(
)
// Add a workaround input with the original property value and RELATIVE path sensitivit
y
task.inputs.dir(originalPropertyValue
)
.withPathSensitivity(PathSensitivity.RELATIVE
)
.withPropertyName("${propertyName}.workaround"
)
.optional(
)
gradle.taskGraph.beforeTask {
}
}
Slide 64
Slide 64 text
tasks.withType(CompileLibraryResourcesTask).configureEach { Task task -
>
DirectoryProperty originalPropertyValue = objects.directoryProperty(
)
// Add a workaround input with the original property value and RELATIVE path sensitivit
y
task.inputs.dir(originalPropertyValue
)
.withPathSensitivity(PathSensitivity.RELATIVE
)
.withPropertyName("${propertyName}.workaround"
)
.optional(
)
gradle.taskGraph.beforeTask
{
if (it == task)
{
originalPropertyValue.set(task.getProperty(propertyName)
)
}
}
}
Slide 65
Slide 65 text
tasks.withType(CompileLibraryResourcesTask).configureEach { Task task -
>
DirectoryProperty originalPropertyValue = objects.directoryProperty(
)
// Add a workaround input with the original property value and RELATIVE path sensitivit
y
task.inputs.dir(originalPropertyValue
)
.withPathSensitivity(PathSensitivity.RELATIVE
)
.withPropertyName("${propertyName}.workaround"
)
.optional(
)
gradle.taskGraph.beforeTask
{
if (it == task)
{
originalPropertyValue.set(task.getProperty(propertyName)
)
}
}
}
Slide 66
Slide 66 text
tasks.withType(CompileLibraryResourcesTask).configureEach { Task task -
>
DirectoryProperty originalPropertyValue = objects.directoryProperty(
)
// Add a workaround input with the original property value and RELATIVE path sensitivit
y
task.inputs.dir(originalPropertyValue
)
.withPathSensitivity(PathSensitivity.RELATIVE
)
.withPropertyName("${propertyName}.workaround"
)
.optional(
)
gradle.taskGraph.beforeTask
{
if (it == task)
{
originalPropertyValue.set(task.getProperty(propertyName)
)
def dummyProperty = objects.directoryProperty(
)
// Dummy file to give the DirectoryProperty a value
.
dummyProperty.set(project.file('/dummy')
)
setPropertyValue(task, dummyProperty
)
}
}
}
Slide 67
Slide 67 text
tasks.withType(CompileLibraryResourcesTask).configureEach { Task task -
>
DirectoryProperty originalPropertyValue = objects.directoryProperty(
)
// Add a workaround input with the original property value and RELATIVE path sensitivit
y
task.inputs.dir(originalPropertyValue
)
.withPathSensitivity(PathSensitivity.RELATIVE
)
.withPropertyName("${propertyName}.workaround"
)
.optional(
)
gradle.taskGraph.beforeTask
{
if (it == task)
{
originalPropertyValue.set(task.getProperty(propertyName)
)
def dummyProperty = objects.directoryProperty(
)
// Dummy file to give the DirectoryProperty a value
.
dummyProperty.set(project.file('/dummy')
)
setPropertyValue(task, dummyProperty
)
}
}
}
Slide 68
Slide 68 text
tasks.withType(CompileLibraryResourcesTask).configureEach { Task task -
>
DirectoryProperty originalPropertyValue = objects.directoryProperty(
)
// Add a workaround input with the original property value and RELATIVE path sensitivit
y
task.inputs.dir(originalPropertyValue
)
.withPathSensitivity(PathSensitivity.RELATIVE
)
.withPropertyName("${propertyName}.workaround"
)
.optional(
)
gradle.taskGraph.beforeTask
{
if (it == task)
{
originalPropertyValue.set(task.getProperty(propertyName)
)
def dummyProperty = objects.directoryProperty(
)
// Dummy file to give the DirectoryProperty a value
.
dummyProperty.set(project.file('/dummy')
)
setPropertyValue(task, dummyProperty
)
}
}
// Set the original task property back to its original valu
e
task.doFirst
{
setPropertyValue(task, originalPropertyValue
)
}
}
Slide 69
Slide 69 text
tasks.withType(CompileLibraryResourcesTask).configureEach { Task task -
>
DirectoryProperty originalPropertyValue = objects.directoryProperty(
)
// Add a workaround input with the original property value and RELATIVE path sensitivit
y
task.inputs.dir(originalPropertyValue
)
.withPathSensitivity(PathSensitivity.RELATIVE
)
.withPropertyName("${propertyName}.workaround"
)
.optional(
)
gradle.taskGraph.beforeTask
{
if (it == task)
{
originalPropertyValue.set(task.getProperty(propertyName)
)
def dummyProperty = objects.directoryProperty(
)
// Dummy file to give the DirectoryProperty a value
.
dummyProperty.set(project.file('/dummy')
)
setPropertyValue(task, dummyProperty
)
}
}
// Set the original task property back to its original valu
e
task.doFirst
{
setPropertyValue(task, originalPropertyValue
)
}
}
Slide 70
Slide 70 text
Please don't ever write that code
Slide 71
Slide 71 text
CompileLibraryResourcesTask is
fixed in AGP 7
Slide 72
Slide 72 text
CompileLibraryResourcesTask is
fixed in AGP 7*
Slide 73
Slide 73 text
* must enable
android.experimental.enableSourceSetPathsMap
and
android.experimental.cacheCompileLibResources
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
}
Slide 93
Slide 93 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
}
Slide 94
Slide 94 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
@Overrid
e
Iterable asArguments()
{
return [] // TODO
}
}
Slide 95
Slide 95 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
@Overrid
e
Iterable asArguments()
{
return [] // TODO
}
}
Slide 96
Slide 96 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider {
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
}
@Overrid
e
Iterable asArguments()
{
return [] // TODO
}
}
Slide 97
Slide 97 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
}
@Overrid
e
Iterable asArguments()
{
return [] // TODO
}
}
Slide 98
Slide 98 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return [] // TODO
}
}
Slide 99
Slide 99 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return [] // TODO
}
}
Slide 100
Slide 100 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return [] // TODO
}
}
Slide 101
Slide 101 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return ["room.schemaLocation"]
}
}
Slide 102
Slide 102 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return ["-Aroom.schemaLocation=schemaLocationDir.get().asFile.absolutePath"]
}
}
Slide 103
Slide 103 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return ["-Aroom.schemaLocation=schemaLocationDir.get().asFile.absolutePath"]
}
}
Slide 104
Slide 104 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
@get:PathSensitive(PathSensitivity.RELATIVE)
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return ["-Aroom.schemaLocation=schemaLocationDir.get().asFile.absolutePath"]
}
}
Slide 105
Slide 105 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
@OutputDirector
y
@get:PathSensitive(PathSensitivity.RELATIVE)
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return ["-Aroom.schemaLocation=schemaLocationDir.get().asFile.absolutePath"]
}
}
Slide 106
Slide 106 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
@OutputDirector
y
@get:PathSensitive(PathSensitivity.RELATIVE)
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return ["-Aroom.schemaLocation=schemaLocationDir.get().asFile.absolutePath"]
}
}
schemas/1.json // exists and is committed to version contro
l
schemas/2.json // written when schema is updated
Slide 116
Slide 116 text
schemas/1.json // inpu
t
schemas/2.json // output
Slide 117
Slide 117 text
schemas // input and output
Slide 118
Slide 118 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
@OutputDirector
y
@get:PathSensitive(PathSensitivity.RELATIVE)
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return ["-Aroom.schemaLocation=schemaLocationDir.get().asFile.absolutePath"]
}
}
Slide 119
Slide 119 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
@InputDirectory
@OutputDirector
y
@get:PathSensitive(PathSensitivity.RELATIVE)
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return ["-Aroom.schemaLocation=schemaLocationDir.get().asFile.absolutePath"]
}
}
Slide 120
Slide 120 text
- In plugin 'kotlin-android' type 'org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask'
property 'annotationProcessorOptionProviders$kotlin_gradle_plugin.$0.$0.schemaLocationDir' has
conflicting type annotations declared: @OutputDirectory, @InputDirectory
.
Reason: The different annotations have different semantics and Gradle cannot determine which
one to pick
.
Possible solution: Choose between one of the conflicting annotations.
- In plugin 'kotlin-android' type 'org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask'
property 'annotationProcessorOptionProviders$kotlin_gradle_plugin.$0.$0.schemaLocationDir' is
annotated with @PathSensitive but that is not allowed for 'OutputDirectory' properties
.
Reason: This modifier is used in conjunction with a property of type 'OutputDirectory' but
this doesn't have semantics
.
Possible solution: Remove the '@PathSensitive' annotation
.
Please refer to https://docs.gradle.org/7.2/userguide/
validation_problems.html#incompatible_annotations for more details about this problem.
Slide 121
Slide 121 text
Input and output cannot be
overlapping
Slide 122
Slide 122 text
?
Slide 123
Slide 123 text
No content
Slide 124
Slide 124 text
No content
Slide 125
Slide 125 text
Task specific output directory
Slide 126
Slide 126 text
android
{
defaultConfig
{
javaCompileOptions
{
annotationProcessorOptions
{
arguments += mapOf
(
"room.schemaLocation" to "$projectDir/schemas",
)
}
}
}
}
kaptDebug and kaptRelease write to the same folder
Slide 127
Slide 127 text
No content
Slide 128
Slide 128 text
No content
Slide 129
Slide 129 text
•Absolute path sensitivity
•Overlapping inputs and outputs
•Non exclusive output directory
•Kapt removes contents of output
directories
Slide 130
Slide 130 text
No content
Slide 131
Slide 131 text
• Annotation Processors are not tools to write arbitrary
fi
les on the
fi
lesystem.
• This is what Gradle tasks and Gradle plugins are for.
Slide 132
Slide 132 text
This needs to be a Gradle plugin
Slide 133
Slide 133 text
This really needs to be a Gradle plugin
Slide 134
Slide 134 text
Until then, the Android Cache Fix plugin will
exist forever. 😭
Slide 135
Slide 135 text
• Absolute path sensitivity
• Overlapping inputs and outputs
• Non exclusive output directory
• Kapt removes contents of output directories
RoomSchemaLocationWorkaround
fi
xes all of these.
Use the Android Cache Fix plugin.
Shout out to Gary!
Slide 136
Slide 136 text
https://issuetracker.google.com/issues/132245929
https://issuetracker.google.com/issues/139438151
What is Google doing about this?
Slide 137
Slide 137 text
Room now has KSP support
because performance matters.
This doesn't solve the cache miss
🥲
Slide 138
Slide 138 text
What looks better in a promotion case?
• Use new technology to rewrite Room to
improve build speeds
• Fix a 41 star bug filed by community with a
Gradle plugin
Slide 139
Slide 139 text
https://issuetracker.google.com/issues/132245929
https://issuetracker.google.com/issues/139438151
What is Google doing about this?
please star ⭐
#fixroom
Slide 140
Slide 140 text
No content
Slide 141
Slide 141 text
What else does the Android Cache
Fix plugin do?
Slide 142
Slide 142 text
No content
Slide 143
Slide 143 text
No content
Slide 144
Slide 144 text
What is caching?
Slide 145
Slide 145 text
JavaCompile task
Source Files
Compiler Args
Dependencies
Class files
Slide 146
Slide 146 text
Copy task
Source Files Source Files
Slide 147
Slide 147 text
Project Directory
Up-to-date checking
is the file already there?
Slide 148
Slide 148 text
Gradle build cache
is there a zip entry with the
same build cache key?
~/.gradle/caches
Gradle Home
Project Directory
zip
unzip
~/.gradle/caches
Build Cache
Copy task
Source Files Source Files
Re-execution
Cache store and fetch
Happens along every execution
Slide 152
Slide 152 text
Sync, Copy, Zip and Jar should not
be cached
https://docs.gradle.org/current/userguide/build_cache_concepts.html#non_cacheable_tasks
Slide 153
Slide 153 text
No content
Slide 154
Slide 154 text
BundleLibraryClassesJar is a Jar task
Slide 155
Slide 155 text
Disabling caching
Slide 156
Slide 156 text
outputs.doNotCacheIf {btrueb}b
Slide 157
Slide 157 text
tasks.withType(BundleLibraryClassesJar).configureEacha{a
outputs.doNotCacheIf ("I/O bound tasks do not benefit from caching") {btrueb}b
}
Slide 158
Slide 158 text
No content
Slide 159
Slide 159 text
Thanks to the Android Gradle
plugin team at Google
Gradle Enterprise Android Features
• Find your slowest tests in the test dashboard
• Learn which tests are flaky with flaky test insights
• Android Instrumentation Test results in Build Scans!
Check it out! https://gradle.com/enterprise-customers/oss-projects/
Tweet, Like, Subscribe, Upvote, Star
https://github.com/gradle/android-cache-fix-gradle-plugin
https://issuetracker.google.com/issues/132245929
https://issuetracker.google.com/issues/139438151
please star ⭐
#fixroom
Slide 165
Slide 165 text
More Resources
Gradle Training
https://gradle.com/training/
https://gradle.com/training/introduction-to-gradle
https://gradle.com/training/build-cache-deep-dive
Gradle Community Slack
https://gradle.com/slack-invite