#!/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.

"""Host wrapper script, for clang, to update flags as necessary.

This script wraps host clang and disables a few warnings.  It can
be used to add compilation flags to all host compilations.  Flags
are added at the beginning of the compilation command line so that
the user can override them.
"""

from __future__ import print_function

# We need to be very careful about adding imports to this function, as the
# imports will increase execution time, and this is called for every
# compiler invocation.

import os
import re
import sys

WRAPPER_ONLY_OPTIONS = set(('-print-cmdline', '-nopie', '-noccache'))

X86_DISABLE_FLAGS = set(['-mno-movbe'])

# GCC flags to remove from the clang command line.
# TODO: Once clang supports GCC compatibility mode, remove
# these checks.
#
# Use of -Qunused-arguments allows this set to be small, just those
# that clang still warns about.
CLANG_UNSUPPORTED = set((
    '-pass-exit-codes',
    '-Wclobbered',
    '-Wunsafe-loop-optimizations',
    '-Wlogical-op',
    '-Wmissing-parameter-type',
    '-Woverride-init',
    '-Wold-style-declaration',
    '-Wno-psabi',
    '-mno-movbe',
))

CLANG_UNSUPPORTED_PREFIXES = ('-Wstrict-aliasing=', '-finline-limit=')

# clang with '-ftrapv' generates 'call __mulodi4', which is only implemented
# in compiler-rt library. However compiler-rt library only has i386/x86_64
# backends (see '/usr/lib/clang/3.7.0/lib/linux/libclang_rt.*'). GCC, on the
# other hand, generate 'call __mulvdi3', which is implemented in libgcc. See
# bug chromium:503229.
CLANG_ARM_OPTIONS_TO_BE_DISCARDED = set(['-ftrapv'])

# Clang may use different options for the same or similar functionality.
GCC_TO_CLANG = {
    '-Wno-error=unused-but-set-variable': '-Wno-error=unused-variable',
    '-Wno-error=maybe-uninitialized': '-Wno-error=uninitialized',
    '-Wno-unused-but-set-variable': '-Wno-unused-variable',
    '-Wunused-but-set-variable': '-Wunused-variable',
    '-Wno-error=cpp': '-Wno-#warnings',
}

def handle_exec_exception(exc, argv0, use_ccache, execargs):
  """Analyze compiler execution errors."""
  import errno

  if use_ccache and exc.errno == errno.ENOENT:
    print('error: make sure you install ccache\n', file=sys.stderr)
  print(
      'error: execution of (%s, %s) failed' % (argv0, execargs),
      file=sys.stderr)
  raise

FLAGS_TO_ADD = set((
    '-Wno-unused-local-typedefs',
    '-Wno-deprecated-declarations',
))


def main(argv):
  """Main function for clang wrapper script."""

  # Only FORTIFY_SOURCE hardening flag is applicable for clang.
  clang_flags = [
      '-Qunused-arguments',
      '-grecord-gcc-switches',
  ]

  myargs = argv[1:]

  print_cmdline = '-print-cmdline' in myargs
  clang_cmdline = clang_flags + list(FLAGS_TO_ADD)
  clang_flags = list(FLAGS_TO_ADD)

  cmdline = [x for x in myargs if x not in WRAPPER_ONLY_OPTIONS]

  if re.match(r'i.86|x86_64', os.path.basename(argv[0])):
    cmdline.extend(X86_DISABLE_FLAGS)

  sysroot = os.environ.get('SYSROOT', '/')

  clang_comp = os.environ.get('CLANG', '/usr/bin/clang')

  # Check for clang or clang++.
  if sys.argv[0].endswith('++'):
    clang_comp += '++'

  for flag in cmdline:
    if not (flag in CLANG_UNSUPPORTED or
            flag.startswith(CLANG_UNSUPPORTED_PREFIXES)):
      # Strip off -Xclang-only= if present.
      if flag.startswith('-Xclang-only='):
        opt = flag.partition('=')[2]
        clang_cmdline.append(opt)
      elif flag in GCC_TO_CLANG.keys():
        clang_cmdline.append(GCC_TO_CLANG[flag])
      elif not flag in CLANG_ARM_OPTIONS_TO_BE_DISCARDED:
        clang_cmdline.append(flag)

  execargs = []

  argv0 = clang_comp
  execargs += [clang_comp] + clang_cmdline

  if print_cmdline:
    print('[%s] %s' % (argv0, ' '.join(execargs)))

  sys.stdout.flush()

  try:
    os.execv(argv0, execargs)
  except OSError as e:
    handle_exec_exception(e, argv0, False, execargs)

  return 0


if __name__ == '__main__':
  sys.exit(main(sys.argv))
