/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
import groovy.json.JsonOutput
import java.net.URI

buildscript {
  repositories {
    mavenCentral()
  }
}

apply plugin: 'java'
apply plugin: 'groovy'

def PACKAGES_GROUP = 'package'

final String VERBOSE = "verbose"
final String DEPS_BUILD = "buildwithdeps"
final boolean buildDependencies = false || System.getProperty(DEPS_BUILD) == 'true'
final String BOM = System.getProperty('bomfile') ?: "$rootDir/bigtop.bom"
def final config  = new ConfigSlurper().parse(new URL("file:$BOM"))

config.bigtop.builddir = projectDir.absolutePath + "/build"
config.bigtop.outputdir = projectDir.absolutePath + "/output"
config.bigtop.distdir = projectDir.absolutePath + "/dist"
config.bigtop.dldir = projectDir.absolutePath + "/dl"

def bomVersions = []

def final BASE_DIR = projectDir.absolutePath
def final REPO_DIR = "$BASE_DIR/bigtop-repos"
def final BUILD_DIR = config.bigtop.builddir
def final OUTPUT_DIR = config.bigtop.outputdir
def final DIST_DIR = config.bigtop.distdir
def final DL_DIR = config.bigtop.dldir

// List of tasks derived from the BOM file is used to preserve the natural order
def bomTasks = []

// Package building and logic around it

def touchTargetFile = { fileName ->
  // to comply with make build
  ant.touch(file:fileName,mkdirs:true)
}
def ifExists = { url ->
  if (url == null) return
  URLConnection uCon = new URL(url).openConnection()
  try {
    return (uCon as HttpURLConnection).responseCode == 200
  } catch(Exception e) {
    return false
  }
}    
def safeDelete = { fileName ->
  exec {
    workingDir '.'
    commandLine 'rm', '-rf', fileName
  }
}
def getDate() {
  new Date().format('E, dd MMM yyyy HH:mm:ss Z')
}

def generatePatch(String threadCount, String path) {
    def patchContent = """
        diff --git a/.mvn/maven.config b/.mvn/maven.config
        new file mode 100644
        index 00000000..e69de29b
        diff --git a/.mvn/maven.config b/.mvn/maven.config
        index e69de29b..0bea21c9 100644
        --- a/.mvn/maven.config
        +++ b/.mvn/maven.config
        @@ -0,0 +1 @@
        +-T ${threadCount}
        \\ No newline at end of file
    """.stripIndent()

    new File(path, "patch0-maven-parallel-build.diff").with {
        write(patchContent)
        println "Patch file created at: ${absolutePath}"
    }
}

def isValidMavenBuildThreads(threads) {
    return threads.matches("\\d+C") || threads.matches("\\d+")
}

def getPkgNameSuffix(component, type) {
    def originalSuffix = type.equalsIgnoreCase("deb") ?
            component.deb_pkg_suffix ?: "" :
            component.rpm_pkg_suffix ?: "%{nil}"
    def defaultSuffix = type.equalsIgnoreCase("deb") ?  "" : "%{nil}"
    def pkgNameSuffix = project.hasProperty("pkgSuffix")? originalSuffix : defaultSuffix
    def gradlePkgNameSuffix = pkgNameSuffix == defaultSuffix ? "" : pkgNameSuffix

    return [pkgNameSuffix: pkgNameSuffix, gradlePkgNameSuffix: gradlePkgNameSuffix]
}

def getParentDir(bigtopBaseVersion, type) {
    def defaultParentDirValue = type.equalsIgnoreCase("deb") ? "" : "%{nil}"
    def parentDir = project.hasProperty("parentDir") ? project.property('parentDir') : defaultParentDirValue
    if (parentDir != defaultParentDirValue) {
        parentDir = "${parentDir}/${bigtopBaseVersion}"
    }
    return parentDir
}


/**
 * To avoid breaking the compat with existing packages let's use the old style names
 */
def toOldStyleName = { newname ->
  newname.toUpperCase().replaceAll("\\-", "_")
}
def setDefaults = { comp ->
  // The closure parameter is a component in the config.bigtop.components
  if (!comp.pkg) {
    comp.pkg = comp.name
  }
  if (!comp.version.pkg) {
    comp.version.pkg = comp.version.base
  }
}
def devNull = new OutputStream() {
  @Override
  public void write(int b) {}
}
def nativePackaging = {
  def result = exec {
    commandLine "/bin/bash", "-c",
    """(dpkg-query -S `realpath /bin/sh` || dpkg-query -S /bin/sh) && exit 1
       rpm -qf /bin/sh && exit 2
       exit 0
    """
    ignoreExitValue true
    errorOutput devNull
    standardOutput devNull
  }
  [false, [pkg:"deb", repo:"apt"], [pkg:"rpm", repo:"yum"]][result.getExitValue()]
}.call()

def generateDistributionsFile = { file ->
  def writer = new File(file);
  writer.text ="""Origin: Bigtop
Label: Bigtop
Suite: stable
Codename: bigtop
Version: ${config.bigtop.version}
Architectures: amd64 ppc64el arm64 source
Components: contrib
Description: Bigtop
""";
}

task "packages-help" (description: "All package build related tasks information", group: PACKAGES_GROUP) doLast {
  config.bigtop.components.each { label, comp ->
    println (comp.name + "\n\t[" + tasks.findAll { alltask -> alltask.name.startsWith(comp.name)}*.name.join(", ") + "]")
  }
}

task "bom-json" (description: "List the components of the stack in json format") doLast {
  def componentObjects = config.bigtop.components.sort().collect {
    setDefaults(it.value)
    [
      name: [
        project: it.value.name,
        pkg: it.value.pkg,
        relNotes: it.value.relNotes,
        packaging: it.value.packaging,
        maven_parallel_build: it.value.maven_parallel_build
      ],
      tarball: [
        destination: it.value.tarball.destination,
        source: it.value.tarball.source,
      ],
      url: [
        site: it.value.url.site,
        archive: it.value.url.archive,
      ],
      version: [
        base: it.value.version.base,
        pkg: it.value.version.pkg,
        release: it.value.version.release,
      ],
      git: [
         repo: it.value.git.repo,
         ref: it.value.git.ref,
         dir: it.value.git.dir,
      ]
    ]
  }
  def fullDefinition = [
    version: config.bigtop.version,
    components: componentObjects
  ]
  def json = JsonOutput.toJson(fullDefinition)
  println JsonOutput.prettyPrint(json)
}

task "all-components" (description: "List the components of the stack") doLast {
  println "${project.name} ${config.bigtop.version} stack includes the following components"
  config.bigtop.components.sort().each { label, comp ->
    println sprintf('\t%1$s %2$s%3$s', comp.name.padRight(20), comp.version.base.padLeft(15),
      comp.packaging ? " (${comp.packaging.toUpperCase()} only)" : '')
  }
}

def genTasks = { target, packaging ->
  Task t = task "${target}-download" (dependsOn: "${target}_vardefines",
      description: "Download $target artifacts",
      group: PACKAGES_GROUP) doLast {

    def final TARBALL_SRC = config.bigtop.components[target].tarball.source
    def final TARBALL_DST = config.bigtop.components[target].tarball.destination
    def final DOWNLOAD_DST = config.bigtop.components[target].downloaddst
    def final DOWNLOAD_URL = config.bigtop.components[target].downloadurl

    def final GIT_REPO = project.hasProperty('git_repo') ? project.property('git_repo') : config.bigtop.components[target].git.repo
    def final GIT_REF = project.hasProperty('git_ref') ? project.property('git_ref') : config.bigtop.components[target].git.ref
    def final GIT_DIR = project.hasProperty('git_dir') ? project.property('git_dir') : config.bigtop.components[target].git.dir
    def final GIT_COMMIT_HASH = project.hasProperty('git_commit_hash') ? project.property('git_commit_hash') : config.bigtop.components[target].git.commit_hash
    def final GIT_USER_NAME = config.bigtop.components[target].git.user ?: config.bigtop.git.user
    def final GIT_ACCESS_TOKEN = config.bigtop.components[target].git.token ?: config.bigtop.git.token

    if (!DOWNLOAD_URL && !(GIT_REPO && GIT_REF))
      return

    mkdir(DL_DIR)
    if (TARBALL_DST?.isEmpty() || new File(DOWNLOAD_DST)?.exists() || new File(config.bigtop.components[target].targetdl)?.exists()) {
      println "\tFile $DOWNLOAD_DST appears to be already downloaded. Exiting..."
      return
    }
    if (GIT_REPO && GIT_REF) {
      def dir = GIT_DIR
      if (dir == null || dir.isEmpty()) {
        dir = TARBALL_SRC.substring(0, TARBALL_SRC.lastIndexOf(".t"))
      }
      delete("${DL_DIR}/${dir}")
      def depth = "--depth 1"
      if ( GIT_COMMIT_HASH ) {
        depth = ""
      }
      if (GIT_USER_NAME && GIT_ACCESS_TOKEN) {
        def uri = new URI(GIT_REPO)
        if (!GIT_REPO.toLowerCase().startsWith("http")) {
          println("\tGit credentials supported only with 'http' or 'https' schemes");
          return
        }
        uri = new URI(uri.getScheme(),
         "${GIT_USER_NAME}:${GIT_ACCESS_TOKEN}".toString(), // userInfo
         uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery(),
         uri.getFragment())
        exec {
          workingDir DL_DIR
          commandLine "git clone ${depth} --branch $GIT_REF ${uri} ${dir}".split()
          errorOutput devNull
          standardOutput devNull
        }
      } else {
        exec {
          workingDir DL_DIR
          commandLine "git clone ${depth} --branch $GIT_REF ${GIT_REPO} ${dir}".split()
          errorOutput devNull
          standardOutput devNull
        }
      }
      exec {
        workingDir "$DL_DIR/${dir}"
        commandLine "git submodule update --init".split()
        errorOutput devNull
        standardOutput devNull
      }
      if ( GIT_COMMIT_HASH ) {
        exec {
          workingDir "$DL_DIR/${dir}"
          commandLine "git reset $GIT_COMMIT_HASH --hard".split()
          errorOutput devNull
          standardOutput devNull
        }
      }
      delete("${DL_DIR}/${dir}/.git")
      exec {
        workingDir DL_DIR
        commandLine "tar -czf ${TARBALL_DST} ${dir}".split()
      }
      delete("${DL_DIR}/${dir}")
    }
    else {
      download {
        src DOWNLOAD_URL
        dest DOWNLOAD_DST
        retries 3
      }
    }
    touchTargetFile(config.bigtop.components[target].targetdl)
  }
  task "${target}-tar" (dependsOn: ["${target}_vardefines", "${target}-download"],
      description: "Preparing a tarball for $target artifacts",
      group: PACKAGES_GROUP) doLast {
    if (new File(config.bigtop.components[target].targettar)?.exists()) {
      println "\tNothing to do. Exiting..."
      return
    }
    def final TAR_DIR = config.bigtop.components[target].tardir
    def final TARBALL_SRC = config.bigtop.components[target].tarball.source ?: ""
    def final TARBALL_DST = config.bigtop.components[target].tarball.destination
    def final DOWNLOAD_DST = config.bigtop.components[target].downloaddst ?: ""
    def final SEED_TAR = config.bigtop.components[target].seedtar

    safeDelete(TAR_DIR); mkdir(TAR_DIR)

    if (TARBALL_SRC.isEmpty() || TARBALL_SRC.endsWith('.zip')) {
      if (TARBALL_SRC.isEmpty()) {
        // if no tar file needed (i.e. bigtop-utils)
        // create some contents to pack later
        copy {
          from 'LICENSE'
          into TAR_DIR
        }
      } else {
        // i.e. a ZIP file
        exec {
          workingDir TAR_DIR
          commandLine "unzip $DOWNLOAD_DST".split(' ')
        }
        def unpacked = new File(TAR_DIR)
        // check wether we have to move contents one level up
        if (unpacked.list().size() == 1) {
          def TOP_LEVEL_DIR = unpacked.list()[0]
          new File("$TAR_DIR/$TOP_LEVEL_DIR").list({ d, f ->
            (f != "." && f != "..")}).each { f ->
              new File("$TAR_DIR/$TOP_LEVEL_DIR/$f").renameTo("$TAR_DIR/$f")
          }
          safeDelete(TOP_LEVEL_DIR)
        }
      }
      // create SEED_TAR
      exec {
        workingDir "$TAR_DIR/.."
        commandLine "tar -czf $SEED_TAR ${new File("$TAR_DIR/..").list().join(' ')}".split(' ')
      }
    } else {
      println "Copy $DOWNLOAD_DST to $SEED_TAR"
      copy {
        from DOWNLOAD_DST
        into config.bigtop.builddir + "/$target/tar/"
        rename TARBALL_DST, SEED_TAR
      }
    }
    touchTargetFile(config.bigtop.components[target].targettar)
  }

  // Keeping the reference to deb task to be used later for correct sequencing
  Task tdeb = task "$target-deb"(dependsOn: "${target}-sdeb",
      description: "Building DEB for $target artifacts",
      group: PACKAGES_GROUP) doLast {
    if (new File(config.bigtop.components[target].targetdeb)?.exists()) {
      println "\tNothing to do. Exiting..."
      return
    }
    def final BIGTOP_BUILD_STAMP = System.getenv('BIGTOP_BUILD_STAMP') ?:
            config.bigtop.components[target].version.release
    def final PKG_NAME = config.bigtop.components[target].pkg
    def final PKG_VERSION = config.bigtop.components[target].version.pkg
    def final PKG_OUTPUT_DIR = config.bigtop.components[target].outputdir
    def final BASE_VERSION = config.bigtop.components[target].version.base
    def final SRCDEB = "${PKG_NAME}_$PKG_VERSION-${BIGTOP_BUILD_STAMP}.dsc"
    def final HADOOP_VERSION = config.bigtop.components["hadoop"].version.pkg
    def final FULL_PARENT_DIR = getParentDir(config.bigtop.base_version, "deb")
    def final DEB_PKG_NAME_SUFFIX =  getPkgNameSuffix(config.bigtop.components[target], "deb").pkgNameSuffix
    def final BIGTOP_BASE_VERSION = "${config.bigtop.base_version}"
    
    exec {
      workingDir PKG_OUTPUT_DIR
      commandLine "dpkg-source -x $SRCDEB".split(' ')
    }
// Order of debuild parameters is important; hence specifying explicitely rather
// than in an array of args
    def command = """debuild \
--preserve-envvar PATH \
--preserve-envvar MAVEN3_HOME \
--preserve-envvar MAVEN_OPTS \
--preserve-envvar JAVA_HOME \
--preserve-envvar BIGTOP_JDK \
--set-envvar=HADOOP_VERSION=$HADOOP_VERSION \
--set-envvar=${toOldStyleName(target)}_BASE_VERSION=$BASE_VERSION \
--set-envvar=${toOldStyleName(target)}_VERSION=$PKG_VERSION \
--set-envvar=${toOldStyleName(target)}_RELEASE=$BIGTOP_BUILD_STAMP \
--set-envvar=PARENT_DIR=$FULL_PARENT_DIR \
--set-envvar=PKG_NAME_SUFFIX=$DEB_PKG_NAME_SUFFIX \
--set-envvar=bigtop_base_version=${BIGTOP_BASE_VERSION} \
-uc -us -b
"""
    exec {
      workingDir "$PKG_OUTPUT_DIR/$PKG_NAME-$PKG_VERSION"
      commandLine command.split(' ')
    }
    exec {
      workingDir "$PKG_OUTPUT_DIR"
      commandLine 'rm','-rf',"$PKG_NAME-$PKG_VERSION"
    }
    touchTargetFile(config.bigtop.components[target].targetdeb)
  }
  // Guarantee that tasks are ran in the order set by BOM file dependencies section
  if (buildDependencies)
    config.bigtop.dependencies.each { dependsOn, dependees ->
      if (dependees.contains(tdeb.name-"-deb")) tdeb.dependsOn "$dependsOn-deb"
    }
  // If dependencies aren't explicitly set, the default build order is defined
  // by the sequence of the components in the BOM file
  if (!config.bigtop.dependencies && bomTasks.size() > 0)
    tdeb.mustRunAfter "${bomTasks.get(bomTasks.size() - 1)}-deb".toLowerCase()
   task "$target-sdeb" (dependsOn: ["${target}_vardefines",  "${target}-tar"],
      description: "Building SDEB for $target artifacts",
      group: PACKAGES_GROUP
  ) doLast {
    if (new File(config.bigtop.components[target].targetsdeb)?.exists()) {
      println "\tNothing to do. Exiting..."
      return
    }

    def final BIGTOP_BASE_VERSION = "${config.bigtop.base_version}"
     def final BIGTOP_BUILD_STAMP = System.getenv('BIGTOP_BUILD_STAMP') ?:
             config.bigtop.components[target].version.release
    def final TARBALL_SRC = config.bigtop.components[target].tarball.source
    def final TARBALL_DST = config.bigtop.components[target].tarball.destination ?: TARBALL_SRC
    def final PKG_BUILD_DIR = config.bigtop.components[target].builddir
    def final NAME = config.bigtop.components[target].name
    def final PKG_NAME = config.bigtop.components[target].pkg
    def final SEED_TAR = config.bigtop.components[target].seedtar
    def final PKG_VERSION = config.bigtop.components[target].version.pkg
    def final PKG_OUTPUT_DIR = config.bigtop.components[target].outputdir
    delete ("$PKG_BUILD_DIR/deb")
    def final DEB_BLD_DIR = "$PKG_BUILD_DIR/deb/$NAME-${PKG_VERSION}"
    def final DEB_PKG_DIR = "$PKG_BUILD_DIR/deb/$PKG_NAME-${PKG_VERSION}-${BIGTOP_BUILD_STAMP}"
    def final ENABLE_MAVEN_PARALLEL_BUILD = config.bigtop.components[target].maven_parallel_build
    def final MAVEN_BUILD_THREADS = project.hasProperty('buildThreads') ? project.property('buildThreads') : null
    def final FULL_PARENT_DIR = getParentDir(config.bigtop.base_version, "deb")
    def final DEB_PKG_NAME_SUFFIX =  getPkgNameSuffix(config.bigtop.components[target], "deb").pkgNameSuffix

    mkdir (DEB_BLD_DIR)
    copy {
      from SEED_TAR
      into "$PKG_BUILD_DIR/deb/"
      rename TARBALL_DST, "${PKG_NAME}_${PKG_VERSION}.orig.tar.gz"
    }
    exec {
      workingDir DEB_BLD_DIR
      commandLine "tar --strip-components 1 -xf $DEB_BLD_DIR/../${PKG_NAME}_${PKG_VERSION}.orig.tar.gz".split(' ')
    }
    copy {
      from fileTree("${BASE_DIR}/bigtop-packages/src/deb/$NAME") {
        include '**/*'
      }
      into "$DEB_BLD_DIR/debian"
    }
    copy {
      from "${BASE_DIR}/bigtop-packages/src/templates/init.d.tmpl"
      into "$DEB_BLD_DIR/debian"
    }
    copy {
      from "${BASE_DIR}/bigtop-packages/src/scripts"
      into "$DEB_BLD_DIR/debian"
    }
    copy {
      from "${BASE_DIR}/bigtop-packages/src/extensions"
      into "$DEB_BLD_DIR/debian"
    }
    copy {
      from fileTree("$BASE_DIR/bigtop-packages/src/common/$NAME") {
        include '**/*'
      }
      into "$DEB_BLD_DIR/debian"
    }
    if (MAVEN_BUILD_THREADS && ENABLE_MAVEN_PARALLEL_BUILD) {
      if (!isValidMavenBuildThreads(MAVEN_BUILD_THREADS)) {
        throw new GradleException("Invalid MAVEN_BUILD_THREADS parameter. It must be either a number or a combination of numbers followed by 'C', such as '2' or '2C'.")
      }
      generatePatch(MAVEN_BUILD_THREADS, "$DEB_BLD_DIR/debian")
    }
    // Prepeare bom file with all the versions
    def bomWriter = new File("$DEB_BLD_DIR/debian/bigtop.bom").newWriter()
    bomVersions.each { bomWriter << "$it\n"}
    bomWriter.close()
    // Create changelog
    def changelog = new File("$DEB_BLD_DIR/debian/changelog").newWriter()
    changelog << "$PKG_NAME ($PKG_VERSION-$BIGTOP_BUILD_STAMP) stable; urgency=low\n"
    changelog << "  Clean build\n"
    changelog << " -- Bigtop <dev@bigtop.apache.org>  ${getDate()}\n"
    changelog.close()

    // Move patches and create "series"
    def patches = new File( "$DEB_BLD_DIR/debian").list({ d, f -> f ==~ /patch.*diff/})

    // sort patches files in numberic sequence
    def fileListWithNumbers = patches.collect { fileName ->
        def matcher = (fileName =~ /patch(\d+)-.*/)
        if (matcher.find()) {
            return [fileName: fileName, number: matcher.group(1).toInteger()]
        } else {
            return [fileName: fileName, number: 0]
        }
    }
    def sortedPatches = fileListWithNumbers.sort { a, b ->
        a.number <=> b.number
    }.collect { it.fileName }
    patches = sortedPatches

    if (patches.size() > 0) {
      mkdir("$DEB_BLD_DIR/debian/patches")
      def seriesWriter = new File("$DEB_BLD_DIR/debian/patches/series").newWriter()
      patches.each { f ->
        def res = new File("$DEB_BLD_DIR/debian/$f").renameTo( "$DEB_BLD_DIR/debian/patches/$f")
        seriesWriter << "$f\n"
      }
      seriesWriter.close()
    }
    // Deleting obsolete files
    delete fileTree(dir: "$DEB_BLD_DIR/debian", includes: ['*.ex', '*.EX', '*.~'])
    // Creating source package
    exec {
      workingDir DEB_BLD_DIR
      environment 'PARENT_DIR', FULL_PARENT_DIR
      environment 'PKG_NAME_SUFFIX', DEB_PKG_NAME_SUFFIX
      environment 'bigtop_base_version', BIGTOP_BASE_VERSION
      commandLine "dpkg-buildpackage -uc -us -sa -S".split(' ')
    }
    mkdir(PKG_OUTPUT_DIR)
    copy {
      from fileTree(dir: "$DEB_PKG_DIR/..", includes: ['*.dsc', '*.diff.gz', '*.debian.tar.gz', '*.debian.tar.xz', "*_source.changes", "*.orig.tar.gz" ])
      into PKG_OUTPUT_DIR
    }
    safeDelete(DEB_BLD_DIR)
    touchTargetFile(config.bigtop.components[target].targetsdeb)
  }

  // Keeping the reference to task to be used later for correct sequencing
  Task trpm = task "$target-rpm" (dependsOn: ["${target}-srpm"],
      description: "Building RPM for $target artifacts",
      group: PACKAGES_GROUP) doLast {
    if (new File(config.bigtop.components[target].targetrpm)?.exists()) {
      println "\tNothing to do. Exiting..."
      return
    }
    def final BIGTOP_BASE_VERSION = "${config.bigtop.base_version}"


    def suffixes = getPkgNameSuffix(config.bigtop.components[target], "rpm")
    def RPM_PKG_NAME_SUFFIX =  suffixes.pkgNameSuffix
    def GRADLE_PKG_NAME_SUFFIX = suffixes.gradlePkgNameSuffix
    def final FULL_PARENT_DIR = getParentDir(BIGTOP_BASE_VERSION, "rpm")

    def final BIGTOP_BUILD_STAMP = System.getenv('BIGTOP_BUILD_STAMP') ?:
            config.bigtop.components[target].version.release
    def final PKG_BUILD_DIR = config.bigtop.components[target].builddir
    def final NAME = config.bigtop.components[target].name
    def final PKG_NAME = config.bigtop.components[target].pkg
    def final PKG_OUTPUT_DIR = config.bigtop.components[target].outputdir
    def final PKG_VERSION = config.bigtop.components[target].version.pkg
    def final BASE_VERSION = config.bigtop.components[target].version.base
    def final HADOOP_VERSION = config.bigtop.components["hadoop"].version.pkg
    def RELEASE_DIST = "rpmbuild --eval '%{?dist}' 2>/dev/null".execute().text.trim().replaceAll("'",'')
    def SRCRPM = "$PKG_OUTPUT_DIR/${PKG_NAME}${GRADLE_PKG_NAME_SUFFIX}-${PKG_VERSION}-$BIGTOP_BUILD_STAMP${RELEASE_DIST}.src.rpm"

    def final DO_MAVEN_DEPLOY = project.findProperty('doMavenDeploy') == 'true'
    def final MAVEN_DEPLOY_SOURCE = project.findProperty('mavenDeploySource') == 'true'
    def final MAVEN_REPO_ID = project.hasProperty('mavenRepoId') ? project.property('mavenRepoId') : 'default'
    def final MAVEN_REPO_URI = project.hasProperty('mavenRepoUri') ? project.property('mavenRepoUri') : null


    def command = [
        '--define', "_topdir $PKG_BUILD_DIR/rpm/",
        '--define', "${NAME}_base_version $BASE_VERSION",
        '--define', "hadoop_version ${HADOOP_VERSION}",
        '--define', "${NAME}_version ${PKG_VERSION}",
        '--define', "${NAME}_release ${BIGTOP_BUILD_STAMP}%{?dist}",
        '--rebuild', SRCRPM,
        '--define', "do_maven_deploy ${DO_MAVEN_DEPLOY}",
        '--define', "maven_deploy_source ${MAVEN_DEPLOY_SOURCE}",
        "--define", "parent_dir ${FULL_PARENT_DIR}",
        "--define", "bigtop_base_version ${BIGTOP_BASE_VERSION}",
        "--define", "pkg_name_suffix ${RPM_PKG_NAME_SUFFIX}",
    ]

   if (MAVEN_REPO_URI != null) {
      command.addAll('--define', "maven_repo_uri ${MAVEN_REPO_URI}")
      command.addAll('--define', "maven_repo_id ${MAVEN_REPO_ID}")
    }

    exec {
      workingDir BASE_DIR
      executable 'rpmbuild'
      environment QA_RPATHS: 0x0002
      args command
    }
    copy {
      from fileTree("$PKG_BUILD_DIR/rpm/RPMS") {
        include '**/*'
      }
      into PKG_OUTPUT_DIR
    }
    touchTargetFile(config.bigtop.components[target].targetrpm)
  }
  // Guarantee that tasks are ran in the order set by BOM file dependencies section
  if (buildDependencies)
    config.bigtop.dependencies.each { dependsOn, dependees ->
      if (dependees.contains(trpm.name - "-rpm")) trpm.dependsOn "$dependsOn-rpm"
    }
  // If dependencies aren't explicitly set, the default build order is defined
  // by the sequence of the components in the BOM file
  if (!config.bigtop.dependencies && bomTasks.size() > 0)
    trpm.mustRunAfter "${bomTasks.get(bomTasks.size() - 1)}-rpm".toLowerCase()
  task "$target-srpm" (dependsOn: ["${target}_vardefines" , "${target}-tar"],
      description: "Building SRPM for $target artifacts",
      group: PACKAGES_GROUP) doLast {
    if (new File(config.bigtop.components[target].targetsrpm)?.exists()) {
      println "\tNothing to do. Exiting..."
      return
    }
    def final BIGTOP_BASE_VERSION = "${config.bigtop.base_version}"


    def suffixes = getPkgNameSuffix(config.bigtop.components[target], "rpm")
    def RPM_PKG_NAME_SUFFIX =  suffixes.pkgNameSuffix
    def GRADLE_PKG_NAME_SUFFIX = suffixes.gradlePkgNameSuffix

    def final BIGTOP_BUILD_STAMP = System.getenv('BIGTOP_BUILD_STAMP') ?:
            config.bigtop.components[target].version.release
    def final NAME = config.bigtop.components[target].name
    def final PKG_NAME = config.bigtop.components[target].pkg
    def final PKG_NAME_FOR_PKG = NAME.replaceAll("-", "_")
    def final PKG_BUILD_DIR = config.bigtop.components[target].builddir
    def final SEED_TAR = config.bigtop.components[target].seedtar
    def final PKG_VERSION = config.bigtop.components[target].version.pkg
    def final BASE_VERSION = config.bigtop.components[target].version.base
    def final PKG_OUTPUT_DIR = config.bigtop.components[target].outputdir
    def final ENABLE_MAVEN_PARALLEL_BUILD = config.bigtop.components[target].maven_parallel_build
    def final MAVEN_BUILD_THREADS = project.hasProperty('buildThreads') ? project.property('buildThreads') : null
    safeDelete ("$PKG_BUILD_DIR/rpm")
    ['INSTALL','SOURCES','BUILD','SRPMS','RPMS'].each { rpmdir ->
      mkdir("$PKG_BUILD_DIR/rpm/$rpmdir")
    }
    copy {
      from fileTree("${BASE_DIR}/bigtop-packages/src/rpm/$NAME") {
        include '**/*'
      }
      into "$PKG_BUILD_DIR/rpm"
    }
    copy {
      from SEED_TAR
      into "$PKG_BUILD_DIR/rpm/SOURCES"
    }
    copy {
      from "${BASE_DIR}/bigtop-packages/src/templates/init.d.tmpl"
      into "$PKG_BUILD_DIR/rpm/SOURCES"
    }
    copy {
      from "${BASE_DIR}/bigtop-packages/src/extensions"
      into "$PKG_BUILD_DIR/rpm/SOURCES"
      include "*"
    }
    copy {
      from "${BASE_DIR}/bigtop-packages/src/scripts"
      into "$PKG_BUILD_DIR/rpm/SOURCES"
      include "*"
    }
    copy {
      from fileTree("$BASE_DIR/bigtop-packages/src/common/$NAME") {
        include '**/*'
      }
      into "$PKG_BUILD_DIR/rpm/SOURCES"
    }
    if (MAVEN_BUILD_THREADS && ENABLE_MAVEN_PARALLEL_BUILD) {
      if (!isValidMavenBuildThreads(MAVEN_BUILD_THREADS)) {
        throw new GradleException("Invalid MAVEN_BUILD_THREADS parameter. It must be either a number or a combination of numbers followed by 'C', such as '2' or '2C'.")
      }
      generatePatch(MAVEN_BUILD_THREADS, "$PKG_BUILD_DIR/rpm/SOURCES")
    }
    // Writing bigtop.bom files with all the versions
    def bomWriter = new File("$PKG_BUILD_DIR/rpm/SOURCES/bigtop.bom").newWriter()
    bomVersions.each { bomWriter << "$it\n"}
    bomWriter.close()

    def specFileName = "${PKG_BUILD_DIR}/rpm/SPECS/${NAME}.spec"
    def specTmpFileName = "${PKG_BUILD_DIR}/rpm/SPECS/tmp_${NAME}.spec"
    def specFile = new File(specFileName)
    def specWriter = new File(specTmpFileName).newWriter()
    def patches = new File("${PKG_BUILD_DIR}/rpm/SOURCES").list({ d, f -> f ==~ /patch.*diff/})

    // sort patches files in numberic sequence
    def fileListWithNumbers = patches.collect { fileName ->
        def matcher = (fileName =~ /patch(\d+)-.*/)
        if (matcher.find()) {
            return [fileName: fileName, number: matcher.group(1).toInteger()]
        } else {
            return [fileName: fileName, number: 0]
        }
    }
    def sortedPatches = fileListWithNumbers.sort { a, b ->
        a.number <=> b.number
    }.collect { it.fileName }
    patches = sortedPatches

    specFile.eachLine { line ->
      if (line.startsWith("#BIGTOP_PATCH_FILES")) {
        def patchNum = 0
        patches.each { p ->
          specWriter << "Patch$patchNum: $p\n"
          patchNum++
        }
      } else {
        if (line.startsWith("#BIGTOP_PATCH_COMMANDS")) {
          def patchNum = 0
          patches.each { p ->
            specWriter << "%patch$patchNum -p1\n"
            patchNum++
          }
        } else {
          specWriter << "$line\n"
        }
      }
    }
    specWriter.close()
    specFile.delete()
    new File(specTmpFileName).renameTo(specFileName)

    def command = [
        '--define', "_topdir $PKG_BUILD_DIR/rpm/",
        '--define', "${PKG_NAME_FOR_PKG}_base_version $BASE_VERSION",
        '--define', "${PKG_NAME_FOR_PKG}_version ${PKG_VERSION}",
        '--define', "${PKG_NAME_FOR_PKG}_release ${BIGTOP_BUILD_STAMP}%{?dist}",
        '-bs', '--nodeps', "--buildroot=${PKG_BUILD_DIR}/rpm/INSTALL",
        specFileName,
        '--define', "pkg_name_suffix ${RPM_PKG_NAME_SUFFIX}",
    ]
    exec {
      workingDir BASE_DIR
      executable 'rpmbuild'
      args command
    }
    mkdir(PKG_OUTPUT_DIR)
    def RELEASE_DIST = "rpmbuild --eval '%{?dist}' 2>/dev/null".execute().text.trim().replaceAll("'",'')
    copy {
      from "$PKG_BUILD_DIR/rpm/SRPMS/${PKG_NAME}${GRADLE_PKG_NAME_SUFFIX}-${PKG_VERSION}-${BIGTOP_BUILD_STAMP}${RELEASE_DIST}.src.rpm"
      into PKG_OUTPUT_DIR
    }
    touchTargetFile(config.bigtop.components[target].targetsrpm)
  }
  if (nativePackaging) {
    def ptype = nativePackaging.pkg
    if (!packaging || ptype.equalsIgnoreCase(packaging)) {
      task "$target-pkg" (dependsOn: "$target-$ptype",
          description: "Invoking a native binary packaging target $ptype",
          group: PACKAGES_GROUP) doLast {
      }
      task "$target-spkg" (dependsOn: "$target-s$ptype",
          description: "Invoking a native binary packaging target s$ptype",
          group: PACKAGES_GROUP) doLast {
      }
    }
  }
  task "$target-pkg-ind" (
          description: "Invoking a native binary packaging for $target in Docker. Usage: \$ ./gradlew " +
                  "-POS=[rockylinux-8|fedora-38|debian-11|ubuntu-22.04] " +
                  "-Pprefix=[trunk|1.4.0|1.3.0|1.2.1|...] $target-pkg-ind " +
                  "-Pnexus=[true|false]" +
                  "[-Ppreferred-java-version=[8|11]]" +
                  "[-Pmvn-cache-volume=[true|false]]",
          group: PACKAGES_GROUP) doLast {
    def _prefix = project.hasProperty("prefix") ? prefix : "trunk"
    def _OS = project.hasProperty("OS") ? OS : "ubuntu-22.04"
    if (_OS == "fedora-38" || _OS == "ubuntu-22.04") {
      logger.warn(_OS + ' requires privileged mode, pass `-Pdocker-run-option="--privileged"` to gradle option')
    }
    def _target_pkg = "$target-pkg"
    def _preferred_java_version = project.hasProperty("preferred-java-version") ? this.'preferred-java-version' : "8"
    def _mvn_cache_volume = project.hasProperty("mvn-cache-volume") ? this.'mvn-cache-volume' : ""
    def _docker_run_option = project.hasProperty("docker-run-option") ? this.'docker-run-option' : ""
    def additionalConfigKeys = ['git_repo', 'git_ref', 'git_dir', 'git_commit_hash', 'base_version']
    additionalConfigKeys.each { key ->
      if (project.hasProperty(key)) {
        _target_pkg += " -P${key}=" + project.property(key)
      }
    }
    def command = [
            'bash', '-x',
            './bigtop-ci/build.sh',
            '--prefix', _prefix,
            '--os', _OS,
            '--target', _target_pkg,
            '--docker-run-option', _docker_run_option,
    ]
    if ( project.hasProperty("nexus") ) {
      command.add('--nexus')
    }
    if ( _preferred_java_version.toInteger() > 8 ) {
      command.add('--preferred-java-version')
      command.add( _preferred_java_version)
    }
    if ( project.hasProperty("mvn-cache-volume") ) {
      command.add('--mvn-cache-volume')
      command.add(_mvn_cache_volume)
    }

    println "Building $_prefix $_target_pkg on $_OS in Docker...\n"

    exec {
      workingDir BASE_DIR
      commandLine command
    }
  }
  task "$target-version" (description: "Show version of $target component", group: PACKAGES_GROUP) doLast {
    println "Base: ${config.bigtop.components[target].version.base}"
  }
  task "${target}_vardefines" doLast {
    if (project.hasProperty('base_version')) {
      def BASE_VERSION = project.property("base_version")
      if (config.bigtop.components[target].tarball.source) {
        config.bigtop.components[target].tarball.source = config.bigtop.components[target].tarball.source
                .replaceAll(config.bigtop.components[target].version.base, BASE_VERSION)
      }
      config.bigtop.components[target].tarball.destination = config.bigtop.components[target].tarball.destination
              .replaceAll(config.bigtop.components[target].version.base, BASE_VERSION)

      config.bigtop.components[target].version.base = BASE_VERSION
      config.bigtop.components[target].version.pkg = BASE_VERSION
    }

    setDefaults(config.bigtop.components[target])

    config.bigtop.components[target].package.release = '1'

    config.bigtop.components[target].builddir = config.bigtop.builddir + "/$target"
    config.bigtop.components[target].outputdir = config.bigtop.outputdir + "/$target"
    config.bigtop.components[target].srcdir = config.bigtop.builddir + "/source"
    config.bigtop.components[target].tardir = config.bigtop.builddir + "/$target/tar/${target}-${config.bigtop.components[target].version.base}"
    config.bigtop.components[target].seedtar = config.bigtop.builddir + "/$target/tar/" + config.bigtop.components[target].tarball.destination

    config.bigtop.components[target].downloadurl =
        (!config.bigtop.components[target].url.isEmpty() &&
            !config.bigtop.components[target].url.site.isEmpty() &&
            !config.bigtop.components[target].tarball.source.isEmpty()) ?
            config.bigtop.components[target].url.site + '/' + config.bigtop.components[target].tarball.source : null
        if (!config.bigtop.components[target].tarball.destination.isEmpty())
          config.bigtop.components[target].downloaddst = DL_DIR + '/' + config.bigtop.components[target].tarball.destination

    // test that the download url will return http 200.  If it does not, use the ARCHIVE url instead of the MIRROR SITE url
    if (!config.bigtop.components[target].url.isEmpty() && !ifExists(config.bigtop.components[target].downloadurl)) {
      config.bigtop.components[target].downloadurl = config.bigtop.components[target].url.archive + '/' + config.bigtop.components[target].tarball.source
    }

    config.bigtop.components[target].targetdl  = config.bigtop.components[target].builddir + '/.download'
    config.bigtop.components[target].targettar  = config.bigtop.components[target].builddir + '/.tar'
    config.bigtop.components[target].targetsrpm  = config.bigtop.components[target].builddir + '/.srpm'
    config.bigtop.components[target].targetrpm  = config.bigtop.components[target].builddir + '/.rpm'
    config.bigtop.components[target].targetsdeb  = config.bigtop.components[target].builddir + '/.sdeb'
    config.bigtop.components[target].targetdeb  = config.bigtop.components[target].builddir + '/.deb'
    config.bigtop.components[target].targetrelnotes  = config.bigtop.components[target].builddir + '/.relnotes'

    if (System.getProperty(VERBOSE)) {
      println "$target properties (explcit and derived)"
      config.bigtop.components[target].toProperties(target).each {k ,v ->
        println "$k\t= $v"
      }
    }
  }

  task "$target-info" (dependsOn: "${target}_vardefines",
      description: "Info about $target component build",
      group: PACKAGES_GROUP) doLast {
    println "Info for package $target"
    println "  Will download from URL: ${config.bigtop.components[target].downloadurl}"
    println "  To destination file: ${config.bigtop.components[target].downloaddst}"
    println "  Then unpack into ${config.bigtop.components[target].srcdir}"
    println "  And create a seed tarball ${config.bigtop.components[target].seedtar}"

    println "Version: " + config.bigtop.components[target].version.base
    //TODO more about stamping
  }
  task "$target-relnotes" (description: "Preparing release notes for $target. No yet implemented!!!", group: PACKAGES_GROUP) doLast {
  }
  task "$target-clean" (dependsOn: "${target}_vardefines",
      description: "Removing $target component build and output directories",
      group: PACKAGES_GROUP) doLast {
    safeDelete(config.bigtop.components[target].builddir)
    safeDelete(config.bigtop.components[target].outputdir)
  }
  task "$target-help" (description: "List of available tasks for $target", group: PACKAGES_GROUP) doLast {
    println (target + "\n\t[" + tasks.findAll { alltask -> alltask.name.startsWith(target)}*.name.join(", ") + "]")
  }

  bomTasks.add(target)
}

// Let's enforce some of the configuration requirements
private void doValidateBOM(config) {
  assert config.bigtop.version
  assert config.bigtop.stack.jdk
  assert config.bigtop.stack.scala
  assert config.bigtop.apache.size() != 0
}

// We need to make sure that all dynamic tasks are available for invocation
project.afterEvaluate {
  doValidateBOM(config)
  config.bigtop.components.each { component_label, comp ->
    assert component_label == comp.name
    genTasks(comp.name, comp.packaging)
  }
  // Versions need to be preserved for more than just component:
  //  - there are JDK version requirement
  //  - possibly more in the future
  bomVersions = config.bigtop.components.collect {
    "${toOldStyleName(it.value.name)}_VERSION=${it.value.version.base}"
  }
  bomVersions += config.bigtop.stack.collect { // Version of the stack have different syntax
    "${toOldStyleName(it.key)}_VERSION=${it.value.version_base}"
  }
  if (System.getProperty(VERBOSE))println "BIGTOP_BOM_VERSIONS:\n${bomVersions.join(' ')}"
  // Putting all targets of different types into one common target
  task "srpm" (dependsOn: tasks.findAll { alltask -> alltask.name.endsWith("-srpm")}*.name,
      description: "Build all SRPM packages for the stack components",
      group: PACKAGES_GROUP
  ) doLast { }
  task "rpm" (dependsOn: tasks.findAll { alltask -> alltask.name.endsWith("-rpm")}*.name,
      description: "Build all RPM packages for the stack",
      group: PACKAGES_GROUP
  ) doLast { }
  task "sdeb" (dependsOn: tasks.findAll { alltask -> alltask.name.endsWith("-sdeb")}*.name,
      description: "Build all SDEB packages for the stack components",
      group: PACKAGES_GROUP
  ) doLast { }
  task "deb" (dependsOn: tasks.findAll { alltask -> alltask.name.endsWith("-deb")}*.name,
      description: "Build all DEB packages for the stack components",
      group: PACKAGES_GROUP
  ) doLast { }
  task "pkgs" (dependsOn: tasks.findAll { alltask -> alltask.name.endsWith("-pkg")}*.name,
      description: "Build all native packages for the stack components",
      group: PACKAGES_GROUP
  ) doLast { }
  task "pkgs-ind" (dependsOn: tasks.findAll { alltask -> alltask.name.endsWith("-pkg-ind")}*.name,
          description: "Build all native packages for the stack components inside Docker",
          group: PACKAGES_GROUP
  ) doLast { }

  task allclean (dependsOn: [clean, tasks.findAll { alltask -> alltask.name.endsWith("-clean")}*.name],
      description: "Removing $BUILD_DIR, $OUTPUT_DIR, and $DIST_DIR.\n\t\t" +
        "Cleaning all components' build and output directories.",
      group: PACKAGES_GROUP) doLast {
    safeDelete(BUILD_DIR)
    safeDelete(OUTPUT_DIR)
    safeDelete(DIST_DIR)
  }
  task realclean (dependsOn: allclean,
      description: "Removing $BUILD_DIR, $OUTPUT_DIR, $DIST_DIR, and $DL_DIR",
      group: PACKAGES_GROUP) doLast {
    delete (DL_DIR)
  }
}

task "apt" (
    description: "Creating APT repository",
    group: PACKAGES_GROUP) doLast {

  delete ( "$OUTPUT_DIR/apt")
  mkdir ("$OUTPUT_DIR/apt/conf")

  generateDistributionsFile("$OUTPUT_DIR/apt/conf/distributions");

  fileTree (OUTPUT_DIR) {
    include "*/*.changes"
  }.each  { changeFile ->
    exec {
      workingDir BUILD_DIR
      commandLine "reprepro -Vb $OUTPUT_DIR/apt include bigtop $changeFile".split(' ')
    }
  }
}

task "yum" (
    description: "Creating YUM repository",
    group: PACKAGES_GROUP) doLast {
  delete ( "$OUTPUT_DIR/repodata")
  exec {
    workingDir BUILD_DIR
    commandLine "createrepo -o $OUTPUT_DIR $OUTPUT_DIR".split(' ')
  }
}

if (nativePackaging) {
  task "repo" (dependsOn: nativePackaging.repo,
      description: "Invoking a native repository target ${nativePackaging.repo}",
      group: PACKAGES_GROUP) doLast { }
}

task "repo-ind" (
    description: "Invoking a native repository in Docker. Usage: \$ ./gradlew " +
            "-POS=[rockylinux-8|fedora-38|debian-11|ubuntu-22.04] " +
            "-Pprefix=[trunk|1.4.0|1.3.0|1.2.1|...] repo-ind",
    group: PACKAGES_GROUP) doLast {
  def _prefix = project.hasProperty("prefix") ? prefix : "trunk"
  def _OS = project.hasProperty("OS") ? OS : "ubuntu-22.04"
  if (_OS == "fedora-38") {
    logger.warn('fedora-38 requires privileged mode, pass `-Pdocker-run-option="--privileged"` to gradle option')
  }
  def _docker_run_option = project.hasProperty("docker-run-option") ? this.'docker-run-option' : ""
  def command = [
      'bash', '-x',
      './bigtop-ci/build.sh',
      '--prefix', _prefix,
      '--os', _OS,
      '--target', 'repo',
      '--docker-run-option', _docker_run_option
  ]
  exec {
    workingDir BASE_DIR
    commandLine command
  }
}
