Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | # The LLVM Compiler Infrastructure |
| 3 | # |
| 4 | # This file is distributed under the University of Illinois Open Source |
| 5 | # License. See LICENSE.TXT for details. |
Laszlo Nagy | 46fc18a | 2017-01-28 22:48:26 +0000 | [diff] [blame] | 6 | """ This module is a collection of methods commonly used in this project. """ |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 7 | import collections |
Laszlo Nagy | 46fc18a | 2017-01-28 22:48:26 +0000 | [diff] [blame] | 8 | import functools |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 9 | import json |
Laszlo Nagy | 46fc18a | 2017-01-28 22:48:26 +0000 | [diff] [blame] | 10 | import logging |
| 11 | import os |
| 12 | import os.path |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 13 | import re |
| 14 | import shlex |
Laszlo Nagy | 46fc18a | 2017-01-28 22:48:26 +0000 | [diff] [blame] | 15 | import subprocess |
| 16 | import sys |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 17 | |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 18 | ENVIRONMENT_KEY = 'INTERCEPT_BUILD' |
| 19 | |
| 20 | Execution = collections.namedtuple('Execution', ['pid', 'cwd', 'cmd']) |
| 21 | |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 22 | |
| 23 | def duplicate_check(method): |
| 24 | """ Predicate to detect duplicated entries. |
| 25 | |
| 26 | Unique hash method can be use to detect duplicates. Entries are |
| 27 | represented as dictionaries, which has no default hash method. |
| 28 | This implementation uses a set datatype to store the unique hash values. |
| 29 | |
| 30 | This method returns a method which can detect the duplicate values. """ |
| 31 | |
| 32 | def predicate(entry): |
| 33 | entry_hash = predicate.unique(entry) |
| 34 | if entry_hash not in predicate.state: |
| 35 | predicate.state.add(entry_hash) |
| 36 | return False |
| 37 | return True |
| 38 | |
| 39 | predicate.unique = method |
| 40 | predicate.state = set() |
| 41 | return predicate |
| 42 | |
| 43 | |
| 44 | def tempdir(): |
| 45 | """ Return the default temorary directory. """ |
| 46 | |
Laszlo Nagy | 46fc18a | 2017-01-28 22:48:26 +0000 | [diff] [blame] | 47 | return os.getenv('TMPDIR', os.getenv('TEMP', os.getenv('TMP', '/tmp'))) |
| 48 | |
| 49 | |
Laszlo Nagy | 52c1d7e | 2017-02-14 10:30:50 +0000 | [diff] [blame] | 50 | def run_build(command, *args, **kwargs): |
| 51 | """ Run and report build command execution |
| 52 | |
| 53 | :param command: array of tokens |
| 54 | :return: exit code of the process |
| 55 | """ |
| 56 | environment = kwargs.get('env', os.environ) |
| 57 | logging.debug('run build %s, in environment: %s', command, environment) |
| 58 | exit_code = subprocess.call(command, *args, **kwargs) |
| 59 | logging.debug('build finished with exit code: %d', exit_code) |
| 60 | return exit_code |
| 61 | |
| 62 | |
Laszlo Nagy | 46fc18a | 2017-01-28 22:48:26 +0000 | [diff] [blame] | 63 | def run_command(command, cwd=None): |
| 64 | """ Run a given command and report the execution. |
| 65 | |
| 66 | :param command: array of tokens |
| 67 | :param cwd: the working directory where the command will be executed |
| 68 | :return: output of the command |
| 69 | """ |
| 70 | def decode_when_needed(result): |
| 71 | """ check_output returns bytes or string depend on python version """ |
| 72 | return result.decode('utf-8') if isinstance(result, bytes) else result |
| 73 | |
| 74 | try: |
| 75 | directory = os.path.abspath(cwd) if cwd else os.getcwd() |
| 76 | logging.debug('exec command %s in %s', command, directory) |
| 77 | output = subprocess.check_output(command, |
| 78 | cwd=directory, |
| 79 | stderr=subprocess.STDOUT) |
| 80 | return decode_when_needed(output).splitlines() |
| 81 | except subprocess.CalledProcessError as ex: |
| 82 | ex.output = decode_when_needed(ex.output).splitlines() |
| 83 | raise ex |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 84 | |
| 85 | |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 86 | def reconfigure_logging(verbose_level): |
| 87 | """ Reconfigure logging level and format based on the verbose flag. |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 88 | |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 89 | :param verbose_level: number of `-v` flags received by the command |
| 90 | :return: no return value |
| 91 | """ |
| 92 | # Exit when nothing to do. |
| 93 | if verbose_level == 0: |
| 94 | return |
| 95 | |
| 96 | root = logging.getLogger() |
| 97 | # Tune logging level. |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 98 | level = logging.WARNING - min(logging.WARNING, (10 * verbose_level)) |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 99 | root.setLevel(level) |
| 100 | # Be verbose with messages. |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 101 | if verbose_level <= 3: |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 102 | fmt_string = '%(name)s: %(levelname)s: %(message)s' |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 103 | else: |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 104 | fmt_string = '%(name)s: %(levelname)s: %(funcName)s: %(message)s' |
| 105 | handler = logging.StreamHandler(sys.stdout) |
| 106 | handler.setFormatter(logging.Formatter(fmt=fmt_string)) |
| 107 | root.handlers = [handler] |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 108 | |
| 109 | |
| 110 | def command_entry_point(function): |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 111 | """ Decorator for command entry methods. |
| 112 | |
| 113 | The decorator initialize/shutdown logging and guard on programming |
| 114 | errors (catch exceptions). |
| 115 | |
| 116 | The decorated method can have arbitrary parameters, the return value will |
| 117 | be the exit code of the process. """ |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 118 | |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 119 | @functools.wraps(function) |
| 120 | def wrapper(*args, **kwargs): |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 121 | """ Do housekeeping tasks and execute the wrapped method. """ |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 122 | |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 123 | try: |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 124 | logging.basicConfig(format='%(name)s: %(message)s', |
| 125 | level=logging.WARNING, |
| 126 | stream=sys.stdout) |
| 127 | # This hack to get the executable name as %(name). |
| 128 | logging.getLogger().name = os.path.basename(sys.argv[0]) |
| 129 | return function(*args, **kwargs) |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 130 | except KeyboardInterrupt: |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 131 | logging.warning('Keyboard interrupt') |
| 132 | return 130 # Signal received exit code for bash. |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 133 | except Exception: |
| 134 | logging.exception('Internal error.') |
| 135 | if logging.getLogger().isEnabledFor(logging.DEBUG): |
| 136 | logging.error("Please report this bug and attach the output " |
| 137 | "to the bug report") |
| 138 | else: |
| 139 | logging.error("Please run this command again and turn on " |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 140 | "verbose mode (add '-vvvv' as argument).") |
| 141 | return 64 # Some non used exit code for internal errors. |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 142 | finally: |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 143 | logging.shutdown() |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 144 | |
| 145 | return wrapper |
Laszlo Nagy | 2e9c922 | 2017-03-04 01:08:05 +0000 | [diff] [blame^] | 146 | |
| 147 | |
| 148 | def compiler_wrapper(function): |
| 149 | """ Implements compiler wrapper base functionality. |
| 150 | |
| 151 | A compiler wrapper executes the real compiler, then implement some |
| 152 | functionality, then returns with the real compiler exit code. |
| 153 | |
| 154 | :param function: the extra functionality what the wrapper want to |
| 155 | do on top of the compiler call. If it throws exception, it will be |
| 156 | caught and logged. |
| 157 | :return: the exit code of the real compiler. |
| 158 | |
| 159 | The :param function: will receive the following arguments: |
| 160 | |
| 161 | :param result: the exit code of the compilation. |
| 162 | :param execution: the command executed by the wrapper. """ |
| 163 | |
| 164 | def is_cxx_compiler(): |
| 165 | """ Find out was it a C++ compiler call. Compiler wrapper names |
| 166 | contain the compiler type. C++ compiler wrappers ends with `c++`, |
| 167 | but might have `.exe` extension on windows. """ |
| 168 | |
| 169 | wrapper_command = os.path.basename(sys.argv[0]) |
| 170 | return re.match(r'(.+)c\+\+(.*)', wrapper_command) |
| 171 | |
| 172 | def run_compiler(executable): |
| 173 | """ Execute compilation with the real compiler. """ |
| 174 | |
| 175 | command = executable + sys.argv[1:] |
| 176 | logging.debug('compilation: %s', command) |
| 177 | result = subprocess.call(command) |
| 178 | logging.debug('compilation exit code: %d', result) |
| 179 | return result |
| 180 | |
| 181 | # Get relevant parameters from environment. |
| 182 | parameters = json.loads(os.environ[ENVIRONMENT_KEY]) |
| 183 | reconfigure_logging(parameters['verbose']) |
| 184 | # Execute the requested compilation. Do crash if anything goes wrong. |
| 185 | cxx = is_cxx_compiler() |
| 186 | compiler = parameters['cxx'] if cxx else parameters['cc'] |
| 187 | result = run_compiler(compiler) |
| 188 | # Call the wrapped method and ignore it's return value. |
| 189 | try: |
| 190 | call = Execution( |
| 191 | pid=os.getpid(), |
| 192 | cwd=os.getcwd(), |
| 193 | cmd=['c++' if cxx else 'cc'] + sys.argv[1:]) |
| 194 | function(result, call) |
| 195 | except: |
| 196 | logging.exception('Compiler wrapper failed complete.') |
| 197 | finally: |
| 198 | # Always return the real compiler exit code. |
| 199 | return result |
| 200 | |
| 201 | |
| 202 | def wrapper_environment(args): |
| 203 | """ Set up environment for interpose compiler wrapper.""" |
| 204 | |
| 205 | return { |
| 206 | ENVIRONMENT_KEY: json.dumps({ |
| 207 | 'verbose': args.verbose, |
| 208 | 'cc': shlex.split(args.cc), |
| 209 | 'cxx': shlex.split(args.cxx) |
| 210 | }) |
| 211 | } |