简体   繁体   中英

Groovy: how to call closure in top scope from another closure

I'm trying to break up code that makes use of the Jenkins Job DSL plugin into reusable pieces, and I suspect that my question is generic to Groovy and not Jenkins-specific. For example, I want to reuse parts of this block:

freeStyleJob() {
    //generic stuff
    name "something"
    description "something else"

    //custom stuff
    scm {
       svn {
           //etc....
       }
    }
}

By placing name and description in a utility method (obviously I want to do more than just that in real life). However, I cannot find the proper syntax to create a closure for the current scope. Here is how I think it should look:

def jobCommonItems() {
    return {
        //generic stuff
        name "something"
        description "something else"
    }
}


freeStyleJob() {
    jobCommonItems().call()

    //custom stuff
    scm {
       svn {
           //etc....
       }
    }
}

(Perhaps with a closure.delegate = this somewhere)

However, that's not working for closures. It is working for methods, as illustrated here: https://dzone.com/articles/groovy-closures-owner-delegate

To illustrate, here is a test that shows three combinations of possible syntax:

String myString = "Top Level: string"
def myMethod() {
    println "Top Level: Method"
}
def myClosure = { println "Top Level: Class"}

class MyClass1 {
    String myString = "Class1: String"
    def myMethod() {
        println "Class1: Method"
    }
    def myClosure = { println "Class1: Closure"}
}

class MyClass2 {
    String myString = "Class2: String"
    def myMethod() {
        println "Class2: Method"
    }
    def myClosure = { println "Class2: Closure"}
}

class MyClass {
    def closure = {
        println "In-Class generated closure begins, delegate is ${delegate}"
        myMethod()
        myClosure()
        println myString
    }
}

def closure = new MyClass().closure
closure.delegate = new MyClass1()
closure()

closure = new MyClass().closure
closure.delegate = new MyClass2()
closure()

// This fails - it can find the top level method, but not closure or string
closure.delegate = this
closure()



def methodMissing(String methodName, args) {
    println "Method not found in class ${this} by name ${methodName}"
}

I get an error that the closure is not in the main class (ie test for test.groovy): Method not found in class test@60611244 by name myClosure

I've tried changing delegate to "this", I tried changing the lookup strategy, etc. I'm probably missing something fundamental.

The job factory methods like freeStyleJob return an object that can be used to apply more configuration using the with method. That method expects a closure argument which has the same properties as the closure passed to the freeStyleJob method.

def basicConfiguration() {
    return {
        description('foo')
        scm {
            // whatever
        }
    }
}

def myJob = freeStyleJob('example') {
    publishers {
        // more config
    }
}
myJob.with basicConfiguration()

The script itself is an instance of DslFactory which is the interface containing eg the freeStyleJob method. You can pass that object to classes or methods to use the freeStyleJob .

def myJobFactory(def dslFactory, def jobName) {
    dslFactory.freeStyleJob(jobName) {
        description('foo')
    }
}

def myJob = myJobFactory(this, 'example')

And then you can use the myJob object to apply further configuration using with .

Seems that one solution is to invert the relationship like this, and to pass "this" as the context to find DSL top-level closures.

class Utils {
    static def makeMeABasicJob(def context) {
        context.freeStyleJob() {
            //generic stuff
            name "something"
            description "something else"
        }

    }
}

def job1 = Utils.makeMeABasicJob(this) //Passing the groovy file class as the resolution context
job1.with({
    //custom stuff
    scm {
        svn {
            //etc....
        }
    }
})

How about something like this?

def jobCommonItems(job) {
    job.name = "something"
    job.description = "something else"
}

freeStyleJob() {
    jobCommonItems(this)

    //custom stuff
    scm {
       svn {
           //etc....
       }
    }
}

When trying to extract out common logic from my Jenkins DSL groovy script, I originally used the style of passing the job object to a common static helper, and then doing job.with{...} similar to daspilker solution.

However I found using delegates a little more straight forward. For example:

freeStyleJob('name') {
    jobDesc(delegate, 'something')
    jobSCM(delegate, 'git@gitlab.com:MyGroup/MyProject.git', 'master')
}

def jobDesc(def context, def descText) {
    context.description(descText)
}

def jobSCM(def context, def repoURL, def branchName)
{
    context.scm {
        git {
                remote {
                    url(repoURL)
                }
                branch('refs/heads/' + branchName)
        }   
    }
}

The jobDesc and jobSCM method could be moved to a separate utility class as static helpers, which you would then import into your DSL groovy script.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM