1

This is similar to Need Groovy syntax help for generating a Closure from a String but a little more complicated:

I'm working with complex DSL that I do not control (Jenkins Job DSL, specifically, but I'm using hypothetical DSL for clarity) and I've broken up my code into returned closures. All closures that don't start with "my" in this code are hypothetical DSL:

//scriptContext is a reference to the original script as this is a util class
static def myMethod(def scriptContext) {
    //everything here comes from some hypothetical DSL
    scriptContext.diningTable() {
        ....
        fruits myCreateFruitsClosure()
        ....
    }
}    
static def myCreateFruitsClosure() {
   return {
       apple('Gala') {
           seed("brown")
           shape {
              funky()
              dimpled()
           }
       }
   }
}

The above works fine.

Now let's say that I want the particulars of apple to be specified dynamically and I load some groovy code from somewhere (say, XML).

Now I have:

// I pass in the script context for the binding
static def myCreateFruitsClosure(def scriptContext) {
   return {
       apple('Gala') {
           // This comes from somewhere in real life:
           def code = """seed("blue"); shape { oval() ; cardioid() }"""
           def evalWrapper = new GroovyShell(context.binding).evaluate(' { -> ' + code + '}')                      
           evalWrapper()
       }
   }
}

And I get "No signature of method: Script1.seed()" Obviously, the seed() method does exist when not using eval since it worked before, so my context/scope are wrong.

I've tried:

  • All sorts of combinations of Eval.me() and Script.evaluate()
  • To create methods and run them both in and out of evaluate()
  • To move the contents of "createFruitsClosure()" directly into "myMethod()"

All of these yield slightly different errors. What's the right way?

Community
  • 1
  • 1
Akom
  • 1,484
  • 19
  • 25

1 Answers1

2

Ultimately I got this working by:

  1. Switching to open code blocks instead of closures
  2. Using an explicit reference to "owner" from the dynamic code, and making that available via the Binding. In this example, "self", which is owner, is the apple closure.

Here is the result:

static def myCreateFruitsClosure() {
    return {
        apple {
            def code = """
                L:{
                   self.seed("blue")
                   self.shape {
                       oval()
                       cardioid()
                   }
                }"""  
            // sometimes "self: delegate" is appropriate instead of this:
            new GroovyShell(new Binding([self: owner])).evaluate(code)
        }
    }
}

I guess that's simpler than I was trying to make it. Note the "L:" to force evaluate() to treat the code as an open code block and not a closure.

If anyone has a better way, let me know, but this works well enough.

Akom
  • 1,484
  • 19
  • 25
  • Oh my. I have spent quite a few hours trying to figure this one out and thought maybe I was just going crazy. Thank you for the solution - it has really helped! – Kat May 02 '18 at 16:18