cpplint.py 7.4 KB

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