diff --git a/pipelines/monolithic.Jenkinsfile b/pipelines/monolithic.Jenkinsfile index af89512..3048f4b 100644 --- a/pipelines/monolithic.Jenkinsfile +++ b/pipelines/monolithic.Jenkinsfile @@ -8,40 +8,54 @@ library "common@${params.JENKINS_SCRIPTS_BRANCH}" -PROPS = null -IMG_PARAMS = null -IMAGES_FAILED = false +// Pipeline-level variables require @Field annotation to avoid Jenkins CPS warnings +// about implicit field creation. These variables persist across pipeline stages. +// Reference: https://www.jenkins.io/doc/book/pipeline/shared-libraries/ +@groovy.transform.Field +def PROPS = null +@groovy.transform.Field +def IMG_PARAMS = null +@groovy.transform.Field +def IMAGES_FAILED = false def parseProps(text) { - def x = {} + // Use [:] for empty Map. Using {} creates a Closure, not a Map. + // Reference: https://groovy-lang.org/groovy-dev-kit.html + def x = [:] for (line in text.split (/\n+/)) { if (line.matches (/\s*(?:#.*)?#/)) { continue } - parts = line.split ("=", 2) - key = parts[0] - value = parts[1] + // Use def keyword for loop variables to avoid CPS field creation warnings + def parts = line.split ("=", 2) + def key = parts[0] + def value = parts[1] x."${key}" = value } return x } def loadEnv() { - def data = {} - data.NEED_BUILD = false - data.SUPPRESS_DOCKER_IMAGE_BUILD_ERRORS = params.SUPPRESS_DOCKER_IMAGE_BUILD_ERRORS + // Use [:] for empty Map. Using {} creates a Closure, not a Map. + // Use bracket notation for Map property access to avoid false positive CPS warnings. + // Dot notation (data.KEY) triggers warnings because CPS cannot distinguish between + // dynamic Map property access and implicit field creation. + // Reference: https://www.jenkins.io/doc/book/pipeline/cps-method-mismatches/ + def data = [:] + data["NEED_BUILD"] = false + data["SUPPRESS_DOCKER_IMAGE_BUILD_ERRORS"] = params.SUPPRESS_DOCKER_IMAGE_BUILD_ERRORS ws(params.BUILD_HOME) { if (fileExists ("NEED_BUILD")) { - data.NEED_BUILD = true + data["NEED_BUILD"] = true } } final String configText = sh (script: "${Constants.SCRIPTS_DIR}/print-config.sh", returnStdout: true) final props = parseProps (configText) - data.BUILD_OUTPUT_HOME_URL = props.BUILD_OUTPUT_HOME_URL - data.PUBLISH_URL = props.PUBLISH_URL - data.BUILD_REMOTE_CLI = props.BUILD_REMOTE_CLI == "true" + data["BUILD_OUTPUT_HOME_URL"] = props.BUILD_OUTPUT_HOME_URL + data["PUBLISH_URL"] = props.PUBLISH_URL + data["BUILD_REMOTE_CLI"] = props.BUILD_REMOTE_CLI == "true" PROPS = data - return data.NEED_BUILD + return data["NEED_BUILD"] } def partJobName (name) { @@ -69,7 +83,7 @@ def runImagesPart (name, params = []) { // current build. Instead we note when an image-related // job has failed, and skip all subsequent image-related // jobs. - if (PROPS.SUPPRESS_DOCKER_IMAGE_BUILD_ERRORS) { + if (PROPS["SUPPRESS_DOCKER_IMAGE_BUILD_ERRORS"]) { if (!IMAGES_FAILED) { final jobName = partJobName (name) final res = runPart (name, IMG_PARAMS + params, false).result @@ -97,10 +111,10 @@ def printBuildFooter() { msg += "\n" msg += "========================================\n" msg += "\n" - if (PROPS.NEED_BUILD) { - msg += "Build output: ${PROPS.BUILD_OUTPUT_HOME_URL}\n" - if (PROPS.PUBLISH_URL) { - msg += "Publish output: ${PROPS.PUBLISH_URL}\n" + if (PROPS["NEED_BUILD"]) { + msg += "Build output: ${PROPS["BUILD_OUTPUT_HOME_URL"]}\n" + if (PROPS["PUBLISH_URL"]) { + msg += "Publish output: ${PROPS["PUBLISH_URL"]}\n" } if (IMAGES_FAILED) { msg += "\n" @@ -278,7 +292,7 @@ or with paths relative to repo root: } // This stage runs only if build is required stage('X0') { - when { expression { PROPS.NEED_BUILD } } + when { expression { PROPS["NEED_BUILD"] } } stages { stage('PREPARE') { steps { @@ -373,7 +387,7 @@ or with paths relative to repo root: } // stages } // stage('IMAGES') stage('remote-cli') { - when { expression { PROPS.BUILD_REMOTE_CLI } } + when { expression { PROPS["BUILD_REMOTE_CLI"] } } steps { runPart ("build-remote-cli") }