brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.1 KiB · ba16008 Raw
216 lines · python
1# -*- coding: utf-8 -*-2# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3# See https://llvm.org/LICENSE.txt for license information.4# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5""" This module is a collection of methods commonly used in this project. """6import collections7import functools8import json9import logging10import os11import os.path12import re13import shlex14import subprocess15import sys16 17ENVIRONMENT_KEY = "INTERCEPT_BUILD"18 19Execution = collections.namedtuple("Execution", ["pid", "cwd", "cmd"])20 21CtuConfig = collections.namedtuple(22    "CtuConfig", ["collect", "analyze", "dir", "extdef_map_cmd"]23)24 25 26def duplicate_check(method):27    """Predicate to detect duplicated entries.28 29    Unique hash method can be use to detect duplicates. Entries are30    represented as dictionaries, which has no default hash method.31    This implementation uses a set datatype to store the unique hash values.32 33    This method returns a method which can detect the duplicate values."""34 35    def predicate(entry):36        entry_hash = predicate.unique(entry)37        if entry_hash not in predicate.state:38            predicate.state.add(entry_hash)39            return False40        return True41 42    predicate.unique = method43    predicate.state = set()44    return predicate45 46 47def run_build(command, *args, **kwargs):48    """Run and report build command execution49 50    :param command: array of tokens51    :return: exit code of the process52    """53    environment = kwargs.get("env", os.environ)54    logging.debug("run build %s, in environment: %s", command, environment)55    exit_code = subprocess.call(command, *args, **kwargs)56    logging.debug("build finished with exit code: %d", exit_code)57    return exit_code58 59 60def run_command(command, cwd=None):61    """Run a given command and report the execution.62 63    :param command: array of tokens64    :param cwd: the working directory where the command will be executed65    :return: output of the command66    """67 68    def decode_when_needed(result):69        """check_output returns bytes or string depend on python version"""70        return result.decode("utf-8") if isinstance(result, bytes) else result71 72    try:73        directory = os.path.abspath(cwd) if cwd else os.getcwd()74        logging.debug("exec command %s in %s", command, directory)75        output = subprocess.check_output(76            command, cwd=directory, stderr=subprocess.STDOUT77        )78        return decode_when_needed(output).splitlines()79    except subprocess.CalledProcessError as ex:80        ex.output = decode_when_needed(ex.output).splitlines()81        raise ex82 83 84def reconfigure_logging(verbose_level):85    """Reconfigure logging level and format based on the verbose flag.86 87    :param verbose_level: number of `-v` flags received by the command88    :return: no return value89    """90    # Exit when nothing to do.91    if verbose_level == 0:92        return93 94    root = logging.getLogger()95    # Tune logging level.96    level = logging.WARNING - min(logging.WARNING, (10 * verbose_level))97    root.setLevel(level)98    # Be verbose with messages.99    if verbose_level <= 3:100        fmt_string = "%(name)s: %(levelname)s: %(message)s"101    else:102        fmt_string = "%(name)s: %(levelname)s: %(funcName)s: %(message)s"103    handler = logging.StreamHandler(sys.stdout)104    handler.setFormatter(logging.Formatter(fmt=fmt_string))105    root.handlers = [handler]106 107 108def command_entry_point(function):109    """Decorator for command entry methods.110 111    The decorator initialize/shutdown logging and guard on programming112    errors (catch exceptions).113 114    The decorated method can have arbitrary parameters, the return value will115    be the exit code of the process."""116 117    @functools.wraps(function)118    def wrapper(*args, **kwargs):119        """Do housekeeping tasks and execute the wrapped method."""120 121        try:122            logging.basicConfig(123                format="%(name)s: %(message)s", level=logging.WARNING, stream=sys.stdout124            )125            # This hack to get the executable name as %(name).126            logging.getLogger().name = os.path.basename(sys.argv[0])127            return function(*args, **kwargs)128        except KeyboardInterrupt:129            logging.warning("Keyboard interrupt")130            return 130  # Signal received exit code for bash.131        except Exception:132            logging.exception("Internal error.")133            if logging.getLogger().isEnabledFor(logging.DEBUG):134                logging.error(135                    "Please report this bug and attach the output " "to the bug report"136                )137            else:138                logging.error(139                    "Please run this command again and turn on "140                    "verbose mode (add '-vvvv' as argument)."141                )142            return 64  # Some non used exit code for internal errors.143        finally:144            logging.shutdown()145 146    return wrapper147 148 149def compiler_wrapper(function):150    """Implements compiler wrapper base functionality.151 152    A compiler wrapper executes the real compiler, then implement some153    functionality, then returns with the real compiler exit code.154 155    :param function: the extra functionality what the wrapper want to156    do on top of the compiler call. If it throws exception, it will be157    caught and logged.158    :return: the exit code of the real compiler.159 160    The :param function: will receive the following arguments:161 162    :param result:       the exit code of the compilation.163    :param execution:    the command executed by the wrapper."""164 165    def is_cxx_compiler():166        """Find out was it a C++ compiler call. Compiler wrapper names167        contain the compiler type. C++ compiler wrappers ends with `c++`,168        but might have `.exe` extension on windows."""169 170        wrapper_command = os.path.basename(sys.argv[0])171        return re.match(r"(.+)c\+\+(.*)", wrapper_command)172 173    def run_compiler(executable):174        """Execute compilation with the real compiler."""175 176        command = executable + sys.argv[1:]177        logging.debug("compilation: %s", command)178        result = subprocess.call(command)179        logging.debug("compilation exit code: %d", result)180        return result181 182    # Get relevant parameters from environment.183    parameters = json.loads(os.environ[ENVIRONMENT_KEY])184    reconfigure_logging(parameters["verbose"])185    # Execute the requested compilation. Do crash if anything goes wrong.186    cxx = is_cxx_compiler()187    compiler = parameters["cxx"] if cxx else parameters["cc"]188    result = run_compiler(compiler)189    # Call the wrapped method and ignore it's return value.190    try:191        call = Execution(192            pid=os.getpid(),193            cwd=os.getcwd(),194            cmd=["c++" if cxx else "cc"] + sys.argv[1:],195        )196        function(result, call)197    except:198        logging.exception("Compiler wrapper failed complete.")199    finally:200        # Always return the real compiler exit code.201        return result202 203 204def wrapper_environment(args):205    """Set up environment for interpose compiler wrapper."""206 207    return {208        ENVIRONMENT_KEY: json.dumps(209            {210                "verbose": args.verbose,211                "cc": shlex.split(args.cc),212                "cxx": shlex.split(args.cxx),213            }214        )215    }216