Designing and writing Gradle plugins

Designing and writing Gradle plugins

Implementing plugins can be a daunting task for Gradle beginners and advanced users alike. The task requires many intricate considerations, decisions and deep knowledge about (sometimes undocumented) features. Have you ever asked yourself one of the following questions, then this session is for you!

- What's the best structure for my plugin code?
- How do I make my plugin as performant, flexible and user-friendly as possible?
- How do I ensure the best forward and backward compatibility for builds consuming my plugin?
- How do I expose a declarative DSL to configure my plugin's runtime behavior?

The main focus of this talk lies on the aspect of designing and writing plugins by applying established, best practices. We'll look at architectural considerations, technologies and tooling involved in building plugins as well as concrete recipes for implementing typical use cases faced during plugin development.

8f2248c6bfcc6df39a2cd8edf4267cb5?s=128

Benjamin Muschko

June 23, 2017
Tweet

Transcript

  1. Designing and Wri,ng Gradle Plugins Benjamin Muschko

  2. writing plugins is easy they said… me a couple of

    years ago
  3. Designing… 1

  4. Binary plugins FTW!

  5. Prefer statically-typed language + @CompileStatic

  6. Only use public API if possible build.gradle dependencies {
 compile

    gradleApi()
 }
 Be cau'ous - dependency pulls in full Gradle run'me API
  7. Minimize external dependencies

  8. identifying external plugin dependencies build.gradle plugins {
 id "com.github.gradle-guides.site" version

    "0.1"
 } ./gradlew buildEnvironment > Task :buildEnvironment ------------------------------------------------------------ Root project ------------------------------------------------------------ classpath \--- gradle.plugin.com.github.gradle-guides:gradle-site-plugin:0.1 \--- org.freemarker:freemarker:2.3.26-incubating
  9. impact on performance

  10. learn performance tips & tricks Op'mizing Gradle Build Performance h2ps:/

    /guides.gradle.org/performance/
  11. scan your build Iden'fy and share performance hotspots h2ps:/ /gradle.com/scans/get-started

  12. dig deep with the Gradle profiler Profiling and benchmarking info

    h2ps:/ /github.com/gradle/gradle-profiler
  13. Convention over configuration

  14. Conventions Out-of-the-box SitePlugin.java public class SitePlugin implements Plugin<Project> {
 public

    void apply(Project project) {
 SitePluginExtension sitePluginExtension = ↵ project.getExtensions().create("site", ↵ SitePluginExtension.class, project); 
 sitePluginExtension.setOutputDir(new ↵ File(project.getBuildDir(), "docs/site"));
 
 ...
 }
 } sets default value
  15. reconfiguring conventional values build.gradle apply plugin: 'com.github.gradle-guides.site'
 
 site {


    outputDir = file("$buildDir/site")
 websiteUrl = 'http://gradle.org'
 vcsUrl = 'https://github.com/gradle-guides/↵ gradle-site-plugin'
 } User declares the “what” not the “how” overrides defaults
  16. CAPABIlities vs. conventions

  17. Common pattern in plugin architecture Java Base Plugin Java Plugin

    Capabilities Conventions applies
  18. CAPAbilities vs. conventions public class BaseSitePlugin extends Plugin<Project> {
 public

    void apply(Project project) {
 // define capabilities
 }
 } public class SitePlugin extends Plugin<Project> {
 public void apply(Project project) {
 project.getPlugins().apply(BaseSitePlugin.class);
 
 // define conventions
 }
 } SitePlugin.java BaseSitePlugin.java applies plugin
  19. implementing… 2

  20. Use the Plugin Development plugin build.gradle dependencies {
 compile 'java-gradle-plugin'


    }
 Reduces boilerplate code significantly
  21. Prefer writing custom tasks types

  22. reusing custom tasks import org.gradle.plugins.site.tasks.SiteGenerate task generateDefaultSite(type: SiteGenerate)
 
 task

    generateSiteWithCustomValues(type: SiteGenerate) {
 outputDir = file("$buildDir/myAwesomeSite")
 
 customData {
 websiteUrl = 'http://gradle.org'
 vcsUrl = 'https://github.com/gradle-guides/↵ gradle-site-plugin'
 }
 } build.gradle Impera've logic is hidden in implementa'on
  23. modeling DSL-like APIs task generateSiteWithCustomValues(type: SiteGenerate) {
 customData {
 websiteUrl

    = 'http://gradle.org'
 vcsUrl = 'https://github.com/gradle-guides/↵ gradle-site-plugin'
 }
 } build.gradle site {
 customData {
 websiteUrl = 'http://gradle.org'
 vcsUrl = 'https://github.com/gradle-guides/↵ gradle-site-plugin'
 }
 } or
  24. avoid closures in Apis SiteGenerate.gradle public class SiteGenerate extends DefaultTask

    {
 private final CustomData customData = new CustomData();
 
 public void customData(Closure closure) {
 closure.setResolveStrategy(Closure.DELEGATE_FIRST);
 closure.setDelegate(customData);
 closure.call();
 }
 } avoid Harder to use from Java and Kotlin
  25. generation of closure method SiteGenerate.gradle task generateSiteWithCustomValues(type: SiteGenerate) {
 customData

    {
 ... }
 } public class SiteGenerate extends DefaultTask {
 private final CustomData customData = new CustomData();
 
 public void customData(Action<? super CustomData> action) {
 action.execute(customData);
 }
 } build.gradle do this
  26. Enable tasks to be incremental

  27. Declare inputs & outputs public class SiteGenerate extends DefaultTask {


    private final PropertyState<File> outputDir;
 private final CustomData customData;
 
 ...
 @Nested
 public CustomData getCustomData() {
 return customData;
 }
 
 @OutputDirectory
 public File getOutputDir() {
 return outputDir.get();
 }
 } SiteGenerate.java use annotations
  28. Lazy evaluation of properties

  29. typical use case SiteGenerate.java SitePluginExtension.java build.gradle Capture user-provided input Hold

    user-provided values Lazily evaluate and apply user-provided values Capture values during configura'on phase Evaluate and use value during execu'on 'me
  30. Evaluate property at execution time public class SiteGenerate extends DefaultTask

    {
 private final PropertyState<File> outputDir;
 
 public SiteGenerate() {
 this.outputDir = getProject().property(File.class);
 }
 
 @OutputDirectory
 public File getOutputDir() {
 return outputDir.get();
 }
 
 @TaskAction
 public void generate() {
 getOutputDir();
 }
 } SiteGenerate.java lazily evaluate value
  31. avoid applying plugins if possible project.getPlugins().apply(JavaPlugin.class);
 JavaPluginConvention javaConvention = project.getConvention()↵

    .getPlugin(JavaPluginConvention.class);
 projectDescriptor.setJavaProject(new JavaProjectDescriptor(↵ javaConvention.getSourceCompatibility().toString(),↵ javaConvention.getTargetCompatibility().toString()) SitePlugin.java Imposes poten'ally unnecessary conven'ons avoid
  32. React to applied plugins project.getPlugins().withType(JavaPlugin.class, new Action<JavaPlugin>() {
 @Override
 public

    void execute(JavaPlugin javaPlugin) {
 JavaPluginConvention javaConvention = project.getConvention()↵ .getPlugin(JavaPluginConvention.class);
 projectDescriptor.setJavaProject(↵ new JavaProjectDescriptor(↵ javaConvention.getSourceCompatibility().toString(),↵ javaConvention.getTargetCompatibility().toString()));
 }
 }); SitePlugin.java do this
  33. Assign appropriate plugin identifiers . !"" src !"" main !""

    resources !"" META-INF !"" gradle-plugins #"" com.github.gradle-guides.base-site.properties !"" com.github.gradle-guides.site.properties 
 Add domain to ensure uniqueness src/main/resources/META-INF/gradle-plugins
  34. What could be next?

  35. top 10 internal apis on github Class Name Count org.gradle.internal.reflect.Instantiator

    1103 org.gradle.api.internal.file.FileResolver 868 org.gradle.util.GradleVersion 680 org.gradle.internal.os.OperatingSystem 680 org.gradle.util.ConfigureUtil 608 org.gradle.internal.service.ServiceRegistry 546 org.gradle.util.GFileUtils 329 org.gradle.util.TestUtil 258 org.gradle.api.internal.ConventionTask 195 org.gradle.api.internal.project.IsolatedAntBuilder 114 Poten'al candidates for public APIs
  36. public apis with bigger impact ➞ Separa'on between internal &

    public API ➞ Declaring custom repository types ➞ BeQer support for wri'ng language plugins ➞ Improved TestKit fixtures ➞ Publishing custom ar'facts
  37. better documentation, more guides ➞ Improved user guide documenta'on ➞

    More sample projects ➞ Guides on tes'ng & documen'ng plugins ➞ Tutorial for wri'ng a plugin by example
  38. improvements to Plugin Ecosystem ➞ Feature-rich plugin portal ➞ Cross-version

    compa'bility tes'ng ➞ Development support by plugin dev plugin ➞ BeQer IDE support
  39. We want your feedback!

  40. Resources Designing Gradle plugins h2ps:/ /guides.gradle.org/designing-gradle-plugins/ Implemen'ng Gradle plugins h2ps:/

    /guides.gradle.org/implemen,ng-gradle-plugins/ Gradle Site Plugin h2ps:/ /github.com/gradle-guides/gradle-site-plugin/
  41. w Thank you Gradle Summit 2017