Apply plugin inside a plugin

Hello,

I have recently created a simple plugin that interfaces with GitVersion.
It can be found here:

https://plugins.gradle.org/plugin/com.bisiach.gradle.gitversion-plugin

Now I am creating a new plugin that should incorporate the above plugin and force some of the configuration elements on the project where the plug-in is applied to.

I have the basic code for my groovy based plugin, like this

package com.bisiach.gradle;

import org.gradle.api.Plugin;
import org.gradle.api.Project;

class CustomPlugin implements Plugin<Project> {
	@Override
	def void apply(Project project) {
		
	}
}

Inside def void apply(Project project) i should apply the above mentioned plugin to the project object. I canā€™t figure out the syntax to use.

Any suggestion?

Thanks

I have tried the following:

class CustomPlugin implements Plugin<Project> {
	@Override
	def void apply(Project project) {
		project.buildscript.dependencies.add("classpath","gradle.plugin.com.bisiach.gradle.gitversion-plugin:gradle-gitversion-plugin:1.0.2");
		project.getPluginManager().apply("com.bisiach.gradle.gitversion-plugin");
	}
}

but when the consumer uses this plugin it raises an error

FAILURE: Build failed with an exception.
Where:
Build file '/Users/andreas/eclipse-workspace/tester/build.gradle' line: 12

* What went wrong:
An exception occurred applying plugin request [id: 'com.bisiach.gradle.custom-plugin', version: '1.0.0']
> Failed to apply plugin [id 'com.bisiach.gradle.custom-plugin']
   > Cannot change dependencies of configuration ':classpath' after it has been resolved.

I have found the solution so I post it here for reference:

  1. in the gradle.build of the custom plugin:

    dependencies {
    runtime ā€˜gradle.plugin.com.bisiach.gradle.gitversion-plugin:gradle-gitversion-plugin:1.0.2ā€™
    }

  2. In the plugin class

    package com.bisiach.gradle;

    import org.gradle.api.Plugin;
    import org.gradle.api.Project;

    class CustomPlugin implements Plugin {
    @Override
    def void apply(Project project) {
    project.getPluginManager().apply(ā€œcom.bisiach.gradle.gitversion-pluginā€)
    project.version = project.GitVersion.SemVer
    }
    }

Hey thanks for sharing that! Youā€™re quick :slight_smile: I was just about to reply with something I found in the User Guide:

Would you mind trying that approach? And sharing whether or not that also works? Thanks.

Thanks a lot for the link to guide!

Unfortunately I discovered the neither my approach or the one in the guide work.
I will use the terminology in the guide to be more clear.
In my scenario the Base Plugin (the capability plugin) is published public here:

https://plugins.gradle.org/plugin/com.bisiach.gradle.gitversion-plugin

On the other hand, the ā€œconventionā€ plugin will be published privately to a company maven repo. The plugin named i.e: com.acme.gradle.standard-development-plugin.
The idea is that this plug-in will be applied to all customer projects using gradle.init.

During develompent, to test com.acme.gradle.standard-development-plugin I am using composite build.
I have a project ā€œtestā€ that includes the build of com.acme.gradle.standard-development-plugin. In this scenario, adding the following to build.gradle of com.acme.gradle.standard-development-plugin worked. but only because in a composite build, the project ā€œtestā€ can see the runtime dependency:

dependencies {
runtime ā€˜gradle.plugin.com.bisiach.gradle.gitversion-plugin:gradle-gitversion-plugin:1.0.2ā€™
}

After publishing my com.acme.gradle.standard-development-plugin to an internal maven repo, and including in the project ā€œtestā€ as a normal plugin, the plug in fails to apply the base (capability) plugin com.bisiach.gradle.gitversion-plugin.

The examples outlined in the guide always shoes capability and convention plugins coming from the same gradle build. That is why it works there I think.
I need to achieve the same between 2 plugins coming from 2 different builds/repositories

Bummer :frowning:

Hereā€™s what I think are the actual implementations of the two plugins the User Guide refers to:

And this other one thatā€™s also extended by the Java Plugin:

Theyā€™re in three different subprojects. Is Gradleā€™s structure similar to what you described for your projects.

Reading the Gradle plugin source, it all flew right over my head. Iā€™m not even going to pretend I understand any of it Hah-hah!

But the Gradle source might give you some leads on how you might proceed.

Iā€™m going to need to do that myself soon. What have you figured out so far? Got any pointers to share yet?

Why donā€™t you work with detached configs?

Conceptually I think i grasp what you mean. But I could really need a little bite more hint.
I guess that instead of

	project.buildscript.dependencies.add("classpath","gradle.plugin.com.bisiach.gradle.gitversion-plugin:gradle-gitversion-plugin:1.0.2");
	project.getPluginManager().apply("com.bisiach.gradle.gitversion-plugin");

I should do something like :

def d = dependencies.create(group: project.group, name: project.name, version: '+')
def c = configurations.detachedConfiguration(d).setTransitive(false)

I cannot see how this will help me load the com.bisiach.gradle.gitversion-plugin in the custom plug-in.
Could you point me in the right direction?

I mistook problem.

To get read of:

dependencies {
   runtime ā€˜gradle.plugin.com.bisiach.gradle.gitversion-plugin:gradle-gitversion-plugin:1.0.2ā€™
}

or:

project.buildscript.dependencies.add("classpath","gradle.plugin.com.bisiach.gradle.gitversion-plugin:gradle-gitversion-plugin:1.0.2");

you need to declare transitive dependency in your plug-in because build script classpath cannot be altered programmatically.

Then I believe below will work:

project.getPluginManager().apply("com.bisiach.gradle.gitversion-plugin");

@andreasb70, I see that something similar to what @gavenkoa suggests is done in the two links I shared previously:

    ...
	
    @Override
    public void apply(Project project) {
        project.getPluginManager().apply(ComponentModelBasePlugin.class);
        project.getPluginManager().apply(JvmResourcesPlugin.class);
    }
	...
    ...
	
    @Override
    public void apply(Project project) {
        project.getPluginManager().apply(LanguageBasePlugin.class);
        project.getPluginManager().apply(BinaryBasePlugin.class);
    }
    ...	

Thereā€™s a good chance something like that could work, if you hadnā€™t tried it yet.

Please let us know if it does or not? Thanks.

EDIT: Actually, I see that you have tried something similar beforeā€¦

Have you tried it with the class literal (.class) of the plugin you want to apply? Like itā€™s done in the Gradle source links? Instead of the pluginā€™s id string?

1 Like

Did anyone end up getting this working? Iā€™m also trying to get this working but have had issues (the plugins are actually in completely different repos that Iā€™m trying to incorporate)

The Issue with the above mentioned solutions is that they declared the dependency in the plugins build.gradle file as runtime. Thatā€™s why you can not apply the Plugin class (as it is missing in the compile classpath). Just change the dependency to implementation.

I donā€™t think that any lazy configuration will help here as one usually wants to interact with the other plugins datastructures and therefor youā€™ll need access to the classes on the compileClasspath and runtimeClasspath what the implementation configuration is meant for.

//build.gradle
repositories.pluginPortal()
dependencies {
    implementation ā€˜gradle.plugin.com.bisiach.gradle.gitversion-plugin:gradle-gitversion-plugin:1.0.2ā€™
}
public class NewPlugin implements Plugin<Project> {
    public void apply(Project project) {
        project.getPlugins().apply(GitVersionPlugin.class);
        // customize...
    }
}

I am using this extensively with gradles internal plugins as well as externals like sonarqube or test-sets.

1 Like

Thanks @geissld, your recommendation worked in my case!

An undesired side effect (in my case anyway) is that the tasks registered by the third-party plugin are now visible from my top-level project.

I donā€™t really care much honestly, but I am curious as to how this could be avoided so the top-level project ends up with a cleaner interface.

Well this is by design. Even when doing it from inside your convinience plugin you technically do write apply plugin: 'includedPluginId' and this will add all Tasks, Configurations etc. to your project and thus itā€™s namespace. Iā€™m pretty sure this is intentional and can not be avoided.

2 Likes

Iā€™m not sure that this works anymore in Gradle 7. For one of my conventions, I have this line declared in my plugin project:

project.plugins.apply("org.sonarqube")
// though i've read we should prefer project.pluginManager instead of project.plugins

The plugin projectā€™s build.gradle has the necessary dependency defined:

dependencies {
  implementation "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0"
}

But in the consuming project, I get errors like:

> Failed to apply plugin 'com.mycompany.code-quality-conventions'.
    > Plugin with id 'org.sonarqube' not found.

Similar to OP, it works with a composite build, but once I build the plugin JAR, and try to use it, I get the above error.

Does that mean I cannot dynamically choose a version for the 3rd party plugin I want to apply? I would like the userā€™s configuration (maybe indirectly) to affect the version of the plugin I will apply from my plugin. How can I do that?

There is very VERY shady way to do that by hacking the settings ClassLoaderScope, but Iā€™d rather notā€¦

The user could use a dependency constraint in the buildscript { ... } block to influence the version, or you could require the user to add the plugin in the wanted version to the buildscritpā€™s classpath.

Thanks, I see. Those options donā€™t allow me to fully encapsulate this plugin application: the user still has to know about this plugin.

Well, how can he not know about the plugin when he is expected to control its version?