How do I apply a plugin to a project from a (shared) applied .gradle file?


(charley) #1

So I have a number of source repositories that I want to point towards the same Artifactory repository, but I’m running into issues getting apply to work correctly:

A generic project build configutation (build.gradle) looks like this:

//someproject/build.gradle
apply from : '${build-tools-home}/tokbox.gradle'
//the rest of the project settings to follow

Whereas the shared config just keeps links to the Artifactory plugin and configures artifactory for correct publishing:

buildscript {
    repositories {
        maven {
            name "artifactory"
            delegate.url("http://repo.jfrog.org/artifactory/gradle-plugins")
        }
    }
    dependencies {
        classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:2.0.16'
    }
}
apply plugin: 'artifactory'
  project.ext.set("artifactory_user", "foo")
project.ext.set("artifactory_password", "bar")
project.ext.set("artifactory_contextUrl", "http://repo.example.com/artifactory")
  /// ...artifactory config closure follows

This (and a number of similar looking permutations) does not work when we run gradle build from the project directory. The above example gives “> Plugin with id ‘artifactory’ not found.”, and if we try to apply the buildscript closure on the project variable, I get “> You can’t change a configuration which is not in unresolved state!”

I fear I don’t understand the underlying meaning behind the apply from statement; I’m stumped. We don’t have the option of using a multi-project approach as these projects are maintained separately, but deploy to the same place. Is what I want to do possible? Has anyone run into trouble with something similar?

Thanks so much.


(Peter Niederwieser) #2

It’s a known problem. To apply a third-party plugin from an external build script, you have to use the plugin’s fully qualified class name, rather than its ID:

apply plugin: org.jfrog.gradle.plugin.artifactory.ArtifactoryPlugin


(charley) #3

I noticed that full classname fix on a separate thread while digging on this issue, but I still am unable to resolve the class:

> Plugin with id 'org.jfrog.gradle.plugin.artifactory.ArtifactoryPlugin' not found.
  //underlying exception with --debug : org.gradle.api.plugins.UnknownPluginException

Is there a convention for how the dependency classpath search is modified? If I move the buildscript closure (at the top of the shared script) into the main build script, the shared script can import the plugin just fine.

EDIT: fulltext gist: https://gist.github.com/wobbals/ad82dcd41df96c7b07c7


(Peter Niederwieser) #4

You have to omit the quotes. It’s a class name, not a string.


(charley) #5

Haha, brutal. Thanks Peter.


(Jesper Thuun-Petersen) #6

Charley, were you able to get this to work? I’m struggling with a very similar solution, but when I omit the quotes in the apply and use the fully qualified classname for my plugin:

apply plugin: com.stibo.gradle.plugin.stibodefaults.StiboDefaults

I get this error: Could not find property ‘com’ on project ‘:components-mapper’.


Applying plugins from external script
(Peter Niederwieser) #7

If you get this error, it’s very likely that you didn’t put the right Jar on the build script class path.


(Jesper Thuun-Petersen) #8

This isn’t the case. If I copy the buildscript block from the external script and paste this into my build.gradle, everything works fine. I’d really like to avoid to duplicate this code across all my 160+ projects.


(Peter Niederwieser) #9

160+ builds, or one build with 160+ projects? Bringing in a ‘buildscript’ block with ‘apply from:’ is technically impossible (it’s a catch-22), but there are other solutions.


(Jesper Thuun-Petersen) #10

It’s essentially 160 different projects, as they are build, versioned and deployed individually. The are inter-dependent, but they usually depend on specific versions, fetched from a nexus repository. Sometimes though, we create a multi module setup, where these dependencies are resolved as project dependencies.

Because of the individual builds, we have refactored our root build.gradle into plugins, and we apply these plugins like described. I would very much like not to have a buildscript block copy/pasted into each and every (sub)project, therefore the need for something like the above.


(Peter Niederwieser) #11

I’ve looked into this for a long time now, and I can’t find a solution other than the official one, which is to put a ‘buildscript’ block into each script plugin that requires external dependencies. For build scripts, it suffices to declare (shared) dependencies in the ‘buildscript’ block of the root build script, although I’ve been told it’s not an official feature.

PS: A script plugin is a build script applied with ‘apply from:’.


(Peter Niederwieser) #12

Eventually I found a solution for 1.7. By putting the following into your ‘init.gradle’, you can share dependencies across all scripts. ‘init.gradle’ goes into Gradle user home or can be packaged along with a custom Gradle distribution (‘init.d/init.gradle’).

initscript {
  repositories {
    mavenCentral()
  }
    dependencies {
    classpath "junit:junit:4.11" // whatever dependencies you want to share
  }
}
  def registry = gradle.services.get(org.gradle.invocation.BuildClassLoaderRegistry)
registry.addRootClassLoader(new URLClassLoader(initscript.configurations.classpath.files*.toURI()*.toURL() as URL[]))

(Jesper Thuun-Petersen) #13

Thanks for your effort, although I’m afraid this solution isn’t good enough for my use case.

As you can see in the other issue I posted today, http://forums.gradle.org/gradle/topics/applying_plugin_dependencies_from_external_gradle_file, I need to version the dependency from my build file to the boilerplate.gradle file. With your solution, the version and the contents of the boilerplate code will be the same across all versions and branches of my bundles. This is also why I find it insufficient to put it in the top level build file (which is almost empty in my case).

What I’m looking for is an ‘apply from’ that actually just includes the contents of an external file in the place it’s invoked.


(Peter Niederwieser) #14

What I’m looking for is an ‘apply from’ that actually just includes the contents of an external file in the place it’s invoked.

It does, except for the ‘buildscript’ block, which is very special. (It has to do with Groovy being a compiled language.) I don’t understand your situation in detail (branches, bundles, etc.), but I think that your best chance is one of the following:

  • Custom Gradle distribution with ‘init.gradle’ and Gradle Wrapper. This can be versioned nicely. Compared to ‘apply from: ‘http://…’’, it doesn’t require users to be online all the time, and doesn’t download a script every time. Only limitation is that everything that’s built together will have to use the same “'boilerplate” version. * Bring in ‘buildscript’ contents and normal script parts separately. To bring in the former, you can do:
buildscript {
  apply from: 'http://buildstrap.stibo.dk/boilerplate/boilerplate-buildscript-1.0.0.gradle', to: buildscript
}

The referenced script needs to contain the contents of the ‘buildscript’ block.

Hope this helps. Good luck!


(Pablo Munoz) #15

org.gradle.invocation.BuildClassLoaderRegistry has been removed in 1.12 what should we use instead?