Volker Leck (@devisnik) - Gradle Inc.
Gradle is a build and automation tool.
agnostic Build System
100% Free Open Source - Apache Standard License 2.0
The company behind Gradle.
Build Happiness
Employs full time engineers
Providing Gradle Build Scans and Gradle Enterprise
Consulting and Training
reliable / reproducible
customizable
universal
self-contained
fast, smart
maintainable
In a healthy culture, developers and engineering leaders understand that the domain of building and integrating software is just as complex and challenging as the domain of application development.
Groovy and Kotlin build scripts
Task configuration and execution
Dependency resolution
Work avoidance
├── [ 962] build.gradle
├── [ 96] gradle
│ ├── [ 54K] gradle-wrapper.jar
│ └── [ 202] gradle-wrapper.properties
├── [5.8K] gradlew
├── [2.9K] gradlew.bat
├── [ 359] settings.gradle
adapt core engine to a domain
make build declarative
Core Plugins
java-library
, application
, groovy
, maven-publish
…
Community Plugins
kotlin
, android
, golang
, pygradle
, asciidoctor
…
Plugins contribute
reusable and configurable tasks
configurable extensions
Plugins contribute a model to configure
in build scripts
using a DSL
plugins {
id "java-library"
}
dependencies {
api "com.acme:foo:1.0"
implementation "com.zoo:monkey:1.1"
}
tasks.withType(JavaCompile) {
// ...
}
plugins {
`cpp-application`
}
application {
baseName = "my-app"
}
toolChains {
// ...
}
apply plugin: 'com.android.application'
dependencies {
implementation "androidx.appcompat:appcompat:1.0.2"
// ...
}
android {
compileSdkVersion 28
buildToolsVersion "28.0.3"
// ...
defaultConfig {
minSdkVersion 19
targetSdkVersion 28
}
// ...
}
structured approach
define scenario
profile
identify bottlenecks
fix
verify fix
repeat
--profile
--scan
Gradle profiler
runs pre-defined scenarios
can produce Build scans
check-no-tests {
tasks = ["check"]
gradle-args = ["-x", "test"]
cleanup-tasks = ["clean"]
run-using = tooling-api // value can also be "cli" or "no-daemon"
warm-ups = 2
}
./gradlew wrapper --gradle-version 5.3.1
classpath 'com.android.tools.build:gradle:3.3.2'
org.gradle.jvmargs=-Xmx4G
provide enough heap space
tweaks often do more harm than good
rather work on structural improvements
org.gradle.daemon = true
enabled by default
in-memory caches
when building repeatedly
Do not disable it!
org.gradle.parallel = true
not enabled by default
usually safe to enable
speeds up multi-project builds
org.gradle.caching = true
// root build.gradle
// only for kotlin < 1.3.30
subprojects {
pluginManager.withPlugin("kotlin-kapt") {
kapt.useBuildCache = true
}
}
startup / settings / buildSrc > 1s
no-op build doing any work
single line change ~= clean build
high gc time
K-9 with -Xmx=1G
: https://gradle.com/s/n7q54jqbq3qma
// settings.gradle
// do not do this
new File('.').eachFile(FileType.DIRECTORIES) { dir ->
include dir.name
}
./gradlew --status
pgrep -lf "GradleDaemon"
Applying plugins
Evaluating build scripts
Running afterEvaluate {} blocks
Before running any task
Even gradlew help / gradlew tasks
Android Studio sync
Avoid Dependency resolution at configuration time!
Be aware of inefficient plugins!
Executing selected tasks
Incremental
Cacheable
Parallelizable
Nothing changed? Executed tasks should be zero!
Watch out for volatile inputs!
Modularization ⇒ Compile avoidance
Decoupled code ⇒ Faster incremental compilation
Careful with Kotlin annotation processing (for now)
https://gradle.com/blog/improving-android-and-java-build-performance/
Tony Robalik: droidcon SF 2018 - Improving Android Build performance https://www.youtube.com/watch?v=XZFnuFUMT7w
applying plugins
evaluating build scripts
running afterEvaluate{}
blocks
on any Gradle run
Android Studio sync
task fatJar(type: Jar) {
from configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
with jar
classifier = 'uber-jar'
}
task fatJar(type: Jar) {
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
with jar
classifier = 'uber-jar'
}
tasks.register("fatJar", Jar) {
from configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
with jar
classifier = 'uber-jar'
}
That build script seems expensive
task projectStats {
def statsFile = new File(buildDir, 'stats.txt')
statsFile.parentFile.mkdirs()
def javaFiles = sourceSets.main.java.size()
def javaSize = sourceSets.main.java
.collect{it.text.bytes}
.flatten()
.sum()
statsFile.text = """
|SourceFiles: ${javaFiles}
|Source size: ${javaSize} bytes
""".stripMargin()
}
Where is the problem?
task projectStats {
def statsFile = new File(buildDir, 'stats.txt')
input.files sourceSet.main.java
outputs.file statsFile
doLast {
statsFile.parentFile.mkdirs()
def javaFiles = sourceSets.main.java.size()
def javaSize = sourceSets.main.java
.collect{it.text.bytes}
.flatten()
.sum()
statsFile.text = """
|SourceFiles: ${javaFiles}
|Source size: ${javaSize} bytes
""".stripMargin()
}
}
Don’t forget doLast{}
task projectStats(type: ProjectStats) {
statsFile = new File(buildDir, 'stats.txt')
sources = sourceSet.main.java
}
class ProjectStats extends DefaultTask {
@InputFiles FileCollection sources
@OutputFile File statsFile
@TaskAction def stats() {
statsFile.text = """
|Files: ${sources.size()}
|Total: ${sources.collect{it.text.bytes}.flatten().sum()} bytes
""".stripMargin()
}
}
// version.gradle
def out = new ByteArrayOutputStream()
exec {
commandLine 'git','rev-parse','HEAD'
standardOutput = out
workingDir = rootDir
}
version = new String(out.toByteArray())
// root 'build.gradle'
allprojects {
apply from:'$rootDir/version.gradle'
}
// root 'build.gradle'
apply from:"$rootDir/version.gradle"
subprojects {
version = rootProject.version
}
variantFilter { variant ->
def flavorName = variant.flavors[0].name
def freeFlavor = flavorName == 'free'
if(!freeFlavor && variant.buildType.name == 'release') {
variant.ignore = true
}
}
Avoid dependency resolution
Avoid I/O
Dont repeat yourself
cache key based on inputs
outputs fetched from cache if available
no task action (re-)executed
opt-in (task declares cacheability)
Local branch switching
CI populated cache for (distributed) team
Faster CI builds with stateless agents
stable inputs
repeatable outputs
classpath normalization
Path relocatability
org.gradle.caching = true
// only for kotlin < 1.3.30
subprojects {
pluginManager.withPlugin("kotlin-kapt") {
kapt.useBuildCache = true
}
}
uses local cache by default
the default for java projects
coming in AGP 3.5
be aware of includeAndroidResources = true
def runsOnCI = System.getEnv("CI") != null
buildCache {
local {
enabled = !runsOnCI
}
remote(HttpBuildCache) {
push = runsOnCI
url = 'https://my.server.com:8080/cache/'
// if cache access is secured
credentials {
username = 'cache-user'
password = 'super-secret-pw'
}
}
}
docker run -d \
-v /opt/build-cache-node:/data \
-p 80:5071 \
gradle/build-cache-node:latest
K-9: tasks created in 3.2.1 vs 3.3.2
K-9: tests in mail
subprojects
AGP3.2.1 https://gradle.com/s/myfmftgb2oq46
AGP3.3.2 https://gradle.com/s/fnyeyuqwq4smc
AGP3.4.0-rc02 https://gradle.com/s/anej7zjlita4m
AGP3.5.0-alpha10 https://gradle.com/s/4syrt4v2kevqw
K-9: check -x test
AGP 3.2.1 / no parallel https://gradle.com/s/4plps3frlfvum
AGP 3.2.1 / parallel https://gradle.com/s/4drqo5ptwx2ku
AGP 3.3.2 / parallel https://gradle.com/s/npoywvn63kg2k
know your build
monitor and measure
avoid unnecessary configuration
avoid repeated work