Can plugins{} DSL be used with plugins that have dependencies on other plugins?

Continuing the discussion from Can plugins{} DSL be used to locate custom plugins packaged in a jar of many plugins?:

After being set straight on the issue above, I made some good progress, but have come across a new stumbling block : dependencies:

My typical project that uses my plugin collection has one or more subprojects each or which usually applies one of the plugins in my collection. But the problem here is that my plugin collection itself applies other upstream plugins such as gradle-ospackage and org.hidetake.ssh. In the old scheme I can put a classpath dependency in the buildscript block of the root project for my plugin container. That no longer works because you’re not allowed to put in a plugins{} declaration a plugin that is already on the classpath. An error message tells me to use apply. It seems the buildscript{} block is not really compatible with the new plugins{} system.

But neither is it okay to put a dependencies{} block in the root project outside of a buildscript{} block. So in order to avoid those errors I must put my dependencies{} block in each subproject in order for the transitive dependencies of my plugin collection to be handled correctly. This is making things more complicated. not less complicated.

Is there some way to declare that every subproject in the build depends on my plugin package (and its transitive dependencies)? I tried putting the dependencies{} block inside a subprojects{} block and that also fails. Specifically, code following the example of Users’ Guide Example 24.6.3:

subprojects {
    repositories {
        ...my repos
    }
    dependencies {
        compile "my:plugincontainer:1.0.12"
    }
}

fails with this error message:

A problem occurred evaluating root project ‘{myRootProj}’
Could not find method compile() for arguments [my:plugincontainer:1.0.12] on object of type org.gradle.api.internal.artifacts.dls.dependencies.DefaultDependencyHandler.

Why?

Hmmm. Sort of need to break this question into a couple of parts.

Part 1: How can I apply plugins to subprojects using the new plugins {} syntax?

You cannot … until the next 3.0-M2 release (probably tomorrow or the next day.) This is one of the features we don’t yet support in the 2.14 release.

Part 2: Why is the example above giving me such a strange error?

It’s hard to be sure without a more complete example. This error might occur if your subproject does not have a configuration called “compile.” Is it possible that you haven’t applied a plugin which would add such a configuration to the project? Notice, in the example you linked to, the ‘war’ plugin is applied before the dependencies block is evaluated. The ‘war’ plugin (via the java plugin) adds a ‘complie’ configuration to the project.

Wait, I also missed what you were saying earlier. You no longer need to explicitly put any of the transitive dependencies of your “plugins collection” on the classpath. When the plugin resolved in the plugins {} block, it will automatically add the transitive dependencies to the build script classpath for the root project in addition to applying the plugin to the root project. So, when you say:

plugins {
  id "org.awesome.myplugin" version "1.0.12"
}

Gradle will find the plugin marker artifact, and add all of its transitive dependencies to the build script’s classpath.

Thanks. The ‘war’ thing was just copied from the User’s Guide. It’s not actually used in my use case. In fact, this particular use case is not doing any compiling at all. So, good call! The only reason I need dependencies at all is to get the transitive dependencies of my plugin into play - at the plugin’s runtime. Alas, though, switching ‘compile’ to ‘runtime’ doesn’t help - as I might have expected, since that’s not what a runtime dependency is. Is there a dependency classification that does what I want? Remember, this is to replace a “classpath” dependency in a buildscript block.

OK, this is exactly what isn’t happening. My.awesome.plugin lists in its plugins{} block my private version of gradle-os-package plugin (which is deployed in the our private plugin repo) and org.hidetake.ssh which is deployed in the gradlePluginPortal(), both of which are are listed in the pluginRepositories{} block of settings.gradle for my awesome plugin

When building a project that lists my.awesome.plugin in its plugins{} block, it finds that plugin in our private plugin repo, but then fails when trying to resolve org.hidetake.ssh. The error messages tell me that it never looks in the gradlePluginPortal() for this plugin but only in our private plugin repo.

Sure seems like something’s broken. I would expect every transitive plugin dependency to be searched in all the pluginRepositories{} listed in settings.gradle. Instead, it seems to expect every transitive plugin dependency to be found in the same repo as its dependent.

Oops.

I think this is a legitimate oversight in our implementation. I’m filing a bug about it now, so we can get a proper fix in place. But, here’s a workaround you can use that should get you unblocked.

If your pluginsRepositories block currently looks like this:

pluginsRepositories {
    maven {
        url 'http://stevescompany.com/m2'
    }
   gradlePluginsPortal()
}

Make it look like this:

pluginsRepositories {
    maven {
        url 'http://stevescompany.com/m2'
    }
    gradlePluginsPortal()
    maven {
        url 'http://plugins.gradle.org/m2'
    }
}

I am fairly confident that will cause us to consult the plugin repository’s maven repo for the dependencies you are missing. Of course, you shouldn’t have to “repeat yourself.” That’s why I’m opening the bug.

Thanks for reporting this, and please let us know if the workaround doesn’t fix things.

Thank you for taking quick action. This fix does fix that problem, but I now notice some other things that are related, all of which happen in initial configuration phase, which this problem masked. I noticed in the apply() method of the base class of all my awesome plugins, this line of code:

    `project.plugins.apply('maven-publish')`

since I already had

plugins {
    id "maven-publish"
}

in build.gradle

I figured that applying this was unnecessary. Not so.

I get this error when evaluation project now:

Failed to apply plugin [id ‘my.awesome.plugin’]
Could not find method publishing() for arguments [my.awesome.plugins.baseclass$_apply_closure2@16ae8e54] on project ‘:myproj’ of type org.gradle.api.Project.

And indeed, there exists a publishing{} block in my.awesome.plugin’s build.gradle:

def u = hasProperty(“maven.central.u”) ? getProperty(“maven.central.u”): “undefined”
def p = hasProperty(“maven.central.c”) ? getProperty(“maven.central.c”): “undefined”

publishing {
	repositories {
		maven {
			credentials {
				username u
				password p
			}
			// change to point to your repo, e.g. http://my.org/repo
			url "http://my.private.plugin.url"
		}
	}

}

The workaround of putting the apply back does make this error go away, only to reveal another error. but it shouldn’t be necessary.
I have some code that tries to work with the clean task:

/**
 * The purpose of this class is to provide support for our
 * convention of copying all the build products into the top level directory.
 * Without this, the clean task would not clean these build products.
  */
class RootDirClean {
    static Delete create(Project project, String archive) {
        Delete rootDirCleanTask = project.tasks.create("rootDirClean", Delete.class)
        rootDirCleanTask.setDelete(archive)
        project.tasks.getByName('clean').each {
            it.dependsOn(rootDirCleanTask)
        }
        rootDirCleanTask
    }
}

Gradle now complains that my subproject doesn’t contain a task named ‘clean’. Arrgggh! This was not previously a problem. Even after I added “base” to my plugins block.

It does seem that the plugins{} block doesn’t do all the things that apply plugin does. This is going to be a painful change for many users, I fear. It seems “apply” covered up a multitude of “sins”. Just do it, it works, it’s magic, and this will not be so with the new model.

If your plugin expects another plugin to be applied, it needs to apply it. That’s unrelated to the plugins block and has always been that way. The apply syntax does not have any “magic” in that regard.

Thanks, Stefan!

That’s clear and concise, and ought to be prominently displayed, so I bolded it. I had been operating under the misconception that apply was “old thinking” and the “new way” meant “out with the old”.