gradle plugin, take control of the build

Post on 15-Apr-2017

313 Views

Category:

Software

3 Downloads

Preview:

Click to see full reader

TRANSCRIPT

@YourTwitterHandle#YourSessionHashtag

Plugin Gradle, prenez le contrôle du build !

Eyal LEZMYhttp://eyal.fr

Cedric CHAMPEAU @CedricChampeau

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!

top related