Skip to content

Instantly share code, notes, and snippets.

@johnrengelman
Created April 8, 2015 13:57
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save johnrengelman/9a20697b2246a9bfaca2 to your computer and use it in GitHub Desktop.
Save johnrengelman/9a20697b2246a9bfaca2 to your computer and use it in GitHub Desktop.
Self applying Gradle plugin project
//place this content in your buildSrc/build.gradle file
//Then apply your plugin to its own build in build.gradle
import org.codehaus.groovy.control.CompilerConfiguration
apply plugin: 'groovy'
repositories {
jcenter()
}
// IntelliJ doesn't let you set the source path for a module outside of its directory.
// This logic basically only performs this configuration when not being resolved in IntelliJ
if (!project.plugins.collect { it.class.name}.any { it.endsWith('JetGradlePlugin')}) {
//Set the source directories for buildSrc to contain the source from the main project
sourceSets {
main {
java.srcDirs = ['src/main/java', '../src/main/java']
groovy.srcDirs = ['src/main/groovy', '../src/main/groovy']
resources.srcDirs = ['src/main/resources', '../src/main/resources']
}
}
}
// Prepare to have your mind blown
// This section evaluates the `build.gradle` from the root diretory and
// then grabs the dependencies block from it and evaluates it on this project.
// So in effect, we are building the main project with itself.
// BOOM
ScriptHolder holder = new ScriptHolder()
CompilerConfiguration cc = new CompilerConfiguration()
cc.setScriptBaseClass(DelegatingScript.class.name)
GroovyShell sh = new GroovyShell(Project.class.classLoader, new Binding(), cc)
//The build file for the main project
File projectBuildFile = file('../build.gradle')
//Use this parse command because Groovy wants to use the file name as the classname
//which fails if your Gradle build file has been renamed to contain an invalid character (i.e. '-')
DelegatingScript script = (DelegatingScript)sh.parse(projectBuildFile.text, 'GradlePlugins')
script.setDelegate(holder)
//Resolve the project main Gradle file against our ScriptHolder
script.run()
//Class for holding the evaluation of a Gradle script
//You may need to add some extra methods here depending on what you have all placed in build.gradle
class ScriptHolder {
Closure dependencies
void dependencies(Closure c) {
this.dependencies = c
}
void apply(Map map) {
}
}
//Grab the dependencies closure and resolve it against
//the buildSrc project dependencies
//This effectively applies the same dependenices from build.gradle into buildSrc/build.gradle
//This is required so that when buildSrc is compiled it has the dependencies to compile the source code
def closure = holder.dependencies.clone()
closure.delegate = project.dependencies
closure()
@ysb33r
Copy link

ysb33r commented Apr 14, 2015

This is a pretty fancy example.

Have you considered a simpler approach such as below

apply plugin: new GroovyScriptEngine(
        [file('src/main/groovy').absolutePath, file('src/main/resources').absolutePath]. 
            toArray(new String[2]), 
        this.class.classLoader
).loadScriptByName('org/ysb33r/gradle/bintray/BintrayPublishPlugin.groovy')

or is that not sufficient for your needs?

@liviutudor
Copy link

Worked like a charm for me, thank you!

@grv87
Copy link

grv87 commented Nov 1, 2017

@ysb33r, your approach has two drawbacks:

  1. It doesn't apply plugin dependencies to your build script. You need to combine both approaches to get it done, or provide dependencies to build script separately. @johnrengelman's approach doesn't apply plugin, it only copies dependencies, so both are required.
  2. Recent versions of Gradle say that script passed to loadScriptByName shouldn't contain a package statement.

I looked for a solution and found out that much nicer and cleaner way is to compile plugin in buildSrc and then apply its class to the main project. It is in the Gradle documentation.

@CliveEvans
Copy link

@grv87 unless I'm missing something, you can't use buildSrc if you're also publishing your plugin. The method used by @ysb33r works, you just need to remember to add any dependencies to your buildscript dependencies (as classpath dependencies).

The script from @johnrengelman, OTOH, just didn't work for me. And it was far from clear why ...

@wheelerlaw
Copy link

wheelerlaw commented Jun 5, 2018

@CliveEvans I also couldn't get @johnrengelman script to work for me, although its likely because of the version of Gradle I am running. This was my error:

No signature of method: GradlePlugins.buildscript() is applicable for argument types: (GradlePlugins$_run_closure1) values: [GradlePlugins$_run_closure1@2e7cad0f]

@hakanai
Copy link

hakanai commented Feb 26, 2019

@wheelerlaw hard to know without the stack trace, but did you have buildscript defined in ScriptHolder?

@ncimino
Copy link

ncimino commented Oct 26, 2021

I'm seeing this error while trying to upgrade Gradle 6.8 to 7.2 (and JUnit 4 to 5, etc.):

No signature of method: GradlePlugins.test() is applicable for argument types: (GradlePlugins$_run_closure5) values: [GradlePlugins$_run_closure5@4a6f4fd5]
  Possible solutions: wait(), getAt(java.lang.String), use([Ljava.lang.Object;), wait(long), tap(groovy.lang.Closure), is(java.lang.Object)

This error is related to adding this code:

test {
    useJUnitPlatform()
}

Any ideas on how to fix this?

@ncimino
Copy link

ncimino commented Oct 26, 2021

I found it - I had to add:

class ScriptHolder {
    ...see gist...

    Closure test
    void test(Closure c) {
        this.test = c
    }
}

def test = holder.test.clone()
test.delegate = project.test
test()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment