4 # written by Sylvain Rouquette, 2014
8 This is an extra tool, not bundled with the default waf binary.
9 To add the cpplint tool to the waf file:
10 $ ./waf-light --tools=compat15,cpplint
12 this tool also requires cpplint for python.
13 If you have PIP, you can install it like this: pip install cpplint
15 When using this tool, the wscript will look like:
18 opt.load('compiler_cxx cpplint')
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
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'))
38 from __future__ import absolute_import
42 from waflib import Task, TaskGen, Logs, Options, Node
44 import cpplint.cpplint as cpplint_tool
47 import cpplint as cpplint_tool
53 CPPLINT_FORMAT = '[CPPLINT] %(filename)s:\nline %(linenum)s, severity %(confidence)s, category: %(category)s\n%(message)s\n'
54 RE_EMACS = re.compile('(?P<filename>.*):(?P<linenum>\d+): (?P<message>.*) \[(?P<category>.*)\] \[(?P<confidence>\d+)\]')
58 'vs7': re.compile('(?P<filename>.*)\((?P<linenum>\d+)\): (?P<message>.*) \[(?P<category>.*)\] \[(?P<confidence>\d+)\]'),
59 'eclipse': re.compile('(?P<filename>.*):(?P<linenum>\d+): warning: (?P<message>.*) \[(?P<category>.*)\] \[(?P<confidence>\d+)\]'),
63 opt.add_option('--cpplint-filters', type='string',
64 default='', dest='CPPLINT_FILTERS',
65 help='add filters to cpplint')
66 opt.add_option('--cpplint-length', type='int',
67 default=80, dest='CPPLINT_LINE_LENGTH',
68 help='specify the line length (default: 80)')
69 opt.add_option('--cpplint-level', default=1, type='int', dest='CPPLINT_LEVEL',
70 help='specify the log level (default: 1)')
71 opt.add_option('--cpplint-break', default=5, type='int', dest='CPPLINT_BREAK',
72 help='break the build if error >= level (default: 5)')
73 opt.add_option('--cpplint-root', type='string',
74 default=None, dest='CPPLINT_ROOT',
75 help='root directory used to derive header guard')
76 opt.add_option('--cpplint-skip', action='store_true',
77 default=False, dest='CPPLINT_SKIP',
78 help='skip cpplint during build')
79 opt.add_option('--cpplint-output', type='string',
80 default='waf', dest='CPPLINT_OUTPUT',
81 help='select output format (waf, emacs, vs7)')
85 conf.start_msg('Checking cpplint')
87 cpplint_tool._cpplint_state
90 conf.env.CPPLINT_SKIP = True
91 conf.end_msg('not found, skipping it.')
94 class cpplint_formatter(Logs.formatter, object):
95 def __init__(self, fmt):
96 logging.Formatter.__init__(self, CPPLINT_FORMAT)
99 def format(self, rec):
100 if self.fmt == 'waf':
101 result = CPPLINT_RE[self.fmt].match(rec.msg).groupdict()
102 rec.msg = CPPLINT_FORMAT % result
103 if rec.levelno <= logging.INFO:
104 rec.c1 = Logs.colors.CYAN
105 return super(cpplint_formatter, self).format(rec)
108 class cpplint_handler(Logs.log_handler, object):
109 def __init__(self, stream=sys.stderr, **kw):
110 super(cpplint_handler, self).__init__(stream, **kw)
114 rec.stream = self.stream
115 self.emit_override(rec)
119 class cpplint_wrapper(object):
122 lock = threading.RLock()
124 def __init__(self, logger, threshold, fmt):
126 self.threshold = threshold
131 with cpplint_wrapper.lock:
132 cpplint_wrapper.tasks_count += 1
133 if cpplint_wrapper.tasks_count == 1:
135 cpplint_wrapper.stream = sys.stderr
139 def __exit__(self, exc_type, exc_value, traceback):
140 with cpplint_wrapper.lock:
141 cpplint_wrapper.tasks_count -= 1
142 if cpplint_wrapper.tasks_count == 0:
143 sys.stderr = cpplint_wrapper.stream
149 def write(self, message):
150 global critical_errors
151 result = CPPLINT_RE[self.fmt].match(message)
154 level = int(result.groupdict()['confidence'])
155 if level >= self.threshold:
158 self.logger.info(message)
160 self.logger.warning(message)
162 self.logger.error(message)
165 cpplint_logger = None
166 def get_cpplint_logger(fmt):
167 global cpplint_logger
169 return cpplint_logger
170 cpplint_logger = logging.getLogger('cpplint')
171 hdlr = cpplint_handler()
172 hdlr.setFormatter(cpplint_formatter(fmt))
173 cpplint_logger.addHandler(hdlr)
174 cpplint_logger.setLevel(logging.DEBUG)
175 return cpplint_logger
178 class cpplint(Task.Task):
181 def __init__(self, *k, **kw):
182 super(cpplint, self).__init__(*k, **kw)
185 global critical_errors
186 with cpplint_wrapper(get_cpplint_logger(self.env.CPPLINT_OUTPUT), self.env.CPPLINT_BREAK, self.env.CPPLINT_OUTPUT):
187 if self.env.CPPLINT_OUTPUT != 'waf':
188 cpplint_tool._SetOutputFormat(self.env.CPPLINT_OUTPUT)
189 cpplint_tool._SetFilters(self.env.CPPLINT_FILTERS)
190 cpplint_tool._line_length = self.env.CPPLINT_LINE_LENGTH
191 cpplint_tool._root = self.env.CPPLINT_ROOT
192 cpplint_tool.ProcessFile(self.inputs[0].abspath(), self.env.CPPLINT_LEVEL)
193 return critical_errors
195 @TaskGen.extension('.h', '.hh', '.hpp', '.hxx')
196 def cpplint_includes(self, node):
199 @TaskGen.feature('cpplint')
200 @TaskGen.before_method('process_source')
201 def post_cpplint(self):
202 if not self.env.CPPLINT_INITIALIZED:
203 for key, value in Options.options.__dict__.items():
204 if not key.startswith('CPPLINT_') or self.env[key]:
206 self.env[key] = value
207 self.env.CPPLINT_INITIALIZED = True
209 if self.env.CPPLINT_SKIP:
212 if not self.env.CPPLINT_OUTPUT in CPPLINT_RE:
215 for src in self.to_list(getattr(self, 'source', [])):
216 if isinstance(src, Node.Node):
219 node = self.path.find_or_declare(src)
221 self.bld.fatal('Could not find %r' % src)
222 self.create_task('cpplint', node)