blob: 903207c6be090ffed0a6814aade9c39ee5b6c34d [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
Ilya Biryukov8b9b3bd2018-03-01 14:54:16 +000022CtuConfig = collections.namedtuple('CtuConfig', ['collect', 'analyze', 'dir',
Rafael Stahl8c487052019-01-10 17:44:04 +000023 'extdef_map_cmd'])
Ilya Biryukov8b9b3bd2018-03-01 14:54:16 +000024
Laszlo Nagybc687582016-01-12 22:38:41 +000025
26def duplicate_check(method):
27 """ Predicate to detect duplicated entries.
28
29 Unique hash method can be use to detect duplicates. Entries are
30 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 False
40 return True
41
42 predicate.unique = method
43 predicate.state = set()
44 return predicate
45
46
Laszlo Nagy52c1d7e2017-02-14 10:30:50 +000047def run_build(command, *args, **kwargs):
48 """ Run and report build command execution
49
50 :param command: array of tokens
51 :return: exit code of the process
52 """
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_code
58
59
Laszlo Nagy46fc18a2017-01-28 22:48:26 +000060def run_command(command, cwd=None):
61 """ Run a given command and report the execution.
62
63 :param command: array of tokens
64 :param cwd: the working directory where the command will be executed
65 :return: output of the command
66 """
67 def decode_when_needed(result):
68 """ check_output returns bytes or string depend on python version """
69 return result.decode('utf-8') if isinstance(result, bytes) else result
70
71 try:
72 directory = os.path.abspath(cwd) if cwd else os.getcwd()
73 logging.debug('exec command %s in %s', command, directory)
74 output = subprocess.check_output(command,
75 cwd=directory,
76 stderr=subprocess.STDOUT)
77 return decode_when_needed(output).splitlines()
78 except subprocess.CalledProcessError as ex:
79 ex.output = decode_when_needed(ex.output).splitlines()
80 raise ex
Laszlo Nagybc687582016-01-12 22:38:41 +000081
82
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000083def reconfigure_logging(verbose_level):
84 """ Reconfigure logging level and format based on the verbose flag.
Laszlo Nagybc687582016-01-12 22:38:41 +000085
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000086 :param verbose_level: number of `-v` flags received by the command
87 :return: no return value
88 """
89 # Exit when nothing to do.
90 if verbose_level == 0:
91 return
92
93 root = logging.getLogger()
94 # Tune logging level.
Laszlo Nagybc687582016-01-12 22:38:41 +000095 level = logging.WARNING - min(logging.WARNING, (10 * verbose_level))
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000096 root.setLevel(level)
97 # Be verbose with messages.
Laszlo Nagybc687582016-01-12 22:38:41 +000098 if verbose_level <= 3:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +000099 fmt_string = '%(name)s: %(levelname)s: %(message)s'
Laszlo Nagybc687582016-01-12 22:38:41 +0000100 else:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000101 fmt_string = '%(name)s: %(levelname)s: %(funcName)s: %(message)s'
102 handler = logging.StreamHandler(sys.stdout)
103 handler.setFormatter(logging.Formatter(fmt=fmt_string))
104 root.handlers = [handler]
Laszlo Nagybc687582016-01-12 22:38:41 +0000105
106
107def command_entry_point(function):
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000108 """ Decorator for command entry methods.
109
110 The decorator initialize/shutdown logging and guard on programming
111 errors (catch exceptions).
112
113 The decorated method can have arbitrary parameters, the return value will
114 be the exit code of the process. """
Laszlo Nagybc687582016-01-12 22:38:41 +0000115
Laszlo Nagybc687582016-01-12 22:38:41 +0000116 @functools.wraps(function)
117 def wrapper(*args, **kwargs):
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000118 """ Do housekeeping tasks and execute the wrapped method. """
Laszlo Nagybc687582016-01-12 22:38:41 +0000119
Laszlo Nagybc687582016-01-12 22:38:41 +0000120 try:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000121 logging.basicConfig(format='%(name)s: %(message)s',
122 level=logging.WARNING,
123 stream=sys.stdout)
124 # This hack to get the executable name as %(name).
125 logging.getLogger().name = os.path.basename(sys.argv[0])
126 return function(*args, **kwargs)
Laszlo Nagybc687582016-01-12 22:38:41 +0000127 except KeyboardInterrupt:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000128 logging.warning('Keyboard interrupt')
129 return 130 # Signal received exit code for bash.
Laszlo Nagybc687582016-01-12 22:38:41 +0000130 except Exception:
131 logging.exception('Internal error.')
132 if logging.getLogger().isEnabledFor(logging.DEBUG):
133 logging.error("Please report this bug and attach the output "
134 "to the bug report")
135 else:
136 logging.error("Please run this command again and turn on "
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000137 "verbose mode (add '-vvvv' as argument).")
138 return 64 # Some non used exit code for internal errors.
Laszlo Nagybc687582016-01-12 22:38:41 +0000139 finally:
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000140 logging.shutdown()
Laszlo Nagybc687582016-01-12 22:38:41 +0000141
142 return wrapper
Laszlo Nagy2e9c9222017-03-04 01:08:05 +0000143
144
145def compiler_wrapper(function):
146 """ Implements compiler wrapper base functionality.
147
148 A compiler wrapper executes the real compiler, then implement some
149 functionality, then returns with the real compiler exit code.
150
151 :param function: the extra functionality what the wrapper want to
152 do on top of the compiler call. If it throws exception, it will be
153 caught and logged.
154 :return: the exit code of the real compiler.
155
156 The :param function: will receive the following arguments:
157
158 :param result: the exit code of the compilation.
159 :param execution: the command executed by the wrapper. """
160
161 def is_cxx_compiler():
162 """ Find out was it a C++ compiler call. Compiler wrapper names
163 contain the compiler type. C++ compiler wrappers ends with `c++`,
164 but might have `.exe` extension on windows. """
165
166 wrapper_command = os.path.basename(sys.argv[0])
167 return re.match(r'(.+)c\+\+(.*)', wrapper_command)
168
169 def run_compiler(executable):
170 """ Execute compilation with the real compiler. """
171
172 command = executable + sys.argv[1:]
173 logging.debug('compilation: %s', command)
174 result = subprocess.call(command)
175 logging.debug('compilation exit code: %d', result)
176 return result
177
178 # Get relevant parameters from environment.
179 parameters = json.loads(os.environ[ENVIRONMENT_KEY])
180 reconfigure_logging(parameters['verbose'])
181 # Execute the requested compilation. Do crash if anything goes wrong.
182 cxx = is_cxx_compiler()
183 compiler = parameters['cxx'] if cxx else parameters['cc']
184 result = run_compiler(compiler)
185 # Call the wrapped method and ignore it's return value.
186 try:
187 call = Execution(
188 pid=os.getpid(),
189 cwd=os.getcwd(),
190 cmd=['c++' if cxx else 'cc'] + sys.argv[1:])
191 function(result, call)
192 except:
193 logging.exception('Compiler wrapper failed complete.')
194 finally:
195 # Always return the real compiler exit code.
196 return result
197
198
199def wrapper_environment(args):
200 """ Set up environment for interpose compiler wrapper."""
201
202 return {
203 ENVIRONMENT_KEY: json.dumps({
204 'verbose': args.verbose,
205 'cc': shlex.split(args.cc),
206 'cxx': shlex.split(args.cxx)
207 })
208 }