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. 6.

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

    gradleApi()
 }
 Be cau'ous - dependency pulls in full Gradle run'me API
  2. 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
  3. 12.

    dig deep with the Gradle profiler Profiling and benchmarking info

    h2ps:/ /github.com/gradle/gradle-profiler
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 38.

    improvements to Plugin Ecosystem ➞ Feature-rich plugin portal ➞ Cross-version

    compa'bility tes'ng ➞ Development support by plugin dev plugin ➞ BeQer IDE support
  21. 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/