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