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
41 from waflib
import Errors
, Task
, TaskGen
, Logs
, Options
, Node
, Utils
45 CPPLINT_FORMAT
= '[CPPLINT] %(filename)s:\nline %(linenum)s, severity %(confidence)s, category: %(category)s\n%(message)s\n'
46 RE_EMACS
= re
.compile(r
'(?P<filename>.*):(?P<linenum>\d+): (?P<message>.*) \[(?P<category>.*)\] \[(?P<confidence>\d+)\]')
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+)\]'),
53 CPPLINT_STR
= ('${CPPLINT} '
54 '--verbose=${CPPLINT_LEVEL} '
55 '--output=${CPPLINT_OUTPUT} '
56 '--filter=${CPPLINT_FILTERS} '
57 '--root=${CPPLINT_ROOT} '
58 '--linelength=${CPPLINT_LINE_LENGTH} ')
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)')
85 conf
.find_program('cpplint', var
='CPPLINT')
86 except Errors
.ConfigurationError
:
87 conf
.env
.CPPLINT_SKIP
= True
90 class cpplint_formatter(Logs
.formatter
, object):
91 def __init__(self
, fmt
):
92 logging
.Formatter
.__init
__(self
, CPPLINT_FORMAT
)
95 def format(self
, rec
):
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
)
104 class cpplint_handler(Logs
.log_handler
, object):
105 def __init__(self
, stream
=sys
.stderr
, **kw
):
106 super(cpplint_handler
, self
).__init
__(stream
, **kw
)
110 rec
.stream
= self
.stream
111 self
.emit_override(rec
)
115 class cpplint_wrapper(object):
116 def __init__(self
, logger
, threshold
, fmt
):
118 self
.threshold
= threshold
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
:
133 def write(self
, message
):
134 global critical_errors
135 result
= CPPLINT_RE
[self
.fmt
].match(message
)
138 level
= int(result
.groupdict()['confidence'])
139 if level
>= self
.threshold
:
142 self
.logger
.info(message
)
144 self
.logger
.warning(message
)
146 self
.logger
.error(message
)
149 cpplint_logger
= None
150 def get_cpplint_logger(fmt
):
151 global 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
162 class cpplint(Task
.Task
):
165 def __init__(self
, *k
, **kw
):
166 super(cpplint
, self
).__init
__(*k
, **kw
)
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'] == '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
,
180 return critical_errors
182 @TaskGen.extension('.h', '.hh', '.hpp', '.hxx')
183 def cpplint_includes(self
, node
):
186 @TaskGen.feature('cpplint')
187 @TaskGen.before_method('process_source')
188 def 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
]:
193 self
.env
[key
] = value
194 self
.env
.CPPLINT_INITIALIZED
= True
196 if self
.env
.CPPLINT_SKIP
:
199 if not self
.env
.CPPLINT_OUTPUT
in CPPLINT_RE
:
202 for src
in self
.to_list(getattr(self
, 'source', [])):
203 if isinstance(src
, Node
.Node
):
206 node
= self
.path
.find_or_declare(src
)
208 self
.bld
.fatal('Could not find %r' % src
)
209 self
.create_task('cpplint', node
)