How can I create a plugin that adds plugins?

I want to create a plugin that I can add that will configure the common plugins I use, for example this is the (kotlin) configuration I use for Error Prone, if I add my plugin to a project, I would simply like all of this applied. I’ve been googling for a tutorial, but so far no luck. How can I apply plugins and their settings with a plugin? p.s. I plan on writing the plugin using Java.

plugins {
    id("net.ltgt.errorprone").version("0.0.13")
}

dependencies {
    errorprone("com.google.guava:guava:latest.release")
    errorprone("com.google.errorprone:error_prone_core:latest.release")
    compileOnly("com.google.errorprone:error_prone_annotations:latest.release")
}

tasks.withType<JavaCompile> {
    options.compilerArgs.addAll(listOf(
        "-XepExcludedPaths:.*/build/generated/.*",
        "-Xep:MissingOverride:ERROR",
        "-Xep:Var:ERROR"
    ))
}

Your plugin gets handed a Project instance. Do project.getPluginManager().apply(MyPlugin) to apply plugin.

Use the project.getExtensions() to configure your plugins.

Why do you want to use Java? From all options available, it is the most tedious way to do things and wouldn’t be noticeably faster than Groovy or Kotlin.

groovy, being dynamically typed largely makes it impossible to figure out what code is being called where. Anything I’ve seen written in groovy I’ve had trouble discerning what it’s doing, and that makes it harder to modify, as I can’t see what it’s actually calling. Also, my experience with dynamic languages prior to using java is that I had to write 3 times the amount of code to do the same things that a statically typed language can verify at compile time.

Having only used Kotlin as a replacment for groovy in gradle. I don’t see that in most cases it is any less tedious than well written java, I’ve also found that at least in that case its “syntax highlighting” in intellij is super buggy compared to java’s. I dislike fighting with my IDE to simply make some code changes. Kotlin does streamline a few problems, such as nulls. In other cases I’ve found what it’s doing to be obscure and hard to read. for example, I have this in every single build.gradle.kts so far, I know why, but there are some pieces of the code that I have no idea why they are written that way, or why they work.

System.getenv("JRS_S3_URI")?.let {
    val sourcesJar by tasks.creating(Jar::class) {
        classifier = "sources"
        from(java.sourceSets["main"].allSource)
    }

    publishing {
        repositories {
            maven {
                url = uri(it)
                credentials(AwsCredentials::class.java) {
                    accessKey = System.getenv("JRS_ACCESSKEYID")
                    secretKey = System.getenv("JRS_SECRETACCESSKEY")
                }
            }
        }
        (publications) {
            "mavenJava"(MavenPublication::class) {
                from(components["java"])
                artifact(sourcesJar)
            }
        }
    }
}

I’ve not found that most of the problems people have with writing short code are the shortfalls of the java language (though sometimes the “stdlib”), but rather shortfalls in their ability to write code (I regularly take 10+ lines of code and write 2 equivalent lines that do the same thing and are IMO more readable). There are a few improvements I’d like to see brought to java first class syntax for properties, full “flat composition mixins”, key value constructors, and perhaps kotlins ? like handling of nulls. none of these particularly get in my way while writing code. Getters and setters are annoying but easy to generate.

is this right?

package com.xenoterracide.gradle.plugin;

import java.util.Arrays;

import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.tasks.compile.JavaCompile;


public class Xeno implements Plugin<Project> {
    private static final String LATEST = ":latest.release";
    private static final String COMPILE = "compileOnly";
    private static final String EP = "errorprone";

    @Override
    public void apply( final Project project ) {

        project.getPluginManager().apply( "net.ltgt.errorprone:0.0.13" );

        DependencyHandler deps = project.getDependencies();
        deps.add( EP, "com.google.guava:guava" + LATEST );
        deps.add( EP, "com.google.errorprone:error_prone_core" + LATEST );
        deps.add( COMPILE, "com.google.errorprone:error_prone_annotations" + LATEST );

        project.getExtensions().configure( JavaCompile.class, javaCompile -> {
            javaCompile.getOptions().getAllCompilerArgs().addAll( Arrays.asList(
                    "-XepExcludedPaths:.*/build/generated/.*",
                    "-Xep:MissingOverride:ERROR",
                    "-Xep:Var:ERROR"
            ) );
        } );
    }
}

Hey @xenoterracide

You can checkout this plugin which is created by the gradle core team themself:

Its pretty simply and do exactly what you want (or better shows you what do you want to archive :wink:).

Hope it helps :slight_smile:

somewhat, although I think I’d figured a lot of that out, but one of my hangups is this now, I’m not sure how to apply the plugin via this api when it’s not core, or perhaps more correctly, I’d expect maybe that it requires a full GAV, but doesn’t when you’re doing it through the DSL?

org.gradle.api.internal.plugins.PluginApplicationException: Failed to apply plugin [class 'com.xenoterracide.gradle.plugin.XenoPluginBundle']

	at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:150)
	at org.gradle.api.internal.plugins.DefaultPluginManager.apply(DefaultPluginManager.java:129)
	at com.xenoterracide.gradle.plugin.XenoPluginBundleTest.testProject(XenoPluginBundleTest.java:21)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:513)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:170)
	at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:166)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:113)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:58)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:113)
	at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:121)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
	at java.util.Iterator.forEachRemaining(Iterator.java:116)
	at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:121)
	at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:121)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
	at java.util.Iterator.forEachRemaining(Iterator.java:116)
	at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:121)
	at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:55)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:43)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:74)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.gradle.api.plugins.UnknownPluginException: Plugin with id 'net.ltgt.errorprone' not found.
	at org.gradle.api.internal.plugins.DefaultPluginManager.apply(DefaultPluginManager.java:123)
	at com.xenoterracide.gradle.plugin.ErrorProne.apply(ErrorProne.java:16)
	at com.xenoterracide.gradle.plugin.ErrorProne.apply(ErrorProne.java:10)
	at com.xenoterracide.gradle.plugin.XenoPluginBundle.lambda$apply$0(XenoPluginBundle.java:24)
	at java.util.Arrays$ArrayList.forEach(Arrays.java:3880)
	at com.xenoterracide.gradle.plugin.XenoPluginBundle.apply(XenoPluginBundle.java:24)
	at com.xenoterracide.gradle.plugin.XenoPluginBundle.apply(XenoPluginBundle.java:11)
	at org.gradle.api.internal.plugins.ImperativeOnlyPluginTarget.applyImperative(ImperativeOnlyPluginTarget.java:42)
	at org.gradle.api.internal.plugins.RuleBasedPluginTarget.applyImperative(RuleBasedPluginTarget.java:50)
	at org.gradle.api.internal.plugins.DefaultPluginManager.addPlugin(DefaultPluginManager.java:164)
	at org.gradle.api.internal.plugins.DefaultPluginManager.access$200(DefaultPluginManager.java:47)
	at org.gradle.api.internal.plugins.DefaultPluginManager$AddPluginBuildOperation.run(DefaultPluginManager.java:252)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:317)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:309)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:185)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:97)
	at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
	at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:144)
	... 56 more

also this,

org.gradle.api.internal.plugins.PluginApplicationException: Failed to apply plugin [class 'com.xenoterracide.gradle.plugin.XenoPluginBundle']

	at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:150)
	at org.gradle.api.internal.plugins.DefaultPluginManager.apply(DefaultPluginManager.java:129)
	at com.xenoterracide.gradle.plugin.XenoPluginBundleTest.testProject(XenoPluginBundleTest.java:21)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:513)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:170)
	at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:166)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:113)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:58)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:113)
	at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:121)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
	at java.util.Iterator.forEachRemaining(Iterator.java:116)
	at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:121)
	at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:121)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
	at java.util.Iterator.forEachRemaining(Iterator.java:116)
	at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:121)
	at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:55)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:43)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:74)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.gradle.api.UnknownDomainObjectException: Extension of type 'JavaCompile' does not exist. Currently registered extension types: [ExtraPropertiesExtension, XenoPluginBundleExtension, IdeaModel, ReportingExtension, CheckstyleExtension, DefaultArtifactPublicationSet]
	at org.gradle.api.internal.plugins.ExtensionsStorage.getHolderByType(ExtensionsStorage.java:88)
	at org.gradle.api.internal.plugins.ExtensionsStorage.configureExtension(ExtensionsStorage.java:71)
	at org.gradle.api.internal.plugins.DefaultConvention.configure(DefaultConvention.java:217)
	at org.gradle.api.internal.plugins.DefaultConvention.configure(DefaultConvention.java:212)
	at com.xenoterracide.gradle.plugin.ErrorProne.apply(ErrorProne.java:24)
	at com.xenoterracide.gradle.plugin.ErrorProne.apply(ErrorProne.java:10)
	at com.xenoterracide.gradle.plugin.XenoPluginBundle.lambda$apply$0(XenoPluginBundle.java:25)
	at java.util.Arrays$ArrayList.forEach(Arrays.java:3880)
	at com.xenoterracide.gradle.plugin.XenoPluginBundle.apply(XenoPluginBundle.java:25)
	at com.xenoterracide.gradle.plugin.XenoPluginBundle.apply(XenoPluginBundle.java:11)
	at org.gradle.api.internal.plugins.ImperativeOnlyPluginTarget.applyImperative(ImperativeOnlyPluginTarget.java:42)
	at org.gradle.api.internal.plugins.RuleBasedPluginTarget.applyImperative(RuleBasedPluginTarget.java:50)
	at org.gradle.api.internal.plugins.DefaultPluginManager.addPlugin(DefaultPluginManager.java:164)
	at org.gradle.api.internal.plugins.DefaultPluginManager.access$200(DefaultPluginManager.java:47)
	at org.gradle.api.internal.plugins.DefaultPluginManager$AddPluginBuildOperation.run(DefaultPluginManager.java:252)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:317)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:309)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:185)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:97)
	at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
	at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:144)
	... 56 more

even after I added java-library prior to applying error prone

        project.getPluginManager().apply( "org.gradle.java-library" );
        project.getPluginManager().apply( "net.ltgt.errorprone" );

Have you solved it?
I’ve seen your repo here and it seems that you have fixed that issue.

It would be great to explain what was the issue and how you have fixed it…

fixed… considering my plugin doesn’t work yet… but yes I managed to get beyond those errors. The first error was that I needed to make my plugin actually depend on the other plugins and add error prone as an implementation. Second Problem was JavaCompile is a Task not an extension.

That should have been obvious, too bad it took you a while to get there… Before it get bitten by it, keep in mind that unlike Maven, Gradle’s build plugins share classloader and if you add too many dependencies, you may end up with version conflict.

Also you may want to check out the Idiomatic Gradle… by Schalk Cronjé [Leanpub PDF/iPad/Kindle] book.