You practice Gradle for a weeks and you are starting to master the way it works and you feel its great potential. Your small scripts are working well and you wonder now how to improve your build system, to make it cleaner. It is time for you to add your own Gradle plugin to your tools.
By taking a simple example, we will see together how it is easy to take control of the build!
Here are a few broached points during this talk:
- Create a Gradle plugin and control it from your build.gradle
- Mange the task graph
- Test well your plugin
- Manage the incremental builds
5. A dependency management
engine
A dependency based execution
system
A plugin system
A set of plugins
DEFINITIONS
What is
Gradle?
by Luke Daley, Gradle core
developer
6. Build scripts
Your build.gradle file
Script plugins
The customization you start writing
Binary plugins
The code I want you to write
DEFINITIONS
Gradle
Plugins
Types
7. Is a piece of work for a build
Compiling a class, generating javadoc, ...
Can be manipulated
doFirst, doLast
Can inherit from another
type
Can depend on another task
dependsOn, finalizedBy
DEFINITIONS
The Gradle
Task
11. task archiveTests (type: Copy) {
from "$reportsDir/reports"
into "$projectDir/reports/report-${currentTimeMillis()}"
}
tasks.test.finalizedBy archiveTests
THE CODE
End of build.gradle
12. task archiveTests (type: Copy) {
from "$reportsDir/reports"
into "$projectDir/reports/report-${currentTimeMillis()}"
}
tasks.test.finalizedBy archiveTests
THE CODE
We create the task
End of build.gradle
13. task archiveTests (type: Copy) {
from "$reportsDir/reports"
into "$projectDir/reports/report-${currentTimeMillis()}"
}
tasks.test.finalizedBy archiveTests
THE CODE
It inherits from Copy task
End of build.gradle
14. task archiveTests (type: Copy) {
from "$reportsDir/reports"
into "$projectDir/reports/report-${currentTimeMillis()}"
}
tasks.test.finalizedBy archiveTests
THE CODE
We define the copy parameters
End of build.gradle
15. task archiveTests (type: Copy) {
from "$reportsDir/reports"
into "$projectDir/reports/report-${currentTimeMillis()}"
}
tasks.test.finalizedBy archiveTests
THE CODE
We define the copy parameters
This is the versioningEnd of build.gradle
16. task archiveTests (type: Copy) {
from "$reportsDir/reports"
into "$projectDir/reports/report-${currentTimeMillis()}"
}
tasks.test.finalizedBy archiveTests
THE CODE
We ask to run it after test task
End of build.gradle
18. A PLUGIN?
Use it on different projects
Share it with others
Keep your build script clean
Keep your build script focused
Causeâ itâs so easy!
Why a
plugin?
19. Is a Gradle project
Basically, a Groovy project
It contains
A build.gradle
A plugin class
A descriptor
One or several tasks
An extension
Examples
Java, Groovy, Maven, Android plugin
A PLUGIN?
The Binary
Plugin
26. class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
//TODO create the extension
//TODO create the tasks
//TODO link the tasks to the build chain
}
}
THE CODE
The Plugin class
27. class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
//TODO create the extension
//TODO create the tasks
//TODO link the tasks to the build chain
}
}
THE CODE
The Plugin class
28. class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
//TODO create the extension
//TODO create the tasks
//TODO link the tasks to the build chain
}
}
THE CODE
The Plugin class
29. class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
//TODO create the extension
//TODO create the tasks
//TODO link the tasks to the build chain
}
}
THE CODE
The Plugin class
30. class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
//create the extension
project.extensions.create(âachivetestâ,ArchiveTestPluginExtension,
project)
//create the tasks
project.task(name:âachivetestâ, type:ArchiveTask){}
//link the tasks to the build chain
Task task = project.tasks.getByName(project.archivetest.task)
task.finalizedBy âachivetestâ
}
}
THE CODE
The Plugin class
31. class ArchiveTask extends Copy {
ArchiveTask(){
from project.archivetest.from
into project.archivetest.into
}
@TaskAction
def exec() {
println "Reports copied into ${project.archivetest.into}"
}
}
THE CODE
The Task class
32. class ArchiveTask extends Copy {
ArchiveTask(){
from project.archivetest.from
into project.archivetest.into
}
@TaskAction
def exec() {
println "Reports copied into ${project.archivetest.into}"
}
}
THE CODE
The Task class
33. class ArchiveTask extends Copy {
ArchiveTask(){
from project.archivetest.from
into project.archivetest.into
}
@TaskAction
def exec() {
println "Reports copied into ${project.archivetest.into}"
}
}
THE CODE
We want a Copy task.
Use DefaultTask to implement your ownThe Task class
34. class ArchiveTask extends Copy {
ArchiveTask(){
from project.archivetest.from
into project.archivetest.into
}
@TaskAction
def exec() {
println "Reports copied into ${project.archivetest.into}"
}
}
THE CODE
The Task class
35. class ArchiveTask extends Copy {
ArchiveTask(){
from project.archivetest.from
into project.archivetest.into
}
@TaskAction
def exec() {
println "Reports copied into ${project.archivetest.into}"
}
}
THE CODE
The Task class
36. class ArchiveTask extends Copy {
ArchiveTask(){
from project.archivetest.from
into project.archivetest.into
}
@TaskAction
def exec() {
println "Reports copied into ${project.archivetest.into}"
}
}
THE CODE
The Task class
Here we use the
extensionâs content
39. THE CODE
The Extension class
class ArchiveTestPluginExtension {
def from
def into
def task
ArchiveTestPluginExtension(Project project) {
from = project. reportsDir
into = project.projectDir+"/reports/report-${currentTimeMillis()}"
task = "test"
}
}
40. THE CODE
The Extension class
class ArchiveTestPluginExtension {
def from
def into
def task
ArchiveTestPluginExtension(Project project) {
from = project. reportsDir
into = project.projectDir+"/reports/report-${currentTimeMillis()}"
task = "test"
}
}
This is a simple POGO
41. THE CODE
The Extension class
class ArchiveTestPluginExtension {
def from
def into
def task
ArchiveTestPluginExtension(Project project) {
from = project.reportsDir
into = project.projectDir+"/reports/report-${currentTimeMillis()}"
task = "test"
}
}
45. apply plugin: 'groovy'
dependencies {
compile gradleApi()
compile localGroovy()
}
...
THE CODE
The build.gradle (1/2) A plugin is a groovy project
46. apply plugin: 'groovy'
dependencies {
compile gradleApi()
compile localGroovy()
}
...
THE CODE
The build.gradle (1/2)
Add a gradle API dependency corresponding
to the gradle version used to compile the
project
47. apply plugin: 'groovy'
dependencies {
compile gradleApi()
compile localGroovy()
}
...
THE CODE
The build.gradle (1/2)
Add a groovy dependency corresponding to
the gradle version used
48. ...
apply plugin: 'maven-publish'
group = 'fr.eyal'
version = '0.1'
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
}
THE CODE
The build.gradle (2/2)
49. ...
apply plugin: 'maven-publish'
group = 'fr.eyal'
version = '0.1'
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
}
THE CODE
The build.gradle (2/2)
50. ...
apply plugin: 'maven-publish'
group = 'fr.eyal'
version = '0.1'
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
}
THE CODE
The build.gradle (2/2)
61. archivetest {
from "build/report"
into "/tmp/archives"
}
RUN IT
$ gradle test
...
Reports copied into /home/user/project/reports/report-1422480068261
...
BUILD SUCCESSFUL
62. archivetest {
from "build/report"
into "/tmp/archives"
}
RUN IT
$ gradle test
...
Reports copied into /home/user/project/reports/report-1422480068261
...
BUILD SUCCESSFUL
Yay!
63. RUN IT
archivetest {
from "build/report"
into "/tmp/archives"
task "hello"
}
task hello << {
println "Sample task"
}
64. RUN IT
archivetest {
from "build/report"
into "/tmp/archives"
task "hello"
}
task hello << {
println "Sample task"
}
Copy after the hello task
65. RUN IT
archivetest {
from "build/report"
into "/tmp/archives"
task "hello"
}
task hello << {
println "Sample task"
}
Copy after the hello task
Create the hello task
66. $ gradle hello
archivetest {
from "build/report"
into "/tmp/archives"
task "hello"
}
task hello << {
println "Sample task"
}
RUN IT
67. RUN IT
$ gradle hello
...
BUILD SUCCESSFUL
archivetest {
from "build/report"
into "/tmp/archives"
task "hello"
}
task hello << {
println "Sample task"
}
No copy
68. RUN IT
$ gradle hello
...
BUILD SUCCESSFUL
archivetest {
from "build/report"
into "/tmp/archives"
task "hello"
}
task hello << {
println "Sample task"
}
Oh no...
84. class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
//create the extension
project.extensions.create(âachivetestâ,ArchiveTestPluginExtension,
project)
//create the tasks
project.task(name:âachivetestâ, type:ArchiveTask){}
//link the tasks to the build chain
Task task = project.tasks.getByName(project.archivetest.task)
task.finalizedBy âachivetestâ
}
}
DEBUG IT
The Plugin class
85. class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
//create the extension
project.extensions.create(âachivetestâ,ArchiveTestPluginExtension,
project)
//create the tasks
project.task(name:âachivetestâ, type:ArchiveTask){}
//link the tasks to the build chain
Task task = project.tasks.getByName(project.archivetest.task)
task.finalizedBy âachivetestâ
}
}
DEBUG IT
The Plugin class
Executed too early
88. class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
...
//link the tasks to the build chain
Task task = project.tasks.getByName(project.archivetest.task)
task.finalizedBy âachivetestâ
}
}
DEBUG IT
The Plugin class
89. class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
...
//link the tasks to the build chain
project.afterEvaluate {
Task task = project.tasks.getByName(project.archivetest.task)
task.finalizedBy âachivetestâ
}
}
}
DEBUG IT
The Plugin class
We add the task after the evaluation
93. Very simple
As simple as Groovy is
Groovy is your best friend
A language very easy to mock
Junit & co
As anybody knows
TEST IT
Gradle
project
testing
94. ProjectBuilder
To create a mock project
Evaluate
To execute your mocked build script
A few
specificities
TEST IT
102. TEST IT
Your first test!
class ArchiveTestPluginTest {
@Test
public void canAddArchiveTestExtension() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'java'
project.apply plugin: 'archivetest'
assert project.archivetest instanceof ArchiveTestPluginExtension
}
}
103. TEST IT
Your first test!
class ArchiveTestPluginTest {
@Test
public void canAddArchiveTestExtension() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'java'
project.apply plugin: 'archivetest'
assert project.archivetest instanceof ArchiveTestPluginExtension
}
}
Mock a Gradle project
104. TEST IT
Your first test!
class ArchiveTestPluginTest {
@Test
public void canAddArchiveTestExtension() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'java'
project.apply plugin: 'archivetest'
assert project.archivetest instanceof ArchiveTestPluginExtension
}
}
Apply Java plugin
To create âtestâ task
105. TEST IT
Your first test!
class ArchiveTestPluginTest {
@Test
public void canAddArchiveTestExtension() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'java'
project.apply plugin: 'archivetest'
assert project.archivetest instanceof ArchiveTestPluginExtension
}
}
Apply our plugin
106. TEST IT
Your first test!
class ArchiveTestPluginTest {
@Test
public void canAddArchiveTestExtension() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'java'
project.apply plugin: 'archivetest'
assert project.archivetest instanceof ArchiveTestPluginExtension
}
}
Test our extension exists
108. TEST IT
Your second test!
class ArchiveTestPluginTest {
@Test
public void canAddArchiveTask() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'java'
project.apply plugin: 'archivetest'
assert project.tasks.archivetest instanceof ArchiveTask
}
}
We initialize our project
109. TEST IT
Your second test!
class ArchiveTestPluginTest {
@Test
public void canAddArchiveTask() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'java'
project.apply plugin: 'archivetest'
assert project.tasks.archivetest instanceof ArchiveTask
}
}
We test the task
110. TEST IT
Run your second test
$ gradle test --stacktrace --debug
111. TEST IT
Run your second test
$ gradle test --stacktrace --debug
...
test.groovy.fr.eyal.ArchiveTestPluginTest > canAddArchiveTask FAILED
MissingPropertyException: Could not find property 'archivetest' on task set
...
BUILD FAILED
112. TEST IT
Run your second test
$ gradle test --stacktrace --debug
...
test.groovy.fr.eyal.ArchiveTestPluginTest > canAddArchiveTask FAILED
MissingPropertyException: Could not find property 'archivetest' on task set
...
BUILD FAILED
Our task is not created
113. class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
...
project.afterEvaluate {
//create the tasks
project.task(name:âachivetestâ, type:ArchiveTask){}
//inject the task
Task task = project.tasks.getByName(project.archivetest.task)
task.finalizedBy âachivetestâ
}
}
}
TEST IT
The Plugin class
Tasks are often created after project.
evaluate()
115. TEST IT
Your first test!
class ArchiveTestPluginTest {
@Test
void canAddArchiveTask() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'java'
project.apply plugin: 'archivetest'
project.evaluate()
assert project.tasks.archivetest instanceof ArchiveTask
}
} We launch evaluate() on the project
116. TEST IT
Run your second test
$ gradle test --stacktrace --debug
117. TEST IT
Run your second test
$ gradle test --stacktrace --debug
...
BUILD SUCCESSFUL
118. TEST IT
Run your second test
$ gradle test --stacktrace --debug
...
BUILD SUCCESSFUL
Yay!
119. TEST IT
LUKE DALEY
Gradleware Principal Engineer
GRADLE FORUM
You don't see this in the API docs for Project
because it is an internal method and is therefore
potentially subject to change in future releases.
There will be a supported mechanism for doing this
kind of thing in the near future.
â
â
120. TEST IT
LUKE DALEY
Gradleware Principal Engineer
GRADLE FORUM
You don't see this in the API docs for Project
because it is an internal method and is therefore
potentially subject to change in future releases.
There will be a supported mechanism for doing this
kind of thing in the near future.
June 2011
â
â