summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaszlo Nagy <rizsotto.mailinglist@gmail.com>2017-03-08 21:18:51 +0000
committerLaszlo Nagy <rizsotto.mailinglist@gmail.com>2017-03-08 21:18:51 +0000
commit0b047d6548d2e745c773f3819fe812d44f6da49f (patch)
tree005c1a42ced32bf619670e52a4cd936a0a748cf6
parenta9ded305be0c0e5c0e703de987dd13286a2d971b (diff)
[scan-build-py] move argument parsing into separate module
Differential Revision: https://reviews.llvm.org/D30601 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@297307 91177308-0d34-0410-b5e6-96231b3b80d8
-rwxr-xr-xtools/scan-build-py/bin/analyze-build4
-rwxr-xr-xtools/scan-build-py/bin/intercept-build4
-rwxr-xr-xtools/scan-build-py/bin/scan-build4
-rw-r--r--tools/scan-build-py/libscanbuild/analyze.py359
-rw-r--r--tools/scan-build-py/libscanbuild/intercept.py90
5 files changed, 55 insertions, 406 deletions
diff --git a/tools/scan-build-py/bin/analyze-build b/tools/scan-build-py/bin/analyze-build
index 2cc9676fd5..991cff0658 100755
--- a/tools/scan-build-py/bin/analyze-build
+++ b/tools/scan-build-py/bin/analyze-build
@@ -13,5 +13,5 @@ import os.path
this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.dirname(this_dir))
-from libscanbuild.analyze import analyze_build_main
-sys.exit(analyze_build_main(this_dir, False))
+from libscanbuild.analyze import analyze_build
+sys.exit(analyze_build())
diff --git a/tools/scan-build-py/bin/intercept-build b/tools/scan-build-py/bin/intercept-build
index 164f2e68be..2c3a26ecdd 100755
--- a/tools/scan-build-py/bin/intercept-build
+++ b/tools/scan-build-py/bin/intercept-build
@@ -13,5 +13,5 @@ import os.path
this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.dirname(this_dir))
-from libscanbuild.intercept import intercept_build_main
-sys.exit(intercept_build_main(this_dir))
+from libscanbuild.intercept import intercept_build
+sys.exit(intercept_build())
diff --git a/tools/scan-build-py/bin/scan-build b/tools/scan-build-py/bin/scan-build
index 601fe89fc3..f0f34695b0 100755
--- a/tools/scan-build-py/bin/scan-build
+++ b/tools/scan-build-py/bin/scan-build
@@ -13,5 +13,5 @@ import os.path
this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.dirname(this_dir))
-from libscanbuild.analyze import analyze_build_main
-sys.exit(analyze_build_main(this_dir, True))
+from libscanbuild.analyze import scan_build
+sys.exit(scan_build())
diff --git a/tools/scan-build-py/libscanbuild/analyze.py b/tools/scan-build-py/libscanbuild/analyze.py
index 7d050fa539..0485ca85f6 100644
--- a/tools/scan-build-py/libscanbuild/analyze.py
+++ b/tools/scan-build-py/libscanbuild/analyze.py
@@ -11,73 +11,68 @@ To run the static analyzer against a build is done in multiple steps:
-- Analyze: run the analyzer against the captured commands,
-- Report: create a cover report from the analyzer outputs. """
-import sys
import re
import os
import os.path
import json
-import argparse
import logging
import tempfile
import multiprocessing
import contextlib
import datetime
from libscanbuild import command_entry_point, compiler_wrapper, \
- wrapper_environment, reconfigure_logging, run_build, tempdir
+ wrapper_environment, run_build
+from libscanbuild.arguments import parse_args_for_scan_build, \
+ parse_args_for_analyze_build
from libscanbuild.runner import run
from libscanbuild.intercept import capture
from libscanbuild.report import document
-from libscanbuild.clang import get_checkers
from libscanbuild.compilation import split_command
-__all__ = ['analyze_build_main', 'analyze_compiler_wrapper']
+__all__ = ['scan_build', 'analyze_build', 'analyze_compiler_wrapper']
COMPILER_WRAPPER_CC = 'analyze-cc'
COMPILER_WRAPPER_CXX = 'analyze-c++'
@command_entry_point
-def analyze_build_main(bin_dir, from_build_command):
- """ Entry point for 'analyze-build' and 'scan-build'. """
-
- parser = create_parser(from_build_command)
- args = parser.parse_args()
- validate(parser, args, from_build_command)
-
- # setup logging
- reconfigure_logging(args.verbose)
- logging.debug('Raw arguments %s', sys.argv)
+def scan_build():
+ """ Entry point for scan-build command. """
+ args = parse_args_for_scan_build()
with report_directory(args.output, args.keep_empty) as target_dir:
- if not from_build_command:
- # run analyzer only and generate cover report
- run_analyzer(args, target_dir)
- number_of_bugs = document(args, target_dir, True)
- return number_of_bugs if args.status_bugs else 0
- elif args.intercept_first:
- # run build command and capture compiler executions
- exit_code = capture(args, bin_dir)
- # next step to run the analyzer against the captured commands
+ # Run against a build command. there are cases, when analyzer run
+ # is not required. But we need to set up everything for the
+ # wrappers, because 'configure' needs to capture the CC/CXX values
+ # for the Makefile.
+ if args.intercept_first:
+ # Run build command with intercept module.
+ exit_code = capture(args)
+ # Run the analyzer against the captured commands.
if need_analyzer(args.build):
run_analyzer(args, target_dir)
- # cover report generation and bug counting
- number_of_bugs = document(args, target_dir, True)
- # remove the compilation database when it was not requested
- if os.path.exists(args.cdb):
- os.unlink(args.cdb)
- # set exit status as it was requested
- return number_of_bugs if args.status_bugs else exit_code
- else:
- return exit_code
else:
- # run the build command with compiler wrappers which
- # execute the analyzer too. (interposition)
- environment = setup_environment(args, target_dir, bin_dir)
+ # Run build command and analyzer with compiler wrappers.
+ environment = setup_environment(args, target_dir)
exit_code = run_build(args.build, env=environment)
- # cover report generation and bug counting
- number_of_bugs = document(args, target_dir, False)
- # set exit status as it was requested
- return number_of_bugs if args.status_bugs else exit_code
+ # Cover report generation and bug counting.
+ number_of_bugs = document(args, target_dir, False)
+ # Set exit status as it was requested.
+ return number_of_bugs if args.status_bugs else exit_code
+
+
+@command_entry_point
+def analyze_build():
+ """ Entry point for analyze-build command. """
+
+ args = parse_args_for_analyze_build()
+ with report_directory(args.output, args.keep_empty) as target_dir:
+ # Run the analyzer against a compilation db.
+ run_analyzer(args, target_dir)
+ # Cover report generation and bug counting.
+ number_of_bugs = document(args, target_dir, True)
+ # Set exit status as it was requested.
+ return number_of_bugs if args.status_bugs else 0
def need_analyzer(args):
@@ -125,14 +120,14 @@ def run_analyzer(args, output_dir):
pool.join()
-def setup_environment(args, destination, bin_dir):
+def setup_environment(args, destination):
""" Set up environment for build command to interpose compiler wrapper. """
environment = dict(os.environ)
environment.update(wrapper_environment(args))
environment.update({
- 'CC': os.path.join(bin_dir, COMPILER_WRAPPER_CC),
- 'CXX': os.path.join(bin_dir, COMPILER_WRAPPER_CXX),
+ 'CC': COMPILER_WRAPPER_CC,
+ 'CXX': COMPILER_WRAPPER_CXX,
'ANALYZE_BUILD_CLANG': args.clang if need_analyzer(args.build) else '',
'ANALYZE_BUILD_REPORT_DIR': destination,
'ANALYZE_BUILD_REPORT_FORMAT': args.output_format,
@@ -262,281 +257,3 @@ def analyzer_params(args):
result.append('-analyzer-viz-egraph-ubigraph')
return prefix_with('-Xclang', result)
-
-
-def print_active_checkers(checkers):
- """ Print active checkers to stdout. """
-
- for name in sorted(name for name, (_, active) in checkers.items()
- if active):
- print(name)
-
-
-def print_checkers(checkers):
- """ Print verbose checker help to stdout. """
-
- print('')
- print('available checkers:')
- print('')
- for name in sorted(checkers.keys()):
- description, active = checkers[name]
- prefix = '+' if active else ' '
- if len(name) > 30:
- print(' {0} {1}'.format(prefix, name))
- print(' ' * 35 + description)
- else:
- print(' {0} {1: <30} {2}'.format(prefix, name, description))
- print('')
- print('NOTE: "+" indicates that an analysis is enabled by default.')
- print('')
-
-
-def validate(parser, args, from_build_command):
- """ Validation done by the parser itself, but semantic check still
- needs to be done. This method is doing that. """
-
- # Make plugins always a list. (It might be None when not specified.)
- args.plugins = args.plugins if args.plugins else []
-
- 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 from_build_command and not args.build:
- parser.error('missing build command')
-
-
-def create_parser(from_build_command):
- """ Command line argument parser factory method. """
-
- parser = argparse.ArgumentParser(
- formatter_class=argparse.ArgumentDefaultsHelpFormatter)
-
- parser.add_argument(
- '--verbose', '-v',
- action='count',
- default=0,
- help="""Enable verbose output from '%(prog)s'. A second and third
- flag increases verbosity.""")
- parser.add_argument(
- '--override-compiler',
- action='store_true',
- help="""Always resort to the compiler wrapper even when better
- interposition methods are available.""")
- parser.add_argument(
- '--intercept-first',
- action='store_true',
- help="""Run the build commands only, build a compilation database,
- then run the static analyzer afterwards.
- Generally speaking it has better coverage on build commands.
- With '--override-compiler' it use compiler wrapper, but does
- not run the analyzer till the build is finished. """)
- parser.add_argument(
- '--cdb',
- metavar='<file>',
- default="compile_commands.json",
- help="""The JSON compilation database.""")
-
- parser.add_argument(
- '--output', '-o',
- metavar='<path>',
- default=tempdir(),
- help="""Specifies the output directory for analyzer reports.
- Subdirectory will be created if default directory is targeted.
- """)
- parser.add_argument(
- '--status-bugs',
- action='store_true',
- help="""By default, the exit status of '%(prog)s' is the same as the
- executed build command. Specifying this option causes the exit
- status of '%(prog)s' to be non zero if it found potential bugs
- and zero otherwise.""")
- parser.add_argument(
- '--html-title',
- metavar='<title>',
- help="""Specify the title used on generated HTML pages.
- If not specified, a default title will be used.""")
- parser.add_argument(
- '--analyze-headers',
- action='store_true',
- help="""Also analyze functions in #included files. By default, such
- functions are skipped unless they are called by functions
- within the main source file.""")
- format_group = parser.add_mutually_exclusive_group()
- format_group.add_argument(
- '--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',
- dest='output_format',
- const='plist-html',
- default='html',
- action='store_const',
- help="""This option outputs the results as a set of .html and .plist
- files.""")
- # TODO: implement '-view '
-
- advanced = parser.add_argument_group('advanced options')
- advanced.add_argument(
- '--keep-empty',
- action='store_true',
- help="""Don't remove the build results directory even if no issues
- were reported.""")
- advanced.add_argument(
- '--no-failure-reports', '-no-failure-reports',
- dest='output_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',
- action='store_true',
- help="""Generates visitation statistics for the project being analyzed.
- """)
- advanced.add_argument(
- '--internal-stats',
- action='store_true',
- help="""Generate internal analyzer statistics.""")
- advanced.add_argument(
- '--maxloop', '-maxloop',
- metavar='<loop count>',
- type=int,
- 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', '-store',
- metavar='<model>',
- dest='store_model',
- choices=['region', 'basic'],
- help="""Specify the store model used by the analyzer.
- 'region' specifies a field- sensitive store model.
- 'basic' which is far less precise but can more quickly
- analyze code. 'basic' was the default store model for
- checker-0.221 and earlier.""")
- advanced.add_argument(
- '--constraints', '-constraints',
- metavar='<model>',
- dest='constraints_model',
- choices=['range', 'basic'],
- help="""Specify the contraint engine used by the analyzer. Specifying
- 'basic' uses a simpler, less powerful constraint model used by
- checker-0.160 and earlier.""")
- advanced.add_argument(
- '--use-analyzer',
- metavar='<path>',
- dest='clang',
- default='clang',
- help="""'%(prog)s' uses the 'clang' executable relative to itself for
- static analysis. One can override this behavior with this
- option by using the 'clang' packaged with Xcode (on OS X) or
- from the PATH.""")
- advanced.add_argument(
- '--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
- comma: 'key1=val1,key2=val2'
-
- Available options:
- stable-report-filename=true or false (default)
-
- Switch the page naming to:
- report-<filename>-<function/method name>-<id>.html
- instead of report-XXXXXX.html""")
- advanced.add_argument(
- '--exclude',
- metavar='<directory>',
- dest='excludes',
- action='append',
- default=[],
- help="""Do not run static analyzer against files found in this
- directory. (You can specify this option multiple times.)
- Could be usefull when project contains 3rd party libraries.
- The directory path shall be absolute path as file names in
- the compilation database.""")
- advanced.add_argument(
- '--force-analyze-debug-code',
- dest='force_debug',
- action='store_true',
- help="""Tells analyzer to enable assertions in code even if they were
- disabled during compilation, enabling more precise results.""")
-
- plugins = parser.add_argument_group('checker options')
- plugins.add_argument(
- '--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', '-enable-checker',
- metavar='<checker name>',
- action=AppendCommaSeparated,
- help="""Enable specific checker.""")
- plugins.add_argument(
- '--disable-checker', '-disable-checker',
- metavar='<checker name>',
- action=AppendCommaSeparated,
- help="""Disable specific checker.""")
- plugins.add_argument(
- '--help-checkers',
- action='store_true',
- help="""A default group of checkers is run unless explicitly disabled.
- Exactly which checkers constitute the default group is a
- function of the operating system in use. These can be printed
- with this flag.""")
- plugins.add_argument(
- '--help-checkers-verbose',
- action='store_true',
- help="""Print all available checkers and mark the enabled ones.""")
-
- if from_build_command:
- parser.add_argument(
- dest='build',
- nargs=argparse.REMAINDER,
- help="""Command to run.""")
-
- return parser
-
-
-class AppendCommaSeparated(argparse.Action):
- """ argparse Action class to support multiple comma separated lists. """
-
- def __call__(self, __parser, namespace, values, __option_string):
- # getattr(obj, attr, default) does not really returns default but none
- if getattr(namespace, self.dest, None) is None:
- setattr(namespace, self.dest, [])
- # once it's fixed we can use as expected
- actual = getattr(namespace, self.dest)
- actual.extend(values.split(','))
- setattr(namespace, self.dest, actual)
diff --git a/tools/scan-build-py/libscanbuild/intercept.py b/tools/scan-build-py/libscanbuild/intercept.py
index 2d1e825d44..71b957d83a 100644
--- a/tools/scan-build-py/libscanbuild/intercept.py
+++ b/tools/scan-build-py/libscanbuild/intercept.py
@@ -27,16 +27,16 @@ import re
import itertools
import json
import glob
-import argparse
import logging
from libear import build_libear, TemporaryDirectory
from libscanbuild import command_entry_point, compiler_wrapper, \
- wrapper_environment, run_command, run_build, reconfigure_logging
+ wrapper_environment, run_command, run_build
from libscanbuild import duplicate_check, tempdir
from libscanbuild.compilation import split_command
+from libscanbuild.arguments import parse_args_for_intercept_build
from libscanbuild.shell import encode, decode
-__all__ = ['capture', 'intercept_build_main', 'intercept_compiler_wrapper']
+__all__ = ['capture', 'intercept_build', 'intercept_compiler_wrapper']
GS = chr(0x1d)
RS = chr(0x1e)
@@ -49,23 +49,14 @@ WRAPPER_ONLY_PLATFORMS = frozenset({'win32', 'cygwin'})
@command_entry_point
-def intercept_build_main(bin_dir):
+def intercept_build():
""" Entry point for 'intercept-build' command. """
- parser = create_parser()
- args = parser.parse_args()
+ args = parse_args_for_intercept_build()
+ return capture(args)
- reconfigure_logging(args.verbose)
- logging.debug('Raw arguments %s', sys.argv)
- if not args.build:
- parser.print_help()
- return 0
-
- return capture(args, bin_dir)
-
-
-def capture(args, bin_dir):
+def capture(args):
""" The entry point of build command interception. """
def post_processing(commands):
@@ -95,7 +86,7 @@ def capture(args, bin_dir):
with TemporaryDirectory(prefix='intercept-', dir=tempdir()) as tmp_dir:
# run the build command
- environment = setup_environment(args, tmp_dir, bin_dir)
+ environment = setup_environment(args, tmp_dir)
exit_code = run_build(args.build, env=environment)
# read the intercepted exec calls
exec_traces = itertools.chain.from_iterable(
@@ -109,7 +100,7 @@ def capture(args, bin_dir):
return exit_code
-def setup_environment(args, destination, bin_dir):
+def setup_environment(args, destination):
""" Sets up the environment for the build command.
It sets the required environment variables and execute the given command.
@@ -129,8 +120,8 @@ def setup_environment(args, destination, bin_dir):
logging.debug('intercept gonna use compiler wrappers')
environment.update(wrapper_environment(args))
environment.update({
- 'CC': os.path.join(bin_dir, COMPILER_WRAPPER_CC),
- 'CXX': os.path.join(bin_dir, COMPILER_WRAPPER_CXX)
+ 'CC': COMPILER_WRAPPER_CC,
+ 'CXX': COMPILER_WRAPPER_CXX
})
elif sys.platform == 'darwin':
logging.debug('intercept gonna preload libear on OSX')
@@ -270,62 +261,3 @@ def entry_hash(entry):
command = ' '.join(decode(entry['command'])[1:])
return '<>'.join([filename, directory, command])
-
-
-def create_parser():
- """ Command line argument parser factory method. """
-
- parser = argparse.ArgumentParser(
- formatter_class=argparse.ArgumentDefaultsHelpFormatter)
-
- parser.add_argument(
- '--verbose', '-v',
- action='count',
- default=0,
- help="""Enable verbose output from '%(prog)s'. A second and third
- flag increases verbosity.""")
- parser.add_argument(
- '--cdb',
- metavar='<file>',
- default="compile_commands.json",
- help="""The JSON compilation database.""")
- group = parser.add_mutually_exclusive_group()
- group.add_argument(
- '--append',
- action='store_true',
- help="""Append new entries to existing compilation database.""")
-
- advanced = parser.add_argument_group('advanced options')
- advanced.add_argument(
- '--override-compiler',
- action='store_true',
- help="""Always resort to the compiler wrapper even when better
- intercept methods are available.""")
- advanced.add_argument(
- '--use-cc',
- metavar='<path>',
- dest='cc',
- default='cc',
- help="""When '%(prog)s' analyzes a project by interposing a compiler
- wrapper, which executes a real compiler for compilation and
- do other tasks (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.""")
-
- parser.add_argument(
- dest='build',
- nargs=argparse.REMAINDER,
- help="""Command to run.""")
-
- return parser