#!/usr/bin/python

# This script is just a simulation of how MEAD process templates
# that we use to be able to do local builds using the same
# template that we use for brew builds.

import lxml.etree
import optparse
import os
import re
import shutil
import subprocess
import sys
import tempfile
import time
import traceback


# Some of the modules we need are not that common, so it is good
# idea to check that they are available:
def safeImport(module, package):
    try:
        return __import__(module)
    except:
        print("Can't import module \"%s\", is the package \"%s\" installed?" % (module, package))
        sys.exit(1)

lxml = safeImport("lxml.etree", "python-lxml")
Cheetah = safeImport("Cheetah.Template", "python-cheetah")


# Template for the settings file used by maven:
SETTINGS="""
<settings
  xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

  <!-- Use an alternative local repository to make sure that only
       the atifacts from MEAD are used: -->
  <localRepository>${env.HOME}/.minimead/repository</localRepository>

  <!-- Mirror everything so that we only use artifacts available
       in the MEAD environment: -->
  <mirrors>
    <mirror>
      <id>mead</id>
      <name>mead</name>
      <url>http://download.devel.redhat.com/brewroot/repos/rhevm-3-rhel-6-mead-build/latest/maven</url>
      <mirrorOf>*</mirrorOf>
    </mirror>
  </mirrors>

</settings>
"""


def findPaths(dir, pattern='.*'):
    dirs = [dir]
    paths = []
    matcher = re.compile(pattern)
    while dirs:
        current = dirs.pop()
        names = os.listdir(current)
        for name in names:
            path = os.path.join(current, name)
            if os.path.isdir(path):
                dirs.append(path)
            elif os.path.isfile(path):
                relative = os.path.relpath(path, dir)
                if matcher.match(relative):
                    paths.append(relative)
    return paths


def createDirectory(*paths):
    path = os.path.join(*paths)
    if not os.path.exists(path):
        print("Creating directory \"%s\" ..." % path)
        os.mkdir(path)
    return path


def runCommand(args):
    print("Running command %s ..." % args)
    return subprocess.call(args)


def evalCommand(args):
    print("Evaluating command \"%s\" ..." % args)
    process = subprocess.Popen(args, stdout=subprocess.PIPE)
    output, errors = process.communicate()
    result = process.wait()
    return result, output


def main():
    # Create the command line parser:
    usage = "Usage: %prog [OPTION]..."
    parser = optparse.OptionParser(usage)
    parser.description = "A simple simulation of MEAD builds for RHEV-M."

    # Help option:
    help = parser.get_option("--help")
    help.help = "Show this help message and exit."

    # Option to force the RPM release:
    parser.add_option(
        "-r", "--release",
        dest="release",
        help=
            "Force the version number of the RPMs to the given value. The "
            "default is 0.X.YYYYMMDDgitFFFFFF.USER, where X is a build number "
            "stored in the $HOME/.minimead/build file and increased by 1 with "
            "each build, YYYYMMDD is the date, FFFFFF is the short git hash of "
            "the latest commit and USER is the name of the user doing the "
            "build."
    )

    # Option to generate slim RPMs (without GWT compilation):
    parser.add_option(
        "-q", "--quick",
        dest="quick",
        action="store_true",
        default=False,
        help=
            "Generate the RPMs quickly avoiding the GWT compilation. Note "
            "that this generates incomplete packages and is only useful "
            "for testing the build process itself or for testing with the "
            "backend and the RESTAPI only. Implies --skip-tests."
    )

    # Option to skip tests:
    parser.add_option(
        "-s", "--skip-tests",
        dest="skip_tests",
        action="store_true",
        default=False,
        help="Skip unit tests."
    )

    # Parse the command line:
    (options, args) = parser.parse_args()

    if options.quick:
        options.skip_tests = True

    # Create the .minimead directory if needed:
    print("Creating .minimead directory ...")
    minimeadDir = os.path.join(os.getenv("HOME"), ".minimead")
    if not os.path.isdir(minimeadDir):
        createDirectory(minimeadDir)

    # Get the next build number:
    print("Calculating build number ...")
    buildNumberFile = os.path.join(minimeadDir, "build")
    if os.path.exists(buildNumberFile):
        with open(buildNumberFile, "r") as buildNumberFd:
            buildNumber = int(buildNumberFd.read()) + 1
    else:
        buildNumber = 1
    with open(buildNumberFile, "w") as buildNumberFd:
        buildNumberFd.write(str(buildNumber) + "\n")

    # Create top level RPM directory:
    print("Creating top level RPM directory ...")
    topDir = os.path.join(minimeadDir, "rpmbuild")
    if os.path.exists(topDir):
        shutil.rmtree(topDir)
    topDir = createDirectory(topDir)

    # Create the rest of RPM directories:
    print("Creating other RPM directories ...")
    specsDir = createDirectory(topDir, "SPECS")
    sourcesDir = createDirectory(topDir, "SOURCES")
    buildDir = createDirectory(topDir, "BUILD")
    buildRootDir = createDirectory(topDir, "BUILDROOT")

    # Create the artifact deployment directory:
    print("Creating artifact deployment directory ...")
    deploymentDir = os.path.join(minimeadDir, "deployment")
    if os.path.exists(deploymentDir):
        shutil.rmtree(deploymentDir)
    deploymentDir = createDirectory(deploymentDir)

    # Generate the maven settins file:
    print("Creating maven settings file ...")
    settingsFile = os.path.join(minimeadDir, "settings.xml")
    with open(settingsFile, "w") as settingsFd:
        settingsFd.write(SETTINGS)

    # Prepare the basic maven command:
    mvnCommand = [
        "mvn",
        "-s", settingsFile,
        "-C",
        "-DaltDeploymentRepository=minimead::default::file://%s" % deploymentDir,
        "clean",
        "deploy",
    ]

    # Add the GWT options only if this is a normal build (quick
    # builds don't include them):
    if not options.quick:
        mvnCommand.extend([
            "-Pgwt-user,gwt-admin",
            "-Dgwt.compiler.localWorkers=1",
            "-Dgwt.userAgent=gecko1_8",
        ])

    if options.skip_tests:
        mvnCommand.extend([
            "-DskipTests=true"
        ])

    # Invoke the maven build, like MEAD would do:
    print("Executing maven build ...")
    result = runCommand(mvnCommand)
    if result != 0:
        print("Maven build failed with exit code %d." % result)
        sys.exit(1)

    # Copy all the artifacts from the temporary deployment
    # directory to the sources directory and add them to the list
    # of artifacts:
    artifactPaths = findPaths(deploymentDir, "^.*\\.(pom|jar|war|ear)$")
    for artifactPath in artifactPaths:
        artifactFile = os.path.join(deploymentDir, artifactPath)
        print("Copying artifact \"%s\" to sources directory ..." % artifactFile)
        shutil.copy(artifactFile, sourcesDir)

    # Parse the root POM file, as we need the details to generate
    # the source .zip file:
    print("Parsing root POM file ...")
    pomDoc = lxml.etree.parse("pom.xml")
    nsMap = { "p": "http://maven.apache.org/POM/4.0.0", }
    groupId = pomDoc.xpath("/p:project/p:groupId", namespaces=nsMap)[0].text
    artifactId = pomDoc.xpath("/p:project/p:artifactId", namespaces=nsMap)[0].text
    version = pomDoc.xpath("/p:project/p:version", namespaces=nsMap)[0].text

    # Generate the source .zip file as MEAD would do:
    print("Generating source .zip file ...")
    sourceFile = "%s/%s-%s-%s-sources.zip" % (sourcesDir, groupId, artifactId, version)
    result = runCommand([
        "git", "archive",
        "--prefix", "ovirt-engine/",
        "--output", sourceFile,
        "HEAD",
    ])
    if result != 0:
        print("Failed to generate source .zip file.")
        sys.exit(1)

    # Build the list of artifacts containing the sources file as
    # the first element and then all the other files in the
    # sources directory:
    print("Building list of artifacts ...")
    allArtifacts = []
    allArtifacts.append(os.path.basename(sourceFile))
    artifactPaths = findPaths(sourcesDir, "^.*\\.(pom|jar|war|ear)$")
    allArtifacts.extend(artifactPaths)

    # Populate the list of objects to pass to the template:
    print("Populating template ...")
    if options.release:
        release = options.release
    else:
        releaseBase = "0.%d" % buildNumber
        releaseDate = time.strftime("%Y%m%d")
        result, releaseHash = evalCommand(["git", "log", "--format=%h", "-1"])
        if result != 0:
            print("Failed to compute git hash.")
            sys.exit(1)
        releaseHash = releaseHash.strip()
        releaseSuffix = os.getenv("USER")
        release = releaseBase + "." + releaseDate + "git" + releaseHash + "." + releaseSuffix
    searchList = [{
        "version": version,
        "release": release,
        "all_artifacts": allArtifacts,
    }]

    # Create the template object:
    print("Generating spec ...")
    specTemplate = Cheetah.Template.Template(file="packaging/fedora/spec/ovirt-engine.spec.in", searchList=searchList)
    specText = str(specTemplate)
    specFile = os.path.join(specsDir, "ovirt-engine.spec")
    with open(specFile, "w") as specFd:
        specFd.write(specText)

    # Create the output directory:
    print("Creating output directory ...")
    outDir = createDirectory(os.getcwd(), "output")
    repoDir = createDirectory(outDir, version + "-" + release)

    # Build the RPM:
    print("Building package ...")
    result = runCommand([
        "rpmbuild",
        "-bb",
        "--define=__jar_repack 0",
        "--define=dist .el6ev",
        "--define=_topdir %s" % topDir,
        "--define=_rpmdir %s" % repoDir,
        "--define=_rpmfilename %{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm",
        specFile,
    ])
    if result != 0:
        print("Build of package failed with exit code %d." % result)
        sys.exit(1)

    # Create the repository metadata:
    print("Creating repository metadata ...")
    result = runCommand(["createrepo", repoDir])
    if result != 0:
        print("Creation of repository metadata failed with exit code %d." % result)
        sys.exit(1)

if __name__ == "__main__":
    try:
        main()
    except SystemExit:
        raise
    except BaseException as exception:
        print(traceback.format_exc())
        sys.exit(1)
