#!/usr/bin/env python #coding=utf-8 import os, re, argparse, sys import posixpath class Path: @staticmethod def _forward_slash(p): return p.replace(os.path.sep, '/') @staticmethod def join(p, *paths): return Path._forward_slash(posixpath.join(p, *paths)) @staticmethod def abspath(p): return Path._forward_slash(posixpath.abspath(p)) @staticmethod def normpath(p): return Path._forward_slash(posixpath.normpath(p)) @staticmethod def relpath(p, s): return Path._forward_slash(posixpath.relpath(p, s)) @staticmethod def exists(p): return os.path.exists(p) @staticmethod def basename(p): return posixpath.basename(p) @staticmethod def extname(p): return posixpath.splitext(p)[1] @staticmethod def dirname(p): return posixpath.dirname(p) class LintContext: def __init__(self, root, fix): self.exclude = [ # exclude some platform specific files. 'platform/win8.1-universal/Cocos2dRenderer.cpp', 'platform/win8.1-universal/OpenGLES.cpp', 'platform/win8.1-universal/OpenGLESPage.xaml.cpp', 'platform/win8.1-universal/OpenGLESPage.xaml.h', 'platform/win8.1-universal/pch.cpp', 'platform/winrt/pch.cpp', 'editor-support/spine/Json.c', 'editor-support/spine/PathConstraint.h', 'editor-support/spine/SkeletonJson.c' ] self.source_exts = ['.h','.hpp','.inl','.c','.cpp', '.m', '.mm'] self.header_exts = ['.h','.hpp','.inl'] self.root = root self.fix = fix self.errors = 0 self.error_files = 0 self._scan_source(root) self._scan_unique_headers(self.headers) def _scan_source(self, top): # find all sources and headers relative to self.root self.sources = [] self.headers = [] for root, dirnames, filenames in os.walk(top): for f in filenames: p = Path.relpath(Path.join(root, f), top) if self._source_to_lint(p): self.sources.append(p) if self._is_header(p): self.headers.append(p) def _source_to_lint(self, p): if p in self.exclude: return False ext = Path.extname(p) return ext in self.source_exts def _is_header(self, name): return Path.extname(name) in self.header_exts # find headers have unique base filenames # this is used to get included headers in other search paths def _scan_unique_headers(self, headers): known = {} for f in headers: name = Path.basename(f) if known.has_key(name): known[name].append(f) else: known[name] = [f] uniq = {} for k,v in known.iteritems(): if len(v) == 1: uniq[k] = v[0] self.uniq = uniq def in_search_path(self, filename): return Path.exists(Path.join(self.root, filename)) def find_uniq(self, basename): return self.uniq[basename] if self.uniq.has_key(basename) else None def get_include_path(self, original, directory): # 1. try search in uniq cocos header names p = self.find_uniq(Path.basename(original)) if not p: # 2. try search in current header directory p = Path.normpath(Path.join(directory, original)) if not self.in_search_path(p): return None return p def fix(match, cwd, ctx, fixed): h = match.group(2) # return original if already in search path (cocos directory) if ctx.in_search_path(h): return match.group(0) p = ctx.get_include_path(h, cwd) if not p: return match.group(0) ctx.errors += 1 fix = '#%s "%s"' % (match.group(1), p) fixed[match.group(0)] = fix return fix def lint_one(header, ctx): cwd = Path.dirname(header) if not cwd: return filename = Path.join(ctx.root, header) content = open(filename, 'r').read() fixed = {} # check all #include "header.*" linted = re.sub('#\s*(include|import)\s*"(.*)"', lambda m: fix(m, cwd, ctx, fixed), content) if content != linted: ctx.error_files += 1 if ctx.fix: with open (filename, 'w') as f: f.write(linted) print('%s: %d error(s) fixed' % (header, len(fixed))) else: print('%s:' % (header)) for k, v in fixed.iteritems(): print('\t%s should be %s' % (k, v)) def lint(ctx): print('Checking headers in: %s' % ctx.root) for f in ctx.sources: lint_one(f, ctx) print('Total: %d errors in %d files' % (ctx.errors, ctx.error_files)) if ctx.errors > 0: if ctx.fix: print('All fixed') else: print('Rerun this script with -f to fixes these errors') sys.exit(1) def main(): default_root = Path.abspath(Path.join(Path.dirname(__file__), '..', '..')) parser = argparse.ArgumentParser(description='The cocos headers lint script.') parser.add_argument('-f','--fix', action='store_true', help='fixe the headers while linting') parser.add_argument('root', nargs='?', default= default_root, help='path to cocos2d-x source root directory') args = parser.parse_args() lint(LintContext(Path.join(args.root, 'cocos'), args.fix)) main()