#!/usr/bin/env python

import argparse
import os
import os.path
import shutil
import subprocess
import sys

class BuildError(Exception):
    def __init__(self,
                 string=None,
                 path=None,
                 inferior_error=None):
        self.m_string = string
        self.m_path = path
        self.m_inferior_error = inferior_error
    def __str__(self):
        if self.m_path and self.m_string:
            return "Build error: %s (referring to %s)" % (self.m_string, self.m_path)
        if self.m_path:
            return "Build error (referring to %s)" % (self.m_path)
        if self.m_string:
            return "Build error: %s" % (self.m_string)
        return "Build error"

class LLDBBuildBot:
    def __init__(self, 
                 build_directory_path,
                 log_path,
                 lldb_repository_url="http://llvm.org/svn/llvm-project/lldb/trunk",
                 llvm_repository_url="http://llvm.org/svn/llvm-project/llvm/trunk",
                 clang_repository_url="http://llvm.org/svn/llvm-project/cfe/trunk",
                 revision=None):
        self.m_build_directory_path = os.path.abspath(build_directory_path)
        self.m_log_path = os.path.abspath(log_path)
        self.m_lldb_repository_url = lldb_repository_url
        self.m_llvm_repository_url = llvm_repository_url
        self.m_clang_repository_url = clang_repository_url
        self.m_revision = revision
        self.m_log_stream = None
    def Setup(self):
        if os.path.exists(self.m_build_directory_path):
            raise BuildError(string="Build directory exists", path=self.m_build_directory_path)
        if os.path.exists(self.m_log_path):
            raise BuildError(string="Log file exists", path=self.m_log_path)
        self.m_log_stream = open(self.m_log_path, 'w')
        os.mkdir(self.m_build_directory_path)
    def Checkout(self):
        os.chdir(self.m_build_directory_path)
        
        cmdline_prefix = []
        
        if self.m_revision != None:
            cmdline_prefix = ["svn", "-r %s" % (self.m_revision), "co"]
        else:
            cmdline_prefix = ["svn", "co"]

        returncode = subprocess.call(cmdline_prefix + [self.m_lldb_repository_url, "lldb"], 
                                     stdout=self.m_log_stream, 
                                     stderr=self.m_log_stream)
        if returncode != 0:
            raise BuildError(string="Couldn't checkout LLDB")

        os.chdir("lldb")

        returncode = subprocess.call(cmdline_prefix + [self.m_llvm_repository_url, "llvm.checkout"], 
                                     stdout=self.m_log_stream, 
                                     stderr=self.m_log_stream)

        if returncode != 0:
            raise BuildError(string="Couldn't checkout LLVM")

        os.symlink("llvm.checkout", "llvm")

        os.chdir("llvm/tools")

        returncode = subprocess.call(cmdline_prefix + [self.m_clang_repository_url, "clang"], 
                                     stdout=self.m_log_stream, 
                                     stderr=self.m_log_stream)

        if returncode != 0:
            raise BuildError(string="Couldn't checkout Clang")
    def Build(self):
        os.chdir(self.m_build_directory_path)
        os.chdir("lldb/llvm")

        returncode = subprocess.call(["./configure", "--disable-optimized", "--enable-assertions", "--enable-targets=x86,x86_64,arm"], 
                                     stdout=self.m_log_stream, 
                                     stderr=self.m_log_stream)

        if returncode != 0:
            raise BuildError(string="Couldn't configure LLVM/Clang")

        returncode = subprocess.call(["make"], 
                                     stdout=self.m_log_stream, 
                                     stderr=self.m_log_stream)

        if returncode != 0:
            raise BuildError(string="Couldn't build LLVM/Clang")

        os.chdir(self.m_build_directory_path)
        os.chdir("lldb")

        returncode = subprocess.call(["xcodebuild", 
                                      "-project", "lldb.xcodeproj", 
                                      "-target", "lldb-tool", 
                                      "-configuration", "Debug", 
                                      "-arch", "x86_64",
                                      "LLVM_CONFIGURATION=Debug+Asserts",
                                      "OBJROOT=build"],
                                      stdout=self.m_log_stream,
                                      stderr=self.m_log_stream)

        if returncode != 0:
            raise BuildError(string="Couldn't build LLDB")
    def Test(self):
        os.chdir(self.m_build_directory_path)
        os.chdir("lldb/test")
        
        returncode = subprocess.call(["./dotest.py", "-t"], 
                                     stdout=self.m_log_stream, 
                                     stderr=self.m_log_stream)
    def Takedown(self):
        os.chdir("/tmp")
        self.m_log_stream.close()
        shutil.rmtree(self.m_build_directory_path)
    def Run(self):
        self.Setup()
        self.Checkout()
        self.Build()
        #self.Test()
        self.Takedown()

def GetArgParser():
    parser = argparse.ArgumentParser(description="Try to build LLDB/LLVM/Clang and run the full test suite.")
    parser.add_argument("--build-path", "-b", required=True, help="A (nonexistent) path to put temporary build products into", metavar="path")
    parser.add_argument("--log-file", "-l", required=True, help="The name of a (nonexistent) log file", metavar="file")
    parser.add_argument("--revision", "-r", required=False, help="The LLVM revision to use", metavar="N")
    return parser

parser = GetArgParser()
arg_dict = vars(parser.parse_args())

build_bot = LLDBBuildBot(build_directory_path=arg_dict["build_path"],
                         log_path=arg_dict["log_file"],
                         revision=arg_dict["revision"])

try:
    build_bot.Run()
except BuildError as err:
    print err
