2 # -*- encoding: utf-8 -*-
3 # Michel Mooij, michel.mooij7@gmail.com
8 This module provides a waf wrapper (i.e. waftool) around the C/C++ source code
9 checking tool 'cppcheck'.
11 See http://cppcheck.sourceforge.net/ for more information on the cppcheck tool
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:
16 'sudo yum install cppcheck'
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:
25 opt.load('cppcheck', tooldir='./waftools')
30 Note that example shown above assumes that the cppcheck waftool is located in
31 the sub directory named 'waftools'.
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.
37 The example shown below for a C program will be used as input for cppcheck when
41 bld.program(name='foo', src='foobar.c')
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:
50 conf.env.CPPCHECK_SINGLE_HTML = False
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
56 In order to exclude a task from source code checking add the skip option to the
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:
73 cppcheck_suppress='bar.suppress'
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.
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.
87 The generation of the html report is originally based on the cppcheck-htmlreport.py
88 script that comes shipped with the cppcheck tool.
92 import xml
.etree
.ElementTree
as ElementTree
93 from waflib
import Task
, TaskGen
, Logs
, Context
, Options
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.
103 from pygments
import formatters
, lexers
104 except ImportError as e
:
105 Logs
.warn(PYGMENTS_EXC_MSG
)
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)')
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)')
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)")
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)")
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)')
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)')
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)')
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)')
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)')
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')
159 # set to True to get a single index.html file
160 conf
.env
.CPPCHECK_SINGLE_HTML
= False
162 @TaskGen.feature('c')
163 @TaskGen.feature('cxx')
164 def cppcheck_execute(self
):
165 if hasattr(self
.bld
, 'conf'):
167 if len(self
.env
.CPPCHECK_SKIP
) or Options
.options
.cppcheck_skip
:
169 if getattr(self
, 'cppcheck_skip', False):
171 task
= self
.create_task('cppcheck')
172 task
.cmd
= _tgen_create_cmd(self
)
174 if not Options
.options
.cppcheck_err_resume
:
175 task
.fatal
.append('error')
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
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
)
192 if 'cxx' in features
:
193 args
.append('--language=c++')
194 args
.append('--std=%s' % std_cxx
)
196 args
.append('--language=c')
197 args
.append('--std=%s' % std_c
)
199 if Options
.options
.cppcheck_check_config
:
200 args
.append('--check-config')
202 if set(['cprogram','cxxprogram']) & set(features
):
203 args
.append('--enable=%s' % bin_enable
)
205 args
.append('--enable=%s' % lib_enable
)
207 for src
in self
.to_list(getattr(self
, 'source', [])):
208 if not isinstance(src
, str):
211 for inc
in self
.to_incnodes(self
.to_list(getattr(self
, 'includes', []))):
212 if not isinstance(inc
, str):
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):
218 args
.append('-I%s' % inc
)
222 class cppcheck(Task
.Task
):
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
)
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.
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
)
248 def _get_defects(self
, xml_string
):
249 '''evaluate the xml string returned by cppcheck (on sdterr) and use it to create
253 for error
in ElementTree
.fromstring(xml_string
).iter('error'):
255 defect
['id'] = error
.get('id')
256 defect
['severity'] = error
.get('severity')
257 defect
['msg'] = str(error
.get('msg')).replace('<','<')
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
)
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
)
271 def _create_html_files(self
, defects
):
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
]
279 sources
[name
].append(defect
)
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
)):
287 if self
.env
.CPPCHECK_SINGLE_HTML
:
288 htmlfile
= 'cppcheck/%i.html' % (i
)
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
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
302 body
= root
.find('body')
303 for div
in body
.findall('div'):
304 if div
.get('id') == 'page':
307 for div
in page
.findall('div'):
308 if div
.get('id') == 'header':
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'
316 indexlink
.attrib
['href'] = 'index-%s.html' % name
317 if div
.get('id') == 'content':
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
)
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
)
333 return css_style_defs
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
341 body
= root
.find('body')
342 for div
in body
.findall('div'):
343 if div
.get('id') == 'page':
346 for div
in page
.findall('div'):
347 if div
.get('id') == 'header':
349 h1
.text
= 'cppcheck report - %s' % name
350 if div
.get('id') == 'content':
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'
358 indexlink
.attrib
['href'] = 'index-%s.html' % name
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
)
369 def _create_html_table(self
, content
, files
):
370 table
= ElementTree
.fromstring(CPPCHECK_HTML_TABLE
)
371 for name
, val
in files
.items():
373 s
= '<tr><td colspan="4"><a href="%s">%s</a></td></tr>\n' % (f
,name
)
374 row
= ElementTree
.fromstring(s
)
377 errors
= sorted(val
['errors'], key
=lambda e
: int(e
['line']) if 'line' in e
else sys
.maxint
)
380 s
= '<tr><td></td><td>%s</td><td>%s</td><td>%s</td></tr>\n' % (e
['id'], e
['severity'], e
['msg'])
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
)
389 content
.append(table
)
391 def _create_css_file(self
, css_style_defs
):
392 css
= str(CPPCHECK_CSS_FILE
)
394 css
= "%s\n%s\n" % (css
, css_style_defs
)
395 node
= self
.generator
.path
.get_bld().find_or_declare('cppcheck/style.css')
398 def _errors_evaluate(self
, errors
, http_index
):
399 name
= self
.generator
.get_name()
401 severity
= [err
['severity'] for err
in errors
]
402 problems
= [err
for err
in errors
if err
['severity'] != 'information']
404 if set(fatal
) & set(severity
):
406 exc
+= "\nccpcheck detected fatal error(s) in task '%s', see report for details:" % name
407 exc
+= "\n file://%r" % (http_index
)
409 self
.generator
.bld
.fatal(exc
)
412 msg
= "\nccpcheck detected (possible) problem(s) in task '%s', see report for details:" % name
413 msg
+= "\n file://%r" % http_index
418 class CppcheckHtmlFormatter(pygments
.formatters
.HtmlFormatter
):
421 def wrap(self
, source
, outfile
):
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.
426 for error
in self
.errors
:
427 if int(error
['line']) == line_no
:
428 t
= t
.replace('\n', CPPCHECK_HTML_ERROR
% error
['msg'])
433 CCPCHECK_HTML_TYPE
= \
434 '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
436 CPPCHECK_HTML_FILE
= """
437 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd" [<!ENTITY nbsp " ">]>
440 <title>cppcheck - report - XXX</title>
441 <link href="style.css" rel="stylesheet" type="text/css" />
442 <style type="text/css">
446 <div id="page-header"> </div>
449 <h1>cppcheck report - XXX</h1>
452 <a href="index.html">Defect list</a>
457 <div>cppcheck - a tool for static C/C++ code analysis</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
467 <div id="page-footer"> </div>
472 CPPCHECK_HTML_TABLE
= """
483 CPPCHECK_HTML_ERROR
= \
484 '<span style="background: #ffaaaa;padding: 3px;"><--- %s</span>\n'
486 CPPCHECK_CSS_FILE
= """
490 background-color: black;
498 background-color: #ffb7b7;
511 margin: 20px auto 0px auto;
513 border-bottom-width: 2px;
514 border-bottom-style: solid;
515 border-bottom-color: #aaaaaa;
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;
536 border-top-width: 2px;
537 border-top-style: solid;
538 border-top-color: #aaaaaa;
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;
569 padding: 0px 10px 10px 10px;
570 border-left-style: solid;
571 border-left-width: thin;
572 border-left-color: #aaaaaa;
578 border-top-style: solid;
579 border-top-width: thin;
580 border-top-color: #aaaaaa;