Интерпретация Jenkins нескольких объявлений объектов в одной строке

9

Это не вопрос, а скорее предостерегающая история: я попытался сэкономить место и объявил свои переменные в декларативном конвейере Jenkins следующим образом:

int a, b, c

Затем я инициализировал их как:

a = b = c = 0

В моем коде я использую эти целые числа как счетчики в цикле for. Мой сценарий продолжал терпеть неудачу снова и снова, некоторые исключения выданы:

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

и я точно знал, что мой список действителен, так как он был жестко закодирован. Итак, я начал интересоваться, что происходит с этими счетчиками, и когда я вызвал для них getClass (), Дженкинс с радостью сказал мне, что они не являются целыми числами, а скорее

org.codehaus.groovy.runtime.NullObject

После изменения кода на

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

все работало как шарм. Просто хотел поделиться этим. Может быть, это поможет кому-то спасти разочарование.

искриться
источник

Ответы:

12

Конвейеры Jenkins выполняют код Groovy в стиле продолжения с использованием интерпретатора groovy-cps . Это не ванильный Groovy, который вы можете выполнить непосредственно в IDE или в Groovy Shell.

Groovy CPS преобразует ваш код для поддержки стиля передачи продолжения и правильного выражения Groovy, например:

a = b = c = 0

превращается в нечто, похожее на:

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

Проблема с этим выражением в интерпретаторе CPS заключается в том, что присваивание не возвращает никакого значения, и, следовательно, nullзначение присваивается переменной b, и то же самое происходит с переменной a.

Если вы хотите копнуть глубже в блоке вызовов CPS, вы можете клонировать проект groovy-cps и написать простой тестовый пример в com.cloudbees.groovy.cps.CpsTransformerTestклассе.

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

Затем вы можете установить точку останова println cpsи запустить отладчик. Когда вы откроете окно проверки, вы увидите изображение, похожее на это:

введите описание изображения здесь

В качестве примечания помните, что Groovy-компилятор также преобразует ваши однострочные назначения при компиляции кода в байт-код. Если вы скомпилируете простой скрипт на Groovy, например:

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

println "$a $b $c"

и затем вы открываете его файл класса в IDE, чтобы декомпилировать байт-код в эквивалент Java, вы увидите что-то вроде этого:

//
// 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[]{"", " ", " ", ""}));
    }
}
Шимон Степняк
источник