Best way to support multiple versions of a plugin in my plugin

Is there a recommended way to support multiple versions of a Plugin in my Gradle plugin?

My use case:

I’ve developed a plugin to remove a lot of the repository- and credentials-related (boilerplate)code from my other Gradle builds. One of the things this plugin should do is configuring the builder and run image and registries in all BootBuildImage tasks, if the org.springframework.boot plugin is applied:

pluginManager.withPlugin("org.springframework.boot") {
    // configure Docker registry in all BootBuildImage tasks.
    // the `all` action will be executed against all current and future elements of the collection.
    tasks.withType( { task ->
        val dockerPullRegistryUrl = myExtension.dockerPullRegistryUrl.get()
        task.builder = "${dockerPullRegistryUrl}/paketobuildpacks/builder:base"
        task.runImage = "${dockerPullRegistryUrl}/paketobuildpacks/run:base-cnb"

        task.docker { docker ->
            docker.builderRegistry { registry ->
                registry.url = dockerPullRegistryUrl
                registry.username = myExtension.dockerPullRegistryUsername.get()
                registry.password = myExtension.dockerPullRegistryPassword.get()

            docker.publishRegistry { registry ->
                registry.url = myExtension.dockerPushRegistryUrl.get()
                registry.username = myExtension.dockerPushRegistryUsername.get()
                registry.password = myExtension.dockerPushRegistryPassword.get()

This works fine in my plugin using version 2.7.16 as the dependency. Now I also want to support version 3.X. Unfortunately they broke compatibility in that version by changing the fields of DockerSpec.DockerRegistrySpec from String to Property<String>.

The only real solution, that comes to my mind is having multiple versions of the plugin supporting the two versions. Is there another one? Maybe even something to support both in the same plugin (version)?

Either by using reflection I guess, or maybe by having multiple implementations.
You could have one implementation for boot 2 and one implementation for boot 3.

In the plugin you then check the version of boot applied in org.springframework.boot.gradle.plugin.SpringBootPlugin.SPRING_BOOT_VERSION. According to this check, you then call into your boot 2 or your boot 3 class that does the actual logic accordingly.

The consumer should then not see anything about this, but it should just work.

I thought about that as well. But how do I make sure I have both versions of the plugin on my plugins classpath, so I can implement against both, the 2.x and 3.x implementation of the task? Is that even possible?

You could probably for example use feature variants for the different versions, but have the dependency as compileOnly so that they do not pull in the dependency actually and then depend on those feature variants from the main variant so that you have them both pulled in or something like that.