groovyjenkins-pipelinevariadic-functionsjenkins-groovygstring

Calling a variadic function in a Jenkinsfile fails unpredictably


Context

I'm running Jenkins on Windows, writing declarative pipelines. I'm trying to put multiple commands in a single bat step, while still making the step fail if any of the included commands fail.

Purpose of this is twofold.

Code

I wrote the following Groovy code in my Jenkinsfile:

def ExecuteMultipleCmdSteps(String... steps)
{
    bat ConcatenateMultipleCmdSteps(steps)
}

String ConcatenateMultipleCmdSteps(String... steps)
{
    String[] commands = []
    steps.each { commands +="echo ^> Now starting: ${it}"; commands += it; }
    return commands.join(" && ")
}

The problem/question

I can't get this to work reliably. That is, in a single Jenkinsfile, I can have multiple calls to ExecuteMultipleCmdSteps(), and some will work as intended, while others will fail with java.lang.NoSuchMethodError: No such DSL method 'ExecuteMultipleCmdSteps' found among steps [addBadge, ...

I have not yet found any pattern in the failures. I thought it only failed when executing from within a warnError block, but now I also have a problem from within a dir() block, while in a different Jenkinsfile, that works fine.

This problem seems to be related to ExecuteMultipleCmdSteps() being a variadic function. If I provide an overload with the correct number of arguments, then that overload is used without problem.

I'm at a loss here. Your input would be most welcome.

Failed solution

At some point I thought it might be a scoping/importing thing, so I enclosed ExecuteMultipleCmdSteps() in a class (code below) as suggested by this answer. Now, the method is called as Helpers.ExecuteMultipleCmdSteps(), and that results in a org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: No such static method found: staticMethod Helpers ExecuteMultipleCmdSteps org.codehaus.groovy.runtime.GStringImpl org.codehaus.groovy.runtime.GStringImpl

public class Helpers {
    public static environment

    public static void ExecuteMultipleCmdSteps(String... steps)
    {
        environment.bat ConcatenateMultipleCmdSteps(steps)
    }

    public static String ConcatenateMultipleCmdSteps(String... steps)
    {
        String[] commands = []
        steps.each { commands +="echo ^> Now starting: ${it}"; commands += it; }
        return commands.join(" && ")
    }

Minimal failing example

Consider the following:

hello = "Hello"

pipeline {
    agent any
    stages {
        stage("Stage") {
            steps {
                SillyEcho("Hello")
                SillyEcho("${hello}" as String)
                SillyEcho("${hello}")
            }
        }
    }
}

def SillyEcho(String... m)
{
    echo m.join(" ")
}

I'd expect all calls to SillyEcho() to result in Hello being echoed. In reality, the first two succeed, and the last one results in java.lang.NoSuchMethodError: No such DSL method 'SillyEcho' found among steps [addBadge, addErrorBadge,...

Curiously succeeding example

Consider the following groovy script, pretty much equivalent to the failing example above:

hello = "Hello"

SillyEcho("Hello")
SillyEcho("${hello}" as String)
SillyEcho("${hello}")

def SillyEcho(String... m)
{
    println m.join(" ")
}

When pasted into a Groovy Script console (for example the one provided by Jenkins), this succeeds (Hello is printed three times).

Even though I'd expect this example to succeed, I'd also expect it to behave consistently with the failing example, above, so I'm a bit torn on this one.


Solution

  • Thank you for adding the failing and succeeding examples. I expect your issues are due to the incompatibility of String and GString.

    With respect to the differences between running it as a pipeline job and running the script in the Jenkins Script Console, I assume based on this that the Jenkins Script Console is not as strict with type references or tries to cast parameters based upon the function signature. I base this assumption on this script, based upon your script:

    hello = "Hello"
    hello2 = "${hello}" as String
    hello3 = "${hello}"
    
    println hello.getClass()
    println hello2.getClass()
    println hello3.getClass()
    
    SillyEcho(hello)
    SillyEcho(hello2)
    SillyEcho(hello3)
    
    def SillyEcho(String... m)
    {
        println m.getClass()
    }
    

    This is the output I got in the Jenkins Script Console:

    class java.lang.String
    class java.lang.String
    class org.codehaus.groovy.runtime.GStringImpl
    class [Ljava.lang.String;
    class [Ljava.lang.String;
    class [Ljava.lang.String;
    

    I expect the pipeline doesn't cast the GString to String but just fails as there is no function with the Gstring as parameter.

    For debugging you could try to invoke .toString() an all elements you pass on to your function.

    Update

    This seems to be a known issue (or at least reported) with the pipeline interpreter: JENKINS-56758.

    In the ticket an extra work-around has been described using collections instead of varargs. This would omit the need to type-cast everything.