1#! /usr/bin/env python 2# encoding: utf-8 3# 4# written by Sylvain Rouquette, 2014 5 6''' 7 8This is an extra tool, not bundled with the default waf binary. 9To add the cpplint tool to the waf file: 10$ ./waf-light --tools=compat15,cpplint 11 12this tool also requires cpplint for python. 13If you have PIP, you can install it like this: pip install cpplint 14 15When using this tool, the wscript will look like: 16 17 def options(opt): 18 opt.load('compiler_cxx cpplint') 19 20 def configure(conf): 21 conf.load('compiler_cxx cpplint') 22 # optional, you can also specify them on the command line 23 conf.env.CPPLINT_FILTERS = ','.join(( 24 '-whitespace/newline', # c++11 lambda 25 '-readability/braces', # c++11 constructor 26 '-whitespace/braces', # c++11 constructor 27 '-build/storage_class', # c++11 for-range 28 '-whitespace/blank_line', # user pref 29 '-whitespace/labels' # user pref 30 )) 31 32 def build(bld): 33 bld(features='cpplint', source='main.cpp', target='app') 34 # add include files, because they aren't usually built 35 bld(features='cpplint', source=bld.path.ant_glob('**/*.hpp')) 36''' 37 38from __future__ import absolute_import 39import sys, re 40import logging 41from waflib import Errors, Task, TaskGen, Logs, Options, Node, Utils 42 43 44critical_errors = 0 45CPPLINT_FORMAT = '[CPPLINT] %(filename)s:\nline %(linenum)s, severity %(confidence)s, category: %(category)s\n%(message)s\n' 46RE_EMACS = re.compile(r'(?P<filename>.*):(?P<linenum>\d+): (?P<message>.*) \[(?P<category>.*)\] \[(?P<confidence>\d+)\]') 47CPPLINT_RE = { 48 'waf': RE_EMACS, 49 'emacs': RE_EMACS, 50 'vs7': re.compile(r'(?P<filename>.*)\((?P<linenum>\d+)\): (?P<message>.*) \[(?P<category>.*)\] \[(?P<confidence>\d+)\]'), 51 'eclipse': re.compile(r'(?P<filename>.*):(?P<linenum>\d+): warning: (?P<message>.*) \[(?P<category>.*)\] \[(?P<confidence>\d+)\]'), 52} 53CPPLINT_STR = ('${CPPLINT} ' 54 '--verbose=${CPPLINT_LEVEL} ' 55 '--output=${CPPLINT_OUTPUT} ' 56 '--filter=${CPPLINT_FILTERS} ' 57 '--root=${CPPLINT_ROOT} ' 58 '--linelength=${CPPLINT_LINE_LENGTH} ') 59 60 61def options(opt): 62 opt.add_option('--cpplint-filters', type='string', 63 default='', dest='CPPLINT_FILTERS', 64 help='add filters to cpplint') 65 opt.add_option('--cpplint-length', type='int', 66 default=80, dest='CPPLINT_LINE_LENGTH', 67 help='specify the line length (default: 80)') 68 opt.add_option('--cpplint-level', default=1, type='int', dest='CPPLINT_LEVEL', 69 help='specify the log level (default: 1)') 70 opt.add_option('--cpplint-break', default=5, type='int', dest='CPPLINT_BREAK', 71 help='break the build if error >= level (default: 5)') 72 opt.add_option('--cpplint-root', type='string', 73 default='', dest='CPPLINT_ROOT', 74 help='root directory used to derive header guard') 75 opt.add_option('--cpplint-skip', action='store_true', 76 default=False, dest='CPPLINT_SKIP', 77 help='skip cpplint during build') 78 opt.add_option('--cpplint-output', type='string', 79 default='waf', dest='CPPLINT_OUTPUT', 80 help='select output format (waf, emacs, vs7, eclipse)') 81 82 83def configure(conf): 84 try: 85 conf.find_program('cpplint', var='CPPLINT') 86 except Errors.ConfigurationError: 87 conf.env.CPPLINT_SKIP = True 88 89 90class cpplint_formatter(Logs.formatter, object): 91 def __init__(self, fmt): 92 logging.Formatter.__init__(self, CPPLINT_FORMAT) 93 self.fmt = fmt 94 95 def format(self, rec): 96 if self.fmt == 'waf': 97 result = CPPLINT_RE[self.fmt].match(rec.msg).groupdict() 98 rec.msg = CPPLINT_FORMAT % result 99 if rec.levelno <= logging.INFO: 100 rec.c1 = Logs.colors.CYAN 101 return super(cpplint_formatter, self).format(rec) 102 103 104class cpplint_handler(Logs.log_handler, object): 105 def __init__(self, stream=sys.stderr, **kw): 106 super(cpplint_handler, self).__init__(stream, **kw) 107 self.stream = stream 108 109 def emit(self, rec): 110 rec.stream = self.stream 111 self.emit_override(rec) 112 self.flush() 113 114 115class cpplint_wrapper(object): 116 def __init__(self, logger, threshold, fmt): 117 self.logger = logger 118 self.threshold = threshold 119 self.fmt = fmt 120 121 def __enter__(self): 122 return self 123 124 def __exit__(self, exc_type, exc_value, traceback): 125 if isinstance(exc_value, Utils.subprocess.CalledProcessError): 126 messages = [m for m in exc_value.output.splitlines() 127 if 'Done processing' not in m 128 and 'Total errors found' not in m] 129 for message in messages: 130 self.write(message) 131 return True 132 133 def write(self, message): 134 global critical_errors 135 result = CPPLINT_RE[self.fmt].match(message) 136 if not result: 137 return 138 level = int(result.groupdict()['confidence']) 139 if level >= self.threshold: 140 critical_errors += 1 141 if level <= 2: 142 self.logger.info(message) 143 elif level <= 4: 144 self.logger.warning(message) 145 else: 146 self.logger.error(message) 147 148 149cpplint_logger = None 150def get_cpplint_logger(fmt): 151 global cpplint_logger 152 if cpplint_logger: 153 return cpplint_logger 154 cpplint_logger = logging.getLogger('cpplint') 155 hdlr = cpplint_handler() 156 hdlr.setFormatter(cpplint_formatter(fmt)) 157 cpplint_logger.addHandler(hdlr) 158 cpplint_logger.setLevel(logging.DEBUG) 159 return cpplint_logger 160 161 162class cpplint(Task.Task): 163 color = 'PINK' 164 165 def __init__(self, *k, **kw): 166 super(cpplint, self).__init__(*k, **kw) 167 168 def run(self): 169 global critical_errors 170 with cpplint_wrapper(get_cpplint_logger(self.env.CPPLINT_OUTPUT), self.env.CPPLINT_BREAK, self.env.CPPLINT_OUTPUT): 171 params = {key: str(self.env[key]) for key in self.env if 'CPPLINT_' in key} 172 if params['CPPLINT_OUTPUT'] is 'waf': 173 params['CPPLINT_OUTPUT'] = 'emacs' 174 params['CPPLINT'] = self.env.get_flat('CPPLINT') 175 cmd = Utils.subst_vars(CPPLINT_STR, params) 176 env = self.env.env or None 177 Utils.subprocess.check_output(cmd + self.inputs[0].abspath(), 178 stderr=Utils.subprocess.STDOUT, 179 env=env, shell=True) 180 return critical_errors 181 182@TaskGen.extension('.h', '.hh', '.hpp', '.hxx') 183def cpplint_includes(self, node): 184 pass 185 186@TaskGen.feature('cpplint') 187@TaskGen.before_method('process_source') 188def post_cpplint(self): 189 if not self.env.CPPLINT_INITIALIZED: 190 for key, value in Options.options.__dict__.items(): 191 if not key.startswith('CPPLINT_') or self.env[key]: 192 continue 193 self.env[key] = value 194 self.env.CPPLINT_INITIALIZED = True 195 196 if self.env.CPPLINT_SKIP: 197 return 198 199 if not self.env.CPPLINT_OUTPUT in CPPLINT_RE: 200 return 201 202 for src in self.to_list(getattr(self, 'source', [])): 203 if isinstance(src, Node.Node): 204 node = src 205 else: 206 node = self.path.find_or_declare(src) 207 if not node: 208 self.bld.fatal('Could not find %r' % src) 209 self.create_task('cpplint', node) 210