mirror of https://gitlab.com/ita1024/waf.git
New cppckeck tool from Michel Mooij
This commit is contained in:
parent
dcea4231ef
commit
9838d0bb08
|
@ -0,0 +1,547 @@
|
|||
#! /usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Michel Mooij, michel.mooij7@gmail.com
|
||||
|
||||
"""
|
||||
Tool Description
|
||||
================
|
||||
This module provides a waf wrapper (i.e. waftool) around the C/C++ source code
|
||||
checking tool 'cppcheck'.
|
||||
|
||||
See http://cppcheck.sourceforge.net/ for more information on the cppcheck tool
|
||||
itself.
|
||||
Note that many linux distributions already provide a ready to install version
|
||||
of cppcheck. On fedora, for instance, it can be installed using yum:
|
||||
|
||||
'sudo yum install cppcheck'
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
In order to use this waftool simply add it to the 'options' and 'configure'
|
||||
functions of your main waf script as shown in the example below:
|
||||
|
||||
def options(opt):
|
||||
opt.load('cppcheck', tooldir='./waftools')
|
||||
|
||||
def configure(conf):
|
||||
conf.load('cppcheck')
|
||||
|
||||
Note that example shown above assumes that the cppcheck waftool is located in
|
||||
the sub directory named 'waftools'.
|
||||
|
||||
When configured as shown in the example above, cppcheck will automatically
|
||||
perform a source code analysis on all C/C++ build tasks that have been
|
||||
defined in your waf build system.
|
||||
|
||||
The example shown below for a C program will be used as input for cppcheck when
|
||||
building the task.
|
||||
|
||||
def build(bld):
|
||||
bld.program(name='foo', src='foobar.c')
|
||||
|
||||
The result of the source code analysis will be stored both as xml and html
|
||||
files in the build location for the task. Should any error be detected by
|
||||
cppcheck the build will be aborted and a link to the html report will be shown.
|
||||
|
||||
When needed source code checking by cppcheck can be disabled per task, per
|
||||
detected error or warning for a particular task. It can be also be disabled for
|
||||
all tasks.
|
||||
|
||||
In order to exclude a task from source code checking add the skip option to the
|
||||
task as shown below:
|
||||
|
||||
def build(bld):
|
||||
bld.program(
|
||||
name='foo',
|
||||
src='foobar.c'
|
||||
cppcheck_skip=True
|
||||
)
|
||||
|
||||
When needed problems detected by cppcheck may be suppressed using a file
|
||||
containing a list of suppression rules. The relative or absolute path to this
|
||||
file can be added to the build task as shown in the example below:
|
||||
|
||||
bld.program(
|
||||
name='bar',
|
||||
src='foobar.c',
|
||||
cppcheck_suppress='bar.suppress'
|
||||
)
|
||||
|
||||
A cppcheck suppress file should contain one suppress rule per line. Each of
|
||||
these rules will be passed as an '--suppress=<rule>' argument to cppcheck.
|
||||
|
||||
Dependencies
|
||||
================
|
||||
This waftool depends on the python pygments module, it is used for source code
|
||||
syntax highlighting when creating the html reports. see http://pygments.org/ for
|
||||
more information on this package.
|
||||
|
||||
Remarks
|
||||
================
|
||||
The generation of the html report is originally based on the cppcheck-htmlreport.py
|
||||
script that comes shipped with the cppcheck tool.
|
||||
"""
|
||||
|
||||
import os, sys
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
from waflib import Task, TaskGen, Logs, Context
|
||||
|
||||
PYGMENTS_EXC_MSG= '''
|
||||
The required module 'pygments' could not be found. Please install it using your
|
||||
platform package manager (e.g. apt-get or yum), using 'pip' or 'easy_install',
|
||||
see 'http://pygments.org/download/' for installation instructions.
|
||||
'''
|
||||
|
||||
try:
|
||||
import pygments
|
||||
from pygments import formatters, lexers
|
||||
except ImportError, e:
|
||||
Logs.warn(PYGMENTS_EXC_MSG)
|
||||
raise e
|
||||
|
||||
|
||||
def options(opt):
|
||||
opt.add_option('--cppcheck-skip', dest='cppcheck_skip',
|
||||
default=False, action='store_true',
|
||||
help='do not check C/C++ sources (default=False)')
|
||||
|
||||
opt.add_option('--cppcheck-err-resume', dest='cppcheck_err_resume',
|
||||
default=False, action='store_true',
|
||||
help='continue in case of errors (default=False)')
|
||||
|
||||
opt.add_option('--cppcheck-bin-enable', dest='cppcheck_bin_enable',
|
||||
default='warning,performance,portability,style,unusedFunction', action='store',
|
||||
help="cppcheck option '--enable=' for binaries (default=warning,performance,portability,style,unusedFunction)")
|
||||
|
||||
opt.add_option('--cppcheck-lib-enable', dest='cppcheck_lib_enable',
|
||||
default='warning,performance,portability,style', action='store',
|
||||
help="cppcheck option '--enable=' for libraries (default=warning,performance,portability,style)")
|
||||
|
||||
opt.add_option('--cppcheck-std-c', dest='cppcheck_std_c',
|
||||
default='c99', action='store',
|
||||
help='cppcheck standard to use when checking C (default=c99)')
|
||||
|
||||
opt.add_option('--cppcheck-std-cxx', dest='cppcheck_std_cxx',
|
||||
default='c++03', action='store',
|
||||
help='cppcheck standard to use when checking C++ (default=c++03)')
|
||||
|
||||
opt.add_option('--cppcheck-check-config', dest='cppcheck_check_config',
|
||||
default=False, action='store_true',
|
||||
help='forced check for missing buildin include files, e.g. stdio.h (default=False)')
|
||||
|
||||
opt.add_option('--cppcheck-max-configs', dest='cppcheck_max_configs',
|
||||
default='20', action='store',
|
||||
help='maximum preprocessor (--max-configs) define iterations (default=20)')
|
||||
|
||||
|
||||
def configure(conf):
|
||||
if conf.options.cppcheck_skip:
|
||||
conf.env.CPPCHECK_SKIP = [True]
|
||||
conf.env.CPPCHECK_STD_C = conf.options.cppcheck_std_c
|
||||
conf.env.CPPCHECK_STD_CXX = conf.options.cppcheck_std_cxx
|
||||
conf.env.CPPCHECK_MAX_CONFIGS = conf.options.cppcheck_max_configs
|
||||
conf.env.CPPCHECK_BIN_ENABLE = conf.options.cppcheck_bin_enable
|
||||
conf.env.CPPCHECK_LIB_ENABLE = conf.options.cppcheck_lib_enable
|
||||
conf.find_program('cppcheck', var='CPPCHECK')
|
||||
|
||||
|
||||
@TaskGen.feature('c')
|
||||
@TaskGen.feature('cxx')
|
||||
def cppcheck_execute(self):
|
||||
if len(self.env.CPPCHECK_SKIP) or self.bld.options.cppcheck_skip:
|
||||
return
|
||||
if getattr(self, 'cppcheck_skip', False):
|
||||
return
|
||||
task = self.create_task('cppcheck')
|
||||
task.cmd = _tgen_create_cmd(self)
|
||||
task.fatal = []
|
||||
if not self.bld.options.cppcheck_err_resume:
|
||||
task.fatal.append('error')
|
||||
|
||||
|
||||
def _tgen_create_cmd(self):
|
||||
features = getattr(self, 'features', [])
|
||||
std_c = self.env.CPPCHECK_STD_C
|
||||
std_cxx = self.env.CPPCHECK_STD_CXX
|
||||
max_configs = self.env.CPPCHECK_MAX_CONFIGS
|
||||
bin_enable = self.env.CPPCHECK_BIN_ENABLE
|
||||
lib_enable = self.env.CPPCHECK_LIB_ENABLE
|
||||
|
||||
cmd = '%s' % self.env.CPPCHECK
|
||||
args = ['--inconclusive','--report-progress','--verbose','--xml','--xml-version=2']
|
||||
args.append('--max-configs=%s' % max_configs)
|
||||
|
||||
if 'cxx' in features:
|
||||
args.append('--language=c++')
|
||||
args.append('--std=%s' % std_cxx)
|
||||
else:
|
||||
args.append('--language=c')
|
||||
args.append('--std=%s' % std_c)
|
||||
|
||||
if self.bld.options.cppcheck_check_config:
|
||||
args.append('--check-config')
|
||||
|
||||
if set(['cprogram','cxxprogram']) & set(features):
|
||||
args.append('--enable=%s' % bin_enable)
|
||||
else:
|
||||
args.append('--enable=%s' % lib_enable)
|
||||
|
||||
for src in self.to_list(getattr(self, 'source', [])):
|
||||
args.append('%r' % src)
|
||||
for inc in self.to_incnodes(self.to_list(getattr(self, 'includes', []))):
|
||||
args.append('-I%r' % inc)
|
||||
for inc in self.to_incnodes(self.to_list(self.env.INCLUDES)):
|
||||
args.append('-I%r' % inc)
|
||||
return '%s %s' % (cmd, ' '.join(args))
|
||||
|
||||
|
||||
class cppcheck(Task.Task):
|
||||
quiet = True
|
||||
|
||||
def run(self):
|
||||
stderr = self.generator.bld.cmd_and_log(self.cmd, quiet=Context.STDERR, output=Context.STDERR)
|
||||
self._save_xml_report(stderr)
|
||||
defects = self._get_defects(stderr)
|
||||
index = self._create_html_report(defects)
|
||||
self._errors_evaluate(defects, index)
|
||||
return 0
|
||||
|
||||
def _save_xml_report(self, s):
|
||||
'''use cppcheck xml result string, add the command string used to invoke cppcheck
|
||||
and save as xml file.
|
||||
'''
|
||||
header = '%s\n' % s.split('\n')[0]
|
||||
root = ElementTree.fromstring(s)
|
||||
cmd = ElementTree.SubElement(root.find('cppcheck'), 'cmd')
|
||||
cmd.text = str(self.cmd)
|
||||
body = ElementTree.tostring(root)
|
||||
node = self.generator.path.get_bld().find_or_declare('cppcheck.xml')
|
||||
node.write(header + body)
|
||||
|
||||
def _get_defects(self, xml_string):
|
||||
'''evaluate the xml string returned by cppcheck (on sdterr) and use it to create
|
||||
a list of defects.
|
||||
'''
|
||||
defects = []
|
||||
for error in ElementTree.fromstring(xml_string).iter('error'):
|
||||
defect = {}
|
||||
defect['id'] = error.get('id')
|
||||
defect['severity'] = error.get('severity')
|
||||
defect['msg'] = str(error.get('msg')).replace('<','<')
|
||||
defect['verbose'] = error.get('verbose')
|
||||
for location in error.findall('location'):
|
||||
defect['file'] = location.get('file')
|
||||
defect['line'] = str(int(location.get('line')) - 1)
|
||||
defects.append(defect)
|
||||
return defects
|
||||
|
||||
def _create_html_report(self, defects):
|
||||
files, css_style_defs = self._create_html_files(defects)
|
||||
index = self._create_html_index(files)
|
||||
self._create_css_file(css_style_defs)
|
||||
return index
|
||||
|
||||
def _create_html_files(self, defects):
|
||||
sources = {}
|
||||
defects = [defect for defect in defects if defect.has_key('file')]
|
||||
for defect in defects:
|
||||
name = defect['file']
|
||||
if not sources.has_key(name):
|
||||
sources[name] = [defect]
|
||||
else:
|
||||
sources[name].append(defect)
|
||||
|
||||
files = {}
|
||||
css_style_defs = None
|
||||
bpath = self.generator.path.get_bld().abspath()
|
||||
names = sources.keys()
|
||||
for i in range(0,len(names)):
|
||||
name = names[i]
|
||||
htmlfile = 'cppcheck/%i.html' % (i)
|
||||
errors = sources[name]
|
||||
files[name] = { 'htmlfile': '%s/%s' % (bpath, htmlfile), 'errors': errors }
|
||||
css_style_defs = self._create_html_file(name, htmlfile, errors)
|
||||
return files, css_style_defs
|
||||
|
||||
def _create_html_file(self, sourcefile, htmlfile, errors):
|
||||
name = self.generator.get_name()
|
||||
root = ElementTree.fromstring(CPPCHECK_HTML_FILE)
|
||||
title = root.find('head/title')
|
||||
title.text = 'cppcheck - report - %s' % name
|
||||
|
||||
body = root.find('body')
|
||||
for div in body.findall('div'):
|
||||
if div.get('id') == 'page':
|
||||
page = div
|
||||
break
|
||||
for div in page.findall('div'):
|
||||
if div.get('id') == 'header':
|
||||
h1 = div.find('h1')
|
||||
h1.text = 'cppcheck report - %s' % name
|
||||
if div.get('id') == 'content':
|
||||
content = div
|
||||
srcnode = self.generator.bld.root.find_node(sourcefile)
|
||||
hl_lines = [e['line'] for e in errors if e.has_key('line')]
|
||||
formatter = CppcheckHtmlFormatter(linenos=True, style='colorful', hl_lines=hl_lines, lineanchors='line')
|
||||
formatter.errors = [e for e in errors if e.has_key('line')]
|
||||
css_style_defs = formatter.get_style_defs('.highlight')
|
||||
lexer = pygments.lexers.guess_lexer_for_filename(sourcefile, "")
|
||||
s = pygments.highlight(srcnode.read(), lexer, formatter)
|
||||
table = ElementTree.fromstring(s)
|
||||
content.append(table)
|
||||
|
||||
s = ElementTree.tostring(root, method='html')
|
||||
s = CCPCHECK_HTML_TYPE + s
|
||||
node = self.generator.path.get_bld().find_or_declare(htmlfile)
|
||||
node.write(s)
|
||||
return css_style_defs
|
||||
|
||||
def _create_html_index(self, files):
|
||||
name = self.generator.get_name()
|
||||
root = ElementTree.fromstring(CPPCHECK_HTML_FILE)
|
||||
title = root.find('head/title')
|
||||
title.text = 'cppcheck - report - %s' % name
|
||||
|
||||
body = root.find('body')
|
||||
for div in body.findall('div'):
|
||||
if div.get('id') == 'page':
|
||||
page = div
|
||||
break
|
||||
for div in page.findall('div'):
|
||||
if div.get('id') == 'header':
|
||||
h1 = div.find('h1')
|
||||
h1.text = 'cppcheck report - %s' % name
|
||||
if div.get('id') == 'content':
|
||||
content = div
|
||||
self._create_html_table(content, files)
|
||||
|
||||
s = ElementTree.tostring(root, method='html')
|
||||
s = CCPCHECK_HTML_TYPE + s
|
||||
node = self.generator.path.get_bld().find_or_declare('cppcheck/index.html')
|
||||
node.write(s)
|
||||
return node
|
||||
|
||||
def _create_html_table(self, content, files):
|
||||
table = ElementTree.fromstring(CPPCHECK_HTML_TABLE)
|
||||
for name, val in files.items():
|
||||
f = val['htmlfile']
|
||||
s = '<tr><td colspan="4"><a href="%s">%s</a></td></tr>\n' % (f,name)
|
||||
row = ElementTree.fromstring(s)
|
||||
table.append(row)
|
||||
|
||||
errors = sorted(val['errors'], key=lambda e: int(e['line']) if e.has_key('line') else sys.maxint)
|
||||
for e in errors:
|
||||
if not e.has_key('line'):
|
||||
s = '<tr><td></td><td>%s</td><td>%s</td><td>%s</td></tr>\n' % (e['id'], e['severity'], e['msg'])
|
||||
else:
|
||||
attr = ''
|
||||
if e['severity'] == 'error':
|
||||
attr = 'class="error"'
|
||||
s = '<tr><td><a href="%s#line-%s">%s</a></td>' % (f, e['line'], e['line'])
|
||||
s+= '<td>%s</td><td>%s</td><td %s>%s</td></tr>\n' % (e['id'], e['severity'], attr, e['msg'])
|
||||
row = ElementTree.fromstring(s)
|
||||
table.append(row)
|
||||
content.append(table)
|
||||
|
||||
def _create_css_file(self, css_style_defs):
|
||||
css = str(CPPCHECK_CSS_FILE)
|
||||
if css_style_defs:
|
||||
css = "%s\n%s\n" % (css, css_style_defs)
|
||||
node = self.generator.path.get_bld().find_or_declare('cppcheck/style.css')
|
||||
node.write(css)
|
||||
|
||||
def _errors_evaluate(self, errors, http_index):
|
||||
name = self.generator.get_name()
|
||||
fatal = self.fatal
|
||||
severity = [err['severity'] for err in errors]
|
||||
problems = [err for err in errors if err['severity'] != 'information']
|
||||
|
||||
if set(fatal) & set(severity):
|
||||
exc = "\n"
|
||||
exc += "\nccpcheck detected fatal error(s) in task '%s', see report for details:" % name
|
||||
exc += "\n file://%r" % (http_index)
|
||||
exc += "\n"
|
||||
self.generator.bld.fatal(exc)
|
||||
|
||||
elif len(problems):
|
||||
msg = "\nccpcheck detected (possible) problem(s) in task '%s', see report for details:" % name
|
||||
msg += "\n file://%r" % http_index
|
||||
msg += "\n"
|
||||
Logs.error(msg)
|
||||
|
||||
|
||||
class CppcheckHtmlFormatter(pygments.formatters.HtmlFormatter):
|
||||
errors = []
|
||||
|
||||
def wrap(self, source, outfile):
|
||||
line_no = 1
|
||||
for i, t in super(CppcheckHtmlFormatter, self).wrap(source, outfile):
|
||||
# If this is a source code line we want to add a span tag at the end.
|
||||
if i == 1:
|
||||
for error in self.errors:
|
||||
if int(error['line']) == line_no:
|
||||
t = t.replace('\n', CPPCHECK_HTML_ERROR % error['msg'])
|
||||
line_no = line_no + 1
|
||||
yield i, t
|
||||
|
||||
|
||||
CCPCHECK_HTML_TYPE = \
|
||||
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
|
||||
|
||||
CPPCHECK_HTML_FILE = """
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd" [<!ENTITY nbsp " ">]>
|
||||
<html>
|
||||
<head>
|
||||
<title>cppcheck - report - XXX</title>
|
||||
<link href="style.css" rel="stylesheet" type="text/css" />
|
||||
<style type="text/css">
|
||||
</style>
|
||||
</head>
|
||||
<body class="body">
|
||||
<div id="page-header"> </div>
|
||||
<div id="page">
|
||||
<div id="header">
|
||||
<h1>cppcheck report - XXX</h1>
|
||||
</div>
|
||||
<div id="menu">
|
||||
<a href="index.html">Defect list</a>
|
||||
</div>
|
||||
<div id="content">
|
||||
</div>
|
||||
<div id="footer">
|
||||
<div>cppcheck - a tool for static C/C++ code analysis</div>
|
||||
<div>
|
||||
Internet: <a href="http://cppcheck.sourceforge.net">http://cppcheck.sourceforge.net</a><br/>
|
||||
Forum: <a href="http://apps.sourceforge.net/phpbb/cppcheck/">http://apps.sourceforge.net/phpbb/cppcheck/</a><br/>
|
||||
IRC: #cppcheck at irc.freenode.net
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="page-footer"> </div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
CPPCHECK_HTML_TABLE = """
|
||||
<table>
|
||||
<tr>
|
||||
<th>Line</th>
|
||||
<th>Id</th>
|
||||
<th>Severity</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</table>
|
||||
"""
|
||||
|
||||
CPPCHECK_HTML_ERROR = \
|
||||
'<span style="background: #ffaaaa;padding: 3px;"><--- %s</span>\n'
|
||||
|
||||
CPPCHECK_CSS_FILE = """
|
||||
body.body {
|
||||
font-family: Arial;
|
||||
font-size: 13px;
|
||||
background-color: black;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.error {
|
||||
font-family: Arial;
|
||||
font-size: 13px;
|
||||
background-color: #ffb7b7;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
min-width: 100px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#page-header {
|
||||
clear: both;
|
||||
width: 1200px;
|
||||
margin: 20px auto 0px auto;
|
||||
height: 10px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #aaaaaa;
|
||||
}
|
||||
|
||||
#page {
|
||||
width: 1160px;
|
||||
margin: auto;
|
||||
border-left-width: 2px;
|
||||
border-left-style: solid;
|
||||
border-left-color: #aaaaaa;
|
||||
border-right-width: 2px;
|
||||
border-right-style: solid;
|
||||
border-right-color: #aaaaaa;
|
||||
background-color: White;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#page-footer {
|
||||
clear: both;
|
||||
width: 1200px;
|
||||
margin: auto;
|
||||
height: 10px;
|
||||
border-top-width: 2px;
|
||||
border-top-style: solid;
|
||||
border-top-color: #aaaaaa;
|
||||
}
|
||||
|
||||
#header {
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
background-image: url(logo.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: left top;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: thin;
|
||||
border-bottom-color: #aaaaaa;
|
||||
}
|
||||
|
||||
#menu {
|
||||
margin-top: 5px;
|
||||
text-align: left;
|
||||
float: left;
|
||||
width: 100px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
#menu > a {
|
||||
margin-left: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#content {
|
||||
float: left;
|
||||
width: 1020px;
|
||||
margin: 5px;
|
||||
padding: 0px 10px 10px 10px;
|
||||
border-left-style: solid;
|
||||
border-left-width: thin;
|
||||
border-left-color: #aaaaaa;
|
||||
}
|
||||
|
||||
#footer {
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
border-top-style: solid;
|
||||
border-top-width: thin;
|
||||
border-top-color: #aaaaaa;
|
||||
clear: both;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
#footer > div {
|
||||
float: left;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
"""
|
||||
|
Loading…
Reference in New Issue