Home | History | Annotate | Download | only in bin
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2017 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 #
     17 
     18 import errno
     19 import fcntl
     20 import os
     21 import shlex
     22 import subprocess
     23 import sys
     24 import time
     25 
     26 BISECT_STAGE = os.environ.get('BISECT_STAGE')
     27 # We do not need bisect functionality with Goma and clang.
     28 # Goma server does not have bisect_driver, so we only import
     29 # bisect_driver when needed. See http://b/34862041
     30 # We should be careful when doing imports because of Goma.
     31 if BISECT_STAGE:
     32     import bisect_driver
     33 
     34 DEFAULT_BISECT_DIR = os.path.expanduser('~/ANDROID_BISECT')
     35 BISECT_DIR = os.environ.get('BISECT_DIR') or DEFAULT_BISECT_DIR
     36 STDERR_REDIRECT_KEY = 'ANDROID_LLVM_STDERR_REDIRECT'
     37 PREBUILT_COMPILER_PATH_KEY = 'ANDROID_LLVM_PREBUILT_COMPILER_PATH'
     38 DISABLED_WARNINGS_KEY = 'ANDROID_LLVM_FALLBACK_DISABLED_WARNINGS'
     39 
     40 
     41 def process_arg_file(arg_file):
     42     args = []
     43     # Read in entire file at once and parse as if in shell
     44     with open(arg_file, 'rb') as f:
     45         args.extend(shlex.split(f.read()))
     46     return args
     47 
     48 
     49 def write_log(path, command, log):
     50     with open(path, 'a+') as f:
     51         while True:
     52             try:
     53                 fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
     54                 break
     55             except IOError as e:
     56                 if e.errno == errno.EAGAIN or e.errno == errno.EACCES:
     57                     time.sleep(0.5)
     58         f.write('==================COMMAND:====================\n')
     59         f.write(' '.join(command) + '\n\n')
     60         f.write(log)
     61         f.write('==============================================\n\n')
     62 
     63 
     64 class CompilerWrapper():
     65 
     66     def __init__(self, argv):
     67         self.argv0_current = argv[0]
     68         self.args = argv[1:]
     69         self.execargs = []
     70         self.real_compiler = None
     71         self.argv0 = None
     72         self.append_flags = []
     73         self.prepend_flags = []
     74         self.custom_flags = {'--gomacc-path': None}
     75 
     76     def set_real_compiler(self):
     77         """Find the real compiler with the absolute path."""
     78         compiler_path = os.path.dirname(self.argv0_current)
     79         if os.path.islink(__file__):
     80             compiler = os.path.basename(os.readlink(__file__))
     81         else:
     82             compiler = os.path.basename(os.path.abspath(__file__))
     83         self.real_compiler = os.path.join(compiler_path, compiler + '.real')
     84         self.argv0 = self.real_compiler
     85 
     86     def process_gomacc_command(self):
     87         """Return the gomacc command if '--gomacc-path' is set."""
     88         gomacc = self.custom_flags['--gomacc-path']
     89         if gomacc and os.path.isfile(gomacc):
     90             self.argv0 = gomacc
     91             self.execargs += [gomacc]
     92 
     93     def parse_custom_flags(self):
     94         i = 0
     95         args = []
     96         while i < len(self.args):
     97             if self.args[i] in self.custom_flags:
     98                 if i >= len(self.args) - 1:
     99                     sys.exit('The value of {} is not set.'.format(self.args[i]))
    100                 self.custom_flags[self.args[i]] = self.args[i + 1]
    101                 i = i + 2
    102             else:
    103                 args.append(self.args[i])
    104                 i = i + 1
    105         self.args = args
    106 
    107     def add_flags(self):
    108         self.args = self.prepend_flags + self.args + self.append_flags
    109 
    110     def prepare_compiler_args(self, enable_fallback):
    111         self.set_real_compiler()
    112         self.parse_custom_flags()
    113         # Goma should not be enabled for new prebuilt.
    114         if not enable_fallback:
    115             self.process_gomacc_command()
    116         self.add_flags()
    117         self.execargs += [self.real_compiler] + self.args
    118 
    119     def exec_clang_with_fallback(self):
    120         # We only want to pass extra flags to clang and clang++.
    121         if os.path.basename(__file__) in ['clang', 'clang++']:
    122             # We may introduce some new warnings after rebasing and we need to
    123             # disable them before we fix those warnings.
    124             disabled_warnings_env = os.environ.get(DISABLED_WARNINGS_KEY, '')
    125             disabled_warnings = disabled_warnings_env.split(' ')
    126             self.execargs += ['-fno-color-diagnostics'] + disabled_warnings
    127 
    128         p = subprocess.Popen(self.execargs, stderr=subprocess.PIPE)
    129         (_, err) = p.communicate()
    130         sys.stderr.write(err)
    131         if p.returncode != 0:
    132             redirect_path = os.environ[STDERR_REDIRECT_KEY]
    133             write_log(redirect_path, self.execargs, err)
    134             fallback_arg0 = os.path.join(os.environ[PREBUILT_COMPILER_PATH_KEY],
    135                                          os.path.basename(__file__))
    136             os.execv(fallback_arg0, [fallback_arg0] + self.execargs[1:])
    137 
    138     def invoke_compiler(self):
    139         enable_fallback = PREBUILT_COMPILER_PATH_KEY in os.environ
    140         self.prepare_compiler_args(enable_fallback)
    141         if enable_fallback:
    142             self.exec_clang_with_fallback()
    143         else:
    144             os.execv(self.argv0, self.execargs)
    145 
    146     def bisect(self):
    147         self.prepare_compiler_args()
    148         # Handle @file argument syntax with compiler
    149         idx = 0
    150         # The length of self.execargs can be changed during the @file argument
    151         # expansion, so we need to use while loop instead of for loop.
    152         while idx < len(self.execargs):
    153             if self.execargs[idx][0] == '@':
    154                 args_in_file = ProcessArgFile(self.execargs[idx][1:])
    155                 self.execargs = self.execargs[0:idx] + args_in_file +\
    156                         self.execargs[idx + 1:]
    157                 # Skip update of idx, since we want to recursively expand
    158                 # response files.
    159             else:
    160                 idx = idx + 1
    161         bisect_driver.bisect_driver(BISECT_STAGE, BISECT_DIR, self.execargs)
    162 
    163 
    164 def main(argv):
    165     cw = CompilerWrapper(argv)
    166     if BISECT_STAGE and BISECT_STAGE in bisect_driver.VALID_MODES\
    167             and '-o' in argv:
    168         cw.bisect()
    169     else:
    170         cw.invoke_compiler()
    171 
    172 
    173 if __name__ == '__main__':
    174     main(sys.argv)
    175