0

I have a simple pipeline script that access a groovy class (which resides in a shared library), the pipeline script pass a reference (to itself) to the class, the class then calls a function or a closure found in the script, that function access a member in that same script,

the result is a groovy.lang.MissingPropertyException exception.

so here is the actual (runnable) code:

Jenkins pipeline script:

@Library("testLib") _

import my.domain.Tester;

public Tester tester = new Tester(this);

def closure()
{
    echo "tester: " + tester;
}

node("master")
{
    tester.test();
}

com.domain.Tester.groovy class:

package my.domain

class Tester
{
    Script scriptRef = null;

    public Tester(Script scriptRef)
    {
        this.scriptRef = scriptRef;
    }

    public void test()
    {
        this.scriptRef.closure();
    }
}

the exception received:

groovy.lang.MissingPropertyException: No such property: tester for class: WorkflowScript
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:53)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.getProperty(ScriptBytecodeAdapter.java:458)
    at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.getProperty(DefaultInvoker.java:39)
    at com.cloudbees.groovy.cps.impl.PropertyAccessBlock.rawGet(PropertyAccessBlock.java:20)
    at WorkflowScript.closure(WorkflowScript:9)
    at my.domain.Tester.test(Tester.groovy:8)
    at WorkflowScript.run(WorkflowScript:23)
    at ___cps.transform___(Native Method)
    at com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.get(PropertyishBlock.java:74)
    at com.cloudbees.groovy.cps.LValueBlock$GetAdapter.receive(LValueBlock.java:30)
    at com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.fixName(PropertyishBlock.java:66)
    at sun.reflect.GeneratedMethodAccessor538.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
    at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
    at com.cloudbees.groovy.cps.Next.step(Next.java:83)
    at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:174)
    at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:163)
    at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:129)
    at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:268)
    at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:163)
    at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:186)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:370)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$200(CpsThreadGroup.java:93)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:282)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:270)
    at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:67)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:131)
    at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
    at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:59)
    at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Finished: FAILURE

so obviously it is something happening due to the cps transform procedure the pipeline script goes through, and the script member is not there anymore (not by name and not at scope [probably]),

  1. does anyone have a solution / idea / direction ?
  2. does anyone have info on the CPS Transform process (specifically to Jenkins, not the CPS theory), or how can i see the end product WorkflowScript after the transformation ?
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Shaybc
  • 2,628
  • 29
  • 43
  • the error `No such property: stringUtil` but there is no such property anywhere in your code sample. mistype? – daggett Nov 27 '19 at 17:08
  • sorry, i have changed my packages and classes so the example would be simple to understand, unfortunately i forgot the stack-trace, i will change it accordingly - thanks for the remark – Shaybc Nov 27 '19 at 19:52
  • i believe, if you call from `node(){ ... }` the method `closure()` directly without library - you'll get the same error - right? – daggett Nov 27 '19 at 20:15
  • you are correct, still my question remains the same, why the 'closure' function can't see the var ? if i try to call: `echo "tester: " + tester` from the node it works fine – Shaybc Nov 27 '19 at 21:58
  • It's a basic of groovy script. Plain variables are local to script itself. And script during compilation appeares as a content of method `run()`. So, it's like you want to access from method `closure()` the variable declared inside method `run()` – daggett Nov 27 '19 at 22:17
  • I don't have pipeline under hands to test, but try to declare `tester` without type and access level. In this case it should be created as a binding of script. `tester = new Tester(this)` – daggett Nov 27 '19 at 22:27
  • i already tried to define without public and without Tester (`def tester = new Tester(this)`) and it did not work, but i haven't tried without def (`tester = new Tester(this)`) - that worked, thanks allot for your time, could you please set it as an answer and i can mark it for others ? – Shaybc Nov 27 '19 at 22:51

1 Answers1

0

based on daggett solution in the remarks:

  1. jenkins pipeline scripted job is written in groovy
  2. it is then compiled and CPS transformed (during runrime) into a WorkflowScript groovy class with a single method that is executed

and since it is just a groovy class then the groovy scoping rules should apply, in groovy if you declare a var / 'def' you can do it without the 'def' prefix and that would declare it globally available everywhere in the script,

so change the declaration in the pipeline script to this:

tester = new Tester(this)

and that's it!

Jenkins pipeline script:

@Library("testLib") _

import my.domain.Tester;

// note the definition without the 'def' or Tester or public
tester = new Tester(this);

def closure()
{
    echo "tester: " + tester;
}

node("master")
{
    tester.test();
}

OR:

since Groovy version 1.8 and up, we can add the @Field annotation and keep the declaration as it is,

how-do-i-create-and-access-the-global-variables-in-groovy

so we can also change the pipeline to look like this:

Jenkins pipeline script:

@Library("testLib") _

import my.domain.Tester;

// note the definition without the 'def' or Tester or public
@Field Tester tester = new Tester(this);

def closure()
{
    echo "tester: " + tester;
}

node("master")
{
    tester.test();
}
Shaybc
  • 2,628
  • 29
  • 43
  • please read: https://blogs.sap.com/2017/06/22/avoid-binding-variables-in-groovy-scripts to understand binding fields without the def definition and understand the risks and behaviors – Shaybc Dec 10 '19 at 01:39