include-linter.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. #!/usr/bin/env python
  2. #coding=utf-8
  3. import os, re, argparse, sys
  4. import posixpath
  5. class Path:
  6. @staticmethod
  7. def _forward_slash(p):
  8. return p.replace(os.path.sep, '/')
  9. @staticmethod
  10. def join(p, *paths):
  11. return Path._forward_slash(posixpath.join(p, *paths))
  12. @staticmethod
  13. def abspath(p):
  14. return Path._forward_slash(posixpath.abspath(p))
  15. @staticmethod
  16. def normpath(p):
  17. return Path._forward_slash(posixpath.normpath(p))
  18. @staticmethod
  19. def relpath(p, s):
  20. return Path._forward_slash(posixpath.relpath(p, s))
  21. @staticmethod
  22. def exists(p):
  23. return os.path.exists(p)
  24. @staticmethod
  25. def basename(p):
  26. return posixpath.basename(p)
  27. @staticmethod
  28. def extname(p):
  29. return posixpath.splitext(p)[1]
  30. @staticmethod
  31. def dirname(p):
  32. return posixpath.dirname(p)
  33. class LintContext:
  34. def __init__(self, root, fix):
  35. self.exclude = [
  36. # exclude some platform specific files.
  37. 'platform/win8.1-universal/Cocos2dRenderer.cpp',
  38. 'platform/win8.1-universal/OpenGLES.cpp',
  39. 'platform/win8.1-universal/OpenGLESPage.xaml.cpp',
  40. 'platform/win8.1-universal/OpenGLESPage.xaml.h',
  41. 'platform/win8.1-universal/pch.cpp',
  42. 'platform/winrt/pch.cpp',
  43. 'editor-support/spine/Json.c',
  44. 'editor-support/spine/PathConstraint.h',
  45. 'editor-support/spine/SkeletonJson.c',
  46. 'editor-support/spine/SkeletonBinary.c',
  47. 'editor-support/spine/kvec.h'
  48. ]
  49. self.source_exts = ['.h','.hpp','.inl','.c','.cpp', '.m', '.mm']
  50. self.header_exts = ['.h','.hpp','.inl']
  51. self.root = root
  52. self.fix = fix
  53. self.errors = 0
  54. self.error_files = 0
  55. self._scan_source(root)
  56. self._scan_unique_headers(self.headers)
  57. def _scan_source(self, top):
  58. # find all sources and headers relative to self.root
  59. self.sources = []
  60. self.headers = []
  61. for root, dirnames, filenames in os.walk(top):
  62. for f in filenames:
  63. p = Path.relpath(Path.join(root, f), top)
  64. if self._source_to_lint(p):
  65. self.sources.append(p)
  66. if self._is_header(p):
  67. self.headers.append(p)
  68. def _source_to_lint(self, p):
  69. if p in self.exclude:
  70. return False
  71. ext = Path.extname(p)
  72. return ext in self.source_exts
  73. def _is_header(self, name):
  74. return Path.extname(name) in self.header_exts
  75. # find headers have unique base filenames
  76. # this is used to get included headers in other search paths
  77. def _scan_unique_headers(self, headers):
  78. known = {}
  79. for f in headers:
  80. name = Path.basename(f)
  81. if known.has_key(name):
  82. known[name].append(f)
  83. else:
  84. known[name] = [f]
  85. uniq = {}
  86. for k,v in known.iteritems():
  87. if len(v) == 1:
  88. uniq[k] = v[0]
  89. self.uniq = uniq
  90. def in_search_path(self, filename):
  91. return Path.exists(Path.join(self.root, filename))
  92. def find_uniq(self, basename):
  93. return self.uniq[basename] if self.uniq.has_key(basename) else None
  94. def get_include_path(self, original, directory):
  95. # 1. try search in uniq cocos header names
  96. p = self.find_uniq(Path.basename(original))
  97. if not p:
  98. # 2. try search in current header directory
  99. p = Path.normpath(Path.join(directory, original))
  100. if not self.in_search_path(p):
  101. return None
  102. return p
  103. def fix(match, cwd, ctx, fixed):
  104. h = match.group(2)
  105. # return original if already in search path (cocos directory)
  106. if ctx.in_search_path(h):
  107. return match.group(0)
  108. p = ctx.get_include_path(h, cwd)
  109. if not p:
  110. return match.group(0)
  111. ctx.errors += 1
  112. fix = '#%s "%s"' % (match.group(1), p)
  113. fixed[match.group(0)] = fix
  114. return fix
  115. def lint_one(header, ctx):
  116. cwd = Path.dirname(header)
  117. if not cwd:
  118. return
  119. filename = Path.join(ctx.root, header)
  120. content = open(filename, 'r').read()
  121. fixed = {}
  122. # check all #include "header.*"
  123. linted = re.sub('#\s*(include|import)\s*"(.*)"', lambda m: fix(m, cwd, ctx, fixed), content)
  124. if content != linted:
  125. ctx.error_files += 1
  126. if ctx.fix:
  127. with open (filename, 'w') as f: f.write(linted)
  128. print('%s: %d error(s) fixed' % (header, len(fixed)))
  129. else:
  130. print('%s:' % (header))
  131. for k, v in fixed.iteritems():
  132. print('\t%s should be %s' % (k, v))
  133. def lint(ctx):
  134. print('Checking headers in: %s' % ctx.root)
  135. for f in ctx.sources:
  136. lint_one(f, ctx)
  137. print('Total: %d errors in %d files' % (ctx.errors, ctx.error_files))
  138. if ctx.errors > 0:
  139. if ctx.fix:
  140. print('All fixed')
  141. else:
  142. print('Rerun this script with -f to fixes these errors')
  143. sys.exit(1)
  144. def main():
  145. default_root = Path.abspath(Path.join(Path.dirname(__file__), '..', '..'))
  146. parser = argparse.ArgumentParser(description='The cocos headers lint script.')
  147. parser.add_argument('-f','--fix', action='store_true', help='fixe the headers while linting')
  148. parser.add_argument('root', nargs='?', default= default_root, help='path to cocos2d-x source root directory')
  149. args = parser.parse_args()
  150. lint(LintContext(Path.join(args.root, 'cocos'), args.fix))
  151. main()