Groovy 中的闭包不捕获外部变量

Dus*_*rea 4 groovy jenkins jenkins-groovy jenkins-pipeline

在 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)
Run Code Online (Sandbox Code Playgroud)

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].
Run Code Online (Sandbox Code Playgroud)

这很荒谬。

我需要添加什么或做不同的事情?

tim*_*tes 7

这与您在 Javascript 中看到的问题相同。

当你在 for 循环中生成闭包时,它们被绑定到一个变量,而不是变量

当循环退出并运行闭包时,它们都将使用相同的值……即——for 循环退出前的最后一个值

例如,您希望打印以下内容1 2 3 4,但它不会

def closures = []

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

closures.each { it() }
Run Code Online (Sandbox Code Playgroud)

它打印 4 4 4 4

要解决此问题,您需要执行以下两件事之一...首先,您可以在局部范围的变量中捕获,然后关闭此变量:

for (i in 1..4) {
    def n = i
    closures << { -> println n }
}
Run Code Online (Sandbox Code Playgroud)

您可以做的第二件事是使用 groovyeachcollect每次调用它们时,变量是不同的实例,因此它再次起作用:

(1..4).each { i ->
    closures << { -> println i }
}
Run Code Online (Sandbox Code Playgroud)

对于您的情况,您可以使用以下方法循环platforms 并同时收集到地图中collectEntries

def tasks = platforms.collectEntries { platformName ->
  [
     platformName,
     { ->
        def componentUploadPath = componentUploadPaths[platformName]
        echo "Uploading for platform [${platformName}] to [${componentUploadPath}]."
     }
  ]
}
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!