3 # WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file
6 # -*- encoding: utf-8 -*-
7 # Michel Mooij, michel.mooij7@gmail.com
12 This module provides a waf wrapper (i.e. waftool) around the C/C++ source code
13 checking tool 'cppcheck'.
15 See http://cppcheck.sourceforge.net/ for more information on the cppcheck tool
17 Note that many linux distributions already provide a ready to install version
18 of cppcheck. On fedora, for instance, it can be installed using yum:
20 'sudo yum install cppcheck'
25 In order to use this waftool simply add it to the 'options' and 'configure'
26 functions of your main waf script as shown in the example below:
29 opt.load('cppcheck', tooldir='./waftools')
34 Note that example shown above assumes that the cppcheck waftool is located in
35 the sub directory named 'waftools'.
37 When configured as shown in the example above, cppcheck will automatically
38 perform a source code analysis on all C/C++ build tasks that have been
39 defined in your waf build system.
41 The example shown below for a C program will be used as input for cppcheck when
45 bld.program(name='foo', src='foobar.c')
47 The result of the source code analysis will be stored both as xml and html
48 files in the build location for the task. Should any error be detected by
49 cppcheck the build will be aborted and a link to the html report will be shown.
50 By default, one index.html file is created for each task generator. A global
51 index.html file can be obtained by setting the following variable
52 in the configuration section:
54 conf.env.CPPCHECK_SINGLE_HTML = False
56 When needed source code checking by cppcheck can be disabled per task, per
57 detected error or warning for a particular task. It can be also be disabled for
60 In order to exclude a task from source code checking add the skip option to the
70 When needed problems detected by cppcheck may be suppressed using a file
71 containing a list of suppression rules. The relative or absolute path to this
72 file can be added to the build task as shown in the example below:
77 cppcheck_suppress='bar.suppress'
80 A cppcheck suppress file should contain one suppress rule per line. Each of
81 these rules will be passed as an '--suppress=<rule>' argument to cppcheck.
85 This waftool depends on the python pygments module, it is used for source code
86 syntax highlighting when creating the html reports. see http://pygments.org/ for
87 more information on this package.
91 The generation of the html report is originally based on the cppcheck-htmlreport.py
92 script that comes shipped with the cppcheck tool.
96 import xml.etree.ElementTree as ElementTree
97 from waflib import Task, TaskGen, Logs, Context, Options
100 The required module 'pygments' could not be found. Please install it using your
101 platform package manager (e.g. apt-get or yum), using 'pip' or 'easy_install',
102 see 'http://pygments.org/download/' for installation instructions.
107 from pygments import formatters, lexers
108 except ImportError as e:
109 Logs.warn(PYGMENTS_EXC_MSG)
114 opt.add_option('--cppcheck-skip', dest='cppcheck_skip',
115 default=False, action='store_true',
116 help='do not check C/C++ sources (default=False)')
118 opt.add_option('--cppcheck-err-resume', dest='cppcheck_err_resume',
119 default=False, action='store_true',
120 help='continue in case of errors (default=False)')
122 opt.add_option('--cppcheck-bin-enable', dest='cppcheck_bin_enable',
123 default='warning,performance,portability,style,unusedFunction', action='store',
124 help="cppcheck option '--enable=' for binaries (default=warning,performance,portability,style,unusedFunction)")
126 opt.add_option('--cppcheck-lib-enable', dest='cppcheck_lib_enable',
127 default='warning,performance,portability,style', action='store',
128 help="cppcheck option '--enable=' for libraries (default=warning,performance,portability,style)")
130 opt.add_option('--cppcheck-std-c', dest='cppcheck_std_c',
131 default='c99', action='store',
132 help='cppcheck standard to use when checking C (default=c99)')
134 opt.add_option('--cppcheck-std-cxx', dest='cppcheck_std_cxx',
135 default='c++03', action='store',
136 help='cppcheck standard to use when checking C++ (default=c++03)')
138 opt.add_option('--cppcheck-check-config', dest='cppcheck_check_config',
139 default=False, action='store_true',
140 help='forced check for missing buildin include files, e.g. stdio.h (default=False)')
142 opt.add_option('--cppcheck-max-configs', dest='cppcheck_max_configs',
143 default='20', action='store',
144 help='maximum preprocessor (--max-configs) define iterations (default=20)')
146 opt.add_option('--cppcheck-jobs', dest='cppcheck_jobs',
147 default='1', action='store',
148 help='number of jobs (-j) to do the checking work (default=1)')
151 if conf.options.cppcheck_skip:
152 conf.env.CPPCHECK_SKIP = [True]
153 conf.env.CPPCHECK_STD_C = conf.options.cppcheck_std_c
154 conf.env.CPPCHECK_STD_CXX = conf.options.cppcheck_std_cxx
155 conf.env.CPPCHECK_MAX_CONFIGS = conf.options.cppcheck_max_configs
156 conf.env.CPPCHECK_BIN_ENABLE = conf.options.cppcheck_bin_enable
157 conf.env.CPPCHECK_LIB_ENABLE = conf.options.cppcheck_lib_enable
158 conf.env.CPPCHECK_JOBS = conf.options.cppcheck_jobs
159 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):
160 Logs.warn('cppcheck: unusedFunction cannot be used with multiple threads, cppcheck will disable it automatically')
161 conf.find_program('cppcheck', var='CPPCHECK')
163 # set to True to get a single index.html file
164 conf.env.CPPCHECK_SINGLE_HTML = False
166 @TaskGen.feature('c')
167 @TaskGen.feature('cxx')
168 def cppcheck_execute(self):
169 if hasattr(self.bld, 'conf'):
171 if len(self.env.CPPCHECK_SKIP) or Options.options.cppcheck_skip:
173 if getattr(self, 'cppcheck_skip', False):
175 task = self.create_task('cppcheck')
176 task.cmd = _tgen_create_cmd(self)
178 if not Options.options.cppcheck_err_resume:
179 task.fatal.append('error')
182 def _tgen_create_cmd(self):
183 features = getattr(self, 'features', [])
184 std_c = self.env.CPPCHECK_STD_C
185 std_cxx = self.env.CPPCHECK_STD_CXX
186 max_configs = self.env.CPPCHECK_MAX_CONFIGS
187 bin_enable = self.env.CPPCHECK_BIN_ENABLE
188 lib_enable = self.env.CPPCHECK_LIB_ENABLE
189 jobs = self.env.CPPCHECK_JOBS
191 cmd = self.env.CPPCHECK
192 args = ['--inconclusive','--report-progress','--verbose','--xml','--xml-version=2']
193 args.append('--max-configs=%s' % max_configs)
194 args.append('-j %s' % jobs)
196 if 'cxx' in features:
197 args.append('--language=c++')
198 args.append('--std=%s' % std_cxx)
200 args.append('--language=c')
201 args.append('--std=%s' % std_c)
203 if Options.options.cppcheck_check_config:
204 args.append('--check-config')
206 if set(['cprogram','cxxprogram']) & set(features):
207 args.append('--enable=%s' % bin_enable)
209 args.append('--enable=%s' % lib_enable)
211 for src in self.to_list(getattr(self, 'source', [])):
212 args.append('%r' % src)
213 for inc in self.to_incnodes(self.to_list(getattr(self, 'includes', []))):
214 args.append('-I%r' % inc)
215 for inc in self.to_incnodes(self.to_list(self.env.INCLUDES)):
216 args.append('-I%r' % inc)
220 class cppcheck(Task.Task):
224 stderr = self.generator.bld.cmd_and_log(self.cmd, quiet=Context.STDERR, output=Context.STDERR)
225 self._save_xml_report(stderr)
226 defects = self._get_defects(stderr)
227 index = self._create_html_report(defects)
228 self._errors_evaluate(defects, index)
231 def _save_xml_report(self, s):
232 '''use cppcheck xml result string, add the command string used to invoke cppcheck
233 and save as xml file.
235 header = '%s\n' % s.splitlines()[0]
236 root = ElementTree.fromstring(s)
237 cmd = ElementTree.SubElement(root.find('cppcheck'), 'cmd')
238 cmd.text = str(self.cmd)
239 body = ElementTree.tostring(root).decode('us-ascii')
240 body_html_name = 'cppcheck-%s.xml' % self.generator.get_name()
241 if self.env.CPPCHECK_SINGLE_HTML:
242 body_html_name = 'cppcheck.xml'
243 node = self.generator.path.get_bld().find_or_declare(body_html_name)
244 node.write(header + body)
246 def _get_defects(self, xml_string):
247 '''evaluate the xml string returned by cppcheck (on sdterr) and use it to create
251 for error in ElementTree.fromstring(xml_string).iter('error'):
253 defect['id'] = error.get('id')
254 defect['severity'] = error.get('severity')
255 defect['msg'] = str(error.get('msg')).replace('<','<')
256 defect['verbose'] = error.get('verbose')
257 for location in error.findall('location'):
258 defect['file'] = location.get('file')
259 defect['line'] = str(int(location.get('line')) - 1)
260 defects.append(defect)
263 def _create_html_report(self, defects):
264 files, css_style_defs = self._create_html_files(defects)
265 index = self._create_html_index(files)
266 self._create_css_file(css_style_defs)
269 def _create_html_files(self, defects):
271 defects = [defect for defect in defects if 'file' in defect]
272 for defect in defects:
273 name = defect['file']
274 if not name in sources:
275 sources[name] = [defect]
277 sources[name].append(defect)
280 css_style_defs = None
281 bpath = self.generator.path.get_bld().abspath()
282 names = list(sources.keys())
283 for i in range(0,len(names)):
285 if self.env.CPPCHECK_SINGLE_HTML:
286 htmlfile = 'cppcheck/%i.html' % (i)
288 htmlfile = 'cppcheck/%s%i.html' % (self.generator.get_name(),i)
289 errors = sources[name]
290 files[name] = { 'htmlfile': '%s/%s' % (bpath, htmlfile), 'errors': errors }
291 css_style_defs = self._create_html_file(name, htmlfile, errors)
292 return files, css_style_defs
294 def _create_html_file(self, sourcefile, htmlfile, errors):
295 name = self.generator.get_name()
296 root = ElementTree.fromstring(CPPCHECK_HTML_FILE)
297 title = root.find('head/title')
298 title.text = 'cppcheck - report - %s' % name
300 body = root.find('body')
301 for div in body.findall('div'):
302 if div.get('id') == 'page':
305 for div in page.findall('div'):
306 if div.get('id') == 'header':
308 h1.text = 'cppcheck report - %s' % name
309 if div.get('id') == 'menu':
310 indexlink = div.find('a')
311 if self.env.CPPCHECK_SINGLE_HTML:
312 indexlink.attrib['href'] = 'index.html'
314 indexlink.attrib['href'] = 'index-%s.html' % name
315 if div.get('id') == 'content':
317 srcnode = self.generator.bld.root.find_node(sourcefile)
318 hl_lines = [e['line'] for e in errors if 'line' in e]
319 formatter = CppcheckHtmlFormatter(linenos=True, style='colorful', hl_lines=hl_lines, lineanchors='line')
320 formatter.errors = [e for e in errors if 'line' in e]
321 css_style_defs = formatter.get_style_defs('.highlight')
322 lexer = pygments.lexers.guess_lexer_for_filename(sourcefile, "")
323 s = pygments.highlight(srcnode.read(), lexer, formatter)
324 table = ElementTree.fromstring(s)
325 content.append(table)
327 s = ElementTree.tostring(root, method='html').decode('us-ascii')
328 s = CCPCHECK_HTML_TYPE + s
329 node = self.generator.path.get_bld().find_or_declare(htmlfile)
331 return css_style_defs
333 def _create_html_index(self, files):
334 name = self.generator.get_name()
335 root = ElementTree.fromstring(CPPCHECK_HTML_FILE)
336 title = root.find('head/title')
337 title.text = 'cppcheck - report - %s' % name
339 body = root.find('body')
340 for div in body.findall('div'):
341 if div.get('id') == 'page':
344 for div in page.findall('div'):
345 if div.get('id') == 'header':
347 h1.text = 'cppcheck report - %s' % name
348 if div.get('id') == 'content':
350 self._create_html_table(content, files)
351 if div.get('id') == 'menu':
352 indexlink = div.find('a')
353 if self.env.CPPCHECK_SINGLE_HTML:
354 indexlink.attrib['href'] = 'index.html'
356 indexlink.attrib['href'] = 'index-%s.html' % name
358 s = ElementTree.tostring(root, method='html').decode('us-ascii')
359 s = CCPCHECK_HTML_TYPE + s
360 index_html_name = 'cppcheck/index-%s.html' % name
361 if self.env.CPPCHECK_SINGLE_HTML:
362 index_html_name = 'cppcheck/index.html'
363 node = self.generator.path.get_bld().find_or_declare(index_html_name)
367 def _create_html_table(self, content, files):
368 table = ElementTree.fromstring(CPPCHECK_HTML_TABLE)
369 for name, val in files.items():
371 s = '<tr><td colspan="4"><a href="%s">%s</a></td></tr>\n' % (f,name)
372 row = ElementTree.fromstring(s)
375 errors = sorted(val['errors'], key=lambda e: int(e['line']) if 'line' in e else sys.maxint)
378 s = '<tr><td></td><td>%s</td><td>%s</td><td>%s</td></tr>\n' % (e['id'], e['severity'], e['msg'])
381 if e['severity'] == 'error':
382 attr = 'class="error"'
383 s = '<tr><td><a href="%s#line-%s">%s</a></td>' % (f, e['line'], e['line'])
384 s+= '<td>%s</td><td>%s</td><td %s>%s</td></tr>\n' % (e['id'], e['severity'], attr, e['msg'])
385 row = ElementTree.fromstring(s)
387 content.append(table)
389 def _create_css_file(self, css_style_defs):
390 css = str(CPPCHECK_CSS_FILE)
392 css = "%s\n%s\n" % (css, css_style_defs)
393 node = self.generator.path.get_bld().find_or_declare('cppcheck/style.css')
396 def _errors_evaluate(self, errors, http_index):
397 name = self.generator.get_name()
399 severity = [err['severity'] for err in errors]
400 problems = [err for err in errors if err['severity'] != 'information']
402 if set(fatal) & set(severity):
404 exc += "\nccpcheck detected fatal error(s) in task '%s', see report for details:" % name
405 exc += "\n file://%r" % (http_index)
407 self.generator.bld.fatal(exc)
410 msg = "\nccpcheck detected (possible) problem(s) in task '%s', see report for details:" % name
411 msg += "\n file://%r" % http_index
416 class CppcheckHtmlFormatter(pygments.formatters.HtmlFormatter):
419 def wrap(self, source, outfile):
421 for i, t in super(CppcheckHtmlFormatter, self).wrap(source, outfile):
422 # If this is a source code line we want to add a span tag at the end.
424 for error in self.errors:
425 if int(error['line']) == line_no:
426 t = t.replace('\n', CPPCHECK_HTML_ERROR % error['msg'])
431 CCPCHECK_HTML_TYPE = \
432 '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
434 CPPCHECK_HTML_FILE = """
435 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd" [<!ENTITY nbsp " ">]>
438 <title>cppcheck - report - XXX</title>
439 <link href="style.css" rel="stylesheet" type="text/css" />
440 <style type="text/css">
444 <div id="page-header"> </div>
447 <h1>cppcheck report - XXX</h1>
450 <a href="index.html">Defect list</a>
455 <div>cppcheck - a tool for static C/C++ code analysis</div>
457 Internet: <a href="http://cppcheck.sourceforge.net">http://cppcheck.sourceforge.net</a><br/>
458 Forum: <a href="http://apps.sourceforge.net/phpbb/cppcheck/">http://apps.sourceforge.net/phpbb/cppcheck/</a><br/>
459 IRC: #cppcheck at irc.freenode.net
465 <div id="page-footer"> </div>
470 CPPCHECK_HTML_TABLE = """
481 CPPCHECK_HTML_ERROR = \
482 '<span style="background: #ffaaaa;padding: 3px;"><--- %s</span>\n'
484 CPPCHECK_CSS_FILE = """
488 background-color: black;
496 background-color: #ffb7b7;
509 margin: 20px auto 0px auto;
511 border-bottom-width: 2px;
512 border-bottom-style: solid;
513 border-bottom-color: #aaaaaa;
519 border-left-width: 2px;
520 border-left-style: solid;
521 border-left-color: #aaaaaa;
522 border-right-width: 2px;
523 border-right-style: solid;
524 border-right-color: #aaaaaa;
525 background-color: White;
534 border-top-width: 2px;
535 border-top-style: solid;
536 border-top-color: #aaaaaa;
542 background-image: url(logo.png);
543 background-repeat: no-repeat;
544 background-position: left top;
545 border-bottom-style: solid;
546 border-bottom-width: thin;
547 border-bottom-color: #aaaaaa;
567 padding: 0px 10px 10px 10px;
568 border-left-style: solid;
569 border-left-width: thin;
570 border-left-color: #aaaaaa;
576 border-top-style: solid;
577 border-top-width: thin;
578 border-top-color: #aaaaaa;