this change try to address the review findings by @dcoughlin.
- it implements two new ways of operation. now it has the following modes:
- installed two-phase mode (using libear dynamic linker preload technique)
- it has the less overhead
- works on FreeBSD/Linux/OSX
- it works with many build system
- non-installed two-phase mode (using compiler wrapper for interception)
- no benefit other than, it supports non installed mode
- it works build system which are using CC/CXX environment variables
- non-installed one-phase mode (using compiler wrapper)
- it supports build systems that move or modify build system intermediates
- it works build system which are using CC/CXX environment variables
tried to address other acceptance criteria as well:
- it was also tuned to be more compatible with the existing scan-build
implementation.
- tested against Clang builds (in multiple operation modes)
- supports installation less mode (see above), supports multiple Clang
installation
run against the SATestBuild.py needs more explanation. (maybe a README file in
that directory would be better than a skype call.)
http://reviews.llvm.org/D9600
Files:
tools/scan-build-py/bin/analyze-build
tools/scan-build-py/bin/analyze-c++
tools/scan-build-py/bin/analyze-cc
tools/scan-build-py/bin/intercept-build
tools/scan-build-py/bin/intercept-c++
tools/scan-build-py/bin/intercept-cc
tools/scan-build-py/bin/scan-build
tools/scan-build-py/libear/config.h.in
tools/scan-build-py/libear/ear.c
tools/scan-build-py/libscanbuild/driver.py
tools/scan-build-py/libscanbuild/intercept.py
tools/scan-build-py/libscanbuild/interposition.py
tools/scan-build-py/libscanbuild/options.py
tools/scan-build-py/libscanbuild/report.py
tools/scan-build-py/libscanbuild/runner.py
tools/scan-build-py/setup.py
tools/scan-build-py/tests/functional/cases/__init__.py
tools/scan-build-py/tests/functional/cases/test_create_cdb.py
tools/scan-build-py/tests/functional/cases/test_exec_anatomy.py
tools/scan-build-py/tests/functional/cases/test_from_cdb.py
tools/scan-build-py/tests/functional/cases/test_from_cmd.py
tools/scan-build-py/tests/unit/test_report.py
EMAIL PREFERENCES
http://reviews.llvm.org/settings/panel/emailpreferences/
Index: tools/scan-build-py/bin/analyze-build
===================================================================
--- /dev/null
+++ tools/scan-build-py/bin/analyze-build
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+
+if __name__ == '__main__':
+ import sys
+ import os.path
+ this_dir = os.path.dirname(os.path.realpath(__file__))
+ sys.path.append(os.path.dirname(this_dir))
+
+ from libscanbuild.interposition import main
+ sys.exit(main(this_dir))
Index: tools/scan-build-py/bin/analyze-c++
===================================================================
--- /dev/null
+++ tools/scan-build-py/bin/analyze-c++
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+
+if __name__ == '__main__':
+ import sys
+ import os.path
+ this_dir = os.path.dirname(os.path.realpath(__file__))
+ sys.path.append(os.path.dirname(this_dir))
+
+ from libscanbuild.interposition import wrapper
+ sys.exit(wrapper(True))
Index: tools/scan-build-py/bin/analyze-cc
===================================================================
--- /dev/null
+++ tools/scan-build-py/bin/analyze-cc
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+
+if __name__ == '__main__':
+ import sys
+ import os.path
+ this_dir = os.path.dirname(os.path.realpath(__file__))
+ sys.path.append(os.path.dirname(this_dir))
+
+ from libscanbuild.interposition import wrapper
+ sys.exit(wrapper(False))
Index: tools/scan-build-py/bin/intercept-build
===================================================================
--- /dev/null
+++ tools/scan-build-py/bin/intercept-build
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+
+if __name__ == '__main__':
+ import multiprocessing
+ multiprocessing.freeze_support()
+
+ import sys
+ import os.path
+ this_dir = os.path.dirname(os.path.realpath(__file__))
+ sys.path.append(os.path.dirname(this_dir))
+
+ from libscanbuild.driver import main
+ sys.exit(main(this_dir))
Index: tools/scan-build-py/bin/intercept-c++
===================================================================
--- /dev/null
+++ tools/scan-build-py/bin/intercept-c++
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+
+if __name__ == '__main__':
+ import sys
+ import os.path
+ this_dir = os.path.dirname(os.path.realpath(__file__))
+ sys.path.append(os.path.dirname(this_dir))
+
+ from libscanbuild.intercept import wrapper
+ sys.exit(wrapper(True))
Index: tools/scan-build-py/bin/intercept-cc
===================================================================
--- /dev/null
+++ tools/scan-build-py/bin/intercept-cc
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+
+if __name__ == '__main__':
+ import sys
+ import os.path
+ this_dir = os.path.dirname(os.path.realpath(__file__))
+ sys.path.append(os.path.dirname(this_dir))
+
+ from libscanbuild.intercept import wrapper
+ sys.exit(wrapper(False))
Index: tools/scan-build-py/bin/scan-build
===================================================================
--- tools/scan-build-py/bin/scan-build
+++ tools/scan-build-py/bin/scan-build
@@ -1,15 +1,16 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# The LLVM Compiler Infrastructure
-#
-# This file is distributed under the University of Illinois Open Source
-# License. See LICENSE.TXT for details.
+#!/usr/bin/env bash
+
+set -o nounset
+set -o errexit
-import sys
-import multiprocessing
-from libscanbuild.driver import main
+SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+# depend on which model you want the scan-build to work you need to choose
+# from the two implementations. 'analyze-build' is working as the old Perl
+# implementation was. while the 'intercept-build' is generate compilation
+# database first and then run the analyzer against the entries.
+#
+#"$SCRIPT_DIR/analyze-build" $@
+#"$SCRIPT_DIR/intercept-build" all $@
-if __name__ == '__main__':
- multiprocessing.freeze_support()
- sys.exit(main())
+"$SCRIPT_DIR/intercept-build" all $@
Index: tools/scan-build-py/libear/config.h.in
===================================================================
--- tools/scan-build-py/libear/config.h.in
+++ tools/scan-build-py/libear/config.h.in
@@ -21,7 +21,7 @@
#cmakedefine APPLE
-#define ENV_OUTPUT "BEAR_OUTPUT"
+#define ENV_OUTPUT "BUILD_INTERCEPT_TARGET_DIR"
#ifdef APPLE
# define ENV_FLAT "DYLD_FORCE_FLAT_NAMESPACE"
Index: tools/scan-build-py/libear/ear.c
===================================================================
--- tools/scan-build-py/libear/ear.c
+++ tools/scan-build-py/libear/ear.c
@@ -44,7 +44,6 @@
typedef char const * bear_env_t[ENV_SIZE];
static int bear_capture_env_t(bear_env_t *env);
-static void bear_restore_env_t(bear_env_t *env);
static void bear_release_env_t(bear_env_t *env);
static char const **bear_update_environment(char *const envp[], bear_env_t *env);
static char const **bear_update_environ(char const **in, char const *key, char const *value);
@@ -293,12 +292,12 @@
DLSYM(func, fp, "execvp");
- bear_env_t current;
- bear_capture_env_t(¤t);
- bear_restore_env_t(&initial_env);
+ char **const original = environ;
+ char const **const modified = bear_update_environment(original, &initial_env);
+ environ = (char **)modified;
int const result = (*fp)(file, argv);
- bear_restore_env_t(¤t);
- bear_release_env_t(¤t);
+ environ = original;
+ bear_strings_release(modified);
return result;
}
@@ -311,12 +310,12 @@
DLSYM(func, fp, "execvP");
- bear_env_t current;
- bear_capture_env_t(¤t);
- bear_restore_env_t(&initial_env);
+ char **const original = environ;
+ char const **const modified = bear_update_environment(original, &initial_env);
+ environ = (char **)modified;
int const result = (*fp)(file, search_path, argv);
- bear_restore_env_t(¤t);
- bear_release_env_t(¤t);
+ environ = original;
+ bear_strings_release(modified);
return result;
}
@@ -421,16 +420,6 @@
return status;
}
-static void bear_restore_env_t(bear_env_t *env) {
- for (size_t it = 0; it < ENV_SIZE; ++it)
- if (((*env)[it])
- ? setenv(env_names[it], (*env)[it], 1)
- : unsetenv(env_names[it])) {
- perror("bear: setenv");
- exit(EXIT_FAILURE);
- }
-}
-
static void bear_release_env_t(bear_env_t *env) {
for (size_t it = 0; it < ENV_SIZE; ++it) {
free((void *)(*env)[it]);
Index: tools/scan-build-py/libscanbuild/driver.py
===================================================================
--- tools/scan-build-py/libscanbuild/driver.py
+++ tools/scan-build-py/libscanbuild/driver.py
@@ -30,7 +30,7 @@
__all__ = ['main']
-def main():
+def main(bin_dir):
""" Entry point for 'scan-build'. """
try:
@@ -42,7 +42,8 @@
logging.debug('Parsed arguments: %s', args)
# run build command and capture compiler executions
- exit_code = capture(args) if args.action in {'all', 'intercept'} else 0
+ exit_code = capture(args, bin_dir) \
+ if args.action in {'all', 'intercept'} else 0
# when we only do interception the job is done
if args.action == 'intercept':
return exit_code
@@ -51,7 +52,7 @@
with ReportDirectory(args.output, args.keep_empty) as target_dir:
run_analyzer(args, target_dir.name)
# cover report generation and bug counting
- number_of_bugs = document(args, target_dir.name)
+ number_of_bugs = document(args, target_dir.name, True)
# remove the compilation database when it was not requested
if args.action == 'all' and os.path.exists(args.cdb):
os.unlink(args.cdb)
@@ -107,8 +108,8 @@
def exclude(filename):
""" Return true when any excluded directory prefix the filename. """
- return any(re.match(r'^' + directory, filename) for directory
- in args.excludes)
+ return any(re.match(r'^' + directory, filename)
+ for directory in args.excludes)
consts = {
'clang': args.clang,
Index: tools/scan-build-py/libscanbuild/intercept.py
===================================================================
--- tools/scan-build-py/libscanbuild/intercept.py
+++ tools/scan-build-py/libscanbuild/intercept.py
@@ -27,23 +27,18 @@
import os.path
import re
import shlex
-import pkg_resources
import itertools
from libscanbuild import duplicate_check, tempdir
from libscanbuild.command import Action, classify_parameters
-__all__ = ['capture']
+__all__ = ['capture', 'wrapper']
-if 'darwin' == sys.platform:
- ENVIRONMENTS = [("ENV_OUTPUT", "BEAR_OUTPUT"),
- ("ENV_PRELOAD", "DYLD_INSERT_LIBRARIES"),
- ("ENV_FLAT", "DYLD_FORCE_FLAT_NAMESPACE")]
-else:
- ENVIRONMENTS = [("ENV_OUTPUT", "BEAR_OUTPUT"),
- ("ENV_PRELOAD", "LD_PRELOAD")]
+GS = chr(0x1d)
+RS = chr(0x1e)
+US = chr(0x1f)
-def capture(args):
+def capture(args, wrappers_dir):
""" The entry point of build command interception. """
def post_processing(commands):
@@ -66,9 +61,11 @@
if os.path.exists(entry['file']) and not duplicate(entry))
return commands
- with TemporaryDirectory(prefix='bear-', dir=tempdir()) as tmpdir:
+ with TemporaryDirectory(prefix='build-intercept', dir=tempdir()) as tmpdir:
# run the build command
- exit_code = run_build(args.build, tmpdir)
+ environment = setup_environment(args, tmpdir, wrappers_dir)
+ logging.debug('run build in environment: %s', environment)
+ exit_code = subprocess.call(args.build, env=environment)
logging.debug('build finished with exit code: %d', exit_code)
# read the intercepted exec calls
commands = (parse_exec_trace(os.path.join(tmpdir, filename))
@@ -81,25 +78,71 @@
return exit_code
-def run_build(command, destination):
- """ Runs the original build command.
+def setup_environment(args, destination, wrappers_dir):
+ """ Sets up the environment for the build command.
It sets the required environment variables and execute the given command.
- The exec calls will be logged by the 'libear' preloaded library. """
-
- lib_name = 'libear.dylib' if 'darwin' == sys.platform else 'libear.so'
- ear_so_file = pkg_resources.resource_filename('libscanbuild', lib_name)
+ The exec calls will be logged by the 'libear' preloaded library or by the
+ 'wrapper' programs. """
environment = dict(os.environ)
- for alias, key in ENVIRONMENTS:
- value = '1'
- if alias == 'ENV_PRELOAD':
- value = ear_so_file
- elif alias == 'ENV_OUTPUT':
- value = destination
- environment.update({key: value})
-
- return subprocess.call(command, env=environment)
+ environment.update({'BUILD_INTERCEPT_TARGET_DIR': destination})
+
+ if sys.platform in {'win32', 'cygwin'} or not ear_library_path(False):
+ environment.update({
+ 'CC': os.path.join(wrappers_dir, 'intercept-cc'),
+ 'CXX': os.path.join(wrappers_dir, 'intercept-cxx'),
+ 'BUILD_INTERCEPT_CC': args.cc,
+ 'BUILD_INTERCEPT_CXX': args.cxx,
+ 'BUILD_INTERCEPT_VERBOSE': 'DEBUG' if args.verbose > 2 else 'INFO'
+ })
+ elif 'darwin' == sys.platform:
+ environment.update({
+ 'DYLD_INSERT_LIBRARIES': ear_library_path(True),
+ 'DYLD_FORCE_FLAT_NAMESPACE': '1'
+ })
+ else:
+ environment.update({'LD_PRELOAD': ear_library_path(False)})
+
+ return environment
+
+
+def wrapper(cplusplus):
+ """ This method implements basic compiler wrapper functionality.
+
+ It does generate execution report into target directory. And execute
+ the wrapped compilation with the real compiler. The parameters for
+ report and execution are from environment variables.
+
+ Those parameters which for 'libear' library can't have meaningful
+ values are faked. """
+
+ # initialize wrapper logging
+ logging.basicConfig(format='intercept: %(levelname)s: %(message)s',
+ level=os.getenv('BUILD_INTERCEPT_VERBOSE', 'INFO'))
+ # write report
+ try:
+ target_dir = os.getenv('BUILD_INTERCEPT_TARGET_DIR')
+ if not target_dir:
+ raise UserWarning('exec report target directory not found')
+ pid = str(os.getpid())
+ target_file = os.path.join(target_dir, pid + '.cmd')
+ logging.debug('writing exec report to: %s', target_file)
+ with open(target_file, 'ab') as handler:
+ working_dir = os.getcwd()
+ command = US.join(sys.argv) + US
+ content = RS.join([pid, pid, 'wrapper', working_dir, command]) + GS
+ handler.write(content.encode('utf-8'))
+ except IOError:
+ logging.exception('writing exec report failed')
+ except UserWarning as warning:
+ logging.warning(warning)
+ # execute with real compiler
+ compiler = os.getenv('BUILD_INTERCEPT_CXX', 'c++') if cplusplus \
+ else os.getenv('BUILD_INTERCEPT_CC', 'cc')
+ compilation = [compiler] + sys.argv[1:]
+ logging.debug('execute compiler: %s', compilation)
+ return subprocess.call(compilation)
def parse_exec_trace(filename):
@@ -109,9 +152,6 @@
generated by the interception library or wrapper command. A single
report file _might_ contain multiple process creation info. """
- GS = chr(0x1d)
- RS = chr(0x1e)
- US = chr(0x1f)
with open(filename, 'r') as handler:
content = handler.read()
for group in filter(bool, content.split(GS)):
@@ -137,14 +177,12 @@
return os.path.normpath(fullname)
atoms = classify_parameters(entry['command'])
- if atoms['action'] <= Action.Compile:
- for filename in atoms.get('files', []):
- if is_source_file(filename):
- yield {
- 'directory': entry['directory'],
- 'command': join_command(entry['command']),
- 'file': abspath(entry['directory'], filename)
- }
+ return ({
+ 'directory': entry['directory'],
+ 'command': join_command(entry['command']),
+ 'file': abspath(entry['directory'], filename)
+ } for filename in atoms.get('files', [])
+ if is_source_file(filename) and atoms['action'] <= Action.Compile)
def shell_escape(arg):
@@ -175,6 +213,7 @@
""" A predicate to decide the entry is a compiler call or not. """
patterns = [
+ re.compile(r'^([^/]*/)*intercept-c(c|\+\+)$'),
re.compile(r'^([^/]*/)*c(c|\+\+)$'),
re.compile(r'^([^/]*/)*([^-]*-)*g(cc|\+\+)(-\d+(\.\d+){0,2})?$'),
re.compile(r'^([^/]*/)*([^-]*-)*clang(\+\+)?(-\d+(\.\d+){0,2})?$'),
@@ -200,6 +239,18 @@
return '<>'.join([filename, directory, command])
+def ear_library_path(darwin):
+ """ Returns the full path to the 'libear' library. """
+
+ try:
+ import pkg_resources
+ lib_name = 'libear.dylib' if darwin else 'libear.so'
+ lib_file = pkg_resources.resource_filename('libscanbuild', lib_name)
+ return lib_file if os.path.exists(lib_file) else None
+ except ImportError:
+ return None
+
+
if sys.version_info.major >= 3 and sys.version_info.minor >= 2:
from tempfile import TemporaryDirectory
else:
Index: tools/scan-build-py/libscanbuild/interposition.py
===================================================================
--- /dev/null
+++ tools/scan-build-py/libscanbuild/interposition.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+
+import os
+import sys
+import argparse
+import subprocess
+import logging
+from libscanbuild.options import (common_parameters, analyze_parameters,
+ build_command)
+from libscanbuild.driver import (initialize_logging, ReportDirectory,
+ analyzer_params, print_checkers,
+ print_active_checkers)
+from libscanbuild.report import document
+from libscanbuild.clang import get_checkers
+from libscanbuild.runner import action_check
+from libscanbuild.intercept import is_source_file
+from libscanbuild.command import classify_parameters
+
+__all__ = ['main', 'wrapper']
+
+
+def main(bin_dir):
+ """ Entry point for 'analyze-build'. """
+
+ try:
+ args = parse_and_validate_arguments()
+ # setup logging
+ initialize_logging(args)
+ logging.debug('Parsed arguments: %s', args)
+ # run the build
+ with ReportDirectory(args.output, args.keep_empty) as target_dir:
+ # run the build command
+ environment = setup_environment(args, target_dir.name, bin_dir)
+ logging.debug('run build in environment: %s', environment)
+ exit_code = subprocess.call(args.build, env=environment)
+ logging.debug('build finished with exit code: %d', exit_code)
+ # cover report generation and bug counting
+ number_of_bugs = document(args, target_dir.name, False)
+ # set exit status as it was requested
+ return number_of_bugs if args.status_bugs else exit_code
+ except KeyboardInterrupt:
+ return 1
+ except Exception:
+ logging.exception("Something unexpected had happened.")
+ return 127
+
+
+def parse_and_validate_arguments():
+ """ Parse and validate command line arguments. """
+
+ # create parser..
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ common_parameters(parser, False)
+ analyze_parameters(parser)
+ build_command(parser)
+ # run it..
+ args = parser.parse_args()
+ # validate..
+ if args.help_checkers_verbose:
+ print_checkers(get_checkers(args.clang, args.plugins))
+ parser.exit()
+ elif args.help_checkers:
+ print_active_checkers(get_checkers(args.clang, args.plugins))
+ parser.exit()
+ if not args.build:
+ parser.error('missing build command')
+ # return it..
+ return args
+
+
+def setup_environment(args, destination, wrapper_dir):
+ """ Sets up the environment for the build command. """
+
+ environment = dict(os.environ)
+ environment.update({
+ 'CC': os.path.join(wrapper_dir, 'analyze-cc'),
+ 'CXX': os.path.join(wrapper_dir, 'analyze-cxx'),
+ 'BUILD_ANALYZE_CC': args.cc,
+ 'BUILD_ANALYZE_CXX': args.cxx,
+ 'BUILD_ANALYZE_CLANG': args.clang,
+ 'BUILD_ANALYZE_VERBOSE': 'DEBUG' if args.verbose > 2 else 'WARNING',
+ 'BUILD_ANALYZE_REPORT_DIR': destination,
+ 'BUILD_ANALYZE_REPORT_FORMAT': args.output_format,
+ 'BUILD_ANALYZE_REPORT_FAILURES': 'yes' if args.report_failures else '',
+ 'BUILD_ANALYZE_PARAMETERS': ' '.join(analyzer_params(args))
+ })
+ return environment
+
+
+def wrapper(cplusplus):
+ """ This method implements basic compiler wrapper functionality. """
+
+ # initialize wrapper logging
+ logging.basicConfig(format='analyze: %(levelname)s: %(message)s',
+ level=os.getenv('BUILD_ANALYZE_VERBOSE', 'INFO'))
+ # execute with real compiler
+ compiler = os.getenv('BUILD_ANALYZE_CXX', 'c++') if cplusplus \
+ else os.getenv('BUILD_ANALYZE_CC', 'cc')
+ compilation = [compiler] + sys.argv[1:]
+ logging.info('execute compiler: %s', compilation)
+ result = subprocess.call(compilation)
+ try:
+ # collect the needed parameters from environment, crash when missing
+ consts = {
+ 'clang': os.getenv('BUILD_ANALYZE_CLANG'),
+ 'output_dir': os.getenv('BUILD_ANALYZE_REPORT_DIR'),
+ 'output_format': os.getenv('BUILD_ANALYZE_REPORT_FORMAT'),
+ 'report_failures': os.getenv('BUILD_ANALYZE_REPORT_FAILURES'),
+ 'direct_args': os.getenv('BUILD_ANALYZE_PARAMETERS',
+ '').split(' '),
+ 'directory': os.getcwd(),
+ }
+ # get relevant parameters from command line arguments
+ args = classify_parameters(sys.argv)
+ filenames = args.pop('files', [])
+ for filename in (name for name in filenames if is_source_file(name)):
+ parameters = dict(args, file=filename, **consts)
+ logging.debug('analyzer parameters %s', parameters)
+ current = action_check(parameters)
+ # display error message from the static analyzer
+ if current is not None:
+ for line in current['error_output']:
+ logging.info(line.rstrip())
+ except Exception:
+ logging.exception("run analyzer inside compiler wrapper failed.")
+ # return compiler exit code
+ return result
Index: tools/scan-build-py/libscanbuild/options.py
===================================================================
--- tools/scan-build-py/libscanbuild/options.py
+++ tools/scan-build-py/libscanbuild/options.py
@@ -29,7 +29,7 @@
help="""Run the static analyzer against the given
build command.""")
- common_parameters(everything)
+ common_parameters(everything, True)
analyze_parameters(everything)
build_command(everything)
@@ -38,7 +38,7 @@
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
help="""Only runs the build and write compilation database.""")
- common_parameters(intercept)
+ common_parameters(intercept, True)
intercept_parameters(intercept)
build_command(intercept)
@@ -48,23 +48,24 @@
help="""Only run the static analyzer against the given
compilation database.""")
- common_parameters(analyze)
+ common_parameters(analyze, True)
analyze_parameters(analyze)
return parser
-def common_parameters(parser):
+def common_parameters(parser, add_cdb):
parser.add_argument(
'--verbose', '-v',
action='count',
default=0,
help="""Enable verbose output from '%(prog)s'. A second and third
'-v' increases verbosity.""")
- parser.add_argument('--cdb',
- metavar='<file>',
- default="compile_commands.json",
- help="""The JSON compilation database.""")
+ if add_cdb:
+ parser.add_argument('--cdb',
+ metavar='<file>',
+ default="compile_commands.json",
+ help="""The JSON compilation database.""")
def build_command(parser):
@@ -112,14 +113,14 @@
within the main source file.""")
format_group = parser.add_mutually_exclusive_group()
format_group.add_argument(
- '--plist',
+ '--plist', '-plist',
dest='output_format',
const='plist',
default='html',
action='store_const',
help="""This option outputs the results as a set of .plist files.""")
format_group.add_argument(
- '--plist-html',
+ '--plist-html', '-plist-html',
dest='output_format',
const='plist-html',
default='html',
@@ -135,13 +136,13 @@
help="""Don't remove the build results directory even if no issues
were reported.""")
advanced.add_argument(
- '--no-failure-reports',
+ '--no-failure-reports', '-no-failure-reports',
dest='report_failures',
action='store_false',
help="""Do not create a 'failures' subdirectory that includes analyzer
crash reports and preprocessed source files.""")
advanced.add_argument(
- '--stats',
+ '--stats', '-stats',
action='store_true',
help="""Generates visitation statistics for the project being analyzed.
""")
@@ -149,14 +150,14 @@
action='store_true',
help="""Generate internal analyzer statistics.""")
advanced.add_argument(
- '--maxloop',
+ '--maxloop', '-maxloop',
metavar='<loop count>',
type=int,
default=4,
help="""Specifiy the number of times a block can be visited before
giving up. Increase for more comprehensive coverage at a cost
of speed.""")
- advanced.add_argument('--store',
+ advanced.add_argument('--store', '-store',
metavar='<model>',
dest='store_model',
default='region',
@@ -167,7 +168,7 @@
analyze code. 'basic' was the default store model for
checker-0.221 and earlier.""")
advanced.add_argument(
- '--constraints',
+ '--constraints', '-constraints',
metavar='<model>',
dest='constraints_model',
default='range',
@@ -185,7 +186,29 @@
option by using the 'clang' packaged with Xcode (on OS X) or
from the PATH.""")
advanced.add_argument(
- '--analyzer-config',
+ '--use-cc',
+ metavar='<path>',
+ dest='cc',
+ default='cc',
+ help="""When '%(prog)s' analyzes a project by interposing a "fake
+ compiler", which executes a real compiler for compilation and
+ do other tasks (to run the static analyzer or just record the
+ compiler invocation). Because of this interposing, '%(prog)s'
+ does not know what compiler your project normally uses.
+ Instead, it simply overrides the CC environment variable, and
+ guesses your default compiler.
+
+ If you need '%(prog)s' to use a specific compiler for
+ *compilation* then you can use this option to specify a path
+ to that compiler.""")
+ advanced.add_argument(
+ '--use-c++',
+ metavar='<path>',
+ dest='cxx',
+ default='c++',
+ help="""This is the same as "--use-cc" but for C++ code.""")
+ advanced.add_argument(
+ '--analyzer-config', '-analyzer-config',
metavar='<options>',
help="""Provide options to pass through to the analyzer's
-analyzer-config flag. Several options are separated with
@@ -211,16 +234,16 @@
plugins = parser.add_argument_group('checker options')
plugins.add_argument(
- '--load-plugin',
+ '--load-plugin', '-load-plugin',
metavar='<plugin library>',
dest='plugins',
action='append',
help="""Loading external checkers using the clang plugin interface.""")
- plugins.add_argument('--enable-checker',
+ plugins.add_argument('--enable-checker', '-enable-checker',
metavar='<checker name>',
action='append',
help="""Enable specific checker.""")
- plugins.add_argument('--disable-checker',
+ plugins.add_argument('--disable-checker', '-disable-checker',
metavar='<checker name>',
action='append',
help="""Disable specific checker.""")
Index: tools/scan-build-py/libscanbuild/report.py
===================================================================
--- tools/scan-build-py/libscanbuild/report.py
+++ tools/scan-build-py/libscanbuild/report.py
@@ -17,7 +17,6 @@
import json
import shutil
import glob
-import pkg_resources
import plistlib
import itertools
from libscanbuild import duplicate_check
@@ -26,7 +25,7 @@
__all__ = ['document']
-def document(args, output_dir):
+def document(args, output_dir, use_cdb):
""" Generates cover report and returns the number of bugs/crashes. """
html_reports_available = args.output_format in {'html', 'plist-html'}
@@ -38,9 +37,8 @@
result = crash_count + bug_counter.total
# generate cover file when it's needed
if html_reports_available and result:
- # generate common prefix for source files to have sort filenames
- with open(args.cdb, 'r') as handle:
- prefix = commonprefix(item['file'] for item in json.load(handle))
+ # common prefix for source files to have sort filenames
+ prefix = commonprefix_from(args.cdb) if use_cdb else os.getcwd()
# assemble the cover from multiple fragments
try:
fragments = []
@@ -49,14 +47,14 @@
fragments.append(bug_report(output_dir, prefix))
if crash_count:
fragments.append(crash_report(output_dir, prefix))
-
assemble_cover(output_dir, prefix, args, fragments)
+ # copy additinal files to the report
+ copy_resource_files(output_dir)
+ if use_cdb:
+ shutil.copy(args.cdb, output_dir)
finally:
for fragment in fragments:
os.remove(fragment)
- # copy additinal files to the report
- copy_resource_files(output_dir)
- shutil.copy(args.cdb, output_dir)
return result
@@ -269,8 +267,8 @@
bugs = itertools.chain.from_iterable(
# parser creates a bug generator not the bug itself
- parser(filename) for filename
- in glob.iglob(os.path.join(output_dir, pattern)))
+ parser(filename)
+ for filename in glob.iglob(os.path.join(output_dir, pattern)))
return (bug for bug in bugs if not duplicate(bug))
@@ -422,10 +420,17 @@
def copy_resource_files(output_dir):
""" Copy the javascript and css files to the report directory. """
- this_package = 'libscanbuild'
- resources_dir = pkg_resources.resource_filename(this_package, 'resources')
- for resource in pkg_resources.resource_listdir(this_package, 'resources'):
- shutil.copy(os.path.join(resources_dir, resource), output_dir)
+ try:
+ import pkg_resources
+ package = 'libscanbuild'
+ resources_dir = pkg_resources.resource_filename(package, 'resources')
+ for resource in pkg_resources.resource_listdir(package, 'resources'):
+ shutil.copy(os.path.join(resources_dir, resource), output_dir)
+ except ImportError:
+ resources_dir = os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'resources')
+ for resource in os.listdir(resources_dir):
+ shutil.copy(os.path.join(resources_dir, resource), output_dir)
def encode_value(container, key, encode):
@@ -439,12 +444,7 @@
def chop(prefix, filename):
""" Create 'filename' from '/prefix/filename' """
- if not len(prefix):
- return filename
- if prefix[-1] != os.path.sep:
- prefix += os.path.sep
- split = filename.split(prefix, 1)
- return split[1] if len(split) == 2 else split[0]
+ return filename if not len(prefix) else os.path.relpath(filename, prefix)
def escape(text):
@@ -480,6 +480,13 @@
return '<!-- {0}{1} -->{2}'.format(name, attributes, os.linesep)
+def commonprefix_from(filename):
+ """ Create file prefix from a compilation database entries. """
+
+ with open(filename, 'r') as handle:
+ return commonprefix(item['file'] for item in json.load(handle))
+
+
def commonprefix(files):
""" Fixed version of os.path.commonprefix. Return the longest path prefix
that is a prefix of all paths in filenames. """
Index: tools/scan-build-py/libscanbuild/runner.py
===================================================================
--- tools/scan-build-py/libscanbuild/runner.py
+++ tools/scan-build-py/libscanbuild/runner.py
@@ -27,9 +27,9 @@
just return and break the chain. """
try:
- logging.debug("Run analyzer against '%s'", opts['command'])
- opts.update(classify_parameters(shlex.split(opts['command'])))
- del opts['command']
+ command = opts.pop('command')
+ logging.debug("Run analyzer against '%s'", command)
+ opts.update(classify_parameters(shlex.split(command)))
return action_check(opts)
except Exception:
@@ -167,16 +167,15 @@
common = []
if 'arch' in opts:
- common.extend(['-arch', opts['arch']])
- del opts['arch']
- if 'compile_options' in opts:
- common.extend(opts['compile_options'])
- del opts['compile_options']
+ common.extend(['-arch', opts.pop('arch')])
+ common.extend(opts.pop('compile_options', []))
common.extend(['-x', opts['language']])
common.append(opts['file'])
- opts.update({'analyze': ['--analyze'] + opts['direct_args'] + common,
- 'report': ['-fsyntax-only', '-E'] + common})
+ opts.update({
+ 'analyze': ['--analyze'] + opts['direct_args'] + common,
+ 'report': ['-fsyntax-only', '-E'] + common
+ })
return continuation(opts)
@@ -237,8 +236,7 @@
key = 'archs_seen'
if key in opts:
# filter out disabled architectures and -arch switches
- archs = [a for a in opts[key]
- if '-arch' != a and a not in disableds]
+ archs = [a for a in opts[key] if '-arch' != a and a not in disableds]
if not archs:
logging.debug('skip analysis, found not supported arch')
@@ -263,8 +261,7 @@
def action_check(opts, continuation=arch_check):
""" Continue analysis only if it compilation or link. """
- if opts['action'] <= Action.Compile:
- del opts['action']
+ if opts.pop('action') <= Action.Compile:
return continuation(opts)
else:
logging.debug('skip analysis, not compilation nor link')
Index: tools/scan-build-py/setup.py
===================================================================
--- tools/scan-build-py/setup.py
+++ tools/scan-build-py/setup.py
@@ -52,7 +52,9 @@
description='static code analyzer wrapper for Clang.',
long_description=open('README.md').read(),
zip_safe=False,
- scripts=['bin/scan-build'],
+ scripts=['bin/scan-build',
+ 'bin/intercept-build', 'bin/intercept-cc', 'bin/intercept-c++',
+ 'bin/analyze-build', 'bin/analyze-cc', 'bin/analyze-c++'],
packages=['libscanbuild'],
package_data={'libscanbuild': ['resources/*']},
cmdclass={'buildear': BuildEAR, 'install': Install, 'build': Build},
Index: tools/scan-build-py/tests/functional/cases/__init__.py
===================================================================
--- tools/scan-build-py/tests/functional/cases/__init__.py
+++ tools/scan-build-py/tests/functional/cases/__init__.py
@@ -23,10 +23,8 @@
def make_args(target):
this_dir, _ = os.path.split(__file__)
path = os.path.normpath(os.path.join(this_dir, '..', 'src'))
- return ['make',
- 'SRCDIR={}'.format(path),
- 'OBJDIR={}'.format(target),
- '-f', os.path.join(path, 'build', 'Makefile')]
+ return ['make', 'SRCDIR={}'.format(path), 'OBJDIR={}'.format(target), '-f',
+ os.path.join(path, 'build', 'Makefile')]
def silent_call(cmd):
Index: tools/scan-build-py/tests/functional/cases/test_create_cdb.py
===================================================================
--- tools/scan-build-py/tests/functional/cases/test_create_cdb.py
+++ tools/scan-build-py/tests/functional/cases/test_create_cdb.py
@@ -13,14 +13,13 @@
class CompilationDatabaseTest(unittest.TestCase):
-
@staticmethod
def run_intercept(tmpdir, args):
- result = os.path.join(tmpdir, 'cdb.json')
- make = make_args(tmpdir) + args
- silent_check_call(['scan-build', 'intercept', '--cdb', result] +
- make)
- return result
+ result = os.path.join(tmpdir, 'cdb.json')
+ make = make_args(tmpdir) + args
+ silent_check_call(
+ ['intercept-build', 'intercept', '--cdb', result] + make)
+ return result
def test_successful_build(self):
with fixtures.TempDir() as tmpdir:
@@ -44,7 +43,7 @@
with fixtures.TempDir() as tmpdir:
result = os.path.join(tmpdir, 'cdb.json')
make = make_args(tmpdir) + ['build_regular']
- silent_check_call(['scan-build', 'intercept', '--cdb', result,
+ silent_check_call(['intercept-build', 'intercept', '--cdb', result,
'env', '-'] + make)
self.assertTrue(os.path.isfile(result))
with open(result, 'r') as handler:
@@ -63,7 +62,8 @@
with fixtures.TempDir() as tmpdir:
result = os.path.join(tmpdir, 'cdb.json')
make = make_args(tmpdir) + ['build_broken']
- silent_call(['scan-build', 'intercept', '--cdb', result] + make)
+ silent_call(
+ ['intercept-build', 'intercept', '--cdb', result] + make)
self.assertTrue(os.path.isfile(result))
with open(result, 'r') as handler:
content = json.load(handler)
@@ -71,13 +71,12 @@
class ExitCodeTest(unittest.TestCase):
-
@staticmethod
def run_intercept(tmpdir, target):
- result = os.path.join(tmpdir, 'cdb.json')
- make = make_args(tmpdir) + [target]
- return silent_call(['scan-build', 'intercept', '--cdb', result] +
- make)
+ result = os.path.join(tmpdir, 'cdb.json')
+ make = make_args(tmpdir) + [target]
+ return silent_call(
+ ['intercept-build', 'intercept', '--cdb', result] + make)
def test_successful_build(self):
with fixtures.TempDir() as tmpdir:
@@ -91,14 +90,13 @@
class ResumeFeatureTest(unittest.TestCase):
-
@staticmethod
def run_intercept(tmpdir, target, args):
- result = os.path.join(tmpdir, 'cdb.json')
- make = make_args(tmpdir) + [target]
- silent_check_call(['scan-build', 'intercept', '--cdb', result] +
- args + make)
- return result
+ result = os.path.join(tmpdir, 'cdb.json')
+ make = make_args(tmpdir) + [target]
+ silent_check_call(
+ ['intercept-build', 'intercept', '--cdb', result] + args + make)
+ return result
def test_overwrite_existing_cdb(self):
with fixtures.TempDir() as tmpdir:
Index: tools/scan-build-py/tests/functional/cases/test_exec_anatomy.py
===================================================================
--- tools/scan-build-py/tests/functional/cases/test_exec_anatomy.py
+++ tools/scan-build-py/tests/functional/cases/test_exec_anatomy.py
@@ -14,7 +14,8 @@
def run(source_dir, target_dir):
def execute(cmd):
- return subprocess.check_call(cmd, cwd=target_dir,
+ return subprocess.check_call(cmd,
+ cwd=target_dir,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
@@ -23,13 +24,12 @@
result_file = os.path.join(target_dir, 'result.json')
expected_file = os.path.join(target_dir, 'expected.json')
- execute(['scan-build', 'intercept', '--cdb', result_file,
- './exec', expected_file])
+ execute(['intercept-build', 'intercept', '--cdb', result_file, './exec',
+ expected_file])
return (expected_file, result_file)
class ExecAnatomyTest(unittest.TestCase):
-
def assertEqualJson(self, expected, result):
def read_json(filename):
with open(filename) as handler:
Index: tools/scan-build-py/tests/functional/cases/test_from_cdb.py
===================================================================
--- tools/scan-build-py/tests/functional/cases/test_from_cdb.py
+++ tools/scan-build-py/tests/functional/cases/test_from_cdb.py
@@ -29,7 +29,8 @@
def run_driver(directory, cdb, args):
- cmd = ['scan-build', 'analyze', '--cdb', cdb, '--output', directory] + args
+ cmd = ['intercept-build', 'analyze', '--cdb', cdb, '--output', directory] \
+ + args
child = subprocess.Popen(cmd,
universal_newlines=True,
stdout=subprocess.PIPE,
@@ -41,7 +42,6 @@
class OutputDirectoryTest(unittest.TestCase):
-
def test_regular_keeps_report_dir(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('regular', tmpdir)
@@ -65,7 +65,6 @@
class ExitCodeTest(unittest.TestCase):
-
def test_regular_does_not_set_exit_code(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('regular', tmpdir)
@@ -112,7 +111,6 @@
class OutputFormatTest(unittest.TestCase):
-
@staticmethod
def get_html_count(directory):
return len(glob.glob(os.path.join(directory, 'report-*.html')))
@@ -151,7 +149,6 @@
class FailureReportTest(unittest.TestCase):
-
def test_broken_creates_failure_reports(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('broken', tmpdir)
@@ -169,12 +166,12 @@
class TitleTest(unittest.TestCase):
-
def assertTitleEqual(self, directory, expected):
import re
patterns = [
re.compile(r'<title>(?P<page>.*)</title>'),
- re.compile(r'<h1>(?P<head>.*)</h1>')]
+ re.compile(r'<h1>(?P<head>.*)</h1>')
+ ]
result = dict()
index = os.path.join(directory, 'result', 'index.html')
Index: tools/scan-build-py/tests/functional/cases/test_from_cmd.py
===================================================================
--- tools/scan-build-py/tests/functional/cases/test_from_cmd.py
+++ tools/scan-build-py/tests/functional/cases/test_from_cmd.py
@@ -12,10 +12,10 @@
class OutputDirectoryTest(unittest.TestCase):
-
@staticmethod
def run_sb(outdir, args):
- return silent_check_call(['scan-build', 'all', '-o', outdir] + args)
+ return silent_check_call(
+ ['intercept-build', 'all', '-o', outdir] + args)
def test_regular_keeps_report_dir(self):
with fixtures.TempDir() as tmpdir:
Index: tools/scan-build-py/tests/unit/test_report.py
===================================================================
--- tools/scan-build-py/tests/unit/test_report.py
+++ tools/scan-build-py/tests/unit/test_report.py
@@ -116,7 +116,11 @@
self.assertEqual('file', sut.chop('/prefix/', '/prefix/file'))
self.assertEqual('lib/file', sut.chop('/prefix/', '/prefix/lib/file'))
self.assertEqual('/prefix/file', sut.chop('', '/prefix/file'))
- self.assertEqual('/prefix/file', sut.chop('apple', '/prefix/file'))
+
+ def test_chop_when_cwd(self):
+ self.assertEqual('../src/file', sut.chop('/cwd', '/src/file'))
+ self.assertEqual('../src/file', sut.chop('/prefix/cwd',
+ '/prefix/src/file'))
class GetPrefixFromCompilationDatabaseTest(fixtures.TestCase):
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits