Interpretação de Jenkins de várias declarações de objetos em uma linha

9

Esta não é uma pergunta, mas uma história de advertência: tentei economizar espaço e declarei minhas variáveis ​​no pipeline declarativo Jenkins da seguinte forma:

int a, b, c

Então, eu os inicializei como:

a = b = c = 0

No meu código, eu uso esses números inteiros como contadores em um loop for. Meu script falhou repetidamente, algumas das exceções lançadas:

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

e eu tinha certeza de que minha lista é válida, pois era codificada. Então, comecei a me perguntar o que estava acontecendo com esses contadores e, quando chamei getClass (), Jenkins me disse alegremente que eles não eram números inteiros, mas sim

org.codehaus.groovy.runtime.NullObject

Depois de alterar o código para

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

tudo funcionou como um encanto. Só queria compartilhar isso. Talvez ajude alguém a economizar alguma frustração.

Brilhar
fonte

Respostas:

12

Os pipelines Jenkins executam o código Groovy no estilo de passagem de continuação usando groovy-cps interpretador . Isso não é o Groovy de baunilha, você pode executar diretamente no IDE ou no Groovy Shell.

O Groovy CPS transforma seu código para oferecer suporte ao estilo de passagem de continuação e à expressão correta do Groovy, como:

a = b = c = 0

é transformado em algo que se parece mais com:

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

O problema com essa expressão no intérprete do CPS é que a atribuição não retorna nenhum valor e, portanto, o nullvalor é atribuído à variável b, e o mesmo ocorre com a variável a.

Se você quiser se aprofundar no bloco de invocações do CPS, poderá clonar o projeto groovy-cps e escrever um caso de teste simples na com.cloudbees.groovy.cps.CpsTransformerTestclasse.

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

Em seguida, você pode colocar um ponto de interrupção no println cpse executar o depurador. Ao abrir a janela de inspeção, você verá a imagem semelhante a esta:

insira a descrição da imagem aqui

Como uma observação lateral, lembre-se de que o compilador Groovy também transforma suas atribuições de linha única ao compilar o código no bytecode. Se você compilar um script Groovy simples como:

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

println "$a $b $c"

e, em seguida, você abre o arquivo de classe no IDE para descompilar o bytecode para o equivalente em Java. Você verá algo assim:

//
// 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
fonte