]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tools/scan-build-py/libscanbuild/__init__.py
Vendor import of clang trunk r300422:
[FreeBSD/FreeBSD.git] / tools / scan-build-py / libscanbuild / __init__.py
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.
6 """ This module is a collection of methods commonly used in this project. """
7 import collections
8 import functools
9 import json
10 import logging
11 import os
12 import os.path
13 import re
14 import shlex
15 import subprocess
16 import sys
17
18 ENVIRONMENT_KEY = 'INTERCEPT_BUILD'
19
20 Execution = collections.namedtuple('Execution', ['pid', 'cwd', 'cmd'])
21
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 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
57 def 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
78
79
80 def reconfigure_logging(verbose_level):
81     """ Reconfigure logging level and format based on the verbose flag.
82
83     :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.
92     level = logging.WARNING - min(logging.WARNING, (10 * verbose_level))
93     root.setLevel(level)
94     # Be verbose with messages.
95     if verbose_level <= 3:
96         fmt_string = '%(name)s: %(levelname)s: %(message)s'
97     else:
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]
102
103
104 def command_entry_point(function):
105     """ 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. """
112
113     @functools.wraps(function)
114     def wrapper(*args, **kwargs):
115         """ Do housekeeping tasks and execute the wrapped method. """
116
117         try:
118             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)
124         except KeyboardInterrupt:
125             logging.warning('Keyboard interrupt')
126             return 130  # Signal received exit code for bash.
127         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 "
134                               "verbose mode (add '-vvvv' as argument).")
135             return 64  # Some non used exit code for internal errors.
136         finally:
137             logging.shutdown()
138
139     return wrapper
140
141
142 def 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
196 def 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     }