Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jenkins interpretation of multiple object declarations on one line

This isn't a question, but rather a cautionary tale: I tried to save some space and declared my variables in Jenkins Declarative pipeline like so:

int a, b, c

Then, I initialized them as:

a = b = c = 0

In my code, I use these integers as counters in a for-loop. My script kept failing over and over, some of the exceptions thrown:

java.lang.NullPointerException: Cannot invoke method next() on null object

and I knew for sure that my list is valid since it was hard-coded. So, I started wondering what's going on with these counters and when I called getClass() on them, Jenkins happily told me that they weren't integers, but rather

org.codehaus.groovy.runtime.NullObject

After changing code to

int a = 0
int b = 0
int c = 0

everything worked like a charm. Just wanted to share this. Maybe it'll help someone to save some frustration.

like image 914
Sparkle Avatar asked Apr 25 '20 04:04

Sparkle


People also ask

How do I comment multiple lines in Jenkinsfile?

They start with two forward slashes (//). Any text between // and the end of the line is commented and ignored in Jenkinsfile.

What is scripted pipeline in Jenkins?

What is the Scripted Pipeline in Jenkins? A Jenkins Scripted Pipeline is a sequence of stages to perform CI/CD-related tasks that can be specified as code, enabling you to develop a pipeline script and add it to your code repository so you can version it.

Which pipeline approach is used in Jenkins as a best practice?

Using a shell script within Jenkins Pipeline can help simplify builds by combining multiple steps into a single stage. The shell script also allows users to add or update commands without having to modify each step or stage separately.

What is difference between freestyle and pipeline in Jenkins?

In contrast to freestyle jobs, pipelines enable you to define the whole application lifecycle. Pipeline functionality helps Jenkins to support continuous delivery (CD). The Pipeline plugin was built with requirements for a flexible, extensible, and script-based CD workflow capability in mind.


1 Answers

Jenkins pipelines execute Groovy code in the continuation-passing style using groovy-cps interpreter. This is not vanilla Groovy you can execute directly in the IDE or in Groovy Shell.

Groovy CPS transforms your code to support the continuation-passing style and the correct Groovy expression like:

a = b = c = 0

gets transformed to something that looks more like:

eval(
  var("a"), 
  assign(
    eval(
      var("b"), 
      assign(
        eval(
          var("c"), 
          assign(0)
        )
      )
    )
  )
)

The problem with this expression in the CPS interpreter is that the assignment does not return any value, and thus the null value gets assigned to the variable b, and the same thing happens to the variable a.

If you want to dig deeper in the CPS invocations block, you can clone groovy-cps project and write a simple test case in the com.cloudbees.groovy.cps.CpsTransformerTest class.

@Test
void testMultiVariablesInlineCPS() {
    def cps = parseCps('''
int a, b, c
a = b = c = 0
''')
    println cps
}

Then you can put a breakpoint at the println cps and run the debugger. When you open the inspection window, you will see the picture similar to this one:

enter image description here

As a side note, keep in mind that the Groovy compiler also transforms your single line assignments when compiled the code to the bytecode. If you compile a simple Groovy script like:

int a, b, c
a = b = c = 0

println "$a $b $c"

and then you open its class file in the IDE to decompile the bytecode to the Java equivalent, you will see something like this:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.GStringImpl;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

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

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

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

    public Object run() {
        CallSite[] var1 = $getCallSiteArray();
        int a = 0;
        int b = 0;
        int c = 0;
        byte var5 = 0;
        return var1[1].callCurrent(this, new GStringImpl(new Object[]{Integer.valueOf(var5), Integer.valueOf(var5), Integer.valueOf(var5)}, new String[]{"", " ", " ", ""}));
    }
}
like image 187
Szymon Stepniak Avatar answered Oct 15 '22 15:10

Szymon Stepniak