#!/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 # 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] 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)