aboutsummaryrefslogtreecommitdiff
path: root/tools/scan-build-py/libscanbuild/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/scan-build-py/libscanbuild/__init__.py')
-rw-r--r--tools/scan-build-py/libscanbuild/__init__.py185
1 files changed, 154 insertions, 31 deletions
diff --git a/tools/scan-build-py/libscanbuild/__init__.py b/tools/scan-build-py/libscanbuild/__init__.py
index c020b4e4345d..800926ebb6f2 100644
--- a/tools/scan-build-py/libscanbuild/__init__.py
+++ b/tools/scan-build-py/libscanbuild/__init__.py
@@ -3,10 +3,21 @@
#
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
-"""
-This module responsible to run the Clang static analyzer against any build
-and generate reports.
-"""
+""" This module is a collection of methods commonly used in this project. """
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import re
+import shlex
+import subprocess
+import sys
+
+ENVIRONMENT_KEY = 'INTERCEPT_BUILD'
+
+Execution = collections.namedtuple('Execution', ['pid', 'cwd', 'cmd'])
def duplicate_check(method):
@@ -30,44 +41,89 @@ def duplicate_check(method):
return predicate
-def tempdir():
- """ Return the default temorary directory. """
-
- from os import getenv
- return getenv('TMPDIR', getenv('TEMP', getenv('TMP', '/tmp')))
-
-
-def initialize_logging(verbose_level):
- """ Output content controlled by the verbosity level. """
-
- import sys
- import os.path
- import logging
+def run_build(command, *args, **kwargs):
+ """ Run and report build command execution
+
+ :param command: array of tokens
+ :return: exit code of the process
+ """
+ environment = kwargs.get('env', os.environ)
+ logging.debug('run build %s, in environment: %s', command, environment)
+ exit_code = subprocess.call(command, *args, **kwargs)
+ logging.debug('build finished with exit code: %d', exit_code)
+ return exit_code
+
+
+def run_command(command, cwd=None):
+ """ Run a given command and report the execution.
+
+ :param command: array of tokens
+ :param cwd: the working directory where the command will be executed
+ :return: output of the command
+ """
+ def decode_when_needed(result):
+ """ check_output returns bytes or string depend on python version """
+ return result.decode('utf-8') if isinstance(result, bytes) else result
+
+ try:
+ directory = os.path.abspath(cwd) if cwd else os.getcwd()
+ logging.debug('exec command %s in %s', command, directory)
+ output = subprocess.check_output(command,
+ cwd=directory,
+ stderr=subprocess.STDOUT)
+ return decode_when_needed(output).splitlines()
+ except subprocess.CalledProcessError as ex:
+ ex.output = decode_when_needed(ex.output).splitlines()
+ raise ex
+
+
+def reconfigure_logging(verbose_level):
+ """ Reconfigure logging level and format based on the verbose flag.
+
+ :param verbose_level: number of `-v` flags received by the command
+ :return: no return value
+ """
+ # Exit when nothing to do.
+ if verbose_level == 0:
+ return
+
+ root = logging.getLogger()
+ # Tune logging level.
level = logging.WARNING - min(logging.WARNING, (10 * verbose_level))
-
+ root.setLevel(level)
+ # Be verbose with messages.
if verbose_level <= 3:
- fmt_string = '{0}: %(levelname)s: %(message)s'
+ fmt_string = '%(name)s: %(levelname)s: %(message)s'
else:
- fmt_string = '{0}: %(levelname)s: %(funcName)s: %(message)s'
-
- program = os.path.basename(sys.argv[0])
- logging.basicConfig(format=fmt_string.format(program), level=level)
+ fmt_string = '%(name)s: %(levelname)s: %(funcName)s: %(message)s'
+ handler = logging.StreamHandler(sys.stdout)
+ handler.setFormatter(logging.Formatter(fmt=fmt_string))
+ root.handlers = [handler]
def command_entry_point(function):
- """ Decorator for command entry points. """
+ """ Decorator for command entry methods.
+
+ The decorator initialize/shutdown logging and guard on programming
+ errors (catch exceptions).
- import functools
- import logging
+ The decorated method can have arbitrary parameters, the return value will
+ be the exit code of the process. """
@functools.wraps(function)
def wrapper(*args, **kwargs):
+ """ Do housekeeping tasks and execute the wrapped method. """
- exit_code = 127
try:
- exit_code = function(*args, **kwargs)
+ logging.basicConfig(format='%(name)s: %(message)s',
+ level=logging.WARNING,
+ stream=sys.stdout)
+ # This hack to get the executable name as %(name).
+ logging.getLogger().name = os.path.basename(sys.argv[0])
+ return function(*args, **kwargs)
except KeyboardInterrupt:
- logging.warning('Keyboard interupt')
+ logging.warning('Keyboard interrupt')
+ return 130 # Signal received exit code for bash.
except Exception:
logging.exception('Internal error.')
if logging.getLogger().isEnabledFor(logging.DEBUG):
@@ -75,8 +131,75 @@ def command_entry_point(function):
"to the bug report")
else:
logging.error("Please run this command again and turn on "
- "verbose mode (add '-vvv' as argument).")
+ "verbose mode (add '-vvvv' as argument).")
+ return 64 # Some non used exit code for internal errors.
finally:
- return exit_code
+ logging.shutdown()
return wrapper
+
+
+def compiler_wrapper(function):
+ """ Implements compiler wrapper base functionality.
+
+ A compiler wrapper executes the real compiler, then implement some
+ functionality, then returns with the real compiler exit code.
+
+ :param function: the extra functionality what the wrapper want to
+ do on top of the compiler call. If it throws exception, it will be
+ caught and logged.
+ :return: the exit code of the real compiler.
+
+ The :param function: will receive the following arguments:
+
+ :param result: the exit code of the compilation.
+ :param execution: the command executed by the wrapper. """
+
+ def is_cxx_compiler():
+ """ Find out was it a C++ compiler call. Compiler wrapper names
+ contain the compiler type. C++ compiler wrappers ends with `c++`,
+ but might have `.exe` extension on windows. """
+
+ wrapper_command = os.path.basename(sys.argv[0])
+ return re.match(r'(.+)c\+\+(.*)', wrapper_command)
+
+ def run_compiler(executable):
+ """ Execute compilation with the real compiler. """
+
+ command = executable + sys.argv[1:]
+ logging.debug('compilation: %s', command)
+ result = subprocess.call(command)
+ logging.debug('compilation exit code: %d', result)
+ return result
+
+ # Get relevant parameters from environment.
+ parameters = json.loads(os.environ[ENVIRONMENT_KEY])
+ reconfigure_logging(parameters['verbose'])
+ # Execute the requested compilation. Do crash if anything goes wrong.
+ cxx = is_cxx_compiler()
+ compiler = parameters['cxx'] if cxx else parameters['cc']
+ result = run_compiler(compiler)
+ # Call the wrapped method and ignore it's return value.
+ try:
+ call = Execution(
+ pid=os.getpid(),
+ cwd=os.getcwd(),
+ cmd=['c++' if cxx else 'cc'] + sys.argv[1:])
+ function(result, call)
+ except:
+ logging.exception('Compiler wrapper failed complete.')
+ finally:
+ # Always return the real compiler exit code.
+ return result
+
+
+def wrapper_environment(args):
+ """ Set up environment for interpose compiler wrapper."""
+
+ return {
+ ENVIRONMENT_KEY: json.dumps({
+ 'verbose': args.verbose,
+ 'cc': shlex.split(args.cc),
+ 'cxx': shlex.split(args.cxx)
+ })
+ }