Gradle is easy to use for building standard Java projects, but it’s rare to find a project that is completely standard. Whenever you have some custom requirement, you need to start using Gradle’s power features. It’s at that point that you can find yourself producing an unmaintainable mess and a hard-to-use build.
This talk will start by explaining Gradle’s model, which you need to understand if you want to retain control over your builds. I will then introduce you to some simple but effective guidelines that will ensure that your builds stay clean and effective.
2. “Safety” in build tools
"Straight Razor" by Horst.Burkhardt - Own work.
Licensed under CC BY-SA 3.0 via Wikimedia Commons
Gradle is often treated like a cut-throat razor: unsafe
Code == spaghetti builds (allegedly)
Some people think it’s better to limit build authors to configuration only
Is this a good comparison though?
3. A better analogy
by Noel C. Hankamer - Creative Commons 2.0
Not all jobs are the same for a workman
Need the right tools for the job
Some tools are dangerous - so you learn to use them
Power drills, heavy duty knives, etc.
4. Gradle
Yes, you can create spaghetti builds, but…
“A bad workman blames
his tools”
7. Gradle is an API
Project
Task
Repository Dependency
Configuration Source set (Java)
This is just part of the Gradle model, which allows you to model your own build processes.
Show Gradle DSL and API references if possible: http://www.gradle.org/doc/latest/…
8. A basic Java build
apply plugin: "java"
version = "1.0"
repositories { jcenter() }
dependencies {
compile "asm:asm-all:3.3.1",
"commons-io:commons-io:1.4"
}
compileJava.options.incremental = true
An instance of Task
A Repository
9. Everything from Project
• project.tasks
• project.configurations
• project.repositories
• project.dependencies
• project.sourceSets (added by Java
plugin)
All the model elements are available from the project
The project itself can be accessed via the `project` property
10. The three phases
Initialisation
Configuration
Execution
Only relevant to multi-
project builds
Configures tasks and builds
the task graph
Executes the necessary tasks
based on the task graph
Beyond the model that defines the build, this is what happens when you actually run that build
11. At execution
Y
X
Z
=>
Z
X Y
W
V
Linear execution orderTask graph
W
V
During configuration Gradle builds a Directed Acyclic Graph (DAG) of tasks
Before execution, the graph is turned into an execution order
There is no distinction between targets and tasks like in Apache Ant
14. Beware the config phase
• Configuration always happens
- even just running grade tasks!
- this will change soon(ish)
• Avoid expensive work
15. Profile your build!
gradle --profile <taskName>
This generates a report with timings for the different phases of each project in the build
16.
17. Example
apply plugin: "java"
...
jar {
version = new URL("http://buildsvr/version").text
}
Evaluated every build!
(This will change soon in Gradle)
Potentially unreliable network call *every* time the build runs
No matter what tasks are executed
Gradle will perform configuration on-demand in the not too distant future, reducing the time cost of the configuration phase
18. Remember
task doSomething << {
println "Doing it!"
}
task customJar(type: Jar) {
archivePath = customJarPath
doLast {
println "Created the JAR file"
}
} Execution
Execution
Configuration
Consider always using `doLast()` instead of `<<` to avoid confusion between configuration and implicit execution
20. Project properties
• Useful for build parameterisation
• Equivalent to global variables
- Same problems!
• Provide sensible defaults
• Assume values are strings
21. Default values
ext {
jarFilename = project.hasProperty("jarFilename")
? jarFilename : "myArchive"
}
task customJar(type: Jar) {
archivePath = file("$buildDir/$jarFilename")
doLast {
println "Created the JAR file: ${archivePath}"
}
}
Check property exists first
Default value
Values can be overridden via
-P cmd line option
gradle.properties
Both only support strings
22. Prefer task properties
task customClean(type: Delete) {
delete "$buildDir/$jarFilename"
}
task customClean(type: Delete) {
delete customJar.archivePath
}
Use the path defined
by the task
Don’t let project properties litter your build
If the JAR task stops using the project property, `customClean` will continue to work
24. Custom tasks
Prefer
task doIt(type: MyTask)
class MyTask extends DefaultTask {
@TaskAction
def action() { println "Hello world!" }
}
task doIt << {
println "Hello world!" }
}
over
More verbose, but easier to maintain
Easier to incorporate properties in custom task classes
Easier to migrate the task implementation out of the build file
25. Custom tasks
Where the task class goes:
1. In build.gradle
2. In buildSrc/src/groovy/…
3. In a JAR file
26. IoC in tasks
class MyTask extends DefaultTask {
File filePath = project.myArchivePath
@TaskAction
def action() {
println filePath.text
}
}
Don’t pull values from
the environment
27. IoC in tasks
task doIt(type: MyTask) {
filePath = project.myArchivePath
}
Leave configuration in the build file
Effectively Inversion of Control - values should always be injected
30. Task order vs dependency
task functionalTest(type: Test, dependsOn: test)
task functionalTest(type: Test) {
mustRunAfter test
}
Do the functional tests require the unit tests to run?
Or is it just the order that counts?
31. Leverage source sets
Instead of
configurations {
clientCompile
}
task compileClient(type: JavaCompile) {
classpath ...
include "src/main/java/**/client/*"
}
// Don't forget the test tasks and dependencies!
...
33. Use incremental build
class SignJar extends DefaultTask {
@InputFiles FileCollection jarFiles
@OutputDirectory File outputDir
File keystore
String keystorePassword
String alias
String aliasPassword
@TaskAction
void sign() {
...
}
}
Changes to these input
files or output force the
task to run again
34. Plugins
• Encapsulate your conventions
• Package common features
• Lightweight alternative:
- apply from: "modules/check.gradle"
36. Questions to ask
• Do you have only one type of user?
• What things can each type of user do?
• What do they want from the build?
• What should they see?
38. Interrogate task graph
gradle.taskGraph.whenReady { graph ->
if (graph.hasTask(":lazybones-app:uploadDist")) {
verifyProperty(project, 'repo.url')
verifyProperty(project, 'repo.username')
verifyProperty(project, 'repo.apiKey')
uploadDist.repositoryUrl = project.'repo.url'
uploadDist.username = project.'repo.username'
uploadDist.apiKey = project.'repo.apiKey'
}
}
Only check for credentials if user is trying to
publish:
Only fail fast if `uploadTask` will be executed
Fail slow would require integration tests to run - adding minutes to deployment
39. Other uses
• Include class obfuscation conditionally
• Limit visibility of tasks based on role
• Update version based on ‘release’ task
40. Summary
• Understand the Gradle model
• Keep refactoring
• Understand your build
• Know your users