Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Gradle Plugin, take the control of the build!

Gradle Plugin, take the control of the build!

Eyal LEZMY

June 29, 2015
Tweet

More Decks by Eyal LEZMY

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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 versioning End of build.gradle
  8. 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
  9. 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?
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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 own The Task class
  19. 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
  20. 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
  21. 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
  22. 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" } }
  23. 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
  24. 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" } }
  25. apply plugin: 'groovy' dependencies { compile gradleApi() compile localGroovy() }

    ... THE CODE The build.gradle (1/2) A plugin is a groovy project
  26. 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
  27. 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
  28. ... apply plugin: 'maven-publish' group = 'fr.eyal' version = '0.1'

    publishing { publications { mavenJava(MavenPublication) { from components.java } } } THE CODE The build.gradle (2/2)
  29. ... apply plugin: 'maven-publish' group = 'fr.eyal' version = '0.1'

    publishing { publications { mavenJava(MavenPublication) { from components.java } } } THE CODE The build.gradle (2/2)
  30. ... apply plugin: 'maven-publish' group = 'fr.eyal' version = '0.1'

    publishing { publications { mavenJava(MavenPublication) { from components.java } } } THE CODE The build.gradle (2/2)
  31. USE THE PLUGIN Add the repository Add dependency Apply the

    plugin Configure the plugin if needed How to use our plugin?
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. archivetest { from "build/report" into "/tmp/archives" } RUN IT $

    gradle test ... Reports copied into /home/user/project/reports/report-1422480068261 ... BUILD SUCCESSFUL
  39. archivetest { from "build/report" into "/tmp/archives" } RUN IT $

    gradle test ... Reports copied into /home/user/project/reports/report-1422480068261 ... BUILD SUCCESSFUL Yay!
  40. RUN IT archivetest { from "build/report" into "/tmp/archives" task "hello"

    } task hello << { println "Sample task" } Copy after the hello task
  41. 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
  42. $ gradle hello archivetest { from "build/report" into "/tmp/archives" task

    "hello" } task hello << { println "Sample task" } RUN IT
  43. RUN IT $ gradle hello ... BUILD SUCCESSFUL archivetest {

    from "build/report" into "/tmp/archives" task "hello" } task hello << { println "Sample task" } No copy
  44. RUN IT $ gradle hello ... BUILD SUCCESSFUL archivetest {

    from "build/report" into "/tmp/archives" task "hello" } task hello << { println "Sample task" } Oh no...
  45. apply plugin: 'archivetest' archivetest { from "build/report" into "/tmp/archives" task

    "hello" } task hello << { println "Sample task" } DEBUG IT The user’s build.gradle
  46. apply plugin: 'archivetest' archivetest { from "build/report" into "/tmp/archives" task

    "hello" } task hello << { println "Sample task" } DEBUG IT The user’s build.gradle Call apply() on ArchiveTestPlugin
  47. apply plugin: 'archivetest' archivetest { from "build/report" into "/tmp/archives" task

    "hello" } task hello << { println "Sample task" } DEBUG IT The user’s build.gradle Call apply() on ArchiveTestPlugin - Create the extension - archivetest.task = "test" - Create the copy task - Finalize archivetest.task by achivetest task
  48. 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
  49. apply plugin: 'archivetest' archivetest { from "build/report" into "/tmp/archives" task

    "hello" } task hello << { println "Sample task" } DEBUG IT The user’s build.gradle Call 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"
  50. 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
  51. 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
  52. DEBUG IT Initialization Choose project(s) to build Configuration Execute build.gradle

    Build task graph Execution Execute tasks chain Gradle build lifecylcle
  53. 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
  54. 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
  55. 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
  56. ProjectBuilder To create a mock project Evaluate To execute your

    mocked build script A few specificities TEST IT
  57. TEST IT Our buid.gradle ... repositories { jcenter() } dependencies

    { testCompile 'junit:junit:4.11' } ... Adding maven central repository
  58. TEST IT Our buid.gradle ... repositories { jcenter() } dependencies

    { testCompile 'junit:junit:4.11' } ... Adding junit as testing dependency
  59. 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 } }
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. 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()
  69. 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
  70. 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. ” “
  71. 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 ” “
  72. Plugin Base Java vs. Plugin Java Defines what is a

    test task tasks.withType(Test) IMPROVE IT Convention over Configuration
  73. Avoid running useless tasks UP-TO-DATE Declare Inputs @Input, @InputFile, @InputFiles,

    @InputDirectory Declare outputs @OutputFile, @OutputDirectory IMPROVE IT Incremental builds