1 # -*- coding: utf-8 -*-
2 # The LLVM Compiler Infrastructure
4 # This file is distributed under the University of Illinois Open Source
5 # License. See LICENSE.TXT for details.
6 """ This module is a collection of methods commonly used in this project. """
18 ENVIRONMENT_KEY = 'INTERCEPT_BUILD'
20 Execution = collections.namedtuple('Execution', ['pid', 'cwd', 'cmd'])
23 def duplicate_check(method):
24 """ Predicate to detect duplicated entries.
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.
30 This method returns a method which can detect the duplicate values. """
33 entry_hash = predicate.unique(entry)
34 if entry_hash not in predicate.state:
35 predicate.state.add(entry_hash)
39 predicate.unique = method
40 predicate.state = set()
44 def run_build(command, *args, **kwargs):
45 """ Run and report build command execution
47 :param command: array of tokens
48 :return: exit code of the process
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)
57 def run_command(command, cwd=None):
58 """ Run a given command and report the execution.
60 :param command: array of tokens
61 :param cwd: the working directory where the command will be executed
62 :return: output of the command
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
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,
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()
80 def reconfigure_logging(verbose_level):
81 """ Reconfigure logging level and format based on the verbose flag.
83 :param verbose_level: number of `-v` flags received by the command
84 :return: no return value
86 # Exit when nothing to do.
87 if verbose_level == 0:
90 root = logging.getLogger()
92 level = logging.WARNING - min(logging.WARNING, (10 * verbose_level))
94 # Be verbose with messages.
95 if verbose_level <= 3:
96 fmt_string = '%(name)s: %(levelname)s: %(message)s'
98 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]
104 def command_entry_point(function):
105 """ Decorator for command entry methods.
107 The decorator initialize/shutdown logging and guard on programming
108 errors (catch exceptions).
110 The decorated method can have arbitrary parameters, the return value will
111 be the exit code of the process. """
113 @functools.wraps(function)
114 def wrapper(*args, **kwargs):
115 """ Do housekeeping tasks and execute the wrapped method. """
118 logging.basicConfig(format='%(name)s: %(message)s',
119 level=logging.WARNING,
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)
124 except KeyboardInterrupt:
125 logging.warning('Keyboard interrupt')
126 return 130 # Signal received exit code for bash.
128 logging.exception('Internal error.')
129 if logging.getLogger().isEnabledFor(logging.DEBUG):
130 logging.error("Please report this bug and attach the output "
133 logging.error("Please run this command again and turn on "
134 "verbose mode (add '-vvvv' as argument).")
135 return 64 # Some non used exit code for internal errors.
142 def compiler_wrapper(function):
143 """ Implements compiler wrapper base functionality.
145 A compiler wrapper executes the real compiler, then implement some
146 functionality, then returns with the real compiler exit code.
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
151 :return: the exit code of the real compiler.
153 The :param function: will receive the following arguments:
155 :param result: the exit code of the compilation.
156 :param execution: the command executed by the wrapper. """
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. """
163 wrapper_command = os.path.basename(sys.argv[0])
164 return re.match(r'(.+)c\+\+(.*)', wrapper_command)
166 def run_compiler(executable):
167 """ Execute compilation with the real compiler. """
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)
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.
187 cmd=['c++' if cxx else 'cc'] + sys.argv[1:])
188 function(result, call)
190 logging.exception('Compiler wrapper failed complete.')
192 # Always return the real compiler exit code.
196 def wrapper_environment(args):
197 """ Set up environment for interpose compiler wrapper."""
200 ENVIRONMENT_KEY: json.dumps({
201 'verbose': args.verbose,
202 'cc': shlex.split(args.cc),
203 'cxx': shlex.split(args.cxx)