Q:

Pipeline Groovy error with custom step and libary

I have the following issue with my Jenkins 2.0 pipeline library and custom step
My Jenkinsfile from repo X contains:
@Library('acme-pipelines@dev')

import acme.jenkins.*

node {
    runGradle {
        version = '3.3'
        cmd = '--version'
    }
}

In repo Y (the pipeline library repo) i have vars/runGradle.groovy containing:
import acme.jenkins.*

def call(body) {
    def config = [:]
    def utils = new Utils()

    body.resolveStrategy = Closure.DELEGATE_FIRST
    body.delegate = config
    body()

    sh "${utils.getGradleBinPath(config.version)} ${config.cmd}"
}

Also in repo Y i have src/acme/jenkins/Utils.groovy containing:
package acme.jenkins

static String getGradleBinPath(String version = null) {
    if (!version) {
        throw new IllegalArgumentException('No Gradle version supplied')
    }
    tool(version).concat('/bin/gradle')
}

When i run the pipeline i get the following error:
Running on master in /var/lib/jenkins/workspace/Jenkins Library Development
[Pipeline] {
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: static softvision.jenkins.Utils.tool() is applicable for argument types: (java.lang.String) values: [3.3]
Possible solutions: wait(), run(), run(), find(), grep(), any()
    at groovy.lang.MetaClassImpl.invokeStaticMissingMethod(MetaClassImpl.java:1506)
    at groovy.lang.MetaClassImpl.invokeStaticMethod(MetaClassImpl.java:1492)
    at org.codehaus.groovy.runtime.callsite.StaticMetaClassSite.call(StaticMetaClassSite.java:53)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
    at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.methodCall(DefaultInvoker.java:18)
    at softvision.jenkins.Utils.getGradleBinPath(file:/var/lib/jenkins/jobs/Jenkins%20Library%20Development/builds/89/libs/softvision-pipelines/src/softvision/jenkins/Utils.groovy:7)
    at runGradle.call(/var/lib/jenkins/jobs/Jenkins Library Development/builds/89/libs/softvision-pipelines/vars/runGradle.groovy:11)
    at WorkflowScript.run(WorkflowScript:6)
    at ___cps.transform___(Native Method)
    at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:57)
    at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:109)
    at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixArg(FunctionCallBlock.java:82)
    at sun.reflect.GeneratedMethodAccessor273.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
    at com.cloudbees.groovy.cps.impl.LocalVariableBlock$LocalVariable.get(LocalVariableBlock.java:39)
    at com.cloudbees.groovy.cps.LValueBlock$GetAdapter.receive(LValueBlock.java:30)
    at com.cloudbees.groovy.cps.impl.LocalVariableBlock.evalLValue(LocalVariableBlock.java:28)
    at com.cloudbees.groovy.cps.LValueBlock$BlockImpl.eval(LValueBlock.java:55)
    at com.cloudbees.groovy.cps.LValueBlock.eval(LValueBlock.java:16)
    at com.cloudbees.groovy.cps.Next.step(Next.java:74)
    at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:154)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:18)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:33)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:30)
    at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox.runInSandbox(GroovySandbox.java:108)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:30)
    at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:165)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:330)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$100(CpsThreadGroup.java:82)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:242)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:230)
    at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:64)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:112)
    at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Finished: FAILURE

This is the same error if i wrap the getGradleBinPath method in a class named Utils, with or without the static keyword prefix for the method, i also tried to implement Serializable when using the class version, but i get the same error.
If i remove the static keyword in the scripted version it works fine, all of the example code scenarios and above mentioned yield the same error.
I'm pretty new to Groovy so please forgive me if this is not related to Jenkins, or very trivial.

A:

You cannot access pipeline standard steps directly in classes\scripts under src directory. You should pass the steps to constructor/setter or as method parameter.
Something like that should work
src/acme/jenkins/Utils.groovy
package acme.jenkins
class Utils implements Serializable {

    def steps

    Utils(steps) {
        this.steps = steps
    }

    String getGradleBinPath(String version = null) {
        if (!version) {
            throw new IllegalArgumentException('No Gradle version supplied')
        }
        steps.tool(version).concat('/bin/gradle')
    }
}

and then in vars/runGradle.groovy
//..
def utils = new Utils(steps)
//...

See Accessing steps.
Note that steps is a standard global variable that hold pipeline steps.

