Замыкания в Groovy не захватывают внешние переменные - PullRequest
1 голос
/ 28 мая 2019

В контексте конвейеров Jenkins у меня есть некоторый код Groovy, который перечисляет список, создает замыкания, а затем использует это значение в замыкании в качестве ключа для поиска другого значения на карте. Похоже, это почти всегда встречается с какой-то аномалией или расой.

Это упрощение кода:

def tasks = [:]
for (platformName in platforms) {
  // ...

  tasks[platformName] = {
    def componentUploadPath = componentUploadPaths[platformName]

    echo "Uploading for platform [${platformName}] to [${componentUploadPath}]."

    // ...
}

tasks.failFast = true
parallel(tasks)

platforms имеет два значения. Обычно я вижу две итерации и две зарегистрированные задачи, и ключи в tasks будут правильными, но оператор echo внутри замыкания указывает, что мы просто запускаем одну из платформ дважды:

14:20:02 [platform2] Uploading for platform [platform1] to [some_path/platform1].
14:20:02 [platform1] Uploading for platform [platform1] to [some_path/platform1].

Это смешно.

Что мне нужно добавить или сделать по-другому?

1 Ответ

3 голосов
/ 28 мая 2019

Это та же проблема, что и в Javascript.

Когда вы генерируете замыкания в цикле for, они связаны с переменной , а не со значением переменной.

Когда цикл завершится и будут запущены замыкания, все они будут использовать одно и то же значение ... то есть - последнее значение в цикле for перед его выходом

Например, вы ожидаете, что следующее напечатает 1 2 3 4, но не

def closures = []

for (i in 1..4) {
    closures << { -> println i }
}

closures.each { it() }

Он печатает 4 4 4 4

Чтобы это исправить, вынужно сделать одно из двух ... Во-первых, вы можете зафиксировать значение в локальной переменной, а затем закрыть эту переменную:

for (i in 1..4) {
    def n = i
    closures << { -> println n }
}

Второе, что вы можете сделатьиспользуйте groovy each или collect, так как каждый раз, когда они вызываются, переменная является другим экземпляром, поэтому она снова работает:

(1..4).each { i ->
    closures << { -> println i }
}

В вашем случае вы можете выполнить цикл platforms и собираются на карту одновременно с помощью collectEntries:

def tasks = platforms.collectEntries { platformName ->
  [
     platformName,
     { ->
        def componentUploadPath = componentUploadPaths[platformName]
        echo "Uploading for platform [${platformName}] to [${componentUploadPath}]."
     }
  ]
}

Надеюсь, это поможет!

...