waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

cpplint.py (7522B)


      1 #! /usr/bin/env python
      2 # encoding: utf-8
      3 #
      4 # written by Sylvain Rouquette, 2014
      5 
      6 '''
      7 
      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
     11 
     12 this tool also requires cpplint for python.
     13 If you have PIP, you can install it like this: pip install cpplint
     14 
     15 When 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 
     38 from __future__ import absolute_import
     39 import sys, re
     40 import logging
     41 from waflib import Errors, Task, TaskGen, Logs, Options, Node, Utils
     42 
     43 
     44 critical_errors = 0
     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+)\]')
     47 CPPLINT_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 }
     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} ')
     59 
     60 
     61 def 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 
     83 def configure(conf):
     84     try:
     85         conf.find_program('cpplint', var='CPPLINT')
     86     except Errors.ConfigurationError:
     87         conf.env.CPPLINT_SKIP = True
     88 
     89 
     90 class 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 
    104 class 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 
    115 class 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 
    149 cpplint_logger = None
    150 def 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 
    162 class 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'] == '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')
    183 def cpplint_includes(self, node):
    184     pass
    185 
    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]:
    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)