Properly suppress exceptions thrown in finally blocks in Jenkinsfile

This commit is contained in:
Yoann Rodière 2024-03-28 17:03:48 +01:00
parent bcbae00552
commit 2a8183371f
2 changed files with 83 additions and 33 deletions

53
Jenkinsfile vendored
View File

@ -132,7 +132,7 @@ stage('Build') {
stage('Checkout') { stage('Checkout') {
checkout scm checkout scm
} }
try { tryFinally({
stage('Start database') { stage('Start database') {
switch (buildEnv.dbName) { switch (buildEnv.dbName) {
case "edb": case "edb":
@ -163,7 +163,7 @@ stage('Build') {
'ge.hibernate.org-access-key-pr' : 'ge.hibernate.org-access-key', 'ge.hibernate.org-access-key-pr' : 'ge.hibernate.org-access-key',
variable: 'GRADLE_ENTERPRISE_ACCESS_KEY')]) { variable: 'GRADLE_ENTERPRISE_ACCESS_KEY')]) {
withGradle { // withDevelocity, actually: https://plugins.jenkins.io/gradle/#plugin-content-capturing-build-scans-from-jenkins-pipeline withGradle { // withDevelocity, actually: https://plugins.jenkins.io/gradle/#plugin-content-capturing-build-scans-from-jenkins-pipeline
try { tryFinally({
if (buildEnv.dbLockableResource == null) { if (buildEnv.dbLockableResource == null) {
withCredentials([file(credentialsId: 'sybase-jconnect-driver', variable: 'jconnect_driver')]) { withCredentials([file(credentialsId: 'sybase-jconnect-driver', variable: 'jconnect_driver')]) {
sh 'cp -f $jconnect_driver ./drivers/jconn4.jar' sh 'cp -f $jconnect_driver ./drivers/jconn4.jar'
@ -182,16 +182,14 @@ stage('Build') {
} }
} }
} }
} }, {
finally {
junit '**/target/test-results/test/*.xml,**/target/test-results/testKitTest/*.xml' junit '**/target/test-results/test/*.xml,**/target/test-results/testKitTest/*.xml'
} })
} }
} }
} }
} }
} }, { // Finally
finally {
if ( state[buildEnv.tag]['containerName'] != null ) { if ( state[buildEnv.tag]['containerName'] != null ) {
sh "docker rm -f ${state[buildEnv.tag]['containerName']}" sh "docker rm -f ${state[buildEnv.tag]['containerName']}"
} }
@ -199,7 +197,7 @@ stage('Build') {
if ( !env.CHANGE_ID && buildEnv.notificationRecipients != null ) { if ( !env.CHANGE_ID && buildEnv.notificationRecipients != null ) {
handleNotifications(currentBuild, buildEnv) handleNotifications(currentBuild, buildEnv)
} }
} })
} }
} }
}) })
@ -230,16 +228,13 @@ class BuildEnvironment {
void runBuildOnNode(String label, Closure body) { void runBuildOnNode(String label, Closure body) {
node( label ) { node( label ) {
pruneDockerContainers() pruneDockerContainers()
try { tryFinally(body, { // Finally
body()
}
finally {
// If this is a PR, we clean the workspace at the end // If this is a PR, we clean the workspace at the end
if ( env.CHANGE_BRANCH != null ) { if ( env.CHANGE_BRANCH != null ) {
cleanWs() cleanWs()
} }
pruneDockerContainers() pruneDockerContainers()
} })
} }
} }
void pruneDockerContainers() { void pruneDockerContainers() {
@ -314,4 +309,34 @@ String getParallelResult( RunWrapper build, String parallelBranchName ) {
return null; return null;
} }
return branch.status.result return branch.status.result
} }
// try-finally construct that properly suppresses exceptions thrown in the finally block.
static def tryFinally(Closure main, Closure ... finallies) {
def mainFailure = null
try {
main()
}
catch (Throwable t) {
mainFailure = t
throw t
}
finally {
finallies.each {it ->
try {
it()
}
catch (Throwable t) {
if ( mainFailure ) {
mainFailure.addSuppressed( t )
}
else {
mainFailure = t
}
}
}
}
if ( mainFailure ) { // We may reach here if only the "finally" failed
throw mainFailure
}
}

View File

@ -103,7 +103,7 @@ stage('Build') {
stage('Checkout') { stage('Checkout') {
checkout scm checkout scm
} }
try { tryFinally({
stage('Start database') { stage('Start database') {
switch (buildEnv.dbName) { switch (buildEnv.dbName) {
case "hsqldb_2_6": case "hsqldb_2_6":
@ -175,7 +175,7 @@ stage('Build') {
stage('Test') { stage('Test') {
String cmd = "./ci/build.sh ${buildEnv.additionalOptions ?: ''} ${state[buildEnv.tag]['additionalOptions'] ?: ''}" String cmd = "./ci/build.sh ${buildEnv.additionalOptions ?: ''} ${state[buildEnv.tag]['additionalOptions'] ?: ''}"
withEnv(["RDBMS=${buildEnv.dbName}"]) { withEnv(["RDBMS=${buildEnv.dbName}"]) {
try { tryFinally({
if (buildEnv.dbLockableResource == null) { if (buildEnv.dbLockableResource == null) {
withCredentials([file(credentialsId: 'sybase-jconnect-driver', variable: 'jconnect_driver')]) { withCredentials([file(credentialsId: 'sybase-jconnect-driver', variable: 'jconnect_driver')]) {
sh 'cp -f $jconnect_driver ./drivers/jconn4.jar' sh 'cp -f $jconnect_driver ./drivers/jconn4.jar'
@ -194,14 +194,12 @@ stage('Build') {
} }
} }
} }
} }, { // Finally
finally {
junit '**/target/test-results/test/*.xml,**/target/test-results/testKitTest/*.xml' junit '**/target/test-results/test/*.xml,**/target/test-results/testKitTest/*.xml'
} })
} }
} }
} }, { // Finally
finally {
if ( state[buildEnv.tag]['containerName'] != null ) { if ( state[buildEnv.tag]['containerName'] != null ) {
sh "docker rm -f ${state[buildEnv.tag]['containerName']}" sh "docker rm -f ${state[buildEnv.tag]['containerName']}"
} }
@ -209,7 +207,7 @@ stage('Build') {
if ( !env.CHANGE_ID && buildEnv.notificationRecipients != null ) { if ( !env.CHANGE_ID && buildEnv.notificationRecipients != null ) {
handleNotifications(currentBuild, buildEnv) handleNotifications(currentBuild, buildEnv)
} }
} })
} }
} }
}) })
@ -239,16 +237,13 @@ class BuildEnvironment {
void runBuildOnNode(String label, Closure body) { void runBuildOnNode(String label, Closure body) {
node( label ) { node( label ) {
pruneDockerContainers() pruneDockerContainers()
try { tryFinally(body, {
body() // If this is a PR, we clean the workspace at the end
} if ( env.CHANGE_BRANCH != null ) {
finally { cleanWs()
// If this is a PR, we clean the workspace at the end }
if ( env.CHANGE_BRANCH != null ) { pruneDockerContainers()
cleanWs() })
}
pruneDockerContainers()
}
} }
} }
void pruneDockerContainers() { void pruneDockerContainers() {
@ -323,4 +318,34 @@ String getParallelResult( RunWrapper build, String parallelBranchName ) {
return null; return null;
} }
return branch.status.result return branch.status.result
} }
// try-finally construct that properly suppresses exceptions thrown in the finally block.
def tryFinally(Closure main, Closure ... finallies) {
def mainFailure = null
try {
main()
}
catch (Throwable t) {
mainFailure = t
throw t
}
finally {
finallies.each {it ->
try {
it()
}
catch (Throwable t) {
if ( mainFailure ) {
mainFailure.addSuppressed( t )
}
else {
mainFailure = t
}
}
}
}
if ( mainFailure ) { // We may reach here if only the "finally" failed
throw mainFailure
}
}