Lazy evaluation for extension attributes


(Michael Brand) #1

I have a somewhat sophisticated project where I’d like to allow extension properties to be defined as either a sting or a closure. When accessed I’d like any closures to be evaluated and their results returned. I’d also like for one extension attribute closure to be able to access the results from another closure.

I’ve worked up an example that describes this:

defaultTasks = ['myTask']
  class MyExtension {
 def a = "hello"
 def b = "world"
 def c = {a + " " + b}
 def d = {c.toUpperCase()}
 def e = {"goodbye"}
 def f
    def getProperty(String name) {
  def prop = super.getProperty(name)
  if (prop instanceof Closure) {
   prop = prop()
  }
  return prop
 }
}
  extensions.create('my', MyExtension)
  task myTask << {
 assert my.a == "hello"
 assert my.b == "world"
 assert my.c == "hello world"
  assert my.d == "HELLO WORLD"
 assert my.e == "goodbye"
}

When I run this I get the following results:

:myTask FAILED
  FAILURE: Build failed with an exception.
  * Where:
Build file '/private/var/folders/qw/0sn7s6xj41n10ccwn3p581pw0000gn/T/CodeRunner/Untitled 3.gradle' line: 25
  * What went wrong:
Execution failed for task ':myTask'.
> Assertion failed:
       assert my.c == "hello world"
         |
| |
         |
| false
         |
MyExtension$_closure1@779632eb
         MyExtension_Decorated@25e91fa3
    * Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
  BUILD FAILED
  Total time: 2.458 secs

In debugging it looks like MyExtension.setProperty() never gets called. I believe this is because the object is decorated, but I don’t now how to get around it. Does anyone know how to make this work?


(René Groeschke) #2

It’s indeed related to the class decoration. The class decoration adds its own getProperty implementation that does not delegate to the super getProperty method (your implementation in MyExtension). Not sure yet, if there is a workaround available for that.


(Michael Brand) #3

Is there a different way that I can implement lazy evaluation without hard-coding it into the build script?


(Peter Niederwieser) #4

Which bigger goal are you trying to achieve with the lazy evaluation?


(Meikel Brandmeyer) #5

@Peter

Assume a property which relates to another property in its default configuration. Silly example:

class MyExtension {

def prop = “${project.buildDir}/some-sub-dir”

}

vs.

class MyExtension {

def prop = {-> “${project.buildDir}/some-sub-dir” }

}

We don’t want to introduce order dependence of application of the plugin setting up the extension and the definition of the project’s ‘buildDir’. One way to solve this is making the property lazy. Alternatively I was told (by you, btw) to use gradle’s internal convention mapping infrastructure.

Is the convention mapping stuff public API? I burned myself several times with gradle’s internals. (My fault. I’m not complaining, but I don’t want to run in that again.)


(Michael Brand) #6

Meikel is addressing exactly the case I’m referring to. I have a “version” attribute in my extension that is used to define a WSDL URL and the name of a local copy of the WSDL. For example:

myExtension {
     version = {"${major}_${minor}_${build}"}
     url = {"http://mysite/wsdl/myservice-${version}.wsdl"}
     localWsdlFile = {file("build/wsdl/service_${version}.wsdl"}
}

I’ve used closures instead of GStrings because “major”, “minor”, and “build” are defined during the configuration stage.

The workaround I’ve used is to repeat the version definition three times in the build script. It works, but introduces maintenance issues.


(Peter Niederwieser) #7

You can either use convention mapping (which isn’t considered public), or one of the other techniques for deferring evaluation (‘gradle.projectsEvaluated’, ‘project.afterEvaluate’, using APIs that accept closures, etc.). There is also a new ‘DeferredConfigurable’ annotation, but it’s still incubating.


(Meikel Brandmeyer) #8

For now I went with a custom Delay-style class stolen from Scheme’s promise and Clojure’s delay. Upon retrieval non-delay objects are passed through, while delay objects contain a closure which is called to get the real value. (Sounds a bit like ‘DeferredConfigurable’ to me.)