New Custom Plugin Feature of gradle-2.14 and plugin bundles

The new gradle-2.14 Release Notes contain a feature that I am happy to see: Easier publication and consumption of plugins to and from custom repositories.

However I do have a question.

In the case that I am eager to convert to this scheme, I have not a plugin, but a bundle of plugins. The ids of each plugin are defined inside a

pluginBundle {
   plugins {
     pluginA {
        id='pluginA'
        ...
     }
      ...
   }
}

block.

This block looks suspiciously similar to the

gradlePlugin {
    plugins {
        secret {
            id = 'com.mycompany.secret'
            implementationClass = 'org.mycompany.plugins.TopSecretPlugin'
        }
    }
}

block shown in the release notes.

So my question is:

Should the gradlePlugin block in 2.14 REPLACE the pluginBundle block or supplement it in the case of a project building a collection of plugins rather than a single plugin?

Also, does this mean that we can dispense with the mapping of plugins to classes that was up to now done in src/main/resources/META-INF/gradle-plugins?

Only if you do not intend to publish to the Gradle plugin portal. If you are publishing several plugins in a single JAR to a custom repository, yes, you will simply configure gradlePlugin { }.

Yes, the ‘java-gradle-plugin’ plugin generates this for you.

Just to be clear, so does that mean that publishing to the plugin portal requires a very different plugin and configuration compared to publishing to somewhere else?

Thanks, Mark, but two more questions:

  1. If I replace pluginBundle{} with gradlePlugin{} will my plugin collection jar remain usable with the old syntax by builds that are using gradle < 2.14?
  2. What server infrastructure, if any, is needed to serve the plugin from the local maven repository? IOW, what ties this URL to any particular maven repository on the server? It seems that you have to setup something like Nexus, JFrog, etc. to take advantage of this feature. Is that correct?

Yes. There should be no difference between the final jars. Nothing has changed with regards to plugin packaging.

Nope. There’s no difference between local and remote repositories.

Yes, there is a specific plugin for publishing to the plugin portal. This is to handle things like defining plugin metadata (description, tags, website, etc) and authentication.

Thanks, again, Mark.

But the Release notes specify a URL as the identifier for the repository on both the plugin side and the plugin user side.

Is it possible to replace this:

pluginRepositories {
    maven {
        url 'https://private.mycompany.com/m2'
    }
}

with

pluginRepositories {
    maven {
        mavenLocal()
    }
}

That is the only way I could see this working without a repo manager. Otherwise, what am I missing?

Correct, mavenLocal() is not available in this context, you’ll want to use a file:// url.

pluginRepositories {
    maven {
        url "file://${System.getProperty('user.home')/.m2/repository"
    }
}

But if I don’t want to use a file url, for instance, if I want to support all of these use cases:

  • user builds on the “build server”
  • jenkins builds on the same “build server”
  • local builds in Eclipse from user PCs

then I must have a repo manager, to take advantage of this plugin repository feature, right? A file URL might support the first two use cases but not the third.

This is somewhat hairy for us right now as we have a special version of a gradle plugin and a special version of an underlying dependency of that plugin. The latter is awaiting pull request acceptance, and once that happens we’ll need to do a pull request on the former and our corporate nexus does not allow us to deploy artifacts outside our group id in the meantime.

So a local repo manager might make some sense in this situation, but I’m not sure that it would be worth the effort, as these conditions would disappear as soon as the pull requests are accepted into official builds.

I’m not sure I understand. Are you just trying to test a locally built plugin? If so you’ll just need to configure whatever publishing mechanism you are using to publish to maven local (or some other local repo) and configure the plugin repositories to resolve plugins from that repo.

Let me try to explain:

No we’re not just trying to test a locally built plugin. We’re also trying to USE that plugin in our day-to-day development.
We have a (hopefully temporary) depth-3 dependency-from-hell situation:

locally-built-plugin which depends on
a forked version of gradle-ospackage-plugin. This fork is necessary to accommodate
a forked version of redline-rpm which it depends on.

The fork of redline-rpm awaits acceptance of a pull request.
Once it is accepted, the we can submit a pull request for a forked version of gradle-ospackage-plugin and once that is accepted,
everything should be in public repositories and usable conventionally.

At that time, it would be possible to deploy our local plugin to our corporate repository. But corporate repository regulations prohibit deployment of our forked versions because they have different group-ids.

I suppose we could temporarily repackage them so we could put them in our corporate repo.

Or we could go on the way we are currently going. But I don’t think this new gradle-2.14 solution will work for us in the interim unless we repackage our forks or get a local repository.

In your current setup, where is the plugin published, and how it is consumed by your build?

up to now we’ve deployed it in local repositories. Jenkins builds it and has its own local repo. Then we have one “local” repo for all user builds on that server. We do some file copying to get it into developers’ local repos on their pcs (or they can build the plugin themselves, but I am probably the only one doing that.

Honestly I don’t see how using the plugins { } syntax for this plugin (albeit still possible) would make your scenario any less of pain. Realistically, this should be built in CI and published to a single repo. Aside from removing the bureaucratic obstacles to make this happen some kind of custom Gradle distribution with the plugin bundled in it might be a another option, although this might also not work well if the plugin changes often.

The only option, a temporary hack (because who knows when the open-source pull requests will be incorporated into the mainline build) would be to repackage them with a groupId acceptable to our corporate repository’s rules, get them into this repo, and use that as our plugin repo.

I would recommend doing this if you can.

OK, I have gotten halfway down this path. I have packaged redline and gradle-os-plugin with a groupId acceptable to our corporate nexus rules and deployed them there.

Now onto the task of trying to adopt this new syntax, which would indeed be helpful. But here I hit a roadblock:

* What went wrong:
Plugin [id: 'nebula.rpm', version: '3.4.2'] was not found in any of the following sources:

- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- maven({our corporate repo url}) (Could not resolve plugin artifact 'nebula.rpm:nebula.rpm.gradle.plugin:3.4.2')
- Gradle Central Plugin Repository (plugin 'nebula.rpm' has no version '3.4.2' - see https://plugins.gradle.org/plugin/nebula.rpm for available versions)

I would only expect the second source to resolve. It contains our private version of gradle.ospackage.plugin, v 3.4.2 which provides the nebula.rpm plugin. But it fails. And so our custom plugin cannot build this way.

I also was intrigued by the error message that it was nebula.rpm:nebula.rpm.gradle.plugin:3.4.2 which could not be resolved. That seemed to indicate that it was looking for a plugin named nebula.rpm in a gradle plugin package called nebula.rpm v3.4.2, rather than in gradle-ospackage-plugin v3.4.2 , which is where it actually lives. So I tried specifying it in my plugin block as

id 'nebula.rpm:gradle-ospackage-plugin' version '3.4.2' 

rather than as

id 'nebula.rpm' version '3.4.2'

but the former is not legal syntax.

A look at the debug output shows that what the resolution process is searching for is:

10:55:31.833 [INFO] [org.gradle.internal.resource.transport.http.HttpClientHelper] Resource missing. [HTTP HEAD: {plugin repo url}/nebula/rpm/nebula.rpm.gradle.plugin/3.4.2/nebula.rpm.gradle.plugin-3.4.2.jar]

This is definitely wrong.

My suspicion had been that this might have something to do with the fact that gradle.ospackage.plugin is built with a gradle 2.10 wrapper so does not support the new plugin marker syntax. I had been hoping to minimize the changes I would have to do with gradle.os.package plugin (i.e. not use the new gradle 2.14 plugin repository concept there) as this is a temporary workaround.

But now I have to wonder about that. If gradle cannot find the jar containing the plugin, then the contents of that jar cannot be relevant.

Can the plugins block notation even be used to find a plugin that is located in a jar that contains many plugins?

And regardless to what the answer to the above might be, I have gone ahead with trying to modify gradle-ospackage to use 2.14. It hasn’t gotten far. I am now stymied by my implementationClass declarations not being found even though these classes are right where they should be. Not sure what I broke to get to that pass.

$ gw clean jar
Failed to notify ProjectEvaluationListener.afterEvaluate(), but primary configuration failure takes precedence.
java.lang.IllegalArgumentException: Missing implementationClass for ospackage

Please have a look at the documentation about plugin markers which explains how Gradle finds plugins in custom Maven repositories. You currently cannot mirror the plugin portal in a way that allows using the plugins block. You could build the plugins from source and deploy them using the new mechanism. In that case make sure you adjust the buildscript to use the java-gradle-plugin and maven-publish plugin as described in the documentation.