# Copyright 2011 Google Inc. All Rights Reserved.
"""Helper modules for Android toolchain test infrastructure.

Provides following Android toolchain test jobs and commands.
. Checkout Android toolchain source code
. Build Android toolchain
. Checkout and build Android tree
. Checkout/build/run Android benchmarks, generate size dashboard
. Transform size dashboard to report, send perflab jobid to
  perflab dashboard server.(TODO)
"""

__author__ = 'jingyu@google.com (Jing Yu)'

import os.path

from automation.clients.helper import jobs
from automation.clients.helper import perforce
from automation.common import command as cmd
from automation.common import job


class JobsFactory(object):

  def __init__(self, gcc_version='4.4.3', build_type='DEVELOPMENT'):
    assert gcc_version in ['4.4.3', '4.6', 'google_main', 'fsf_trunk']
    assert build_type in ['DEVELOPMENT', 'RELEASE']

    self.gcc_version = gcc_version
    self.commands = CommandsFactory(gcc_version, build_type)
    self.tc_tag = 'gcc-%s-%s' % (gcc_version, build_type)

  def CheckoutAndroidToolchain(self):
    """Check out Android toolchain sources by release and gcc version."""
    command = self.commands.CheckoutAndroidToolchain()
    new_job = jobs.CreateLinuxJob('AndroidCheckoutToolchain(%s)' % self.tc_tag,
                                  command)
    checkout_dir_dep = job.FolderDependency(new_job, self.commands.CHECKOUT_DIR)
    return new_job, checkout_dir_dep

  def BuildAndroidToolchain(self, checkout_dir_dep):
    """Build Android Toolchain."""
    command = self.commands.BuildAndroidToolchain()
    new_job = jobs.CreateLinuxJob('AndroidBuildToolchain(%s)' % self.tc_tag,
                                  command)
    new_job.DependsOnFolder(checkout_dir_dep)
    tc_prefix_dep = job.FolderDependency(new_job,
                                         self.commands.toolchain_prefix_dir)
    return new_job, tc_prefix_dep

  def BuildAndroidImage(self,
                        tc_prefix_dep,
                        product='stingray',
                        branch='ics-release'):
    assert product in ['stingray', 'passion', 'trygon', 'soju']
    assert branch in ['honeycomb-release', 'ics-release']
    command = self.commands.BuildAndroidImage(product, branch)
    new_job = jobs.CreateLinuxJob('AndroidGetBuildTree(%s)' % self.tc_tag,
                                  command)
    new_job.DependsOnFolder(tc_prefix_dep)
    return new_job

  def Benchmark(self, tc_prefix_dep, arch='soju'):
    assert arch in ['soju', 'stingray']
    script_cmd = self.commands.CheckoutScripts()
    experiment_tag = 'android/nightly/%s/%s/$JOB_ID' % (self.tc_tag, arch)
    build_run_benchmark_cmd = self.commands.BuildRunBenchmark(arch,
                                                              experiment_tag)
    command = cmd.Chain(script_cmd, build_run_benchmark_cmd)
    new_job = jobs.CreateLinuxJob('AndroidBenchmarking(%s)' % self.tc_tag,
                                  command)
    new_job.DependsOnFolder(tc_prefix_dep)
    return new_job


class CommandsFactory(object):
  CHECKOUT_DIR = 'androidtc-checkout-dir'
  TOOLCHAIN_SRC_DIR = os.path.join(CHECKOUT_DIR, 'src')
  TOOLCHAIN_BUILD_DIR = 'obj'
  ANDROID_TREES_DIR = 'android_trees'
  TOOLS_DIR = 'android-tools'
  BENCHMARK_OUT_DIR = 'results'

  def __init__(self, gcc_version, build_type):
    assert gcc_version in ['4.4.3', '4.6', 'google_main', 'fsf_trunk']
    assert build_type in ['DEVELOPMENT', 'RELEASE']

    self.build_type = build_type
    self.gcc_version = gcc_version
    self.binutils_version = '2.21'
    self.gold_version = '2.21'
    self.toolchain_prefix_dir = 'install-gcc-%s-%s' % (gcc_version, build_type)
    self.p4client = self._CreatePerforceClient()
    self.scripts = ScriptsFactory(self.gcc_version, self.binutils_version,
                                  self.gold_version)

  def _CreatePerforceClient(self):
    p4_dev_path = 'gcctools/google_vendor_src_branch'
    mobile_rel_branch = ('branches/'
                         'mobile_toolchain_v15_release_branch/gcctools/'
                         'google_vendor_src_branch')
    gcc_443_rel_branch = ('branches/'
                          'android_compiler_v14_release_branch/gcctools/'
                          'google_vendor_src_branch')

    # Common views for tools
    p4view = perforce.View('depot2', perforce.PathMapping.ListFromPathTuples([(
        'gcctools/android/build/...', 'src/build/...'), (
            'gcctools/android/Tarballs/...', 'src/tarballs/...')]))
    for mapping in perforce.PathMapping.ListFromPathDict(
        {'gcctools/android': ['tools/scripts/...', 'master/...']}):
      p4view.add(mapping)

    # Add views for gdb
    p4view.add(perforce.PathMapping(p4_dev_path, 'src',
                                    'gdb/gdb-7.1.x-android/...'))

    # Add view for binutils for ld and gold
    if self.build_type is 'RELEASE':
      binutils_branch = mobile_rel_branch
    else:
      binutils_branch = p4_dev_path
    p4view.add(perforce.PathMapping(binutils_branch, 'src', (
        'binutils/binutils-%s/...' % self.binutils_version)))
    if self.binutils_version != self.gold_version:
      p4view.add(perforce.PathMapping(binutils_branch, 'src', (
          'binutils/binutils-%s/...' % self.gold_version)))

    # Add view for gcc if gcc_version is '4.4.3'.
    if self.gcc_version == '4.4.3':
      gcc443_path = 'gcc/gcc-4.4.3/...'
      if self.build_type is 'RELEASE':
        p4view.add(perforce.PathMapping(gcc_443_rel_branch, 'src', gcc443_path))
      else:
        p4view.add(perforce.PathMapping(p4_dev_path, 'src', gcc443_path))

    return perforce.CommandsFactory(self.CHECKOUT_DIR, p4view)

  def _CheckoutGCCFromSVN(self):
    """Check out gcc from fsf svn.

       Return the command that check out gcc from svn
       to gcc_required_dir (=TOOLCHAIN_SRC_DIR/src/gcc/gcc-xxx).

       TODO:
         Create a svn class that does these jobs.
         Parallelize p4 checkout and svn checkout.
    """
    if self.gcc_version == '4.4.3':
      return ''
    assert self.gcc_version in ['4.6', 'google_main', 'fsf_trunk']

    gcc_branches_dir = {'4.6': 'branches/google/gcc-4_6',
                        'google_main': 'branches/google/main',
                        'fsf_trunk': 'trunk'}

    # Find GCC revision number, output it to TOOLCHAIN_SRC_DIR/CLNUM_GCC
    svn_get_revision = cmd.Pipe(
        cmd.Shell('svn', 'info'),
        cmd.Shell('grep', '"Revision:"'),
        cmd.Shell('sed', '-E', '"s,Revision: ([0-9]+).*,\\1,"'),
        output='../../../CLNUM_GCC')

    svn_co_command = 'svn co svn://gcc.gnu.org/svn/gcc/%s .' % (
        gcc_branches_dir[self.gcc_version])

    gcc_required_dir = os.path.join(self.TOOLCHAIN_SRC_DIR, 'gcc',
                                    'gcc-%s' % self.gcc_version)

    return cmd.Chain(
        cmd.MakeDir(gcc_required_dir),
        cmd.Wrapper(
            cmd.Chain(svn_co_command, svn_get_revision),
            cwd=gcc_required_dir))

  def CheckoutAndroidToolchain(self):
    p4client = self.p4client
    command = p4client.SetupAndDo(p4client.Sync(),
                                  p4client.SaveCurrentCLNumber('CLNUM'),
                                  p4client.Remove())
    if self.gcc_version != '4.4.3':
      command.append(self._CheckoutGCCFromSVN())

    return command

  def BuildAndroidToolchain(self):
    script_cmd = self.scripts.BuildAndroidToolchain(
        self.toolchain_prefix_dir, self.CHECKOUT_DIR, self.TOOLCHAIN_BUILD_DIR,
        self.TOOLCHAIN_SRC_DIR)

    # Record toolchain and gcc CL number
    record_cl_cmd = cmd.Copy(
        os.path.join(self.CHECKOUT_DIR, 'CLNUM*'),
        to_dir=self.toolchain_prefix_dir)
    save_cmd = cmd.Tar(
        os.path.join('$JOB_TMP', 'results', '%s.tar.bz2' %
                     self.toolchain_prefix_dir), self.toolchain_prefix_dir)
    return cmd.Chain(script_cmd, record_cl_cmd, save_cmd)

  def _BuildAndroidTree(self, local_android_branch_dir, product):
    target_tools_prefix = os.path.join('$JOB_TMP', self.toolchain_prefix_dir,
                                       'bin', 'arm-linux-androideabi-')
    java_path = '/usr/lib/jvm/java-6-sun/bin'
    build_cmd = cmd.Shell('make', '-j8', 'PRODUCT-%s-userdebug' % product,
                          'TARGET_TOOLS_PREFIX=%s' % target_tools_prefix,
                          'PATH=%s:$PATH' % java_path)
    return cmd.Wrapper(build_cmd, cwd=local_android_branch_dir)

  def BuildAndroidImage(self, product, branch):
    assert product in ['stingray', 'passion', 'trygon', 'soju']

    # Copy the tree from atree.mtv.corp to ANDROID_TREES_DIR/branch
    androidtrees_host = 'atree.mtv.corp.google.com'
    androidtrees_path = ('/usr/local/google2/home/mobiletc-prebuild/'
                         'android_trees')
    remote_android_branch_path = os.path.join(androidtrees_path, branch)
    local_android_branch_dir = os.path.join(self.ANDROID_TREES_DIR, branch)
    gettree_cmd = cmd.RemoteCopyFrom(
        androidtrees_host, remote_android_branch_path, local_android_branch_dir)

    # Configure and build the tree
    buildtree_cmd = self._BuildAndroidTree(local_android_branch_dir, product)

    # Compress and copy system.img to result
    result_system_img = os.path.join(local_android_branch_dir, 'out', 'target',
                                     'product', product, 'system.img')
    copy_img = cmd.Copy(result_system_img, to_dir='results')
    compress_img = cmd.Shell('bzip2', os.path.join('results', 'system.img'))

    return cmd.Chain(gettree_cmd, buildtree_cmd, copy_img, compress_img)

  def CheckoutScripts(self):
    p4view = perforce.View('depot2',
                           [perforce.PathMapping('gcctools/android/tools/...',
                                                 'tools/...')])
    p4client = perforce.CommandsFactory(self.TOOLS_DIR, p4view)
    return p4client.SetupAndDo(p4client.Sync(), p4client.Remove())

  def BuildRunBenchmark(self, arch, run_experiment):
    # Copy base benchmark binaries from atree.mtv.corp
    base_benchbin_host = 'atree.mtv.corp.google.com'
    base_benchbin_path = ('/usr/local/google2/home/mobiletc-prebuild/'
                          'archive/v3binaries/2011-10-18')
    local_basebenchbin_dir = 'base_benchmark_bin'
    getbase_cmd = cmd.RemoteCopyFrom(base_benchbin_host, base_benchbin_path,
                                     local_basebenchbin_dir)

    # Build and run benchmark.
    android_arch = 'android_%s' % arch
    run_label = 'normal'
    benchmark_cmd = self.scripts.RunBenchmark(
        self.toolchain_prefix_dir, self.TOOLS_DIR, self.BENCHMARK_OUT_DIR,
        run_label, run_experiment, android_arch, local_basebenchbin_dir)

    # Extract jobid from BENCHMARK_OUT_DIR/log/jobid_normal.log file.
    # Copy jobid to www server to generate performance dashboard.
    # TODO(jingyu)

    return cmd.Chain(getbase_cmd, benchmark_cmd)


class ScriptsFactory(object):

  def __init__(self, gcc_version, binutils_version, gold_version):
    self._gcc_version = gcc_version
    self._binutils_version = binutils_version
    self._gold_version = gold_version

  def BuildAndroidToolchain(self, toolchain_prefix_dir, checkout_dir,
                            toolchain_build_dir, androidtc_src_dir):
    if self._gcc_version == '4.4.3':
      gold_option = 'both/gold'
    else:
      gold_option = 'default'

    return cmd.Shell(
        'build_androidtoolchain.sh',
        '--toolchain-src=%s' % os.path.join('$JOB_TMP', androidtc_src_dir),
        '--build-path=%s' % os.path.join('$JOB_TMP', toolchain_build_dir),
        '--install-prefix=%s' % os.path.join('$JOB_TMP', toolchain_prefix_dir),
        '--target=arm-linux-androideabi',
        '--enable-gold=%s' % gold_option,
        '--with-gcc-version=%s' % self._gcc_version,
        '--with-binutils-version=%s' % self._binutils_version,
        '--with-gold-version=%s' % self._gold_version,
        '--with-gdb-version=7.1.x-android',
        '--log-path=%s/logs' % '$JOB_HOME',
        '--android-sysroot=%s' % os.path.join('$JOB_TMP', checkout_dir,
                                              'gcctools', 'android', 'master',
                                              'honeycomb_generic_sysroot'),
        path=os.path.join(checkout_dir, 'gcctools', 'android', 'tools',
                          'scripts'))

  def RunBenchmark(self,
                   toolchain_prefix_dir,
                   checkout_dir,
                   output_dir,
                   run_label,
                   run_experiment,
                   arch,
                   base_bench_bin=None):
    if base_bench_bin:
      base_bench_opt = '--base_benchmark_bin=%s' % base_bench_bin
    else:
      base_bench_opt = ''

    return cmd.Shell(
        'benchmark.sh',
        '--android_toolchain=%s' % os.path.join('$JOB_TMP',
                                                toolchain_prefix_dir),
        '--bench_space=%s' % os.path.join('$JOB_TMP', 'bench'),
        '--benchmark_bin=%s' % os.path.join('$JOB_TMP', output_dir,
                                            'bench_bin'),
        base_bench_opt,
        '--log_path=%s' % os.path.join('$JOB_TMP', output_dir, 'log'),
        '--arch=%s' % arch,
        '--run_label=%s' % run_label,
        '--run_experiment=%s' % run_experiment,
        path=os.path.join(checkout_dir, 'tools', 'scripts'))