• 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:PathSensitive(PathSensitivity.ABSOLUTE
)
@get:InputFile
s
abstract val mergedLibraryResourcesDir: DirectoryProperty
}
Slide 40
Slide 40 text
@CacheableTas
k
abstract class CompileLibraryResourcesTask : NewIncrementalTask()
{
@get:PathSensitive(PathSensitivity.RELATIVE
)
@get:InputFile
s
abstract val mergedLibraryResourcesDir: DirectoryProperty
}
Slide 41
Slide 41 text
How does the cache fix plugin
workaround this?
https://issuetracker.google.com/issues/155218379
Slide 42
Slide 42 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 43
Slide 43 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 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 workaround
}
Slide 47
Slide 47 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 48
Slide 48 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 49
Slide 49 text
No content
Slide 50
Slide 50 text
No content
Slide 51
Slide 51 text
Gradle Monte
🎩
Slide 52
Slide 52 text
Gradle Monte
🎩
don't try this at home
Slide 53
Slide 53 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 57
Slide 57 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 58
Slide 58 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('/doesnt-exist')
)
setPropertyValue(task, dummyProperty
)
}
}
}
Slide 59
Slide 59 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(
)
// Non-existent file to give the DirectoryProperty a value
.
dummyProperty.set(project.file('/doesnt-exist')
)
setPropertyValue(task, dummyProperty
)
}
}
// Set the original task property back to its original valu
e
task.doFirst
{
setPropertyValue(task, originalPropertyValue
)
}
}
Slide 60
Slide 60 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(
)
// Non-existent file to give the DirectoryProperty a value
.
dummyProperty.set(project.file('/doesnt-exist')
)
setPropertyValue(task, dummyProperty
)
}
}
// Set the original task property back to its original valu
e
task.doFirst
{
setPropertyValue(task, originalPropertyValue
)
}
}
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(
)
gradle.taskGraph.beforeTask
{
if (it == task)
{
originalPropertyValue.set(task.getProperty(propertyName)
)
def dummyProperty = objects.directoryProperty(
)
// Non-existent file to give the DirectoryProperty a value
.
dummyProperty.set(project.file('/doesnt-exist')
)
setPropertyValue(task, dummyProperty
)
}
}
// Set the original task property back to its original valu
e
task.doFirst
{
setPropertyValue(task, originalPropertyValue
)
}
}
Slide 62
Slide 62 text
Please don't ever write that code
Slide 63
Slide 63 text
CompileLibraryResourcesTask is
fixed in AGP 7
Slide 64
Slide 64 text
CompileLibraryResourcesTask is
fixed in AGP 7*
Slide 65
Slide 65 text
* must enable
android.experimental.enableSourceSetPathsMap
and
android.experimental.cacheCompileLibResources
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
}
Slide 85
Slide 85 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
@Overrid
e
Iterable asArguments()
{
return [] // TODO
}
}
Slide 86
Slide 86 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider {
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
}
@Overrid
e
Iterable asArguments()
{
return [] // TOD
O
}
}
Slide 87
Slide 87 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return [] // TOD
O
}
}
Slide 88
Slide 88 text
class RoomSchemaLocationArgumentProvider implements CommandLineArgumentProvider
{
final Directory schemaLocationDi
r
RoomSchemaLocationArgumentProvider(Directory schemaLocationDir)
{
this.schemaLocationDir = schemaLocationDi
r
}
@Overrid
e
Iterable asArguments()
{
return ["room.schemaLocation"]
}
}
Slide 89
Slide 89 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 90
Slide 90 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"
]
}
b
}a
Slide 91
Slide 91 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"
]
}
b
}a
Slide 92
Slide 92 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 102
Slide 102 text
schemas/1.json // inpu
t
schemas/2.json // output
Slide 103
Slide 103 text
schemas // input and output
Slide 104
Slide 104 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 105
Slide 105 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 106
Slide 106 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 107
Slide 107 text
Input and output cannot be
overlapping
Slide 108
Slide 108 text
?
Slide 109
Slide 109 text
No content
Slide 110
Slide 110 text
No content
Slide 111
Slide 111 text
Task specific output directory
Slide 112
Slide 112 text
android
{
defaultConfig
{
javaCompileOptions
{
annotationProcessorOptions
{
arguments += mapOf
(
"room.schemaLocation" to "$projectDir/schemas",
)
}
}
}
}
kaptDebug and kaptRelease write to the same folder
Slide 113
Slide 113 text
No content
Slide 114
Slide 114 text
No content
Slide 115
Slide 115 text
•Absolute path sensitivity
•Overlapping inputs and outputs
•Non exclusive output directory
•Kapt removes contents of output
directories
Slide 116
Slide 116 text
No content
Slide 117
Slide 117 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 118
Slide 118 text
This needs to be a Gradle plugin
Slide 119
Slide 119 text
This really needs to be a Gradle plugin
Slide 120
Slide 120 text
Until then, the Android Cache Fix plugin will
exist forever. 😭
Slide 121
Slide 121 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 122
Slide 122 text
https://issuetracker.google.com/issues/132245929
https://issuetracker.google.com/issues/139438151
What is Google doing about this?
Slide 123
Slide 123 text
Room now has KSP support
because performance matters.
This doesn't solve the cache miss
🥲
Slide 124
Slide 124 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 125
Slide 125 text
https://issuetracker.google.com/issues/132245929
https://issuetracker.google.com/issues/139438151
What is Google doing about this?
please star ⭐
#fixroom
Slide 126
Slide 126 text
No content
Slide 127
Slide 127 text
What else does the Android Cache
Fix plugin do?
Slide 128
Slide 128 text
No content
Slide 129
Slide 129 text
No content
Slide 130
Slide 130 text
What is caching?
Slide 131
Slide 131 text
JavaCompile task
Source Files
Compiler Args
Dependencies
Class files
Slide 132
Slide 132 text
Copy task
Source Files Source Files
Slide 133
Slide 133 text
Project Directory
Up-to-date checking
is the file already there?
Slide 134
Slide 134 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 138
Slide 138 text
Sync, Copy, Zip and Jar should not
be cached
https://docs.gradle.org/current/userguide/build_cache_concepts.html#non_cacheable_tasks
Slide 139
Slide 139 text
No content
Slide 140
Slide 140 text
BundleLibraryClassesJar is a Jar task
Slide 141
Slide 141 text
Disabling caching
Slide 142
Slide 142 text
outputs.doNotCacheIf {btrueb}b
Slide 143
Slide 143 text
tasks.withType(BundleLibraryClassesJar).configureEacha{a
outputs.doNotCacheIf ("I/O bound tasks do not benefit from caching") {btrueb}b
}
Slide 144
Slide 144 text
No content
Slide 145
Slide 145 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 151
Slide 151 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
Slide 152
Slide 152 text
Thank you!
Gradle Enterprise Trial
osacky.com
https://gradle.com/enterprise/trial/
https://go.gradle.com/solutions-engineer
Gradle Solutions is hiring
#fixroom @nellyspageli
Find us at our booth here at Droidcon