blob: 800926ebb6f2f1379248637498f969792ec889b3 [file] [log] [blame]
Laszlo Nagybc687582016-01-12 22:38:41 +00001# -*- 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 Nagy46fc18a2017-01-28 22:48:26 +00006""" This module is a collection of methods commonly used in this project. """
Laszlo Nagy2e9c9222017-03-04 01:08:05 +00007import collections
Laszlo Nagy46fc18a2017-01-28 22:48:26 +00008import functools
Laszlo Nagy2e9c9222017-03-04 01:08:05 +00009import json
Laszlo Nagy46fc18a2017-01-28 22:48:26 +000010import logging
11import os
12import os.path
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000013import re
14import shlex
Laszlo Nagy46fc18a2017-01-28 22:48:26 +000015import subprocess
16import sys
Laszlo Nagybc687582016-01-12 22:38:41 +000017
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000018ENVIRONMENT_KEY = 'INTERCEPT_BUILD'
19
20Execution = collections.namedtuple('Execution', ['pid', 'cwd', 'cmd'])
21
Laszlo Nagybc687582016-01-12 22:38:41 +000022
23def 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
Laszlo Nagy52c1d7e2017-02-14 10:30:50 +000044def run_build(command, *args, **kwargs):
45 """ Run and report build command execution
46
47 :param command: array of tokens
48 :return: exit code of the process
49 """
50 environment = kwargs.get('env', os.environ)
51 logging.debug('run build %s, in environment: %s', command, environment)
52 exit_code = subprocess.call(command, *args, **kwargs)
53 logging.debug('build finished with exit code: %d', exit_code)
54 return exit_code
55
56
Laszlo Nagy46fc18a2017-01-28 22:48:26 +000057def run_command(command, cwd=None):
58 """ Run a given command and report the execution.
59
60 :param command: array of tokens
61 :param cwd: the working directory where the command will be executed
62 :return: output of the command
63 """
64 def decode_when_needed(result):
65 """ check_output returns bytes or string depend on python version """
66 return result.decode('utf-8') if isinstance(result, bytes) else result
67
68 try:
69 directory = os.path.abspath(cwd) if cwd else os.getcwd()
70 logging.debug('exec command %s in %s', command, directory)
71 output = subprocess.check_output(command,
72 cwd=directory,
73 stderr=subprocess.STDOUT)
74 return decode_when_needed(output).splitlines()
75 except subprocess.CalledProcessError as ex:
76 ex.output = decode_when_needed(ex.output).splitlines()
77 raise ex
Laszlo Nagybc687582016-01-12 22:38:41 +000078
79
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000080def reconfigure_logging(verbose_level):
81 """ Reconfigure logging level and format based on the verbose flag.
Laszlo Nagybc687582016-01-12 22:38:41 +000082
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000083 :param verbose_level: number of `-v` flags received by the command
84 :return: no return value
85 """
86 # Exit when nothing to do.
87 if verbose_level == 0:
88 return
89
90 root = logging.getLogger()
91 # Tune logging level.
Laszlo Nagybc687582016-01-12 22:38:41 +000092 level = logging.WARNING - min(logging.WARNING, (10 * verbose_level))
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000093 root.setLevel(level)
94 # Be verbose with messages.
Laszlo Nagybc687582016-01-12 22:38:41 +000095 if verbose_level <= 3:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000096 fmt_string = '%(name)s: %(levelname)s: %(message)s'
Laszlo Nagybc687582016-01-12 22:38:41 +000097 else:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000098 fmt_string = '%(name)s: %(levelname)s: %(funcName)s: %(message)s'
99 handler = logging.StreamHandler(sys.stdout)
100 handler.setFormatter(logging.Formatter(fmt=fmt_string))
101 root.handlers = [handler]
Laszlo Nagybc687582016-01-12 22:38:41 +0000102
103
104def command_entry_point(function):
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000105 """ Decorator for command entry methods.
106
107 The decorator initialize/shutdown logging and guard on programming
108 errors (catch exceptions).
109
110 The decorated method can have arbitrary parameters, the return value will
111 be the exit code of the process. """
Laszlo Nagybc687582016-01-12 22:38:41 +0000112
Laszlo Nagybc687582016-01-12 22:38:41 +0000113 @functools.wraps(function)
114 def wrapper(*args, **kwargs):
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000115 """ Do housekeeping tasks and execute the wrapped method. """
Laszlo Nagybc687582016-01-12 22:38:41 +0000116
Laszlo Nagybc687582016-01-12 22:38:41 +0000117 try:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000118 logging.basicConfig(format='%(name)s: %(message)s',
119 level=logging.WARNING,
120 stream=sys.stdout)
121 # This hack to get the executable name as %(name).
122 logging.getLogger().name = os.path.basename(sys.argv[0])
123 return function(*args, **kwargs)
Laszlo Nagybc687582016-01-12 22:38:41 +0000124 except KeyboardInterrupt:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000125 logging.warning('Keyboard interrupt')
126 return 130 # Signal received exit code for bash.
Laszlo Nagybc687582016-01-12 22:38:41 +0000127 except Exception:
128 logging.exception('Internal error.')
129 if logging.getLogger().isEnabledFor(logging.DEBUG):
130 logging.error("Please report this bug and attach the output "
131 "to the bug report")
132 else:
133 logging.error("Please run this command again and turn on "
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000134 "verbose mode (add '-vvvv' as argument).")
135 return 64 # Some non used exit code for internal errors.
Laszlo Nagybc687582016-01-12 22:38:41 +0000136 finally:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000137 logging.shutdown()
Laszlo Nagybc687582016-01-12 22:38:41 +0000138
139 return wrapper
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000140
141
142def compiler_wrapper(function):
143 """ Implements compiler wrapper base functionality.
144
145 A compiler wrapper executes the real compiler, then implement some
146 functionality, then returns with the real compiler exit code.
147
148 :param function: the extra functionality what the wrapper want to
149 do on top of the compiler call. If it throws exception, it will be
150 caught and logged.
151 :return: the exit code of the real compiler.
152
153 The :param function: will receive the following arguments:
154
155 :param result: the exit code of the compilation.
156 :param execution: the command executed by the wrapper. """
157
158 def is_cxx_compiler():
159 """ Find out was it a C++ compiler call. Compiler wrapper names
160 contain the compiler type. C++ compiler wrappers ends with `c++`,
161 but might have `.exe` extension on windows. """
162
163 wrapper_command = os.path.basename(sys.argv[0])
164 return re.match(r'(.+)c\+\+(.*)', wrapper_command)
165
166 def run_compiler(executable):
167 """ Execute compilation with the real compiler. """
168
169 command = executable + sys.argv[1:]
170 logging.debug('compilation: %s', command)
171 result = subprocess.call(command)
172 logging.debug('compilation exit code: %d', result)
173 return result
174
175 # Get relevant parameters from environment.
176 parameters = json.loads(os.environ[ENVIRONMENT_KEY])
177 reconfigure_logging(parameters['verbose'])
178 # Execute the requested compilation. Do crash if anything goes wrong.
179 cxx = is_cxx_compiler()
180 compiler = parameters['cxx'] if cxx else parameters['cc']
181 result = run_compiler(compiler)
182 # Call the wrapped method and ignore it's return value.
183 try:
184 call = Execution(
185 pid=os.getpid(),
186 cwd=os.getcwd(),
187 cmd=['c++' if cxx else 'cc'] + sys.argv[1:])
188 function(result, call)
189 except:
190 logging.exception('Compiler wrapper failed complete.')
191 finally:
192 # Always return the real compiler exit code.
193 return result
194
195
196def wrapper_environment(args):
197 """ Set up environment for interpose compiler wrapper."""
198
199 return {
200 ENVIRONMENT_KEY: json.dumps({
201 'verbose': args.verbose,
202 'cc': shlex.split(args.cc),
203 'cxx': shlex.split(args.cxx)
204 })
205 }