#!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.

// This is image used to build the tarball, check source formatting and build
// the docs
DOCKER_IMAGE_BASE = 'apache/couchdbci-debian:bookworm-erlang'

// Erlang version embedded in binary packages. Also the version most builds
// will run.
ERLANG_VERSION = '26.2.5.20'

// 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 = '26.2.5.20'

// Highest support Erlang version.
MAXIMUM_ERLANG_VERSION = '28.4.3'

// Test a min + 1 version. This is specially useful before we're about
// to bump the minimum version to at least make sure the CI image
// works.
INTERMEDIATE_ERLANG_VERSION = '27.3.4.11'

// Default GNU Make Eunit Options for supported platforms
DEFAULT_GNU_MAKE_EUNIT_OPTS = '-j2 --output-sync=target'

// Use these to detect if just documents changed
docs_changed = "git diff --name-only origin/${env.CHANGE_TARGET} | grep -q '^src/docs/'"
github_changed = "git diff --name-only origin/${env.CHANGE_TARGET} | grep -q '^.github'"
other_changes = "git diff --name-only origin/${env.CHANGE_TARGET} | grep -q -v -e '^src/docs/' -e '^.github'"

// 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 = [
  'centos8': [
    name: 'CentOS 8',
    spidermonkey_vsn: '60',
    with_nouveau: true,
    with_clouseau: true,
    clouseau_java_home: '/usr',
    quickjs_test262: true,
    image: "apache/couchdbci-centos:8-erlang-${ERLANG_VERSION}",
    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  'centos9': [
    name: 'CentOS 9',
    spidermonkey_vsn: '78',
    with_nouveau: true,
    with_clouseau: true,
    clouseau_java_home: '/usr',
    quickjs_test262: true,
    image: "apache/couchdbci-centos:9-erlang-${ERLANG_VERSION}",
    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  'centos10': [
    name: 'CentOS 10',
    disable_spidermonkey: true,
    with_nouveau: true,
    with_clouseau: true,
    clouseau_java_home: '/usr',
    quickjs_test262: true,
    image: "apache/couchdbci-centos:10-erlang-${ERLANG_VERSION}",
    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  'jammy': [
    name: 'Ubuntu 22.04',
    spidermonkey_vsn: '91',
    with_nouveau: true,
    with_clouseau: true,
    clouseau_java_home: '/opt/java/openjdk',
    quickjs_test262: true,
    image: "apache/couchdbci-ubuntu:jammy-erlang-${ERLANG_VERSION}",
    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  'noble': [
    name: 'Ubuntu 24.04',
    spidermonkey_vsn: '115',
    with_nouveau: true,
    with_clouseau: true,
    clouseau_java_home: '/opt/java/openjdk',
    quickjs_test262: true,
    image: "apache/couchdbci-ubuntu:noble-erlang-${ERLANG_VERSION}",
    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  'bullseye': [
    name: 'Debian x86_64',
    spidermonkey_vsn: '78',
    with_nouveau: true,
    with_clouseau: true,
    clouseau_java_home: '/opt/java/openjdk',
    quickjs_test262: true,
    image: "apache/couchdbci-debian:bullseye-erlang-${ERLANG_VERSION}",
    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  // Sometimes we "pick up" ppc64le workers from the asf jenkins intance That
  // seems like a good thing, however, those workers allow running multiple
  // agents at a time and often time out even with retries. The build times
  // take close to an hour, which is at least x2 as long as it takes to run
  // other arch CI jobs and they still time out often and fail. Disable for
  // now. This is a low demand arch distro, maybe remove support altogether?
  //
  // 'base-ppc64': [
  //   name: 'Debian POWER',
  //   spidermonkey_vsn: '78',
  //   with_nouveau: true,
  //   with_clouseau: true,
  //   quickjs_test262: true,
  //   image: "${DOCKER_IMAGE_BASE}-${ERLANG_VERSION}",
  //   node_label: 'ppc64le',
  //   gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  // ],

  // Just like in the ppc64le case we sometimes "pick up" built-in s390x workers added to
  // our jenkins, but since those are managed by us, our .mix/.venv/.hex packaging
  // cache hack in /home/jenkins doesn't work, and so elixir tests fail. Skip this arch build
  // until we figure out caching (probably need to use a proper caching plugin).
  //
  // 'base-s390x': [
  //   name: 'Debian s390x',
  //   spidermonkey_vsn: '78',
  //   with_nouveau: true,
  //   // QuickJS test262 shows a discrepancy typedarray-arg-set-values-same-buffer-other-type.js
  //   // Test262Error: 51539607552,42,0,4,5,6,7,8
  //   quickjs_test262: false,
  //   image: "${DOCKER_IMAGE_BASE}-${ERLANG_VERSION}",
  //   node_label: 's390x',
  //   gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  // ],

  'base': [
    name: 'Debian x86_64',
    spidermonkey_vsn: '78',
    with_nouveau: true,
    with_clouseau: true,
    clouseau_java_home: '/opt/java/openjdk',
    // Test this in in the bookworm-quickjs variant
    quickjs_test262: false,
    image: "${DOCKER_IMAGE_BASE}-${ERLANG_VERSION}",
    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  'base-max-erlang': [
    name: 'Debian x86_64',
    spidermonkey_vsn: '78',
    with_nouveau: true,
    with_clouseau: true,
    clouseau_java_home: '/opt/java/openjdk',
    quickjs_test262: false,
    image: "${DOCKER_IMAGE_BASE}-${MAXIMUM_ERLANG_VERSION}",
    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  'base-intermediate-erlang': [
    name: 'Debian x86_64',
    spidermonkey_vsn: '78',
    with_nouveau: true,
    with_clouseau: true,
    clouseau_java_home: '/opt/java/openjdk',
    quickjs_test262: false,
    image: "${DOCKER_IMAGE_BASE}-${INTERMEDIATE_ERLANG_VERSION}",
    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  'base-quickjs': [
    name: 'Debian 12 with QuickJS',
    disable_spidermonkey: true,
    with_nouveau: true,
    with_clouseau: true,
    clouseau_java_home: '/opt/java/openjdk',
    quickjs_test262: true,
    image: "${DOCKER_IMAGE_BASE}-${ERLANG_VERSION}",
    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  // This runs on a docker ARM64 host. Normally we should be able to run all
  // ubuntu and centos containers on top any docker host, however spidermonkey
  // 60 from CentOS cannot build on ARM64 so we're forced to separate it as a
  // separate build usable for ubuntu/debian only and isolated it from other
  // builds. At some point when we remove CentOS 8 or switch to QuickJS only,
  // remove the docker-arm64 label on ubuntu-nc-arm64-12 node in Jenkins and
  // remove this flavor
  //
  'base-arm64': [
    name: 'Debian ARM64',
    spidermonkey_vsn: '78',
    with_nouveau: true,
    with_clouseau: true,
    clouseau_java_home: '/opt/java/openjdk',
    // Test this in in the bookworm-quickjs variant
    quickjs_test262: false,
    image: "${DOCKER_IMAGE_BASE}-${ERLANG_VERSION}",
    node_label: 'docker-arm64',
    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  'trixie': [
    name: 'Debian x86_64',
    spidermonkey_vsn: '128',
    with_nouveau: true,
    with_clouseau: true,
    clouseau_java_home: '/opt/java/openjdk',
    quickjs_test262: true,
    image: "apache/couchdbci-debian:trixie-erlang-${ERLANG_VERSION}",
    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  'freebsd-x86_64': [
      name: 'FreeBSD x86_64',
      spidermonkey_vsn: '91',
      with_nouveau: false,
      with_clouseau: true,
      clouseau_java_home: '/usr/local/openjdk21',
      quickjs_test262: false,
      gnu_make: 'gmake',
      gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  ],

  // Spidermonkey 91 has issues on ARM64 FreeBSD
  // use QuickJS for now
  // Temporarily disabled because on too slow a host
  // 'freebsd-arm64': [
  //    name: 'FreeBSD ARM64 QuickJS',
  //    disable_spidermonkey: true,
  //    with_clouseau: true,
  //    clouseau_java_home: '/usr/local/openjdk21',
  //    quickjs_test262: false,
  //    gnu_make: 'gmake',
  //    gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
  // ],

  'macos': [
     name: 'macOS',
     disable_spidermonkey: true,
     with_nouveau: true,
     with_clouseau: true,
     clouseau_java_home: '/opt/homebrew/opt/openjdk@21',
     gnu_make: 'gmake',
     gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
   ],

   'win2022': [
     name: 'Windows 2022',
     spidermonkey_vsn: '128',
     with_nouveau: true,
     with_clouseau: true,
     clouseau_java_home: /C:\tools\zulu21.46.19-ca-jdk21.0.9-win_x64/,
     quickjs_test262: false,
     node_label: 'win',
     gnu_make_eunit_opts: "${DEFAULT_GNU_MAKE_EUNIT_OPTS}"
   ]
]

containerClouseauConfig = '''logger: {
  format: Raw
  output: Stdout
  level: debug
}
config: [
  {
    node: {
      name: clouseau1
      domain: 127.0.0.1
    }
    clouseau: {
      commit_interval_secs: 1
    }
  }
]
'''

def String configure(config) {
  if (config.disable_spidermonkey) {
      result = "./configure --skip-deps --disable-spidermonkey"
  } else {
      result = "./configure --skip-deps --spidermonkey-version ${config.spidermonkey_vsn}"
  }
  if (config.with_nouveau) {
    result += " --with-nouveau"
  }
  if (config.with_clouseau) {
    result += " --with-clouseau"
  }
  return result
}

// Use this to delete all the files inside the workspace dir without deleting
// the directory itself. There is an issue withing docker containers related to
// that https://issues.jenkins-ci.org/browse/JENKINS-41894 (also
// https://github.com/jenkinsci/docker-workflow-plugin/issues/478, but both
// stale and closed now).
//
def String wscleanup() {
  return 'rm -rf ${WORKSPACE}/* ${WORKSPACE}/.[a-zA-Z0-9]*'
}

// 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: 180, unit: "MINUTES") {
          // Steps to configure and build CouchDB on *nix platforms
          if (isUnix()) {
            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',
                'PATH+ERTS=/opt/homebrew/lib/erlang/bin',
                'MAKE='+meta[platform].gnu_make,
                'CLOUSEAU_JAVA_HOME='+meta[platform].clouseau_java_home ?: ''
              ]) {
                sh 'echo "JAIL_HOST: ${JAIL_HOST}"'
                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 xref'
                  retry (3) {sh "$MAKE ${meta[platform].gnu_make_eunit_opts} eunit" }
                  if (meta[platform].quickjs_test262) {retry(3) {sh 'make quickjs-test262'}}
                  retry (3) {sh '$MAKE elixir'}
                  retry (3) {timeout(time: 5, unit: "MINUTES") {sh '$MAKE elixir-search ERLANG_COOKIE=crumbles'}}
                  retry (3) {timeout(time: 5, unit: "MINUTES") {sh '$MAKE mango-test ERLANG_COOKIE=crumbles'}}
                  retry (3) {sh '$MAKE weatherreport-test'}
                  retry (3) {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'
              // deleteDir is OK here because we're not inside of a Docker container!
              deleteDir()
            }
          } else {
            //steps to configure and build CouchDB on Windows platforms
            stage("${meta[platform].name} - build & test") {
              try {
                // deleteDir is OK here because we're not inside of a Docker container!
                deleteDir()
                unstash 'tarball'
                powershell( script: "git clone https://github.com/apache/couchdb-glazier", label: 'Cloning couchdb-glazier repository' )
                powershell( script: "New-Item -ItemType Directory -Path '${platform}/build' -Force", label: 'Create build directories' )
                powershell( script: "tar -xf (Get-Item apache-couchdb-*.tar.gz) -C '${platform}/build' --strip-components=1", label: 'Unpack release' )
                dir( "${platform}/build" ) {
                  def withNouveau = meta[platform].with_nouveau ? '-WithNouveau' : ''
                  def withClouseau = meta[platform].with_clouseau ? '-WithClouseau' : ''
                  def setClouseauJavaHome = ''

                  if (meta[platform].clouseau_java_home) {
                    setClouseauJavaHome = "Set-Item -Path env:CLOUSEAU_JAVA_HOME -Value '${meta[platform].clouseau_java_home}'"
                  }

                  powershell( script: """
                    ..\\..\\couchdb-glazier\\bin\\shell.ps1
                    .\\configure.ps1 -SkipDeps ${withNouveau} ${withClouseau} -SpiderMonkeyVersion ${meta[platform].spidermonkey_vsn}
                    Set-Item -Path env:GRADLE_OPTS -Value '-Dorg.gradle.daemon=false'
                    make -f Makefile.win release
                  """, label: 'Configure and Build')

                  retry (3) {
                    powershell( script: """
                      ..\\..\\couchdb-glazier\\bin\\shell.ps1
                      Write-Output 'The following tests are skipped:'
                      ..\\..\\couchdb-glazier\\bin\\exclude_tests_win.ps1 -Path . | Out-Host
                      make ${meta[platform].gnu_make_eunit_opts} -f Makefile.win eunit
                    """, label: 'EUnit tests')
                  }

                  retry (3) {
                    powershell( script: """
                      ..\\..\\couchdb-glazier\\bin\\shell.ps1
                      make -f Makefile.win elixir
                      """, label: 'Elixir tests')
                  }

                  retry (3) {
                    timeout(time: 5, unit: "MINUTES") {
                      powershell( script: """
                        ..\\..\\couchdb-glazier\\bin\\shell.ps1
                        Set-Item -Path env:GRADLE_OPTS -Value '-Dorg.gradle.daemon=false'
                        ${setClouseauJavaHome}
                        make -f Makefile.win elixir-search ERLANG_COOKIE=crumbles
                      """, label: 'Clouseau tests')
                    }
                  }

                  retry (3) {
                    timeout(time: 5, unit: "MINUTES") {
                      powershell( script: """
                        ..\\..\\couchdb-glazier\\bin\\shell.ps1
                        Set-Item -Path env:GRADLE_OPTS -Value '-Dorg.gradle.daemon=false'
                        ${setClouseauJavaHome}
                        make -f Makefile.win mango-test ERLANG_COOKIE=crumbles
                      """, label: 'Mango tests')
                    }
                  }

                  powershell( script: '..\\..\\couchdb-glazier\\bin\\shell.ps1; Write-Host "NOT AVAILABLE: make -f Makefile.win weatherreport-test"', label: 'N/A Weatherreport tests')

                  retry (3) {
                    powershell( script: """
                      ..\\..\\couchdb-glazier\\bin\\shell.ps1
                      Set-Item -Path env:GRADLE_OPTS -Value '-Dorg.gradle.daemon=false'
                      make -f Makefile.win nouveau-test
                    """, label: 'Nouveau tests')
                  }
                }
              }
              catch (err) {
                powershell( script: "Get-ChildItem ${WORKSPACE}")
                dir( "${platform}/build" ) {
                  powershell( script: '..\\..\\couchdb-glazier\\bin\\shell.ps1; make -f Makefile.win build-report')
                  powershell( script: 'Get-Content test-results.log')
                }
                error("Build step failed with error: ${err.getMessage()}")
              }
              finally {
                powershell( script: 'Get-ChildItem')
                powershell( script: 'Get-Process erl -ErrorAction SilentlyContinue | Stop-Process -PassThru')
              }
            }

            stage("${meta[platform].name} - package") {
              try {
                powershell( script: """
                  .\\couchdb-glazier\\bin\\shell.ps1
                  .\\couchdb-glazier\\bin\\build_installer.ps1 -Path '${platform}/build' -IncludeGitSha -DisableICEChecks
                """, label: 'Build Windows Installer file')
                archiveArtifacts artifacts: '*.msi', fingerprint: true, onlyIfSuccessful: true
              }
              catch (err) {
                powershell( script: "Get-ChildItem ${WORKSPACE}")
                error("Build step failed with error: ${err.getMessage()}")
              }
              finally {
                // deleteDir is OK here because we're not inside of a Docker container!
                deleteDir()
              }
            }
          }
        }
      }
    }
  }
}

// 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: 180, 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' )
                  quickjs_tests262 = meta[platform].quickjs_test262
                  def setClouseauJavaHome = ''

                  if (meta[platform].clouseau_java_home) {
                    setClouseauJavaHome = "env CLOUSEAU_JAVA_HOME='${meta[platform].clouseau_java_home}' "
                  }

                  dir( "${platform}/build" ) {
                    sh "${configure(meta[platform])}"
                    sh 'make'
                    sh 'make xref'
                    retry (3) {sh "make ${meta[platform].gnu_make_eunit_opts} eunit" }
                    if (meta[platform].quickjs_test262) {retry(3) {sh 'make quickjs-test262'}}
                    retry(3) {sh 'make elixir'}
                    writeFile(file: 'clouseau/clouseau.conf', text: containerClouseauConfig)
                    retry(3) {sh "${setClouseauJavaHome}timeout 5m make elixir-search ERLANG_COOKIE=crumbles"}
                    retry(3) {sh "${setClouseauJavaHome}timeout 5m make mango-test ERLANG_COOKIE=crumbles"}
                    retry(3) {sh 'make weatherreport-test'}
                    retry(3) {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 "${wscleanup()}"
                }
              }

              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 "${wscleanup()}"
                }
              }
            }
          }
        }
      }
    }
  }
}

// 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=/home/jenkins/.npm -e HOME=. -e MIX_HOME=/home/jenkins/.mix -e HEX_HOME=/home/jenkins/.hex -e PIP_CACHE_DIR=/home/jenkins/.cache/pip -v=/etc/passwd:/etc/passwd -v /etc/group:/etc/group -v /home/jenkins/.gradle:/home/jenkins/.gradle:rw,z -v /home/jenkins/.hex:/home/jenkins/.hex:rw,z -v /home/jenkins/.npm:/home/jenkins/.npm:rw,z -v /home/jenkins/.cache/pip:/home/jenkins/.cache/pip:rw,z -v /home/jenkins/.mix:/home/jenkins/.mix:rw,z'
  }

  options {
    buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '10'))
    preserveStashes(buildCount: 10)
    timeout(time: 4, unit: 'HOURS')
    timestamps()
  }

  stages {

    stage('Setup Env') {
      agent {
        docker {
          image "${DOCKER_IMAGE_BASE}-${MINIMUM_ERLANG_VERSION}"
          label 'docker'
          args "${DOCKER_ARGS}"
          registryUrl 'https://docker.io/'
          registryCredentialsId 'dockerhub_creds'
        }
      }
      steps {
        script {
          env.DOCS_CHANGED = '0'
          env.ONLY_DOCS_CHANGED = '0'
          env.GITHUB_CHANGED = '0'
          env.ONLY_GITHUB_CHANGED = '0'
          if ( sh(returnStatus: true, script: docs_changed) == 0 ) {
            env.DOCS_CHANGED = '1'
            if (sh(returnStatus: true, script: other_changes) == 1) {
              env.ONLY_DOCS_CHANGED = '1'
            }
          }
          if ( sh(returnStatus: true, script: github_changed) == 0 ) {
            env.GITHUB_CHANGED = '1'
            if (sh(returnStatus: true, script: other_changes) == 1) {
              env.ONLY_GITHUB_CHANGED = '1'
            }
          }
        }
      }
      post {
        cleanup {
          sh "${wscleanup()}"
        }
      }
    } // stage 'Setup Environment'

    stage('Build Docs') {
      // Build docs separately if only docs changed. If there are other changes, docs are
      // already built as part of `make dist`
      when {
        beforeOptions true
        expression { ONLY_DOCS_CHANGED == '1' }
      }
      agent {
        docker {
          image "${DOCKER_IMAGE_BASE}-${MINIMUM_ERLANG_VERSION}"
          label 'docker'
          args "${DOCKER_ARGS}"
          registryUrl 'https://docker.io/'
          registryCredentialsId 'dockerhub_creds'
        }
      }
      steps {
        sh 'make python-black'
        sh '(cd src/docs && ./setup.sh  ; make check)'
        sh '(cd src/docs && make html)'
      }
      post {
        cleanup {
          sh "${wscleanup()}"
        }
      }
    } // stage Build Docs

    stage('Build Release Tarball') {
      when {
        beforeOptions true
        expression { ONLY_DOCS_CHANGED == '0' && ONLY_GITHUB_CHANGED == '0' }
      }
      agent {
        docker {
          label 'docker'
          image "${DOCKER_IMAGE_BASE}-${MINIMUM_ERLANG_VERSION}"
          args "${DOCKER_ARGS}"
          registryUrl 'https://docker.io/'
          registryCredentialsId 'dockerhub_creds'
        }
      }
      steps {
        sh (script: 'rm -rf apache-couchdb-*', label: 'Clean workspace of any previous release artifacts' )
        sh "./configure --spidermonkey-version 78 --with-nouveau"
        sh 'make erlfmt-check'
        sh 'make elixir-source-checks'
        sh 'make python-black'
        sh 'make -j4 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 {
          sh "${wscleanup()}"
        }
      }
    } // stage Build Release Tarball

    stage('Test and Package') {
      when {
        beforeOptions true
        expression { ONLY_DOCS_CHANGED == '0' && ONLY_GITHUB_CHANGED == '0' }
      }
      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
        }
      }
    }
  } // stages
} // pipeline
