- # -*- coding: utf-8 -*- 
- # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 
- # See https://llvm.org/LICENSE.txt for license information. 
- # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 
- """ This module is a collection of methods commonly used in this project. """ 
- import collections 
- import functools 
- import json 
- import logging 
- import os 
- import os.path 
- import re 
- import shlex 
- import subprocess 
- import sys 
-   
- ENVIRONMENT_KEY = 'INTERCEPT_BUILD' 
-   
- Execution = collections.namedtuple('Execution', ['pid', 'cwd', 'cmd']) 
-   
- CtuConfig = collections.namedtuple('CtuConfig', ['collect', 'analyze', 'dir', 
-                                                  'extdef_map_cmd']) 
-   
-   
- def duplicate_check(method): 
-     """ Predicate to detect duplicated entries. 
-   
-     Unique hash method can be use to detect duplicates. Entries are 
-     represented as dictionaries, which has no default hash method. 
-     This implementation uses a set datatype to store the unique hash values. 
-   
-     This method returns a method which can detect the duplicate values. """ 
-   
-     def predicate(entry): 
-         entry_hash = predicate.unique(entry) 
-         if entry_hash not in predicate.state: 
-             predicate.state.add(entry_hash) 
-             return False 
-         return True 
-   
-     predicate.unique = method 
-     predicate.state = set() 
-     return predicate 
-   
-   
- def run_build(command, *args, **kwargs): 
-     """ Run and report build command execution 
-   
-     :param command: array of tokens 
-     :return: exit code of the process 
-     """ 
-     environment = kwargs.get('env', os.environ) 
-     logging.debug('run build %s, in environment: %s', command, environment) 
-     exit_code = subprocess.call(command, *args, **kwargs) 
-     logging.debug('build finished with exit code: %d', exit_code) 
-     return exit_code 
-   
-   
- def run_command(command, cwd=None): 
-     """ Run a given command and report the execution. 
-   
-     :param command: array of tokens 
-     :param cwd: the working directory where the command will be executed 
-     :return: output of the command 
-     """ 
-     def decode_when_needed(result): 
-         """ check_output returns bytes or string depend on python version """ 
-         return result.decode('utf-8') if isinstance(result, bytes) else result 
-   
-     try: 
-         directory = os.path.abspath(cwd) if cwd else os.getcwd() 
-         logging.debug('exec command %s in %s', command, directory) 
-         output = subprocess.check_output(command, 
-                                          cwd=directory, 
-                                          stderr=subprocess.STDOUT) 
-         return decode_when_needed(output).splitlines() 
-     except subprocess.CalledProcessError as ex: 
-         ex.output = decode_when_needed(ex.output).splitlines() 
-         raise ex 
-   
-   
- def reconfigure_logging(verbose_level): 
-     """ Reconfigure logging level and format based on the verbose flag. 
-   
-     :param verbose_level: number of `-v` flags received by the command 
-     :return: no return value 
-     """ 
-     # Exit when nothing to do. 
-     if verbose_level == 0: 
-         return 
-   
-     root = logging.getLogger() 
-     # Tune logging level. 
-     level = logging.WARNING - min(logging.WARNING, (10 * verbose_level)) 
-     root.setLevel(level) 
-     # Be verbose with messages. 
-     if verbose_level <= 3: 
-         fmt_string = '%(name)s: %(levelname)s: %(message)s' 
-     else: 
-         fmt_string = '%(name)s: %(levelname)s: %(funcName)s: %(message)s' 
-     handler = logging.StreamHandler(sys.stdout) 
-     handler.setFormatter(logging.Formatter(fmt=fmt_string)) 
-     root.handlers = [handler] 
-   
-   
- def command_entry_point(function): 
-     """ Decorator for command entry methods. 
-   
-     The decorator initialize/shutdown logging and guard on programming 
-     errors (catch exceptions). 
-   
-     The decorated method can have arbitrary parameters, the return value will 
-     be the exit code of the process. """ 
-   
-     @functools.wraps(function) 
-     def wrapper(*args, **kwargs): 
-         """ Do housekeeping tasks and execute the wrapped method. """ 
-   
-         try: 
-             logging.basicConfig(format='%(name)s: %(message)s', 
-                                 level=logging.WARNING, 
-                                 stream=sys.stdout) 
-             # This hack to get the executable name as %(name). 
-             logging.getLogger().name = os.path.basename(sys.argv[0]) 
-             return function(*args, **kwargs) 
-         except KeyboardInterrupt: 
-             logging.warning('Keyboard interrupt') 
-             return 130  # Signal received exit code for bash. 
-         except Exception: 
-             logging.exception('Internal error.') 
-             if logging.getLogger().isEnabledFor(logging.DEBUG): 
-                 logging.error("Please report this bug and attach the output " 
-                               "to the bug report") 
-             else: 
-                 logging.error("Please run this command again and turn on " 
-                               "verbose mode (add '-vvvv' as argument).") 
-             return 64  # Some non used exit code for internal errors. 
-         finally: 
-             logging.shutdown() 
-   
-     return wrapper 
-   
-   
- def compiler_wrapper(function): 
-     """ Implements compiler wrapper base functionality. 
-   
-     A compiler wrapper executes the real compiler, then implement some 
-     functionality, then returns with the real compiler exit code. 
-   
-     :param function: the extra functionality what the wrapper want to 
-     do on top of the compiler call. If it throws exception, it will be 
-     caught and logged. 
-     :return: the exit code of the real compiler. 
-   
-     The :param function: will receive the following arguments: 
-   
-     :param result:       the exit code of the compilation. 
-     :param execution:    the command executed by the wrapper. """ 
-   
-     def is_cxx_compiler(): 
-         """ Find out was it a C++ compiler call. Compiler wrapper names 
-         contain the compiler type. C++ compiler wrappers ends with `c++`, 
-         but might have `.exe` extension on windows. """ 
-   
-         wrapper_command = os.path.basename(sys.argv[0]) 
-         return re.match(r'(.+)c\+\+(.*)', wrapper_command) 
-   
-     def run_compiler(executable): 
-         """ Execute compilation with the real compiler. """ 
-   
-         command = executable + sys.argv[1:] 
-         logging.debug('compilation: %s', command) 
-         result = subprocess.call(command) 
-         logging.debug('compilation exit code: %d', result) 
-         return result 
-   
-     # Get relevant parameters from environment. 
-     parameters = json.loads(os.environ[ENVIRONMENT_KEY]) 
-     reconfigure_logging(parameters['verbose']) 
-     # Execute the requested compilation. Do crash if anything goes wrong. 
-     cxx = is_cxx_compiler() 
-     compiler = parameters['cxx'] if cxx else parameters['cc'] 
-     result = run_compiler(compiler) 
-     # Call the wrapped method and ignore it's return value. 
-     try: 
-         call = Execution( 
-             pid=os.getpid(), 
-             cwd=os.getcwd(), 
-             cmd=['c++' if cxx else 'cc'] + sys.argv[1:]) 
-         function(result, call) 
-     except: 
-         logging.exception('Compiler wrapper failed complete.') 
-     finally: 
-         # Always return the real compiler exit code. 
-         return result 
-   
-   
- def wrapper_environment(args): 
-     """ Set up environment for interpose compiler wrapper.""" 
-   
-     return { 
-         ENVIRONMENT_KEY: json.dumps({ 
-             'verbose': args.verbose, 
-             'cc': shlex.split(args.cc), 
-             'cxx': shlex.split(args.cxx) 
-         }) 
-     } 
-