There are several common pitfalls in the above example:
1 - Avoid for
loops in EVERYWHERE in Jenkins, they are very likely to not do what you expect, instead use closure methods - i.e. for (file in files_list)
becomes files_list.each { String file -> ... }
- the logic is essentially like a loop, but it is far more reliable. You can also do more advanced things like Map tasks = files_list.collectEntries { String file -> return (file, { sh ... }} }
Example that demos this:
List files_list = ['one', 'two', 'three']
Map tasks_for = [:]
// Wrong way
for (file in files_list) {
tasks_for[file] = { echo "With 'for' we got: ${file}" }
}
// Right way
Map tasks_each = [:]
files_list.each { String file ->
tasks_each[file] = { echo "With 'each' we got: ${file}" }
}
parallel tasks_for
parallel tasks_each
you will get some very different results with these:
[one] With 'for' we got: three
[two] With 'for' we got: three
[three] With 'for' we got: three
[one] With 'each' we got: one
[two] With 'each' we got: two
[three] With 'each' we got: three
What happens here is that the context within a closure is shared with the closure owner, so with for loop, all closures use the same instance of the variable file
, using its value at the time the closure is executed which is AFTER the loop is over, so they all get the last value provided
2 - Even with collection iteration methods like each {}
you can very easily get into trouble.
Consider this variant:
List files_list = ['one', 'two', 'three']
Map tasks_each = [:]
files_list.each { String filename ->
file = "${filename}.tmp"
echo "Converted ${filename} into ${file} "
tasks_each[file] = {
echo "We got: ${file} from ${filename}"
}
}
parallel tasks_each
You get:
Converted one into one.tmp
Converted two into two.tmp
Converted three into three.tmp
[one.tmp] We got: three.tmp from one
[two.tmp] We got: three.tmp from two
[three.tmp] We got: three.tmp from three
This is the same context problem as before because in this case file
is shared with all. While you can manually ensure that every variable is isolated, much easier way to do this is to generate the closure in an external method and call the method to create the task. Each call creates a new context and it will force you to isolate each run.
3 - Now, your actual question is similar - short answer is - use Collection methods:
Same as what you have (assuming the value is an int):
results.each { String branch, int exitCode ->
echo "file = ${branch}, exit code = ${exitCode}"
}
Or you can output it all as a single echo statement:
// Generate list of strings
List resultStrings = results.collect { String branch, int exitCode ->
"file = ${branch}, exit code = ${exitCode}"
}
// Join them into a newline delimited string
String resultInfoText = resultStrings.join('\n')
// Print results
echo "Results are:\n${resultInfoText}"
// or use it anywhere there is a need for text, including email
HTH