#!groovy // // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. // Erlang version embedded in binary packages ERLANG_VERSION = '24.3.4.10' // Erlang version used for rebar in release process. CouchDB will not build from // the release tarball on Erlang versions older than this MINIMUM_ERLANG_VERSION = '23.3.4.18' // We create parallel build / test / package stages for each OS using the metadata // in this map. Adding a new OS should ideally only involve adding a new entry here. meta = [ 'centos7': [ name: 'CentOS 7', spidermonkey_vsn: '1.8.5', enable_nouveau: true, image: "apache/couchdbci-centos:7-erlang-${ERLANG_VERSION}" ], 'centos8': [ name: 'CentOS 8', spidermonkey_vsn: '60', enable_nouveau: true, image: "apache/couchdbci-centos:8-erlang-${ERLANG_VERSION}" ], 'bionic': [ name: 'Ubuntu 18.04', spidermonkey_vsn: '1.8.5', enable_nouveau: true, image: "apache/couchdbci-ubuntu:bionic-erlang-${ERLANG_VERSION}" ], 'focal': [ name: 'Ubuntu 20.04', spidermonkey_vsn: '68', enable_nouveau: true, image: "apache/couchdbci-ubuntu:focal-erlang-${ERLANG_VERSION}" ], 'jammy': [ name: 'Ubuntu 22.04', spidermonkey_vsn: '91', enable_nouveau: true, image: "apache/couchdbci-ubuntu:jammy-erlang-${ERLANG_VERSION}" ], 'buster': [ name: 'Debian 10', spidermonkey_vsn: '60', enable_nouveau: true, image: "apache/couchdbci-debian:buster-erlang-${ERLANG_VERSION}" ], 'bullseye-arm64': [ name: 'Debian 11 ARM', spidermonkey_vsn: '78', enable_nouveau: true, image: "apache/couchdbci-debian:bullseye-erlang-${ERLANG_VERSION}", node_label: 'arm64v8' ], 'bullseye-ppc64': [ name: 'Debian 11 POWER', spidermonkey_vsn: '78', enable_nouveau: true, image: "apache/couchdbci-debian:bullseye-erlang-${ERLANG_VERSION}", node_label: 'ppc64le' ], 'bullseye-s390x': [ name: 'Debian 11 s390x', spidermonkey_vsn: '78', enable_nouveau: true, image: "apache/couchdbci-debian:bullseye-erlang-${ERLANG_VERSION}", node_label: 's390x' ], 'bullseye': [ name: 'Debian 11', spidermonkey_vsn: '78', enable_nouveau: true, image: "apache/couchdbci-debian:bullseye-erlang-${ERLANG_VERSION}" ], // Skip freebsd builds for now as adviced by node owner // 'freebsd': [ // name: 'FreeBSD', // spidermonkey_vsn: '1.8.5', // gnu_make: 'gmake' // ], 'macos': [ name: 'macOS', spidermonkey_vsn: '91', enable_nouveau: false, gnu_make: 'make' ] ] def String configure(config) { result = "./configure --skip-deps --spidermonkey-version ${config.spidermonkey_vsn}" if (config.enable_nouveau) { result += " --enable-nouveau" } return result } // Credit to https://stackoverflow.com/a/69222555 for this technique. // We can use the scripted pipeline syntax to dynamically generate stages, // and inject them into a map that we pass to the `parallel` step in a script. // While the scripting approach is very flexible, it's not able to use some // functionality specific to Declarative Pipelines, like the `agent` and `post` // directives, so you'll see alternatives like try-catch-finally used for flow // control and the nested `node` and `docker` blocks in the container stage to // configure the worker environment. // Returns a build stage suitable for non-containerized environments (currently // macOS and FreeBSD). Coincidentally we do not currently support automated // package generation on these platforms. This method in invoked when we create // `parallelStagesMap` below. def generateNativeStage(platform) { return { stage(platform) { node(platform) { timeout(time: 90, unit: "MINUTES") { try { // deleteDir is OK here because we're not inside of a Docker container! deleteDir() unstash 'tarball' withEnv([ 'HOME='+pwd(), 'PATH+USRLOCAL=/usr/local/bin', 'MAKE='+meta[platform].gnu_make ]) { sh( script: "mkdir -p ${platform}/build", label: 'Create build directories' ) sh( script: "tar -xf apache-couchdb-*.tar.gz -C ${platform}/build --strip-components=1", label: 'Unpack release' ) dir( "${platform}/build" ) { sh "${configure(meta[platform])}" sh '$MAKE' sh '$MAKE eunit' sh '$MAKE elixir-suite' sh '$MAKE exunit' sh '$MAKE mango-test' sh '$MAKE weatherreport-test' sh '$MAKE nouveau-test' } } } catch (err) { sh 'ls -l ${WORKSPACE}' withEnv([ 'HOME='+pwd(), 'PATH+USRLOCAL=/usr/local/bin', 'MAKE='+meta[platform].gnu_make ]) { dir( "${platform}/build" ) { sh 'ls -l' sh '${MAKE} build-report' } } error("Build step failed with error: ${err.getMessage()}") } finally { junit '**/.eunit/*.xml, **/_build/*/lib/couchdbtest/*.xml, **/src/mango/nosetests.xml, **/test/javascript/junit.xml' sh 'killall -9 beam.smp || true' sh 'rm -rf ${WORKSPACE}/*' } } } } } } // Returns a build stage suitable for container-based deployments. This method // is invoked when we create the `parallelStagesMap` in the pipeline below. def generateContainerStage(platform) { return { // Important: the stage name here must match the parallelStagesMap key for the // Jenkins UI to render the pipeline stages correctly. Don't ask why. -APK stage(platform) { node(meta[platform].get('node_label', 'docker')) { docker.withRegistry('https://docker.io/', 'dockerhub_creds') { docker.image(meta[platform].image).inside("${DOCKER_ARGS}") { timeout(time: 90, unit: "MINUTES") { stage("${meta[platform].name} - build & test") { try { sh( script: "rm -rf ${platform} apache-couchdb-*", label: 'Clean workspace' ) unstash 'tarball' sh( script: "mkdir -p ${platform}/build", label: 'Create build directories' ) sh( script: "tar -xf apache-couchdb-*.tar.gz -C ${platform}/build --strip-components=1", label: 'Unpack release' ) dir( "${platform}/build" ) { sh "${configure(meta[platform])}" sh 'make' sh 'make eunit' sh 'make elixir-suite' sh 'make exunit' sh 'make mango-test' sh 'make weatherreport-test' sh 'make nouveau-test' } } catch (err) { sh 'ls -l ${WORKSPACE}' dir( "${platform}/build" ) { sh 'ls -l' sh 'make build-report' } error("Build step failed with error: ${err.getMessage()}") } finally { junit '**/.eunit/*.xml, **/_build/*/lib/couchdbtest/*.xml, **/src/mango/nosetests.xml, **/test/javascript/junit.xml' sh 'rm -rf ${WORKSPACE}/*' } } stage("${meta[platform].name} - package") { try { unstash 'tarball' sh( script: "mkdir -p ${platform}/couchdb", label: 'Create build directory' ) sh( script: "tar -xf apache-couchdb-*.tar.gz -C ${platform}/couchdb", label: 'Unpack release' ) sh( script: "cd ${platform} && git clone https://github.com/apache/couchdb-pkg", label: 'Clone packaging helper repo' ) dir( "${platform}/couchdb-pkg" ) { sh( script: 'make', label: 'Build packages' ) } sh( label: 'Stage package artifacts for archival', script: """ rm -rf pkgs/${platform} mkdir -p pkgs/${platform} mv ${platform}/rpmbuild/RPMS/\$(arch)/*rpm pkgs/${platform} || true mv ${platform}/couchdb/*.deb pkgs/${platform} || true """ ) archiveArtifacts artifacts: 'pkgs/**', fingerprint: true, onlyIfSuccessful: true } catch (err) { sh 'ls -l ${WORKSPACE}' error("Build step failed with error: ${err.getMessage()}") } finally { sh 'rm -rf ${WORKSPACE}/*' } } } } } } } } } // Finally we have the actual Pipeline. It's mostly a Declarative Pipeline, // except for the 'Test and Package' stage where we use the `script` step as an // "escape hatch" to dynamically generate a set of parallel stages to execute. pipeline { // no top-level agent; agents must be declared for each stage agent none environment { // Following fix an issue with git <= 2.6.5 where no committer // name or email are present for reflog, required for git clone GIT_COMMITTER_NAME = 'Jenkins User' GIT_COMMITTER_EMAIL = 'couchdb@apache.org' // https://github.com/jenkins-infra/jenkins.io/blob/master/Jenkinsfile#64 // We need the jenkins user mapped inside of the image // npm config cache below deals with /home/jenkins not mapping correctly // inside the image DOCKER_ARGS = '-e npm_config_cache=npm-cache -e HOME=. -v=/etc/passwd:/etc/passwd -v /etc/group:/etc/group -v /root/.m2:/root/.m2' } options { buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '10')) preserveStashes(buildCount: 10) timeout(time: 3, unit: 'HOURS') timestamps() } stages { stage('Build Release Tarball') { agent { docker { label 'docker' image "apache/couchdbci-debian:bullseye-erlang-${MINIMUM_ERLANG_VERSION}" args "${DOCKER_ARGS}" registryUrl 'https://docker.io/' registryCredentialsId 'dockerhub_creds' } } steps { timeout(time: 15, unit: "MINUTES") { sh (script: 'rm -rf apache-couchdb-*', label: 'Clean workspace of any previous release artifacts' ) sh "./configure --spidermonkey-version 78 --enable-nouveau" sh 'make dist' } } post { success { stash includes: 'apache-couchdb-*.tar.gz', name: 'tarball' archiveArtifacts artifacts: 'apache-couchdb-*.tar.gz', fingerprint: true } failure { sh 'ls -l ${WORKSPACE}' } cleanup { // UGH see https://issues.jenkins-ci.org/browse/JENKINS-41894 sh 'rm -rf ${WORKSPACE}/*' } } } // stage Build Release Tarball stage('Test and Package') { steps { script { // Including failFast: true in map fails the build immediately if any parallel step fails parallelStagesMap = meta.collectEntries( [failFast: false] ) { key, values -> if (values.image) { ["${key}": generateContainerStage(key)] } else { ["${key}": generateNativeStage(key)] } } parallel parallelStagesMap } } } stage('Publish') { when { expression { return env.BRANCH_NAME ==~ /main|2.*.x|3.*.x|4.*.x|jenkins-.*/ } } agent { docker { image "apache/couchdbci-debian:bullseye-erlang-${ERLANG_VERSION}" label 'docker' args "${DOCKER_ARGS}" registryUrl 'https://docker.io/' registryCredentialsId 'dockerhub_creds' } } options { skipDefaultCheckout() timeout(time: 90, unit: "MINUTES") } steps { sh 'rm -rf ${WORKSPACE}/*' unstash 'tarball' unarchive mapping: ['pkgs/' : '.'] sh( label: 'Build Debian repo', script: ''' mkdir -p $BRANCH_NAME/debian $BRANCH_NAME/el7 $BRANCH_NAME/el8 $BRANCH_NAME/source git clone https://github.com/apache/couchdb-pkg for plat in buster bullseye focal do reprepro -b couchdb-pkg/repo includedeb $plat pkgs/$plat/*.deb done ''' ) sh( label: 'Build CentOS repos', script: ''' #cp js/centos-7/*rpm pkgs/centos7 #cp js/centos-8/*rpm pkgs/centos8 cd pkgs/centos7 && createrepo_c --database . cd ../centos8 && createrepo_c --database . ''' ) sh( label: 'Build unified repo', script: ''' mv couchdb-pkg/repo/pool $BRANCH_NAME/debian mv couchdb-pkg/repo/dists $BRANCH_NAME/debian mv pkgs/centos7/* $BRANCH_NAME/el7 mv pkgs/centos8/* $BRANCH_NAME/el8 mv apache-couchdb-*.tar.gz $BRANCH_NAME/source cd $BRANCH_NAME/source ls -1tr | head -n -10 | xargs -d '\n' rm -f -- cd ../.. ''' ) } // steps } // stage } // stages post { success { mail to: 'notifications@couchdb.apache.org', replyTo: 'notifications@couchdb.apache.org', subject: "[Jenkins] SUCCESS: ${currentBuild.fullDisplayName}", body: "Yay, we passed. ${env.RUN_DISPLAY_URL}" } unstable { mail to: 'notifications@couchdb.apache.org', replyTo: 'notifications@couchdb.apache.org', subject: "[Jenkins] SUCCESS: ${currentBuild.fullDisplayName}", body: "Eep! Build is unstable... ${env.RUN_DISPLAY_URL}" } failure { mail to: 'notifications@couchdb.apache.org', replyTo: 'notifications@couchdb.apache.org', subject: "[Jenkins] FAILURE: ${currentBuild.fullDisplayName}", body: "Boo, we failed. ${env.RUN_DISPLAY_URL}" } } } // pipeline