Upstream version 9.37.193.0
[platform/framework/web/crosswalk.git] / src / xwalk / tools / lint.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2013 Intel Corporation. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 ''' This script is used to lint changeset before code checkin
8     It get the changeset from the repo and base specified by
9     command line arguments. And run cpplint over the changeset.
10 '''
11 # TODO(wang16): Only show error for the lines do changed in the changeset
12
13 import os
14 import re
15 import sys
16
17 from utils import GitExe, GetCommandOutput, TryAddDepotToolsToPythonPath
18
19 PYTHON_EXTS = ['.py']
20
21 def find_depot_tools_in_path():
22   paths = os.getenv('PATH').split(os.path.pathsep)
23   for path in paths:
24     if os.path.basename(path) == 'depot_tools':
25       return path
26   return None
27
28 def repo_is_dirty():
29   return GetCommandOutput([GitExe(), 'diff', 'HEAD']).strip() != ''
30
31 def get_tracking_remote():
32   branch = [GitExe(), 'branch', '-vv', '-a']
33   # The output of git branch -vv will be in format
34   # * <branch>     <hash> [<remote>: ahead <n>, behind <m>] <subject>
35   #   <branch>     <hash> <subject>
36   output = GetCommandOutput(branch)
37   branches = output.split('\n')
38   for branch in branches:
39     # we only need active branch first.
40     if not branch.startswith('*'):
41       continue
42     detail = \
43         branch[1:].strip().split(' ', 1)[1].strip().split(' ', 1)[1].strip()
44     if detail.startswith('['):
45       remote = detail[1:].split(']', 1)[0]
46       remote = remote.split(':', 1)[0].strip()
47       # verify that remotes/branch or branch is a real branch
48       # There is still chance that developer named his commit
49       # as [origin/branch], in this case
50       exists = [\
51           r_branch for r_branch in branches \
52               if r_branch.strip().startswith('remotes/'+remote) or \
53                  r_branch.strip().startswith(remote)]
54       if len(exists) == 0:
55         remote = ''
56     else:
57       remote = ''
58     break
59   if remote == '':
60     if repo_is_dirty():
61       remote = 'HEAD'
62     else:
63       remote = 'HEAD~'
64   print 'Base is not specified, '\
65         'will use %s as comparasion base for linting' % remote
66   return remote
67
68 # return pyfiles, others
69 def get_change_file_list(base):
70   diff = [GitExe(), 'diff', '--name-only', base]
71   output = GetCommandOutput(diff)
72   changes = [line.strip() for line in output.strip().split('\n')]
73   pyfiles = []
74   others = []
75   # pylint: disable=W0612
76   for change in changes:
77     root, ext = os.path.splitext(change)
78     if ext.lower() in PYTHON_EXTS:
79       pyfiles.append(change)
80     else:
81       others.append(change)
82   return pyfiles, others
83
84 def do_cpp_lint(changeset, repo, args):
85   # Try to import cpplint from depot_tools first
86   try:
87     import cpplint
88   except ImportError:
89     TryAddDepotToolsToPythonPath()
90
91   try:
92     import cpplint
93     import cpplint_chromium
94     import gcl
95   except ImportError:
96     sys.stderr.write("Can't find cpplint, please add your depot_tools "\
97                      "to PATH or PYTHONPATH\n")
98     raise
99
100   origin_error = cpplint.Error
101   def MyError(filename, linenum, category, confidence, message):
102     # Skip no header guard  error for MSVC generated files.
103     if (filename.endswith('resource.h')):
104       sys.stdout.write('Ignored Error:\n  %s(%s):  %s  [%s] [%d]\n' % (
105           filename, linenum, message, category, confidence))
106     # Skip no header guard  error for ipc messages definition,
107     # because they will be included multiple times for different macros.
108     elif (filename.endswith('messages.h') and linenum == 0 and
109         category == 'build/header_guard'):
110       sys.stdout.write('Ignored Error:\n  %s(%s):  %s  [%s] [%d]\n' % (
111           filename, linenum, message, category, confidence))
112     else:
113       origin_error(filename, linenum, category, confidence, message)
114   cpplint.Error = MyError
115
116   origin_FileInfo = cpplint.FileInfo
117   class MyFileInfo(origin_FileInfo):
118     def RepositoryName(self):
119       ''' Origin FileInfo find the first .git and take it as project root,
120           it's not the case for xwalk, header in xwalk should have guard
121           relevant to root dir of chromium project, which is one level
122           upper of the origin output of RepositoryName.
123       '''
124       repo_name = origin_FileInfo.RepositoryName(self)
125       if repo == "xwalk" and not repo_name.startswith('xwalk'):
126         return 'xwalk/%s' % repo_name
127       else:
128         return repo_name
129   cpplint.FileInfo = MyFileInfo
130
131   print '_____ do cpp lint'
132   if len(changeset) == 0:
133     print 'changeset is empty except python files'
134     return
135   # Following code is referencing depot_tools/gcl.py: CMDlint
136   # Process cpplints arguments if any.
137   filenames = cpplint.ParseArguments(args + changeset)
138
139   white_list = gcl.GetCodeReviewSetting("LINT_REGEX")
140   if not white_list:
141     white_list = gcl.DEFAULT_LINT_REGEX
142   white_regex = re.compile(white_list)
143   black_list = gcl.GetCodeReviewSetting("LINT_IGNORE_REGEX")
144   if not black_list:
145     black_list = gcl.DEFAULT_LINT_IGNORE_REGEX
146   black_regex = re.compile(black_list)
147   extra_check_functions = [cpplint_chromium.CheckPointerDeclarationWhitespace]
148   # pylint: disable=W0212
149   cpplint_state = cpplint._cpplint_state
150   for filename in filenames:
151     if white_regex.match(filename):
152       if black_regex.match(filename):
153         print "Ignoring file %s" % filename
154       else:
155         cpplint.ProcessFile(filename, cpplint_state.verbose_level,
156                             extra_check_functions)
157     else:
158       print "Skipping file %s" % filename
159   print "Total errors found: %d\n" % cpplint_state.error_count
160
161 def do_py_lint(changeset):
162   print '_____ do python lint'
163   if sys.platform.startswith('win'):
164     pylint_cmd = ['pylint.bat']
165   else:
166     pylint_cmd = ['pylint']
167   _has_import_error = False
168   for pyfile in changeset:
169     py_dir, py_name = os.path.split(os.path.abspath(pyfile))
170     previous_cwd = os.getcwd()
171     os.chdir(py_dir)
172     print 'pylint %s' % pyfile
173     try:
174       output = GetCommandOutput(pylint_cmd + [py_name]).strip()
175       if len(output) > 0:
176         print output
177     except Exception, e:
178       if not _has_import_error and \
179           'F0401:' in [error[:6] for error in str(e).splitlines()]:
180         _has_import_error = True
181       print e
182     os.chdir(previous_cwd)
183   if _has_import_error:
184     print 'You have error for python importing, please check your PYTHONPATH'
185
186 def do_lint(repo, base, args):
187   # dir structure should be src/xwalk for xwalk
188   #                         src/third_party/WebKit for blink
189   #                         src/ for chromium
190   # lint.py should be located in src/xwalk/tools/lint.py
191   _lint_py = os.path.abspath(__file__)
192   _dirs = _lint_py.split(os.path.sep)
193   src_root = os.path.sep.join(_dirs[:len(_dirs)-3])
194   if repo == 'xwalk':
195     base_repo = os.path.join(src_root, 'xwalk')
196   elif repo == 'chromium':
197     base_repo = src_root
198   elif repo == 'blink':
199     base_repo = os.path.join(src_root, 'third_party', 'WebKit')
200   else:
201     raise NotImplementedError('repo must in xwalk, blink and chromium')
202   previous_cwd = os.getcwd()
203   os.chdir(base_repo)
204   if base == None:
205     base = get_tracking_remote()
206   changes_py, changes_others = get_change_file_list(base)
207   do_cpp_lint(changes_others, repo, args)
208   do_py_lint(changes_py)
209   os.chdir(previous_cwd)
210   return 1
211
212 from optparse import OptionParser, BadOptionError
213 class PassThroughOptionParser(OptionParser):
214   def _process_long_opt(self, rargs, values):
215     try:
216       OptionParser._process_long_opt(self, rargs, values)
217     except BadOptionError, err:
218       self.largs.append(err.opt_str)
219
220   def _process_short_opts(self, rargs, values):
221     try:
222       OptionParser._process_short_opts(self, rargs, values)
223     except BadOptionError, err:
224       self.largs.append(err.opt_str)
225
226 def main():
227   option_parser = PassThroughOptionParser()
228
229   option_parser.add_option('--repo', default='xwalk',
230       help='The repo to do lint, should be in [xwalk, blink, chromium]\
231             xwalk by default')
232   option_parser.add_option('--base', default=None,
233       help='The base point to get change set. If not specified,' +
234            ' it will choose:\r\n' +
235            '  1. Active branch\'s tracking branch if exist\n' +
236            '  2. HEAD if current repo is dirty\n' +
237            '  3. HEAD~ elsewise')
238
239   options, args = option_parser.parse_args()
240
241   sys.exit(do_lint(options.repo, options.base, args))
242
243 if __name__ == '__main__':
244   main()