Interpretación de Jenkins de declaraciones de objetos múltiples en una línea

9

Esta no es una pregunta, sino más bien una historia de advertencia: intenté ahorrar algo de espacio y declaró mis variables en la canalización declarativa de Jenkins de esta manera:

int a, b, c

Entonces, los inicialicé como:

a = b = c = 0

En mi código, uso estos enteros como contadores en un ciclo for. Mi script seguía fallando una y otra vez, algunas de las excepciones lanzadas:

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

y sabía con certeza que mi lista es válida ya que estaba codificada. Entonces, comencé a preguntarme qué estaba pasando con estos contadores y cuando llamé a getClass () sobre ellos, Jenkins felizmente me dijo que no eran enteros, sino más bien

org.codehaus.groovy.runtime.NullObject

Después de cambiar el código a

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

Todo funcionó a las mil maravillas. Solo quería compartir esto. Tal vez ayude a alguien a ahorrar algo de frustración.

Brillar
fuente

Respuestas:

12

Las tuberías de Jenkins ejecutan el código Groovy en el estilo de paso de continuación utilizando el intérprete groovy-cps . Esto no es Vanilla Groovy que puede ejecutar directamente en el IDE o en Groovy Shell.

Groovy CPS transforma su código para admitir el estilo de paso continuo y la expresión correcta de Groovy como:

a = b = c = 0

se transforma en algo que se parece más a:

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

El problema con esta expresión en el intérprete de CPS es que la asignación no devuelve ningún valor y, por lo tanto, el nullvalor se asigna a la variable b, y lo mismo le sucede a la variable a.

Si desea profundizar en el bloque de invocaciones de CPS, puede clonar el proyecto groovy-cps y escribir un caso de prueba simple en la com.cloudbees.groovy.cps.CpsTransformerTestclase.

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

Luego puede poner un punto de interrupción en el println cpsy ejecutar el depurador. Cuando abra la ventana de inspección, verá una imagen similar a esta:

ingrese la descripción de la imagen aquí

Como nota al margen, tenga en cuenta que el compilador Groovy también transforma sus asignaciones de una sola línea cuando compila el código en el código de bytes. Si compila un script Groovy simple como:

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

println "$a $b $c"

y luego abres su archivo de clase en el IDE para descompilar el bytecode al equivalente de Java, verás algo como esto:

//
// 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[]{"", " ", " ", ""}));
    }
}
Szymon Stepniak
fuente