Programmatically apply a plugin

The recently added pluginManagement block in settings.gradle is a most welcomed feature, however I can’t find a way to conditionally apply a plugin that has been declared in this block.

Say you have a settings.gradle file that looks like this

pluginManagement {
    repositories {
        gradlePluginPortal()
    }
    plugins {
        id 'com.github.johnrengelman.shadow' version '5.2.0'
    }
}

The usage of shadow is just an example, it could be any plugin. Then later in the build file you’d apply this plugin if a particular condition holds true, such as

if (something) {
    apply plugin: 'com.github.johnrengelman.shadow'
}

Of course this does not work because plugin requests defined in the pluginManagement block appear to be fulfilled ONLY_IF they are coming from the plugins block. Unfortunately the plugins block is quite restrictive as:

  • You’re not allowed to define statements other than id(...).
  • Contents on this block must be defined in advance, leaving no room for a meta-plugin to customize.

Is there a way around this problem? What I;'m looking for is to define all required plugins inside the pluginManagement block form settings.gradle, then apply them to the root project & subprojects depending on particular conditions.

TIA

1 Like

You can build your own plugin to do that depending on the conditions you would like. Would that help you?

To make it more understandable I am suggesting to build a standalone plugin, which then will apply all of your desired plugins depending on conditions.

Sadly no. It won’t work and here’s why as far as I understand the current mechanism:

  1. The pluginManagerment block in settings.gradle defines which plugins may be resolved in build.gradle.
  2. Plugins are resolved using a plugins block in build.gradle.

If the project happens to be a multiproject (which is my case) one can apply the plugins in a subproject by
a. Using a plugins block in the subproject’s build file
b. Define the plugin with apply false in the plugins block of the root’s build file, then either use plugins or apply plugin: in the subproject’s build file.

The secret sauce lies in 2. and b., as it requires defining the plugin request in a plugins block, otherwise no plugin resolution occurs. I can’t place do this in a meta-plugin due to 2 reasons:

  1. the meta-plugin has no clue which additional plugins may be applied.
  2. the plugin resolution mechanism currently requires an explicit plugins block to be used, which the meta-plugin doesn’t have access to.

The purpose of the plugins block in settings.gradle is only to define default versions. Only the declaration in a project’s plugins block causes the resolution and addition to the buildscript’s classpath (but you can omit the version).

You would need the following to use a settings.gradle to specify default versions (noting that the plugins block need not be in the same project as the conditional, as long as it’s in the plugins block of a parent project. The end result is duplication of the plugins block (but maybe without versions) in the project:

// settings.gradle
pluginManagement {
    repositories {
        gradlePluginPortal()
    }
    plugins {
        id 'com.github.johnrengelman.shadow' version '5.2.0'
    }
}
// build.gradle
plugins {
    id 'com.github.johnrengelman.shadow' apply false
}

if (something) {
    apply plugin: 'com.github.johnrengelman.shadow'
}

However, there is also technically nothing that requires you to do anything with the plugins themselves (just the repository if you need something special) in settings.gradle. It is equally valid to specify all plugin information in the root project, and then conditionally apply them anywhere else in the build. i.e.:

// build.gradle
plugins {
    id 'com.github.johnrengelman.shadow' version '5.2.0' apply false
}
// subproject/build.gradle
if (something) {
    apply plugin: 'com.github.johnrengelman.shadow'
}

There’s also technically no value in caring about the plugins block in the meta-plugin case. You can only conditionally apply plugins that are on the classpath. The plugins block puts them on the classpath, but you can decide to not apply them. They’re all going to be on the classpath of the buildscript anyway (and that’s inherited by subprojects), so you might as well just define them all as dependencies in meta-plugin case and then conditionally apply them, not worrying at all about the plugins block.

Yes, except in my case I have a meta-plugin that may or may not apply other plugins based on conditions. This meta-plugin does not know in advance which plugins may be applied, thus the potential plugins are not yet found in the classpath, which is why invoking apply plugin: without explicit plugin resolution inside a plugins block on a build file is a non starter.

As far as I can find, the plugins block found on a build file is processed using APIs that are not reachable by the Settings nor Project types, that is unless you’re willing to risk using internal APIs, and yet is not that easy. This is what prompted me to ask if there is an alternative.

This is an old question, but I ran into the same problem today.
Did you ever find a viable solution to this problem?

Which “same problem” do you have?
There were quite some things spoken about here.
If you just mean the last comment, then that “meta plugin” would either have the plugins to apply as dependencies, or have them as compileOnly dependencies and then require them to be on the classpath already when they should get applied, depending on concrete needs.

I see, it became crystal clear when I read your reply, @Vampire .
I got confused from the thread and thought I always had to have the plugins defined in the target project, now matter what.

I tried a few experiments with the things and got it all working. Appreciate your response.

1 Like