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(&current);
-    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(&current);
-    bear_release_env_t(&current);
+    environ = original;
+    bear_strings_release(modified);
 
     return result;
 }
@@ -311,12 +310,12 @@
 
     DLSYM(func, fp, "execvP");
 
-    bear_env_t current;
-    bear_capture_env_t(&current);
-    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(&current);
-    bear_release_env_t(&current);
+    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
cfe-commits@cs.uiuc.edu
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to