waf/waflib/extras/cppcheck.py

550 lines
16 KiB
Python
Raw Normal View History

2013-09-22 17:41:17 +02:00
#! /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
2013-09-22 17:41:17 +02:00
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
2013-09-22 17:41:17 +02:00
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'
2013-09-22 17:41:17 +02:00
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
2013-09-22 17:41:17 +02:00
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
2013-09-22 17:41:17 +02:00
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
2013-09-22 17:41:17 +02:00
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
2013-09-22 17:41:17 +02:00
all tasks.
In order to exclude a task from source code checking add the skip option to the
task as shown below:
2013-09-22 17:41:17 +02:00
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
2013-09-22 17:41:17 +02:00
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
2013-09-22 17:41:17 +02:00
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
2013-09-22 17:41:17 +02:00
more information on this package.
Remarks
================
The generation of the html report is originally based on the cppcheck-htmlreport.py
2013-09-22 17:41:17 +02:00
script that comes shipped with the cppcheck tool.
"""
import sys
2013-09-22 17:41:17 +02:00
import xml.etree.ElementTree as ElementTree
from waflib import Task, TaskGen, Logs, Context, Options
2013-09-22 17:41:17 +02:00
PYGMENTS_EXC_MSG= '''
The required module 'pygments' could not be found. Please install it using your
2013-09-22 17:41:17 +02:00
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
2014-10-11 19:24:43 +02:00
except ImportError as e:
2013-09-22 17:41:17 +02:00
Logs.warn(PYGMENTS_EXC_MSG)
raise e
2013-09-22 17:41:17 +02:00
def options(opt):
opt.add_option('--cppcheck-skip', dest='cppcheck_skip',
default=False, action='store_true',
2013-09-22 17:41:17 +02:00
help='do not check C/C++ sources (default=False)')
opt.add_option('--cppcheck-err-resume', dest='cppcheck_err_resume',
default=False, action='store_true',
2013-09-22 17:41:17 +02:00
help='continue in case of errors (default=False)')
opt.add_option('--cppcheck-bin-enable', dest='cppcheck_bin_enable',
2013-09-22 17:41:17 +02:00
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',
2013-09-22 17:41:17 +02:00
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',
2013-09-22 17:41:17 +02:00
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',
2013-09-22 17:41:17 +02:00
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',
2013-09-22 17:41:17 +02:00
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',
2013-09-22 17:41:17 +02:00
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 hasattr(self.bld, 'conf'):
return
if len(self.env.CPPCHECK_SKIP) or Options.options.cppcheck_skip:
2013-09-22 17:41:17 +02:00
return
if getattr(self, 'cppcheck_skip', False):
return
task = self.create_task('cppcheck')
task.cmd = _tgen_create_cmd(self)
task.fatal = []
if not Options.options.cppcheck_err_resume:
2013-09-22 17:41:17 +02:00
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 = self.env.CPPCHECK
2013-09-22 17:41:17 +02:00
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 Options.options.cppcheck_check_config:
2013-09-22 17:41:17 +02:00
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 cmd + args
2013-09-22 17:41:17 +02:00
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.
'''
2015-03-03 12:19:25 +01:00
header = '%s\n' % s.splitlines()[0]
2013-09-22 17:41:17 +02:00
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('<','&lt;')
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)
2013-09-22 17:41:17 +02:00
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()
2013-09-22 17:41:17 +02:00
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'])
2016-05-22 00:45:47 +02:00
line_no += 1
2013-09-22 17:41:17 +02:00
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 "&#160;">]>
<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">&nbsp;</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>
&nbsp;
</div>
&nbsp;
2013-09-22 17:41:17 +02:00
</div>
<div id="page-footer">&nbsp;</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;">&lt;--- %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%;
}
"""