| #!/usr/bin/env python |
| # |
| # Copyright (C) 2017 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| # pylint: disable=not-callable, relative-import |
| |
| # Note that adding top-level imports is discouraged unless they're guaranteed to |
| # be used. Unnecessary imports eat a measurable number of cycles of this |
| # wrapper. |
| import os |
| import sys |
| |
| BISECT_STAGE = os.environ.get('BISECT_STAGE') |
| # We do not need bisect functionality with Goma and clang. |
| # Goma server does not have bisect_driver, so we only import |
| # bisect_driver when needed. See http://b/34862041 |
| # We should be careful when doing imports because of Goma. |
| if BISECT_STAGE: |
| import bisect_driver |
| |
| DEFAULT_BISECT_DIR = os.path.expanduser('~/ANDROID_BISECT') |
| BISECT_DIR = os.environ.get('BISECT_DIR') or DEFAULT_BISECT_DIR |
| STDERR_REDIRECT_KEY = 'ANDROID_LLVM_STDERR_REDIRECT' |
| PREBUILT_COMPILER_PATH_KEY = 'ANDROID_LLVM_PREBUILT_COMPILER_PATH' |
| DISABLED_WARNINGS_KEY = 'ANDROID_LLVM_FALLBACK_DISABLED_WARNINGS' |
| |
| |
| def ProcessArgFile(arg_file): |
| import shlex |
| |
| args = [] |
| # Read in entire file at once and parse as if in shell |
| with open(arg_file, 'rb') as f: |
| args.extend(shlex.split(f.read())) |
| return args |
| |
| |
| def write_log(path, command, log): |
| import errno |
| import fcntl |
| import time |
| |
| with open(path, 'a+') as f: |
| while True: |
| try: |
| fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) |
| break |
| except IOError as e: |
| if e.errno == errno.EAGAIN or e.errno == errno.EACCES: |
| time.sleep(0.5) |
| f.write('==================COMMAND:====================\n') |
| f.write(' '.join(command) + '\n\n') |
| f.write(log) |
| f.write('==============================================\n\n') |
| |
| |
| class CompilerWrapper(object): |
| |
| def __init__(self, argv): |
| self.argv0_current = argv[0] |
| self.args = argv[1:] |
| self.execargs = [] |
| self.real_compiler = None |
| self.argv0 = None |
| self.append_flags = [] |
| self.prepend_flags = [] |
| self.custom_flags = {'--gomacc-path': None} |
| |
| def set_real_compiler(self): |
| """Find the real compiler with the absolute path.""" |
| compiler_path = os.path.dirname(self.argv0_current) |
| if os.path.islink(__file__): |
| compiler = os.path.basename(os.readlink(__file__)) |
| else: |
| compiler = os.path.basename(os.path.abspath(__file__)) |
| self.real_compiler = os.path.join(compiler_path, compiler + '.real') |
| self.argv0 = self.real_compiler |
| |
| def process_gomacc_command(self): |
| """Return the gomacc command if '--gomacc-path' is set.""" |
| gomacc = self.custom_flags['--gomacc-path'] |
| if gomacc and os.path.isfile(gomacc): |
| self.argv0 = gomacc |
| self.execargs += [gomacc] |
| |
| def parse_custom_flags(self): |
| i = 0 |
| args = [] |
| while i < len(self.args): |
| if self.args[i] in self.custom_flags: |
| if i >= len(self.args) - 1: |
| sys.exit('The value of {} is not set.'.format(self.args[i])) |
| self.custom_flags[self.args[i]] = self.args[i + 1] |
| i = i + 2 |
| else: |
| args.append(self.args[i]) |
| i = i + 1 |
| self.args = args |
| |
| def add_flags(self): |
| self.args = self.prepend_flags + self.args + self.append_flags |
| |
| def prepare_compiler_args(self, enable_fallback=False): |
| self.set_real_compiler() |
| self.parse_custom_flags() |
| # Goma should not be enabled for new prebuilt. |
| if not enable_fallback: |
| self.process_gomacc_command() |
| self.add_flags() |
| self.execargs += [self.real_compiler] + self.args |
| |
| def exec_clang_with_fallback(self): |
| import subprocess |
| |
| extra_args_begin = len(self.execargs) |
| |
| # We only want to pass extra flags to clang and clang++. |
| if os.path.basename(__file__) in ['clang', 'clang++']: |
| # We may introduce some new warnings after rebasing and we need to |
| # disable them before we fix those warnings. |
| disabled_warnings_env = os.environ.get(DISABLED_WARNINGS_KEY, '') |
| disabled_warnings = disabled_warnings_env.split(' ') |
| self.execargs += ['-fno-color-diagnostics'] + disabled_warnings |
| |
| p = subprocess.Popen(self.execargs, stderr=subprocess.PIPE) |
| (_, err) = p.communicate() |
| sys.stderr.write(err) |
| if p.returncode != 0: |
| redirect_path = os.environ[STDERR_REDIRECT_KEY] |
| write_log(redirect_path, self.execargs, err) |
| fallback_arg0 = os.path.join(os.environ[PREBUILT_COMPILER_PATH_KEY], |
| os.path.basename(__file__)) |
| |
| # Delete PREBUILT_COMPILER_PATH_KEY so the fallback doesn't keep |
| # calling itself in case of an error. |
| del os.environ[PREBUILT_COMPILER_PATH_KEY] |
| |
| # Strip extra args added (from DISABLED_WARNINGS_KEY) for clang and |
| # clang++ above. They may not be recognized by the fallback clang. |
| self.execargs = self.execargs[:extra_args_begin] |
| |
| os.execv(fallback_arg0, [fallback_arg0] + self.execargs[1:]) |
| |
| def invoke_compiler(self): |
| enable_fallback = PREBUILT_COMPILER_PATH_KEY in os.environ |
| self.prepare_compiler_args(enable_fallback) |
| if enable_fallback: |
| self.exec_clang_with_fallback() |
| else: |
| os.execv(self.argv0, self.execargs) |
| |
| def bisect(self): |
| self.prepare_compiler_args() |
| # Handle @file argument syntax with compiler |
| idx = 0 |
| # The length of self.execargs can be changed during the @file argument |
| # expansion, so we need to use while loop instead of for loop. |
| while idx < len(self.execargs): |
| if self.execargs[idx][0] == '@': |
| args_in_file = ProcessArgFile(self.execargs[idx][1:]) |
| self.execargs = self.execargs[0:idx] + args_in_file +\ |
| self.execargs[idx + 1:] |
| # Skip update of idx, since we want to recursively expand |
| # response files. |
| else: |
| idx = idx + 1 |
| bisect_driver.bisect_driver(BISECT_STAGE, BISECT_DIR, self.execargs) |
| |
| |
| def main(argv): |
| cw = CompilerWrapper(argv) |
| if BISECT_STAGE and BISECT_STAGE in bisect_driver.VALID_MODES\ |
| and '-o' in argv: |
| cw.bisect() |
| else: |
| cw.invoke_compiler() |
| |
| |
| if __name__ == '__main__': |
| main(sys.argv) |