Running Groovy Closure in a Configuration

In Ant, it’s easy to run a specific blurb of Groovy code in a custom classpath, e.g.

<groovy>
  <classpath>
    <pathelement path="guava-11.0.1.jar"/>
  </classpath>
  println Joiner.on(',').join(['A','B'])
</groovy>

I’m glossing over the details of the classpath here, but the point is that I can inline domain specific Groovy code directly in my build file, with it’s own classpath. How do I do the same in Gradle? It’s trivial to create a configuration, and get it resolved. But how would I run a specific closure in a class loader based on this configuration? I could write a plugin or build up an AntBuilder, but it seems like this is something that could be done natively on a Configuration. I’d like to be able to something like this in Gradle:

configuration {
    deployment
}
dependencies {
    deployment 'netflix:odin:1+' // Orchestration library
}
task(deploy) << {
    deployment.execute {
        new Odin('us-west-1').deployAmi('ami-12345678')
    }
}

I could also pollute the buildscript scope with these dependencies, but then it removes my ability to isolate and customize the classpath for a specific task. Another use case is for testing with different versions, which isn’t possible when using buildscript:

configuration {
    V1, V2
}
dependencies {
    V1 'netflix:client:1+' // Version 1 of client api
    V2 'netflix:client:2+' // Version 2 of client api
}
  def clientCall = new Closure() {
    import app.client.*;
    def client = new Client();
    client.call();
}
task(legacyClient) << {
    V1.execute clientCall
}
task(modernClient) << {
    V2.execute clientCall
}

I can see if this was in place, most of my reasons for using buildSrc would go away. Since I could compile my code into one configuration, then use that code right away.

Doing this as code would be pretty tricky. We’d have to use some pretty complicated compile transforms and detecting what to transform would be tricky. However, executing the code as a string of script is reasonably straightforward. Here’s a quick (working) sketch of how a plugin for this might look:

import org.codehaus.groovy.ast.*
import org.codehaus.groovy.classgen.*
import org.codehaus.groovy.control.*
import org.codehaus.groovy.control.customizers.*
import groovy.transform.InheritConstructors
  class InlineScriptPlugin implements Plugin<Project> {
 void apply(Project project) {
  project.extensions.create("inlineScript", InlineScriptEngine, project)
 }
}
  abstract class DelegatingScript extends groovy.lang.Script {
  private final Project project
    DelegatingScript(Project project) {
 this.project = project
  }
     def getProperty(String property) {
    project."$property"
    }
    void setProperty(String property, newValue) {
 project."$property" = newValue
  }
    def invokeMethod(String name, args) {
 project."$name"(*args)
  }
  }
  class InlineScriptEngine {
  final Project project
    InlineScriptEngine(Project project) {
  this.project = project
 }
    def run(Iterable<File> classpath, String script) {
  def compilerConfiguration = new CompilerConfiguration()
  compilerConfiguration.setScriptBaseClass(DelegatingScript.name)
  def scriptClassLoader = new GroovyClassLoader(this.class.classLoader, compilerConfiguration)
  compilerConfiguration.addCompilationCustomizers(new CompilationCustomizer(CompilePhase.CONVERSION) {
       public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
         classNode.addAnnotation(new AnnotationNode(new ClassNode(InheritConstructors.class)));
       }
     });
  classpath.each { scriptClassLoader.addClasspath(it.absolutePath) }
  scriptClassLoader.parseClass(script, "InlineScript").newInstance(project).run()
 }
}
  apply plugin: InlineScriptPlugin
  repositories {
 mavenCentral()
}
  configurations {
  guava
}
  dependencies {
 guava 'com.google.guava:guava:11.0'
}
  task joinStuff << {
 def joined = inlineScript.run(configurations.guava, """
  import com.google.common.base.Joiner
  Joiner.on(',').join(['A', 'B', name])
 """)
    assert joined == "A,B,${project.name}"
}