waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

cppcheck.py (18052B)


      1 #! /usr/bin/env python
      2 # -*- encoding: utf-8 -*-
      3 # Michel Mooij, michel.mooij7@gmail.com
      4 
      5 """
      6 Tool Description
      7 ================
      8 This module provides a waf wrapper (i.e. waftool) around the C/C++ source code
      9 checking tool 'cppcheck'.
     10 
     11 See http://cppcheck.sourceforge.net/ for more information on the cppcheck tool
     12 itself.
     13 Note that many linux distributions already provide a ready to install version
     14 of cppcheck. On fedora, for instance, it can be installed using yum:
     15 
     16 	'sudo yum install cppcheck'
     17 
     18 
     19 Usage
     20 =====
     21 In order to use this waftool simply add it to the 'options' and 'configure'
     22 functions of your main waf script as shown in the example below:
     23 
     24 	def options(opt):
     25 		opt.load('cppcheck', tooldir='./waftools')
     26 
     27 	def configure(conf):
     28 		conf.load('cppcheck')
     29 
     30 Note that example shown above assumes that the cppcheck waftool is located in
     31 the sub directory named 'waftools'.
     32 
     33 When configured as shown in the example above, cppcheck will automatically
     34 perform a source code analysis on all C/C++ build tasks that have been
     35 defined in your waf build system.
     36 
     37 The example shown below for a C program will be used as input for cppcheck when
     38 building the task.
     39 
     40 	def build(bld):
     41 		bld.program(name='foo', src='foobar.c')
     42 
     43 The result of the source code analysis will be stored both as xml and html
     44 files in the build location for the task. Should any error be detected by
     45 cppcheck the build will be aborted and a link to the html report will be shown.
     46 By default, one index.html file is created for each task generator. A global
     47 index.html file can be obtained by setting the following variable
     48 in the configuration section:
     49 
     50 	conf.env.CPPCHECK_SINGLE_HTML = False
     51 
     52 When needed source code checking by cppcheck can be disabled per task, per
     53 detected error or warning for a particular task. It can be also be disabled for
     54 all tasks.
     55 
     56 In order to exclude a task from source code checking add the skip option to the
     57 task as shown below:
     58 
     59 	def build(bld):
     60 		bld.program(
     61 				name='foo',
     62 				src='foobar.c'
     63 				cppcheck_skip=True
     64 		)
     65 
     66 When needed problems detected by cppcheck may be suppressed using a file
     67 containing a list of suppression rules. The relative or absolute path to this
     68 file can be added to the build task as shown in the example below:
     69 
     70 		bld.program(
     71 				name='bar',
     72 				src='foobar.c',
     73 				cppcheck_suppress='bar.suppress'
     74 		)
     75 
     76 A cppcheck suppress file should contain one suppress rule per line. Each of
     77 these rules will be passed as an '--suppress=<rule>' argument to cppcheck.
     78 
     79 Dependencies
     80 ================
     81 This waftool depends on the python pygments module, it is used for source code
     82 syntax highlighting when creating the html reports. see http://pygments.org/ for
     83 more information on this package.
     84 
     85 Remarks
     86 ================
     87 The generation of the html report is originally based on the cppcheck-htmlreport.py
     88 script that comes shipped with the cppcheck tool.
     89 """
     90 
     91 import sys
     92 import xml.etree.ElementTree as ElementTree
     93 from waflib import Task, TaskGen, Logs, Context, Options
     94 
     95 PYGMENTS_EXC_MSG= '''
     96 The required module 'pygments' could not be found. Please install it using your
     97 platform package manager (e.g. apt-get or yum), using 'pip' or 'easy_install',
     98 see 'http://pygments.org/download/' for installation instructions.
     99 '''
    100 
    101 try:
    102 	import pygments
    103 	from pygments import formatters, lexers
    104 except ImportError as e:
    105 	Logs.warn(PYGMENTS_EXC_MSG)
    106 	raise e
    107 
    108 
    109 def options(opt):
    110 	opt.add_option('--cppcheck-skip', dest='cppcheck_skip',
    111 		default=False, action='store_true',
    112 		help='do not check C/C++ sources (default=False)')
    113 
    114 	opt.add_option('--cppcheck-err-resume', dest='cppcheck_err_resume',
    115 		default=False, action='store_true',
    116 		help='continue in case of errors (default=False)')
    117 
    118 	opt.add_option('--cppcheck-bin-enable', dest='cppcheck_bin_enable',
    119 		default='warning,performance,portability,style,unusedFunction', action='store',
    120 		help="cppcheck option '--enable=' for binaries (default=warning,performance,portability,style,unusedFunction)")
    121 
    122 	opt.add_option('--cppcheck-lib-enable', dest='cppcheck_lib_enable',
    123 		default='warning,performance,portability,style', action='store',
    124 		help="cppcheck option '--enable=' for libraries (default=warning,performance,portability,style)")
    125 
    126 	opt.add_option('--cppcheck-std-c', dest='cppcheck_std_c',
    127 		default='c99', action='store',
    128 		help='cppcheck standard to use when checking C (default=c99)')
    129 
    130 	opt.add_option('--cppcheck-std-cxx', dest='cppcheck_std_cxx',
    131 		default='c++03', action='store',
    132 		help='cppcheck standard to use when checking C++ (default=c++03)')
    133 
    134 	opt.add_option('--cppcheck-check-config', dest='cppcheck_check_config',
    135 		default=False, action='store_true',
    136 		help='forced check for missing buildin include files, e.g. stdio.h (default=False)')
    137 
    138 	opt.add_option('--cppcheck-max-configs', dest='cppcheck_max_configs',
    139 		default='20', action='store',
    140 		help='maximum preprocessor (--max-configs) define iterations (default=20)')
    141 
    142 	opt.add_option('--cppcheck-jobs', dest='cppcheck_jobs',
    143 		default='1', action='store',
    144 		help='number of jobs (-j) to do the checking work (default=1)')
    145 
    146 def configure(conf):
    147 	if conf.options.cppcheck_skip:
    148 		conf.env.CPPCHECK_SKIP = [True]
    149 	conf.env.CPPCHECK_STD_C = conf.options.cppcheck_std_c
    150 	conf.env.CPPCHECK_STD_CXX = conf.options.cppcheck_std_cxx
    151 	conf.env.CPPCHECK_MAX_CONFIGS = conf.options.cppcheck_max_configs
    152 	conf.env.CPPCHECK_BIN_ENABLE = conf.options.cppcheck_bin_enable
    153 	conf.env.CPPCHECK_LIB_ENABLE = conf.options.cppcheck_lib_enable
    154 	conf.env.CPPCHECK_JOBS = conf.options.cppcheck_jobs
    155 	if conf.options.cppcheck_jobs != '1' and ('unusedFunction' in conf.options.cppcheck_bin_enable or 'unusedFunction' in conf.options.cppcheck_lib_enable or 'all' in conf.options.cppcheck_bin_enable or 'all' in conf.options.cppcheck_lib_enable):
    156 		Logs.warn('cppcheck: unusedFunction cannot be used with multiple threads, cppcheck will disable it automatically')
    157 	conf.find_program('cppcheck', var='CPPCHECK')
    158 
    159 	# set to True to get a single index.html file
    160 	conf.env.CPPCHECK_SINGLE_HTML = False
    161 
    162 @TaskGen.feature('c')
    163 @TaskGen.feature('cxx')
    164 def cppcheck_execute(self):
    165 	if hasattr(self.bld, 'conf'):
    166 		return
    167 	if len(self.env.CPPCHECK_SKIP) or Options.options.cppcheck_skip:
    168 		return
    169 	if getattr(self, 'cppcheck_skip', False):
    170 		return
    171 	task = self.create_task('cppcheck')
    172 	task.cmd = _tgen_create_cmd(self)
    173 	task.fatal = []
    174 	if not Options.options.cppcheck_err_resume:
    175 		task.fatal.append('error')
    176 
    177 
    178 def _tgen_create_cmd(self):
    179 	features = getattr(self, 'features', [])
    180 	std_c = self.env.CPPCHECK_STD_C
    181 	std_cxx = self.env.CPPCHECK_STD_CXX
    182 	max_configs = self.env.CPPCHECK_MAX_CONFIGS
    183 	bin_enable = self.env.CPPCHECK_BIN_ENABLE
    184 	lib_enable = self.env.CPPCHECK_LIB_ENABLE
    185 	jobs = self.env.CPPCHECK_JOBS
    186 
    187 	cmd  = self.env.CPPCHECK
    188 	args = ['--inconclusive','--report-progress','--verbose','--xml','--xml-version=2']
    189 	args.append('--max-configs=%s' % max_configs)
    190 	args.append('-j %s' % jobs)
    191 
    192 	if 'cxx' in features:
    193 		args.append('--language=c++')
    194 		args.append('--std=%s' % std_cxx)
    195 	else:
    196 		args.append('--language=c')
    197 		args.append('--std=%s' % std_c)
    198 
    199 	if Options.options.cppcheck_check_config:
    200 		args.append('--check-config')
    201 
    202 	if set(['cprogram','cxxprogram']) & set(features):
    203 		args.append('--enable=%s' % bin_enable)
    204 	else:
    205 		args.append('--enable=%s' % lib_enable)
    206 
    207 	for src in self.to_list(getattr(self, 'source', [])):
    208 		if not isinstance(src, str):
    209 			src = repr(src)
    210 		args.append(src)
    211 	for inc in self.to_incnodes(self.to_list(getattr(self, 'includes', []))):
    212 		if not isinstance(inc, str):
    213 			inc = repr(inc)
    214 		args.append('-I%s' % inc)
    215 	for inc in self.to_incnodes(self.to_list(self.env.INCLUDES)):
    216 		if not isinstance(inc, str):
    217 			inc = repr(inc)
    218 		args.append('-I%s' % inc)
    219 	return cmd + args
    220 
    221 
    222 class cppcheck(Task.Task):
    223 	quiet = True
    224 
    225 	def run(self):
    226 		stderr = self.generator.bld.cmd_and_log(self.cmd, quiet=Context.STDERR, output=Context.STDERR)
    227 		self._save_xml_report(stderr)
    228 		defects = self._get_defects(stderr)
    229 		index = self._create_html_report(defects)
    230 		self._errors_evaluate(defects, index)
    231 		return 0
    232 
    233 	def _save_xml_report(self, s):
    234 		'''use cppcheck xml result string, add the command string used to invoke cppcheck
    235 		and save as xml file.
    236 		'''
    237 		header = '%s\n' % s.splitlines()[0]
    238 		root = ElementTree.fromstring(s)
    239 		cmd = ElementTree.SubElement(root.find('cppcheck'), 'cmd')
    240 		cmd.text = str(self.cmd)
    241 		body = ElementTree.tostring(root).decode('us-ascii')
    242 		body_html_name = 'cppcheck-%s.xml' % self.generator.get_name()
    243 		if self.env.CPPCHECK_SINGLE_HTML:
    244 			body_html_name = 'cppcheck.xml'
    245 		node = self.generator.path.get_bld().find_or_declare(body_html_name)
    246 		node.write(header + body)
    247 
    248 	def _get_defects(self, xml_string):
    249 		'''evaluate the xml string returned by cppcheck (on sdterr) and use it to create
    250 		a list of defects.
    251 		'''
    252 		defects = []
    253 		for error in ElementTree.fromstring(xml_string).iter('error'):
    254 			defect = {}
    255 			defect['id'] = error.get('id')
    256 			defect['severity'] = error.get('severity')
    257 			defect['msg'] = str(error.get('msg')).replace('<','&lt;')
    258 			defect['verbose'] = error.get('verbose')
    259 			for location in error.findall('location'):
    260 				defect['file'] = location.get('file')
    261 				defect['line'] = str(int(location.get('line')) - 1)
    262 			defects.append(defect)
    263 		return defects
    264 
    265 	def _create_html_report(self, defects):
    266 		files, css_style_defs = self._create_html_files(defects)
    267 		index = self._create_html_index(files)
    268 		self._create_css_file(css_style_defs)
    269 		return index
    270 
    271 	def _create_html_files(self, defects):
    272 		sources = {}
    273 		defects = [defect for defect in defects if 'file' in defect]
    274 		for defect in defects:
    275 			name = defect['file']
    276 			if not name in sources:
    277 				sources[name] = [defect]
    278 			else:
    279 				sources[name].append(defect)
    280 
    281 		files = {}
    282 		css_style_defs = None
    283 		bpath = self.generator.path.get_bld().abspath()
    284 		names = list(sources.keys())
    285 		for i in range(0,len(names)):
    286 			name = names[i]
    287 			if self.env.CPPCHECK_SINGLE_HTML:
    288 				htmlfile = 'cppcheck/%i.html' % (i)
    289 			else:
    290 				htmlfile = 'cppcheck/%s%i.html' % (self.generator.get_name(),i)
    291 			errors = sources[name]
    292 			files[name] = { 'htmlfile': '%s/%s' % (bpath, htmlfile), 'errors': errors }
    293 			css_style_defs = self._create_html_file(name, htmlfile, errors)
    294 		return files, css_style_defs
    295 
    296 	def _create_html_file(self, sourcefile, htmlfile, errors):
    297 		name = self.generator.get_name()
    298 		root = ElementTree.fromstring(CPPCHECK_HTML_FILE)
    299 		title = root.find('head/title')
    300 		title.text = 'cppcheck - report - %s' % name
    301 
    302 		body = root.find('body')
    303 		for div in body.findall('div'):
    304 			if div.get('id') == 'page':
    305 				page = div
    306 				break
    307 		for div in page.findall('div'):
    308 			if div.get('id') == 'header':
    309 				h1 = div.find('h1')
    310 				h1.text = 'cppcheck report - %s' % name
    311 			if div.get('id') == 'menu':
    312 				indexlink = div.find('a')
    313 				if self.env.CPPCHECK_SINGLE_HTML:
    314 					indexlink.attrib['href'] = 'index.html'
    315 				else:
    316 					indexlink.attrib['href'] = 'index-%s.html' % name
    317 			if div.get('id') == 'content':
    318 				content = div
    319 				srcnode = self.generator.bld.root.find_node(sourcefile)
    320 				hl_lines = [e['line'] for e in errors if 'line' in e]
    321 				formatter = CppcheckHtmlFormatter(linenos=True, style='colorful', hl_lines=hl_lines, lineanchors='line')
    322 				formatter.errors = [e for e in errors if 'line' in e]
    323 				css_style_defs = formatter.get_style_defs('.highlight')
    324 				lexer = pygments.lexers.guess_lexer_for_filename(sourcefile, "")
    325 				s = pygments.highlight(srcnode.read(), lexer, formatter)
    326 				table = ElementTree.fromstring(s)
    327 				content.append(table)
    328 
    329 		s = ElementTree.tostring(root, method='html').decode('us-ascii')
    330 		s = CCPCHECK_HTML_TYPE + s
    331 		node = self.generator.path.get_bld().find_or_declare(htmlfile)
    332 		node.write(s)
    333 		return css_style_defs
    334 
    335 	def _create_html_index(self, files):
    336 		name = self.generator.get_name()
    337 		root = ElementTree.fromstring(CPPCHECK_HTML_FILE)
    338 		title = root.find('head/title')
    339 		title.text = 'cppcheck - report - %s' % name
    340 
    341 		body = root.find('body')
    342 		for div in body.findall('div'):
    343 			if div.get('id') == 'page':
    344 				page = div
    345 				break
    346 		for div in page.findall('div'):
    347 			if div.get('id') == 'header':
    348 				h1 = div.find('h1')
    349 				h1.text = 'cppcheck report - %s' % name
    350 			if div.get('id') == 'content':
    351 				content = div
    352 				self._create_html_table(content, files)
    353 			if div.get('id') == 'menu':
    354 				indexlink = div.find('a')
    355 				if self.env.CPPCHECK_SINGLE_HTML:
    356 					indexlink.attrib['href'] = 'index.html'
    357 				else:
    358 					indexlink.attrib['href'] = 'index-%s.html' % name
    359 
    360 		s = ElementTree.tostring(root, method='html').decode('us-ascii')
    361 		s = CCPCHECK_HTML_TYPE + s
    362 		index_html_name = 'cppcheck/index-%s.html' % name
    363 		if self.env.CPPCHECK_SINGLE_HTML:
    364 			index_html_name = 'cppcheck/index.html'
    365 		node = self.generator.path.get_bld().find_or_declare(index_html_name)
    366 		node.write(s)
    367 		return node
    368 
    369 	def _create_html_table(self, content, files):
    370 		table = ElementTree.fromstring(CPPCHECK_HTML_TABLE)
    371 		for name, val in files.items():
    372 			f = val['htmlfile']
    373 			s = '<tr><td colspan="4"><a href="%s">%s</a></td></tr>\n' % (f,name)
    374 			row = ElementTree.fromstring(s)
    375 			table.append(row)
    376 
    377 			errors = sorted(val['errors'], key=lambda e: int(e['line']) if 'line' in e else sys.maxint)
    378 			for e in errors:
    379 				if not 'line' in e:
    380 					s = '<tr><td></td><td>%s</td><td>%s</td><td>%s</td></tr>\n' % (e['id'], e['severity'], e['msg'])
    381 				else:
    382 					attr = ''
    383 					if e['severity'] == 'error':
    384 						attr = 'class="error"'
    385 					s = '<tr><td><a href="%s#line-%s">%s</a></td>' % (f, e['line'], e['line'])
    386 					s+= '<td>%s</td><td>%s</td><td %s>%s</td></tr>\n' % (e['id'], e['severity'], attr, e['msg'])
    387 				row = ElementTree.fromstring(s)
    388 				table.append(row)
    389 		content.append(table)
    390 
    391 	def _create_css_file(self, css_style_defs):
    392 		css = str(CPPCHECK_CSS_FILE)
    393 		if css_style_defs:
    394 			css = "%s\n%s\n" % (css, css_style_defs)
    395 		node = self.generator.path.get_bld().find_or_declare('cppcheck/style.css')
    396 		node.write(css)
    397 
    398 	def _errors_evaluate(self, errors, http_index):
    399 		name = self.generator.get_name()
    400 		fatal = self.fatal
    401 		severity = [err['severity'] for err in errors]
    402 		problems = [err for err in errors if err['severity'] != 'information']
    403 
    404 		if set(fatal) & set(severity):
    405 			exc  = "\n"
    406 			exc += "\nccpcheck detected fatal error(s) in task '%s', see report for details:" % name
    407 			exc += "\n    file://%r" % (http_index)
    408 			exc += "\n"
    409 			self.generator.bld.fatal(exc)
    410 
    411 		elif len(problems):
    412 			msg =  "\nccpcheck detected (possible) problem(s) in task '%s', see report for details:" % name
    413 			msg += "\n    file://%r" % http_index
    414 			msg += "\n"
    415 			Logs.error(msg)
    416 
    417 
    418 class CppcheckHtmlFormatter(pygments.formatters.HtmlFormatter):
    419 	errors = []
    420 
    421 	def wrap(self, source, outfile):
    422 		line_no = 1
    423 		for i, t in super(CppcheckHtmlFormatter, self).wrap(source, outfile):
    424 			# If this is a source code line we want to add a span tag at the end.
    425 			if i == 1:
    426 				for error in self.errors:
    427 					if int(error['line']) == line_no:
    428 						t = t.replace('\n', CPPCHECK_HTML_ERROR % error['msg'])
    429 				line_no += 1
    430 			yield i, t
    431 
    432 
    433 CCPCHECK_HTML_TYPE = \
    434 '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
    435 
    436 CPPCHECK_HTML_FILE = """
    437 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd" [<!ENTITY nbsp "&#160;">]>
    438 <html>
    439 	<head>
    440 		<title>cppcheck - report - XXX</title>
    441 		<link href="style.css" rel="stylesheet" type="text/css" />
    442 		<style type="text/css">
    443 		</style>
    444 	</head>
    445 	<body class="body">
    446 		<div id="page-header">&nbsp;</div>
    447 		<div id="page">
    448 			<div id="header">
    449 				<h1>cppcheck report - XXX</h1>
    450 			</div>
    451 			<div id="menu">
    452 				<a href="index.html">Defect list</a>
    453 			</div>
    454 			<div id="content">
    455 			</div>
    456 			<div id="footer">
    457 				<div>cppcheck - a tool for static C/C++ code analysis</div>
    458 				<div>
    459 				Internet: <a href="http://cppcheck.sourceforge.net">http://cppcheck.sourceforge.net</a><br/>
    460           		Forum: <a href="http://apps.sourceforge.net/phpbb/cppcheck/">http://apps.sourceforge.net/phpbb/cppcheck/</a><br/>
    461 				IRC: #cppcheck at irc.freenode.net
    462 				</div>
    463 				&nbsp;
    464 			</div>
    465 		&nbsp;
    466 		</div>
    467 		<div id="page-footer">&nbsp;</div>
    468 	</body>
    469 </html>
    470 """
    471 
    472 CPPCHECK_HTML_TABLE = """
    473 <table>
    474 	<tr>
    475 		<th>Line</th>
    476 		<th>Id</th>
    477 		<th>Severity</th>
    478 		<th>Message</th>
    479 	</tr>
    480 </table>
    481 """
    482 
    483 CPPCHECK_HTML_ERROR = \
    484 '<span style="background: #ffaaaa;padding: 3px;">&lt;--- %s</span>\n'
    485 
    486 CPPCHECK_CSS_FILE = """
    487 body.body {
    488 	font-family: Arial;
    489 	font-size: 13px;
    490 	background-color: black;
    491 	padding: 0px;
    492 	margin: 0px;
    493 }
    494 
    495 .error {
    496 	font-family: Arial;
    497 	font-size: 13px;
    498 	background-color: #ffb7b7;
    499 	padding: 0px;
    500 	margin: 0px;
    501 }
    502 
    503 th, td {
    504 	min-width: 100px;
    505 	text-align: left;
    506 }
    507 
    508 #page-header {
    509 	clear: both;
    510 	width: 1200px;
    511 	margin: 20px auto 0px auto;
    512 	height: 10px;
    513 	border-bottom-width: 2px;
    514 	border-bottom-style: solid;
    515 	border-bottom-color: #aaaaaa;
    516 }
    517 
    518 #page {
    519 	width: 1160px;
    520 	margin: auto;
    521 	border-left-width: 2px;
    522 	border-left-style: solid;
    523 	border-left-color: #aaaaaa;
    524 	border-right-width: 2px;
    525 	border-right-style: solid;
    526 	border-right-color: #aaaaaa;
    527 	background-color: White;
    528 	padding: 20px;
    529 }
    530 
    531 #page-footer {
    532 	clear: both;
    533 	width: 1200px;
    534 	margin: auto;
    535 	height: 10px;
    536 	border-top-width: 2px;
    537 	border-top-style: solid;
    538 	border-top-color: #aaaaaa;
    539 }
    540 
    541 #header {
    542 	width: 100%;
    543 	height: 70px;
    544 	background-image: url(logo.png);
    545 	background-repeat: no-repeat;
    546 	background-position: left top;
    547 	border-bottom-style: solid;
    548 	border-bottom-width: thin;
    549 	border-bottom-color: #aaaaaa;
    550 }
    551 
    552 #menu {
    553 	margin-top: 5px;
    554 	text-align: left;
    555 	float: left;
    556 	width: 100px;
    557 	height: 300px;
    558 }
    559 
    560 #menu > a {
    561 	margin-left: 10px;
    562 	display: block;
    563 }
    564 
    565 #content {
    566 	float: left;
    567 	width: 1020px;
    568 	margin: 5px;
    569 	padding: 0px 10px 10px 10px;
    570 	border-left-style: solid;
    571 	border-left-width: thin;
    572 	border-left-color: #aaaaaa;
    573 }
    574 
    575 #footer {
    576 	padding-bottom: 5px;
    577 	padding-top: 5px;
    578 	border-top-style: solid;
    579 	border-top-width: thin;
    580 	border-top-color: #aaaaaa;
    581 	clear: both;
    582 	font-size: 10px;
    583 }
    584 
    585 #footer > div {
    586 	float: left;
    587 	width: 33%;
    588 }
    589 
    590 """
    591