blob: ca75174912fcd4ab3c899248cffcce837ac8c4af [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
44def tempdir():
45 """ Return the default temorary directory. """
46
Laszlo Nagy46fc18a2017-01-28 22:48:26 +000047 return os.getenv('TMPDIR', os.getenv('TEMP', os.getenv('TMP', '/tmp')))
48
49
Laszlo Nagy52c1d7e2017-02-14 10:30:50 +000050def 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 Nagy46fc18a2017-01-28 22:48:26 +000063def 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 Nagybc687582016-01-12 22:38:41 +000084
85
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000086def reconfigure_logging(verbose_level):
87 """ Reconfigure logging level and format based on the verbose flag.
Laszlo Nagybc687582016-01-12 22:38:41 +000088
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000089 :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 Nagybc687582016-01-12 22:38:41 +000098 level = logging.WARNING - min(logging.WARNING, (10 * verbose_level))
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000099 root.setLevel(level)
100 # Be verbose with messages.
Laszlo Nagybc687582016-01-12 22:38:41 +0000101 if verbose_level <= 3:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000102 fmt_string = '%(name)s: %(levelname)s: %(message)s'
Laszlo Nagybc687582016-01-12 22:38:41 +0000103 else:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000104 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 Nagybc687582016-01-12 22:38:41 +0000108
109
110def command_entry_point(function):
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000111 """ 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 Nagybc687582016-01-12 22:38:41 +0000118
Laszlo Nagybc687582016-01-12 22:38:41 +0000119 @functools.wraps(function)
120 def wrapper(*args, **kwargs):
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000121 """ Do housekeeping tasks and execute the wrapped method. """
Laszlo Nagybc687582016-01-12 22:38:41 +0000122
Laszlo Nagybc687582016-01-12 22:38:41 +0000123 try:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000124 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 Nagybc687582016-01-12 22:38:41 +0000130 except KeyboardInterrupt:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000131 logging.warning('Keyboard interrupt')
132 return 130 # Signal received exit code for bash.
Laszlo Nagybc687582016-01-12 22:38:41 +0000133 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 Nagy2e9c9222017-03-04 01:08:05 +0000140 "verbose mode (add '-vvvv' as argument).")
141 return 64 # Some non used exit code for internal errors.
Laszlo Nagybc687582016-01-12 22:38:41 +0000142 finally:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000143 logging.shutdown()
Laszlo Nagybc687582016-01-12 22:38:41 +0000144
145 return wrapper
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000146
147
148def 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
202def 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 }