improving your gradle builds
TRANSCRIPT
Improving your Gradle builds
Peter Ledbrook
e: [email protected]: http://www.cacoethes.co.ukt: @pledbrook
“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: unsafeCode == spaghetti builds (allegedly)Some people think it’s better to limit build authors to configuration onlyIs this a good comparison though?
A better analogy
by Noel C. Hankamer - Creative Commons 2.0
Not all jobs are the same for a workmanNeed the right tools for the jobSome tools are dangerous - so you learn to use them Power drills, heavy duty knives, etc.
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/…
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
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 projectThe project itself can be accessed via the `project` property
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
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 tasksBefore execution, the graph is turned into an execution orderThere is no distinction between targets and tasks like in Apache Ant
Beware the config phase
• Configuration always happens
- even just running grade tasks!
- this will change soon(ish)
• Avoid expensive work
Profile your build!
gradle --profile <taskName>
This generates a report with timings for the different phases of each project in the build
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 runsNo 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
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
Project properties
• Useful for build parameterisation
• Equivalent to global variables
- Same problems!
• Provide sensible defaults
• Assume values are strings
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 optiongradle.properties
Both only support strings
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 buildIf the JAR task stops using the project property, `customClean` will continue to work
Custom tasksPrefer
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 maintainEasier to incorporate properties in custom task classesEasier to migrate the task implementation out of the build file
Custom tasks
Where the task class goes:
1. In build.gradle
2. In buildSrc/src/groovy/…
3. In a JAR file
IoC in tasks
class MyTask extends DefaultTask { File filePath = project.myArchivePath
@TaskAction def action() { println filePath.text } }
Don’t pull values from the environment
IoC in tasks
task doIt(type: MyTask) { filePath = project.myArchivePath }
Leave configuration in the build fileEffectively Inversion of Control - values should always be injected
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?
Leverage source setsInstead of
configurations { clientCompile }
task compileClient(type: JavaCompile) { classpath ... include "src/main/java/**/client/*" }
// Don't forget the test tasks and dependencies! ...
Leverage source setsUse source sets
sourceSets { client { java { srcDirs file("src/main/java") include "src/main/java/**/client/*" } } }
Get:• clientCompile, clientRuntime configurations• compileClientJava task
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
Plugins
• Encapsulate your conventions
• Package common features
• Lightweight alternative:
- apply from: "modules/check.gradle"
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?
Example
compile
test
package/install
publishproject
lead only
Lazybones project
If the project is published, require credentialsFail fastOther users don’t require credentials
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 executedFail slow would require integration tests to run - adding minutes to deployment
Other uses
• Include class obfuscation conditionally
• Limit visibility of tasks based on role
• Update version based on ‘release’ task
Thank you!
e: [email protected]: http://www.cacoethes.co.ukt: @pledbrook