Error java.lang.NoSuchMethodError: No such DSL method '***' found among steps

schglurps :

I'm a beginner with Jenkins and Groovy. I'm working on a pipeline library.

One file (version.groovy) is defined as follow:

def dateInternal = { new Date().format('yy.Mdd.Hmm') }.memoize()

def date() {
   dateInternal()
}

In another file I call version.date().

When I do this I encounter the following error:

java.lang.NoSuchMethodError: No such DSL method 'dateInternal' found among steps [ansiColor, archive, bat, ...

This is probably a noob question, but I didn't find a way to resolve this until now...

Szymon Stepniak :

There are two things you have to be aware of. When you define a method in Groovy script (like your date() method) it gets compiled as a class level method (each Groovy script compiles to a class that extends groovy.lang.Script class). Variables on the other hand (like your dateInternal which is a variable holding a closure) get compiled as a local variables existing inside run() method. So when we follow the code you have written we will find out that when you call date() method it tries to call a closure stored in a dateInternal variable and this variable exists only inside run() method.

If you decompile compiled version.groovy script you will see something like this:

import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.Script;
import java.util.Date;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class version extends Script {
    public version() {
        CallSite[] var1 = $getCallSiteArray();
        super();
    }

    public version(Binding context) {
        CallSite[] var2 = $getCallSiteArray();
        super(context);
    }

    public static void main(String... args) {
        CallSite[] var1 = $getCallSiteArray();
        var1[0].call(InvokerHelper.class, version.class, args);
    }

    public Object run() {
        CallSite[] var1 = $getCallSiteArray();
        final class _run_closure1 extends Closure implements GeneratedClosure {
            public _run_closure1(Object _thisObject) {
                CallSite[] var3 = $getCallSiteArray();
                super(version.this, _thisObject);
            }

            public Object doCall(Object it) {
                CallSite[] var2 = $getCallSiteArray();
                return var2[0].call(var2[1].callConstructor(Date.class), "yy.Mdd.Hmm");
            }

            public Object doCall() {
                CallSite[] var1 = $getCallSiteArray();
                return this.doCall((Object)null);
            }
        }

        Object dateInternal = var1[1].call(new _run_closure1(this));
        return dateInternal;
    }

    public Object date() {
        CallSite[] var1 = $getCallSiteArray();
        return var1[2].callCurrent(this);
    }
}

Solution

You can solve it by promoting dateInternal to be a class level field instead of a local variable. You can to this with groovy.transform.Field annotation:

import groovy.transform.Field

@Field
def dateInternal = { new Date().format('yy.Mdd.Hmm') }.memoize()

def date() {
    dateInternal()
}

Now when you take a look at decompiled version of compiled version.groovy script you will see something like this:

import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.Script;
import java.util.Date;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class version extends Script {
    Object dateInternal;

    public version() {
        CallSite[] var1 = $getCallSiteArray();
        super();
        Object var2 = var1[0].call(new version._closure1(this));
        this.dateInternal = var2;
    }

    public version(Binding context) {
        CallSite[] var2 = $getCallSiteArray();
        super(context);
        Object var3 = var2[1].call(new version._closure1(this));
        this.dateInternal = var3;
    }

    public static void main(String... args) {
        CallSite[] var1 = $getCallSiteArray();
        var1[2].call(InvokerHelper.class, version.class, args);
    }

    public Object run() {
        CallSite[] var1 = $getCallSiteArray();
        return null;
    }

    public Object date() {
        CallSite[] var1 = $getCallSiteArray();
        return ScriptBytecodeAdapter.invokeClosure(this.dateInternal, new Object[0]);
    }

    public final class _closure1 extends Closure implements GeneratedClosure {
        public _closure1(Object _thisObject) {
            CallSite[] var3 = $getCallSiteArray();
            super(version.this, _thisObject);
        }

        public Object doCall(Object it) {
            CallSite[] var2 = $getCallSiteArray();
            return var2[0].call(var2[1].callConstructor(Date.class), "yy.Mdd.Hmm");
        }

        public Object doCall() {
            CallSite[] var1 = $getCallSiteArray();
            return this.doCall((Object)null);
        }
    }
}

You can see that dateInternal became a class level field and date() method can simply access it.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=78149&siteId=1