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

Anatomy of a Gradle plugin

Anatomy of a Gradle plugin

Slides from my talk at Android TechTalks@Lohika Kyiv, 2017-06-29

Dmytro Zaitsev

June 29, 2017
Tweet

More Decks by Dmytro Zaitsev

Other Decks in Programming

Transcript

  1. Build script Not visible outside the build script Can’t reuse

    the plugin outside the build script it’s defined in Easy to add Automatically compiled and included in the classpath
  2. buildSrc project Not visible outside the build Can’t reuse the

    plugin outside the build it’s defined in Has dedicated directory Automatically compiled and included in the classpath Visible to every build script used by the build |____rootProjectDir | |____buildSrc | | |____src | | | |____main | | | | |____groovy
  3. Standalone project Can be used in multiple builds Requires a

    separate project Can be published and shared with others Packaged JAR may include many plugins Requires an ID (e.g. ‘java’, `com.android.application` etc)
  4. Standalone project apply plugin: 'groovy' dependencies { compile gradleApi() compile

    localGroovy() } implementation-class=GreeterPlugin src/main/resources/META-INF/gradle-plugins/greeter.properties
  5. Language *.JAVA class GreeterPlugin implements Plugin<Project> { @Override public void

    apply(Project project) { System.out.println("Hello Java!"); } } class GreeterPlugin implements Plugin<Project> { @Override def apply(Project project) { println(‘Hello Groovy!’) } } *.GROOVY class GreeterPlugin : Plugin<Project> { override fun apply(project: Project) { println("Hello Kotlin!") } } *.KOTLIN class GreeterPlugin extends Plugin[Project] { override def apply(project: Project) { println("Hello Scala!") } } *.SCALA
  6. GreeterPlugin ENTER FILENAME/LANG import org.gradle.api.Plugin import org.gradle.api.Project class GreeterPlugin implements

    Plugin<Project> { @Override void apply(Project project) { println 'Hello World!' } }
  7. GreeterPlugin ENTER FILENAME/LANG import org.gradle.api.Plugin import org.gradle.api.Project class GreeterPlugin implements

    Plugin<Project> { @Override void apply(Project project) { println 'Hello World!' } } Represents an extension to Gradle This interface is the main API you use to interact with Gradle
  8. GreeterPlugin ENTER FILENAME/LANG import org.gradle.api.Plugin import org.gradle.api.Project class GreeterPlugin implements

    Plugin<Project> { @Override void apply(Project project) { println 'Hello World!' } }
  9. GreeterPlugin ENTER FILENAME/LANG class GreeterPlugin implements Plugin<Project> { @Override void

    apply(Project project) { project.tasks.create('sayHello') { group 'greeter' description 'Says hello' doLast { println 'Hello World!' } } } } TaskContainer Task name Task action
  10. $ gradle tasks -q ENTER FILENAME/LANG ------------------------------------------------------------ All tasks runnable

    from root project ------------------------------------------------------------ Build Setup tasks ----------------- init - Initializes a new Gradle build. wrapper - Generates Gradle wrapper files. Greeter tasks ------------- sayHello - Says hello Help tasks ---------- Our task with specified group and description
  11. SayHelloTask ENTER FILENAME/LANG import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction class SayHelloTask extends

    DefaultTask { @TaskAction void sayHello() { println 'Hello World!' } } the standard Task implementation. You can extend this to implement your own task types. Marks a method as the action to run when the task is executed.
  12. GreeterPlugin ENTER FILENAME/LANG import org.gradle.api.Plugin import org.gradle.api.Project class GreeterPlugin implements

    Plugin<Project> { @Override void apply(Project project) { project.tasks.create('sayHello', SayHelloTask) { group 'greeter' description 'Says hello' } } }
  13. GreeterPlugin ENTER FILENAME/LANG import org.gradle.api.Plugin import org.gradle.api.Project class GreeterPlugin implements

    Plugin<Project> { @Override void apply(Project project) { project.tasks.create('sayHello', SayHelloTask) { group 'greeter' description 'Says hello' } } } Create the Task of specific type
  14. build.gradle ENTER FILENAME/LANG apply plugin: GreeterPlugin // or apply plugin:

    `greeter` greeter { speaker 'Dmytro Zaitsev' } build.gradle
  15. build.gradle ENTER FILENAME/LANG apply plugin: GreeterPlugin greeter { speaker 'Dmytro

    Zaitsev' } build.gradle Project extension we need to register in our plugin
  16. GreeterExtension ENTER FILENAME/LANG class GreeterExtension { @Optional String speaker =

    ‘Dmytro Zaitsev` } build.gradle So, we create a class...
  17. GreeterPlugin ENTER FILENAME/LANG class GreeterPlugin implements Plugin<Project> { @Override void

    apply(Project project) { project.extensions.create('greeter', GreeterExtension) /* … */ } } … and register new Project property ExtensionContainer
  18. SayHelloTask ENTER FILENAME/LANG import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction class SayHelloTask extends

    DefaultTask { @TaskAction void sayHello() { println "Hello ${project.greeter.speaker}!" } }
  19. Create a DSL ENTER FILENAME/LANG apply plugin: GreeterPlugin greeter {

    addSpeaker('Dmytro Zaitsev', 'Kiev', 'Lohika') addSpeaker('Jake Wharton', 'San Francisco', 'Square') addSpeaker('Mateusz Herych', 'Kraków', 'IGcom') } build.gradle UGLY!
  20. Create readable DSL! ENTER FILENAME/LANG apply plugin: GreeterPlugin greeter {

    speakers { ‘Dmytro Zaitsev’ { city 'Kyiv' company 'Lóhika' } ‘Jake Wharton’ { city 'San Francisco' company 'Square' } ‘Mateusz Herych’ { city 'Kraków' company 'IGcom' } } } build.gradle NamedDomainObjectContainer Domain objects
  21. Speaker domain object ENTER FILENAME/LANG class Speaker { String name

    String city String company Speaker(String name) { this.name = name } def city(String city) { this.city = city } def company(String company) { this.company = company } } We need a name property so the object can be created by Gradle using a DSL.
  22. GreeterExtension ENTER FILENAME/LANG import org.gradle.api. NamedDomainObjectContainer class GreeterExtension { NamedDomainObjectContainer

    <Speaker> speakers GreeterExtension (NamedDomainObjectContainer <Speaker> speakers) { this.speakers = speakers } def speakers(Closure<NamedDomainObjectContainer <Speaker>> closure) { this.speakers.configure (closure) } } GreeterExtension.groovy
  23. GreeterExtension ENTER FILENAME/LANG import org.gradle.api.NamedDomainObjectContainer class GreeterExtension { NamedDomainObjectContainer <Speaker>

    speakers GreeterExtension(NamedDomainObjectContainer <Speaker> speakers) { this.speakers = speakers } def speakers(Closure<NamedDomainObjectContainer<Speaker>> closure) { this.speakers.configure(closure) } } GreeterExtension.groovy
  24. SayHelloTask ENTER FILENAME/LANG import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction class SayHelloTask extends

    DefaultTask { @TaskAction void sayHello() { project.greeter.speakers.all { println "Hello ${it.name}!" } } }
  25. GreeterPlugin ENTER FILENAME/LANG class GreeterPlugin implements Plugin<Project> { @Override void

    apply(Project project) { // add extensions project.extensions.create ('greeter', GreeterExtension , project.container(Speaker)) // add tasks project.tasks.create('sayHello', SayHelloTask) { group 'greeter' description 'Says hello' } } }
  26. $ gradle -q sayHello ENTER FILENAME/LANG $ gradle sayHello -q

    Hello Dmytro Zaitsev! Hello Jake Wharton! Hello Mateusz Herych! $
  27. Extension test ENTER FILENAME/LANG class GreeterPluginTest { @Test public void

    canAddGreeterExtension() { Project project = ProjectBuilder.builder().build() project.apply plugin: 'greeter' assert project.greeter instanceof GreeterExtension } }
  28. Extension test ENTER FILENAME/LANG class GreeterPluginTest { @Test public void

    canAddGreeterExtension() { Project project = ProjectBuilder.builder().build() project.apply plugin: 'greeter' assert project.greeter instanceof GreeterExtension } } Stub a Project
  29. Extension test ENTER FILENAME/LANG class GreeterPluginTest { @Test public void

    canAddGreeterExtension() { Project project = ProjectBuilder.builder().build() project.apply plugin: 'greeter' assert project.greeter instanceof GreeterExtension } } Apply the plugin
  30. Extension test ENTER FILENAME/LANG class GreeterPluginTest { @Test public void

    canAddGreeterExtension() { Project project = ProjectBuilder.builder().build() project.apply plugin: 'greeter' assert project.greeter instanceof GreeterExtension } } Check that extension exists
  31. Task test ENTER FILENAME/LANG class GreeterPluginTest { @Test public void

    canAddSayHelloTask() { Project project = ProjectBuilder.builder().build() project.apply plugin: 'greeter' assert project.tasks.sayHello instanceof SayHelloTask } }
  32. Links 1) Official Gradle documentation: https://docs.gradle.org/current/userguide/custom_plugins.html 2) Example of plugin

    written in Kotlin: https://github.com/RxViper/RxViper/tree/1.x/rxviper-gradle-plugin 3) Books: https://gradle.org/books/