Download - Gradle plugin, take control of the build
@YourTwitterHandle#YourSessionHashtag
Plugin Gradle, prenez le contrôle du build !
Eyal LEZMYhttp://eyal.fr
Cedric CHAMPEAU @CedricChampeau
SLIDES bit.ly/gradle-plugin-devoxx
DEFINITIONS01
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
Build scriptsYour build.gradle file
Script pluginsThe customization you start writing
Binary pluginsThe code I want you to write
DEFINITIONS
Gradle Plugins Types
Is a piece of work for a buildCompiling a class, generating javadoc, ...
Can be manipulateddoFirst, doLast
Can inherit from anothertype
Can depend on another taskdependsOn, finalizedBy
DEFINITIONS
The Gradle Task
LET’S GET STARTED!02
Your unit tests results, you have to
version
THE SUBJECT
THE JOB
Copy and version this folder
task archiveTests (type: Copy) { from "$reportsDir/reports" into "$projectDir/reports/report-${currentTimeMillis()}"}
tasks.test.finalizedBy archiveTests
THE CODE
End of build.gradle
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
task archiveTests (type: Copy) { from "$reportsDir/reports" into "$projectDir/reports/report-${currentTimeMillis()}"}
tasks.test.finalizedBy archiveTests
THE CODE
It inherits from Copy taskEnd of build.gradle
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
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
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
BUILD A PLUGIN03
… for real
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?
Is a Gradle projectBasically, a Groovy project
It containsA build.gradleA plugin classA descriptorOne or several tasksAn extension
ExamplesJava, Groovy, Maven, Android plugin
A PLUGIN?
The Binary Plugin
THE ARCHITECTURE
THE ARCHITECTURE
The plugin class
THE ARCHITECTURE
The plugin class
A task
THE ARCHITECTURE
The plugin class
A task
The extension
THE ARCHITECTURE
The plugin class
A task
The extension
The descriptor
THE ARCHITECTURE
The plugin class
A task
The extension
The descriptor
The build.gradle
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
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
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
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
class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
//create the extensionproject.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
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
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
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
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
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
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
archivetest {
from "origin/folder" into "destination/folder" task "taskToLaunchCopy"
}
THE CODE
The Extension: the syntax expected
archivetest {
from "origin/folder" into "destination/folder" task "taskToLaunchCopy"
}
THE CODE
The Extension: the syntax expected
THE CODE
The Extension classclass ArchiveTestPluginExtension {
def from def into def task
ArchiveTestPluginExtension(Project project) { from = project. reportsDir into = project.projectDir+"/reports/report-${currentTimeMillis()}"
task = "test" }}
THE CODE
The Extension classclass 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
THE CODE
The Extension classclass ArchiveTestPluginExtension {
def from def into def task
ArchiveTestPluginExtension(Project project) { from = project.reportsDir into = project.projectDir+"/reports/report-${currentTimeMillis()}"
task = "test" }}
implementation-class= main.groovy.fr.eyal.ArchiveTestPlugin
THE CODE
The Descriptor: achivetest.properties
implementation-class=main.groovy.fr.eyal.ArchiveTestPlugin
THE CODE
The name of this file impliesapply plugin: “archivetest”
The Descriptor: achivetest.properties
apply plugin: 'groovy'
dependencies { compile gradleApi() compile localGroovy()}
...
THE CODE
The build.gradle (1/2)
apply plugin: 'groovy'
dependencies { compile gradleApi() compile localGroovy()}
...
THE CODE
The build.gradle (1/2) A plugin is a groovy project
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
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
...
apply plugin: 'maven-publish'
group = 'fr.eyal'version = '0.1'
publishing { publications { mavenJava(MavenPublication) { from components.java } }}
THE CODE
The build.gradle (2/2)
...
apply plugin: 'maven-publish'
group = 'fr.eyal'version = '0.1'
publishing { publications { mavenJava(MavenPublication) { from components.java } }}
THE CODE
The build.gradle (2/2)
...
apply plugin: 'maven-publish'
group = 'fr.eyal'version = '0.1'
publishing { publications { mavenJava(MavenPublication) { from components.java } }}
THE CODE
The build.gradle (2/2)
USE THE PLUGIN04
USE THE PLUGIN
Add the repository
Add dependency
Apply the plugin
Configure the plugin if needed
How to use our plugin?
buildscript { repositories { mavenLocal() } dependencies { classpath 'fr.eyal:archivetest:0.1' }}
apply plugin: 'archivetest'
archivetest { from "build/report"
into "/tmp/archives"task “connectedAndroidTest”
}
THE CODE
The user’s build.gradle
buildscript { repositories { mavenLocal() } dependencies { classpath 'fr.eyal:archivetest:0.1' }}
apply plugin: 'archivetest'
archivetest { from "build/report"
into "/tmp/archives"task “connectedAndroidTest”
}
THE CODE
The user’s build.gradle
buildscript { repositories { mavenLocal() } dependencies { classpath 'fr.eyal:archivetest:0.1' }}
apply plugin: 'archivetest'
archivetest { from "build/report"
into "/tmp/archives"task “connectedAndroidTest”
}
THE CODE
The user’s build.gradle The local repository folder
buildscript { repositories { mavenLocal() } dependencies { classpath ' fr.eyal:archivetest:0.1' }}
apply plugin: 'archivetest'
archivetest { from "build/report"
into "/tmp/archives"task “connectedAndroidTest”
}
THE CODE
The user’s build.gradle
pom.group pom.artifactId pom.version
buildscript { repositories { mavenLocal() } dependencies { classpath 'fr.eyal:archivetest:0.1' }}
apply plugin: 'archivetest'
archivetest { from "build/report"
into "/tmp/archives"task “connectedAndroidTest”
}
THE CODE
The user’s build.gradle
archivetest.properties
buildscript { repositories { mavenLocal() } dependencies { classpath 'fr.eyal:archivetest:0.1' }}
apply plugin: 'archivetest'
archivetest { from "build/report"
into "/tmp/archives"task “connectedAndroidTest”
}
THE CODE
The user’s build.gradle
Our extension
RUN IT05
archivetest {from "build/report"into "/tmp/archives"
}
RUN IT
$ gradle test
archivetest {from "build/report"into "/tmp/archives"
}
RUN IT
$ gradle test
...
Reports copied into /home/user/project/reports/report-1422480068261
...
BUILD SUCCESSFUL
archivetest {from "build/report"into "/tmp/archives"
}
RUN IT
$ gradle test
...
Reports copied into /home/user/project/reports/report-1422480068261
...
BUILD SUCCESSFUL
Yay!
RUN IT
archivetest {from "build/report"into "/tmp/archives"task "hello"
}
task hello << {println "Sample task"
}
RUN IT
archivetest {from "build/report"into "/tmp/archives"task "hello"
}
task hello << {println "Sample task"
}
Copy after the hello task
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
$ gradle hello
archivetest {from "build/report"into "/tmp/archives"task "hello"
}
task hello << {println "Sample task"
}
RUN IT
RUN IT
$ gradle hello
...
BUILD SUCCESSFUL
archivetest {from "build/report"into "/tmp/archives"task "hello"
}
task hello << {println "Sample task"
}
No copy
RUN IT
$ gradle hello
...
BUILD SUCCESSFUL
archivetest {from "build/report"into "/tmp/archives"task "hello"
}
task hello << {println "Sample task"
}
Oh no...
To figure out you have to
apply plugin: 'archivetest'
archivetest { from "build/report"
into "/tmp/archives"task "hello"
}
task hello << {println "Sample task"
}
DEBUG IT
The user’s build.gradle
apply plugin: 'archivetest'
archivetest { from "build/report"
into "/tmp/archives"task "hello"
}
task hello << {println "Sample task"
}
DEBUG IT
The user’s build.gradleCall apply() on ArchiveTestPlugin
apply plugin: 'archivetest'
archivetest { from "build/report"
into "/tmp/archives"task "hello"
}
task hello << {println "Sample task"
}
DEBUG IT
The user’s build.gradleCall apply() on ArchiveTestPlugin- Create the extension - archivetest.task = "test"- Create the copy task- Finalize archivetest.task by achivetest task
apply plugin: 'archivetest'
archivetest { from "build/report"
into "/tmp/archives"task "hello"
}
task hello << {println "Sample task"
}
DEBUG IT
The user’s build.gradle
Modify the extension content- archivetest.task = "hello"
Call apply() on ArchiveTestPlugin- Create the extension - archivetest.task = "test"- Create the copy task- Finalize archivetest.task by achivetest task
apply plugin: 'archivetest'
archivetest { from "build/report"
into "/tmp/archives"task "hello"
}
task hello << {println "Sample task"
}
DEBUG IT
The user’s build.gradleCall apply() on ArchiveTestPlugin
Create the hello task
- Create the extension - archivetest.task = "test"- Create the copy task- Finalize archivetest.task by achivetest task
Modify the extension content- archivetest.task = "hello"
THE TASK GRAPH
THE TASK GRAPH
Created by Java/Groovy plugin
test
THE TASK GRAPH
Created by archivetest plugin
test test
archivetest
THE TASK GRAPH
‘test’ finalized by ‘archivetest’
test test
archivetest
THE TASK GRAPH
test test
archivetest
test
archivetest
Created by build.gradlehello
THE TASK GRAPH
test
archivetest
hello Launch hello
THE TASK GRAPH
test
archivetest
hello Launch hello
THE TASK GRAPH
test
archivetest
hello Launch hello
THE TASK GRAPH
test
archivetest
hello Launch hello
No link to our copy task
class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
//create the extensionproject.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
class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
//create the extensionproject.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
DEBUG IT
InitializationChoose project(s) to build
ConfigurationExecute build.gradleBuild task graph
ExecutionExecute tasks chain
Gradle build
lifecylcle
DEBUG IT
Project evaluationbeforeEvaluateafterEvaluate
Task GraphwhenTaskAddedwhenReadybeforeTaskafterTask
The lifecycleevents
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
class ArchiveTestsPlugin implements Plugin<Project> {
void apply(Project project) {
...
//link the tasks to the build chainproject.afterEvaluate {
Task task = project.tasks.getByName(project.archivetest.task) task.finalizedBy “achivetest”
}}
}
DEBUG IT
The Plugin class
We add the task after the evaluation
THE TASK GRAPH
test
archivetest
hello
THE TASK GRAPH
test
archivetest
hello
Yay!
TEST IT06
Very simpleAs simple as Groovy is
Groovy is your best friendA language very easy to mock
Junit & coAs anybody knows
TEST IT
Gradle project testing
ProjectBuilderTo create a mock project
EvaluateTo execute your mocked build script
A few specificities
TEST IT
TEST IT
TEST IT
Adding a test class
TEST IT
Adding a test class
Adding junit dependency
TEST IT
Our buid.gradle
...
repositories { jcenter()}
dependencies { testCompile 'junit:junit:4.11'}
...
TEST IT
Our buid.gradle
...
repositories { jcenter()}
dependencies { testCompile 'junit:junit:4.11'}
...
Adding maven central repository
TEST IT
Our buid.gradle
...
repositories { jcenter()}
dependencies { testCompile 'junit:junit:4.11'}
...
Adding junit as testing dependency
Now, test the extension
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 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
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 pluginTo create ‘test’ task
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
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
Now, test the task
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
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
TEST IT
Run your second test$ gradle test --stacktrace --debug
TEST IT
Run your second test$ gradle test --stacktrace --debug
...
test.groovy.fr.eyal.ArchiveTestPluginTest > canAddArchiveTask FAILEDMissingPropertyException: Could not find property 'archivetest' on task set
...
BUILD FAILED
TEST IT
Run your second test$ gradle test --stacktrace --debug
...
test.groovy.fr.eyal.ArchiveTestPluginTest > canAddArchiveTask FAILEDMissingPropertyException: Could not find property 'archivetest' on task set
...
BUILD FAILED
Our task is not created
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()
So, evaluate.
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
TEST IT
Run your second test$ gradle test --stacktrace --debug
TEST IT
Run your second test$ gradle test --stacktrace --debug
...
BUILD SUCCESSFUL
TEST IT
Run your second test$ gradle test --stacktrace --debug
...
BUILD SUCCESSFUL Yay!
TEST IT
LUKE DALEYGradleware 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.
”
“
TEST IT
LUKE DALEYGradleware 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
”
“
IMPROVE IT07
Plugin Base Javavs. Plugin Java
Defines what is a test tasktasks.withType(Test)
IMPROVE IT
Convention over
Configuration
Avoid running useless tasksUP-TO-DATE
Declare Inputs@Input, @InputFile, @InputFiles, @InputDirectory
Declare outputs@OutputFile, @OutputDirectory
IMPROVE IT
Incremental builds
More flexibility of annotations@InputFile String filePath@InputDirectory String directoryPath...
IMPROVE IT
Coming soon
OnlyIfConditional task execution
IMPROVE IT
Tweaking Execution
Graph
ArchiveTask() { onlyIf { !parent.state.skipped }}
IMPROVE IT
Gradle Plugin documentationhttps://gradle.org/documentation
References
SLIDES http://bit.ly/gradle-plugin-devoxx
Thank you!