Apply plugin depending on project current plugins

Hi!
I have a root project where i want to to add a plugin to its children depending if they themselves used a specific plugin

the issue is i can only use
pluginManager.hasPlugin("pluginA")

in an afterEvaluate script block

and if i

afterEvaluate {
if (pluginManager.hasPlugin(“pluginA”)) {
apply plugin: ‘pluginB’
}
}

i get

Failed to apply plugin ‘pluginB’.
Cannot run Project.afterEvaluate(Action) when the project is already evaluated.

originally i wanted the developer only to add the plugins A task, and by detecting that code block i will add both pluginA and pluginB

but couldnt find a way to identify that code block so i resorted to the case above which is very close to what i want

the reasoning is i want the developer to not handle the plugins etc and only to add a code block

Reacting to a plugin being applied would be done using pluginManager.withPlugin(...) { ... } without the need to use afterEvaluate which you should practically never use.

But actually applying plugins to subprojects is a bad-practice anti-pattern anyway that you should avoid.
Better provide convention plugins that apply the plugins you want and do the configuration you need and then the developers can simply apply that convention plugin to the projects where it is appropriate to have these conventions applied. Cross-project configuration harms more advance Gradle features and typically makes builds harder to understand and harder to maintain.

thanks for your answer!
using pluginManager.withPlugin(...) { ... } did the trick!

but i dont want to use bad partice - so i would rather elaborate my issue and maybe you can help me find a best practice solution to it-

my goal is to reduce the developers handling in the subproject gradle.
they will decide when pluginA is needed and add the corresponding task/scriptblock that pluginA provides.
I dont want to teach all the developers how pluginB works as it works the same and i want it to be changed in my parent project if needed

that said, i dont want all the subprojects to have these 2 plugins, only those that the programer decided that has pluginA, i want to add pluginB to them in order for my devops team to be able to use pluginB

since the if statement was written in my parent project i dont know which subprojects have that plugin
(well, i do now with the bad practice solution)

it might help to understand -
pluginA = is a typescript generator
pluginB = npm publisher

how would you approach this issue with best practices in mind?
note - im kinda new to gradle

Well, as I said, it sounds like you should provide convention plugins to your devs like described at Sharing Build Logic between Subprojects.

You provide a convention plugin like com.company.typescript, this plugin will apply pluginA and pluginB and maybe also do some configuration all projects applying these should have. The devs then apply the com.company.typescript plugin which will implicitly apply pluginA and pluginB. The devs can then still configure the extension of pluginA as they like. Or you could even provide them an own extension that abstracts away logic they don’t need to care about and that your convention plugin then handles.

Thanks for the advice!
im having trouble actaully implementing the solution suggested

what ive done is creating a buildSrc in my root project in that folder ive created the following:

buildSrc/src/main/groovy/com/company/typescript.generator.gradle

the gradle file contains:

package com.company

plugins{
id “cz.habarta.typescript-generator”
id “dev.petuska.npm.publish”
}

npmPublish {
registries {
register(“npmjs”) {
uri.set(“url”)
}
}
}

but then when i try to use this plugin in my subproject like so:

plugins {
id “com.company.typescript-generator”
}

i get the following error:

Exception is:
org.gradle.api.plugins.UnknownPluginException: Plugin [id: ‘com.company.typescript-generator’] was not found in any of the following sources:

  • Gradle Core Plugins (plugin is not in ‘org.gradle’ namespace)
    Plugin Repositories (plugin dependency must include a version number for this source)

where do i set the plugin version ?

it seems like i need some very simple gradle configuration incapsulation and all i can find in the docs is more complicated version of plugin creation i dont need

thanks again for your help!

it is just a typo on the post, the names are the same in the file system

does a gradle file in the buildSrc folder act as a plugin?
with no additional java classes?

Im doing exactly as the post you linked is showing but my gradle doesnt find the plugin…

this is in the root project
Screenshot from 2022-12-20 17-39-02

EDIT : ok so i made huge progress, my issue was i didnt have a build.gradle in my buildSrc project…

but my issue now is that i cant put the method i want to be defined in the “convention” plugin so every project that has it can use the convention plugin method

plugins{
id “cz.habarta.typescript-generator”
id “dev.petuska.npm.publish”
}

npmPublish {
registries {
register(“npmjs”) {
uri.set(“uri”)
}
}
}

but this throws

Caused by: java.lang.RuntimeException: org.gradle.internal.metaobject.AbstractDynamicObject$CustomMessageMissingMethodException: Could not find method npmPublish() for arguments [precompiled_TypescriptGenerator$_run_closure1@25f66c6e] on project ‘:project’ of type org.gradle.api.Project.

i might lack some basic understanding - but i thought that when ill specify the arguments in the convention plugin, any subproject that will use it wont have to, then i can simply call the npmPublish() on the subproject and the convention one will be executed.
am i wrong?

I’m not sure what you mean with your last sentence.
The plugins applying the plugin would not “call an npmPublish of the convention plugin” and your convention plugin also does not define any such method from what you showed.

But yes, what you showed in the example looks fine.
Any project applying that convention plugin will get this configuration done automatically.

That it does not work is most probably because you have wrong content in your npmPublish block.
But the content you actually showed seems to be fine to me.
So the problem is, that you post modified code with an error message you get from completely different code.
How should this enable anyone to sanely help? :frowning:

One general advice I can give.
Do not use Groovy DSL, but Kotlin DSL.
Then you instantly have type-safe scripts and much better IDE support. At least when using a good IDE like IntelliJ.