#!/usr/bin/env python2
#
# Copyright 2017 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# pylint: disable=cros-logging-import

"""Script to build the benchmark locally with toolchain settings."""
from __future__ import print_function

import argparse
import config
import logging
import os
import subprocess
import sys

# Turn the logging level to INFO before importing other code, to avoid having
# failed import logging messages confuse the user.
logging.basicConfig(level=logging.INFO)


def _parse_arguments_internal(argv):
  parser = argparse.ArgumentParser(description='Build benchmarks with '
                                   'specified toolchain settings')

  parser.add_argument(
      '-b', '--bench', required=True, help='Select the benchmark to be built.')

  parser.add_argument(
      '-c',
      '--compiler_dir',
      metavar='DIR',
      help='Specify the path to the compiler bin '
      'directory.')

  parser.add_argument(
      '-o', '--build_os', help='Specify the host OS to build benchmark.')

  parser.add_argument(
      '-l',
      '--llvm_prebuilts_version',
      help='Specify the version of prebuilt LLVM.')

  parser.add_argument(
      '-f',
      '--cflags',
      help='Specify the optimization cflags for '
      'the toolchain.')

  parser.add_argument(
      '--ldflags', help='Specify linker flags for the toolchain.')

  return parser.parse_args(argv)


# Set flags for compiling benchmarks, by changing the local
# CFLAGS/LDFLAGS in the android makefile of each benchmark
def set_flags(bench, cflags, ldflags):
  if not cflags:
    logging.info('No CFLAGS specified, using default settings.')
    cflags = ''
  else:
    logging.info('Cflags setting to "%s"...', cflags)

  if not ldflags:
    logging.info('No LDFLAGS specifed, using default settings.')
    ldflags = ''
  else:
    logging.info('Ldflags setting to "%s"...', ldflags)

  add_flags = config.bench_flags_dict[bench]
  add_flags(cflags, ldflags)
  logging.info('Flags set successfully!')


def set_build_os(build_os):
  # Set $BUILD_OS variable for android makefile
  if build_os:
    os.environ['BUILD_OS'] = build_os
    logging.info('BUILD_OS set to "%s"...', build_os)
  else:
    logging.info('No BUILD_OS specified, using linux as default...')


def set_llvm_prebuilts_version(llvm_prebuilts_version):
  # Set $LLVM_PREBUILTS_VERSION for android makefile
  if llvm_prebuilts_version:
    os.environ['LLVM_PREBUILTS_VERSION'] = llvm_prebuilts_version
    logging.info('LLVM_PREBUILTS_VERSION set to "%s"...',
                 llvm_prebuilts_version)
  else:
    logging.info('No LLVM_PREBUILTS_VERSION specified, using default one...')


def set_compiler(compiler):
  # If compiler_dir has been specified, copy the binaries to
  # a temporary location, set BUILD_OS and LLVM_PREBUILTS_VERSION
  # variables to the location
  if compiler:
    # Report error if path not exits
    if not os.path.isdir(compiler):
      logging.error('Error while setting compiler: '
                    'Directory %s does not exist!', compiler)
      raise OSError('Directory %s not exist.' % compiler)

    # Specify temporary directory for compiler
    tmp_dir = os.path.join(config.android_home,
                           'prebuilts/clang/host/linux-x86', 'clang-tmp')

    compiler_content = os.path.join(compiler, '.')

    # Copy compiler to new directory
    try:
      subprocess.check_call(['cp', '-rf', compiler_content, tmp_dir])
    except subprocess.CalledProcessError:
      logging.error('Error while copying the compiler to '
                    'temporary directory %s!', tmp_dir)
      raise

    # Set environment variable
    os.environ['LLVM_PREBUILTS_VERSION'] = 'clang-tmp'

    logging.info('Prebuilt Compiler set as %s.', os.path.abspath(compiler))


def set_compiler_env(bench, compiler, build_os, llvm_prebuilts_version, cflags,
                     ldflags):
  logging.info('Setting compiler options for benchmark...')

  # If no specific prebuilt compiler directory, use BUILD_OS and
  # LLVM_PREBUILTS_VERSION to set the compiler version.
  # Otherwise, use the new prebuilt compiler.
  if not compiler:
    set_build_os(build_os)
    set_llvm_prebuilts_version(llvm_prebuilts_version)
  else:
    set_compiler(compiler)

  set_flags(bench, cflags, ldflags)

  return 0


def remove_tmp_dir():
  tmp_dir = os.path.join(config.android_home, 'prebuilts/clang/host/linux-x86',
                         'clang-tmp')

  try:
    subprocess.check_call(['rm', '-r', tmp_dir])
  except subprocess.CalledProcessError:
    logging.error('Error while removing the temporary '
                  'compiler directory %s!', tmp_dir)
    raise


# Recover the makefile/blueprint from our patch after building
def restore_makefile(bench):
  pwd = os.path.join(config.android_home, config.bench_dict[bench])
  mk_file = os.path.join(pwd, 'Android.mk')
  if not os.path.exists(mk_file):
    mk_file = os.path.join(pwd, 'Android.bp')
  subprocess.check_call(['mv', os.path.join(pwd, 'tmp_makefile'), mk_file])


# Run script to build benchmark
def build_bench(bench, source_dir):
  logging.info('Start building benchmark...')

  raw_cmd = ('cd {android_home} '
             '&& source build/envsetup.sh '
             '&& lunch {product_combo} '
             '&& mmma {source_dir} -j48'.format(
                 android_home=config.android_home,
                 product_combo=config.product_combo,
                 source_dir=source_dir))

  log_file = os.path.join(config.bench_suite_dir, 'build_log')
  with open(log_file, 'a') as logfile:
    log_head = 'Log for building benchmark: %s\n' % (bench)
    logfile.write(log_head)
    try:
      subprocess.check_call(
          ['bash', '-c', raw_cmd], stdout=logfile, stderr=logfile)
    except subprocess.CalledProcessError:
      logging.error('Error while running %s, please check '
                    '%s for more info.', raw_cmd, log_file)
      restore_makefile(bench)
      raise

  logging.info('Logs for building benchmark %s are written to %s.', bench,
               log_file)
  logging.info('Benchmark built successfully!')


def main(argv):
  arguments = _parse_arguments_internal(argv)

  bench = arguments.bench
  compiler = arguments.compiler_dir
  build_os = arguments.build_os
  llvm_version = arguments.llvm_prebuilts_version
  cflags = arguments.cflags
  ldflags = arguments.ldflags

  try:
    source_dir = config.bench_dict[bench]
  except KeyError:
    logging.error('Please select one benchmark from the list below:\n\t' +
                  '\n\t'.join(config.bench_list))
    raise

  set_compiler_env(bench, compiler, build_os, llvm_version, cflags, ldflags)

  build_bench(bench, source_dir)

  # If flags has been set, remember to restore the makefile/blueprint to
  # original ones.
  restore_makefile(bench)

  # If a tmp directory is used for compiler path, remove it after building.
  if compiler:
    remove_tmp_dir()


if __name__ == '__main__':
  main(sys.argv[1:])