forked from OSchip/llvm-project
[scan-build-py] move argument parsing into separate module
Forgot to add the new module. llvm-svn: 297308
This commit is contained in:
parent
5270bb978f
commit
908ed4c90a
|
@ -0,0 +1,430 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# The LLVM Compiler Infrastructure
|
||||
#
|
||||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
""" This module parses and validates arguments for command-line interfaces.
|
||||
|
||||
It uses argparse module to create the command line parser. (This library is
|
||||
in the standard python library since 3.2 and backported to 2.7, but not
|
||||
earlier.)
|
||||
|
||||
It also implements basic validation methods, related to the command.
|
||||
Validations are mostly calling specific help methods, or mangling values.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
from libscanbuild import reconfigure_logging, tempdir
|
||||
from libscanbuild.clang import get_checkers
|
||||
|
||||
__all__ = ['parse_args_for_intercept_build', 'parse_args_for_analyze_build',
|
||||
'parse_args_for_scan_build']
|
||||
|
||||
|
||||
def parse_args_for_intercept_build():
|
||||
""" Parse and validate command-line arguments for intercept-build. """
|
||||
|
||||
parser = create_intercept_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
reconfigure_logging(args.verbose)
|
||||
logging.debug('Raw arguments %s', sys.argv)
|
||||
|
||||
# short validation logic
|
||||
if not args.build:
|
||||
parser.error(message='missing build command')
|
||||
|
||||
logging.debug('Parsed arguments: %s', args)
|
||||
return args
|
||||
|
||||
|
||||
def parse_args_for_analyze_build():
|
||||
""" Parse and validate command-line arguments for analyze-build. """
|
||||
|
||||
from_build_command = False
|
||||
parser = create_analyze_parser(from_build_command)
|
||||
args = parser.parse_args()
|
||||
|
||||
reconfigure_logging(args.verbose)
|
||||
logging.debug('Raw arguments %s', sys.argv)
|
||||
|
||||
normalize_args_for_analyze(args, from_build_command)
|
||||
validate_args_for_analyze(parser, args, from_build_command)
|
||||
logging.debug('Parsed arguments: %s', args)
|
||||
return args
|
||||
|
||||
|
||||
def parse_args_for_scan_build():
|
||||
""" Parse and validate command-line arguments for scan-build. """
|
||||
|
||||
from_build_command = True
|
||||
parser = create_analyze_parser(from_build_command)
|
||||
args = parser.parse_args()
|
||||
|
||||
reconfigure_logging(args.verbose)
|
||||
logging.debug('Raw arguments %s', sys.argv)
|
||||
|
||||
normalize_args_for_analyze(args, from_build_command)
|
||||
validate_args_for_analyze(parser, args, from_build_command)
|
||||
logging.debug('Parsed arguments: %s', args)
|
||||
return args
|
||||
|
||||
|
||||
def normalize_args_for_analyze(args, from_build_command):
|
||||
""" Normalize parsed arguments for analyze-build and scan-build.
|
||||
|
||||
:param args: Parsed argument object. (Will be mutated.)
|
||||
:param from_build_command: Boolean value tells is the command suppose
|
||||
to run the analyzer against a build command or a compilation db. """
|
||||
|
||||
# make plugins always a list. (it might be None when not specified.)
|
||||
if args.plugins is None:
|
||||
args.plugins = []
|
||||
|
||||
# make exclude directory list unique and absolute.
|
||||
uniq_excludes = set(os.path.abspath(entry) for entry in args.excludes)
|
||||
args.excludes = list(uniq_excludes)
|
||||
|
||||
# because shared codes for all tools, some common used methods are
|
||||
# expecting some argument to be present. so, instead of query the args
|
||||
# object about the presence of the flag, we fake it here. to make those
|
||||
# methods more readable. (it's an arguable choice, took it only for those
|
||||
# which have good default value.)
|
||||
if from_build_command:
|
||||
# add cdb parameter invisibly to make report module working.
|
||||
args.cdb = 'compile_commands.json'
|
||||
|
||||
|
||||
def validate_args_for_analyze(parser, args, from_build_command):
|
||||
""" Command line parsing is done by the argparse module, but semantic
|
||||
validation still needs to be done. This method is doing it for
|
||||
analyze-build and scan-build commands.
|
||||
|
||||
:param parser: The command line parser object.
|
||||
:param args: Parsed argument object.
|
||||
:param from_build_command: Boolean value tells is the command suppose
|
||||
to run the analyzer against a build command or a compilation db.
|
||||
:return: No return value, but this call might throw when validation
|
||||
fails. """
|
||||
|
||||
if args.help_checkers_verbose:
|
||||
print_checkers(get_checkers(args.clang, args.plugins))
|
||||
parser.exit(status=0)
|
||||
elif args.help_checkers:
|
||||
print_active_checkers(get_checkers(args.clang, args.plugins))
|
||||
parser.exit(status=0)
|
||||
elif from_build_command and not args.build:
|
||||
parser.error(message='missing build command')
|
||||
elif not from_build_command and not os.path.exists(args.cdb):
|
||||
parser.error(message='compilation database is missing')
|
||||
|
||||
|
||||
def create_intercept_parser():
|
||||
""" Creates a parser for command-line arguments to 'intercept'. """
|
||||
|
||||
parser = create_default_parser()
|
||||
parser_add_cdb(parser)
|
||||
|
||||
parser_add_prefer_wrapper(parser)
|
||||
parser_add_compilers(parser)
|
||||
|
||||
advanced = parser.add_argument_group('advanced options')
|
||||
group = advanced.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
'--append',
|
||||
action='store_true',
|
||||
help="""Extend existing compilation database with new entries.
|
||||
Duplicate entries are detected and not present in the final output.
|
||||
The output is not continuously updated, it's done when the build
|
||||
command finished. """)
|
||||
|
||||
parser.add_argument(
|
||||
dest='build', nargs=argparse.REMAINDER, help="""Command to run.""")
|
||||
return parser
|
||||
|
||||
|
||||
def create_analyze_parser(from_build_command):
|
||||
""" Creates a parser for command-line arguments to 'analyze'. """
|
||||
|
||||
parser = create_default_parser()
|
||||
|
||||
if from_build_command:
|
||||
parser_add_prefer_wrapper(parser)
|
||||
parser_add_compilers(parser)
|
||||
|
||||
parser.add_argument(
|
||||
'--intercept-first',
|
||||
action='store_true',
|
||||
help="""Run the build commands first, intercept compiler
|
||||
calls and 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.""")
|
||||
else:
|
||||
parser_add_cdb(parser)
|
||||
|
||||
parser.add_argument(
|
||||
'--status-bugs',
|
||||
action='store_true',
|
||||
help="""The exit status of '%(prog)s' is the same as the executed
|
||||
build command. This option ignores the build exit status and sets to
|
||||
be non zero if it found potential bugs or zero otherwise.""")
|
||||
parser.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 useful when project contains 3rd party libraries.""")
|
||||
|
||||
output = parser.add_argument_group('output control options')
|
||||
output.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.""")
|
||||
output.add_argument(
|
||||
'--keep-empty',
|
||||
action='store_true',
|
||||
help="""Don't remove the build results directory even if no issues
|
||||
were reported.""")
|
||||
output.add_argument(
|
||||
'--html-title',
|
||||
metavar='<title>',
|
||||
help="""Specify the title used on generated HTML pages.
|
||||
If not specified, a default title will be used.""")
|
||||
format_group = output.add_mutually_exclusive_group()
|
||||
format_group.add_argument(
|
||||
'--plist',
|
||||
'-plist',
|
||||
dest='output_format',
|
||||
const='plist',
|
||||
default='html',
|
||||
action='store_const',
|
||||
help="""Cause 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="""Cause the results as a set of .html and .plist files.""")
|
||||
# TODO: implement '-view '
|
||||
|
||||
advanced = parser.add_argument_group('advanced options')
|
||||
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(
|
||||
'--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.""")
|
||||
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.""")
|
||||
advanced.add_argument(
|
||||
'--stats',
|
||||
'-stats',
|
||||
action='store_true',
|
||||
help="""Generates visitation statistics for the project.""")
|
||||
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 constraint engine used by the analyzer. Specifying
|
||||
'basic' uses a simpler, less powerful constraint model used by
|
||||
checker-0.160 and earlier.""")
|
||||
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(
|
||||
'--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
|
||||
|
||||
|
||||
def create_default_parser():
|
||||
""" Creates command line parser for all build wrapper commands. """
|
||||
|
||||
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, third and
|
||||
fourth flags increases verbosity.""")
|
||||
return parser
|
||||
|
||||
|
||||
def parser_add_cdb(parser):
|
||||
parser.add_argument(
|
||||
'--cdb',
|
||||
metavar='<file>',
|
||||
default="compile_commands.json",
|
||||
help="""The JSON compilation database.""")
|
||||
|
||||
|
||||
def parser_add_prefer_wrapper(parser):
|
||||
parser.add_argument(
|
||||
'--override-compiler',
|
||||
action='store_true',
|
||||
help="""Always resort to the compiler wrapper even when better
|
||||
intercept methods are available.""")
|
||||
|
||||
|
||||
def parser_add_compilers(parser):
|
||||
parser.add_argument(
|
||||
'--use-cc',
|
||||
metavar='<path>',
|
||||
dest='cc',
|
||||
default=os.getenv('CC', '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.""")
|
||||
parser.add_argument(
|
||||
'--use-c++',
|
||||
metavar='<path>',
|
||||
dest='cxx',
|
||||
default=os.getenv('CXX', 'c++'),
|
||||
help="""This is the same as "--use-cc" but for C++ code.""")
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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('')
|
Loading…
Reference in New Issue