1#!/usr/bin/python
2
3import os
4import shlex
5import sys
6
7BISECT_STAGE = os.environ.get('BISECT_STAGE')
8
9# We do not need bisect functionality with Goma and clang.
10# Goma server does not have bisect_driver, so we only import
11# bisect_driver when needed. See http://b/34862041
12# We should be careful when doing imports because of Goma.
13if BISECT_STAGE:
14  import bisect_driver
15
16DEFAULT_BISECT_DIR = os.path.expanduser('~/ANDROID_BISECT')
17BISECT_DIR = os.environ.get('BISECT_DIR') or DEFAULT_BISECT_DIR
18
19
20def ProcessArgFile(arg_file):
21  args = []
22  # Read in entire file at once and parse as if in shell
23  with open(arg_file, 'rb') as f:
24    args.extend(shlex.split(f.read()))
25  return args
26
27
28class CompilerWrapper():
29  def __init__(self, argv):
30    self.argv0_current = argv[0]
31    self.args = argv[1:]
32    self.execargs = []
33    self.real_compiler = None
34    self.argv0 = None
35    self.append_flags = []
36    self.prepend_flags = []
37    self.custom_flags = {
38      '--gomacc-path': None
39    }
40
41  def set_real_compiler(self):
42    """Find the real compiler with the absolute path."""
43    compiler_path = os.path.dirname(self.argv0_current)
44    if os.path.islink(__file__):
45      compiler = os.path.basename(os.readlink(__file__))
46    else:
47      compiler = os.path.basename(os.path.abspath(__file__))
48    self.real_compiler = os.path.join(
49        compiler_path,
50        compiler + '.real')
51    self.argv0 = self.real_compiler
52
53  def process_gomacc_command(self):
54    """Return the gomacc command if '--gomacc-path' is set."""
55    gomacc = self.custom_flags['--gomacc-path']
56    if gomacc and os.path.isfile(gomacc):
57      self.argv0 = gomacc
58      self.execargs += [gomacc]
59
60  def parse_custom_flags(self):
61    i = 0
62    args = []
63    while i < len(self.args):
64      if self.args[i] in self.custom_flags:
65        if i >= len(self.args) - 1:
66          sys.exit('The value of {} is not set.'.format(self.args[i]))
67        self.custom_flags[self.args[i]] = self.args[i + 1]
68        i = i + 2
69      else:
70        args.append(self.args[i])
71        i = i + 1
72    self.args = args
73
74  def add_flags(self):
75    self.args = self.prepend_flags + self.args + self.append_flags
76
77  def prepare_compiler_args(self):
78    self.set_real_compiler()
79    self.parse_custom_flags()
80    self.process_gomacc_command()
81    self.add_flags()
82    self.execargs += [self.real_compiler] + self.args
83
84  def invoke_compiler(self):
85    self.prepare_compiler_args()
86    os.execv(self.argv0, self.execargs)
87
88  def bisect(self):
89    self.prepare_compiler_args()
90    # Handle @file argument syntax with compiler
91    idx = 0
92    # The length of self.execargs can be changed during the @file argument
93    # expansion, so we need to use while loop instead of for loop.
94    while idx < len(self.execargs):
95      if self.execargs[idx][0] == '@':
96        args_in_file = ProcessArgFile(self.execargs[idx][1:])
97        self.execargs = self.execargs[0:idx] + args_in_file + self.execargs[idx + 1:]
98        # Skip update of idx, since we want to recursively expand response files.
99      else:
100        idx = idx + 1
101    bisect_driver.bisect_driver(BISECT_STAGE, BISECT_DIR, self.execargs)
102
103
104def main(argv):
105  cw = CompilerWrapper(argv)
106  if BISECT_STAGE and BISECT_STAGE in bisect_driver.VALID_MODES and '-o' in argv:
107    cw.bisect()
108  else:
109    cw.invoke_compiler()
110
111
112if __name__ == '__main__':
113  main(sys.argv)
114