Loops, conditions, maps and try/catch/finally work as in Groovy, which makes dynamic pipelines easy: the parallel step accepts a map of branch names to closures, built at run time:
def genres = ['Fiction', 'Cooking', 'Travel']
def branches = [:]
for (g in genres) {
def genre = g // a fresh variable for each closure
branches[genre] = { echo "checking ${genre}" }
}
stage('Per genre') { parallel branches }
stage('Smoke test') {
try {
node('linux') { sh 'exit 3' }
} catch (err) {
echo "Caught: ${err.message}"
currentBuild.result = 'UNSTABLE'
} finally {
echo 'finally runs either way'
}
}Output
checking Fiction ... checking Cooking ... checking Travel ... Caught: script returned exit code 3 ... finally runs either way Finished: UNSTABLE
The def genre = g line matters. Without it, a test job whose closures used the loop variable g directly printed "checking Travel" three times: every closure captured the same variable, which held the last value by the time the branches ran. The catch turned a failed step into an UNSTABLE build instead of a failure, logic that Declarative can express only through post conditions or a script block.