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.
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.
11 # TODO(wang16): Only show error for the lines do changed in the changeset
17 from utils import GitExe, GetCommandOutput, TryAddDepotToolsToPythonPath
21 def find_depot_tools_in_path():
22 paths = os.getenv('PATH').split(os.path.pathsep)
24 if os.path.basename(path) == 'depot_tools':
29 return GetCommandOutput([GitExe(), 'diff', 'HEAD']).strip() != ''
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('*'):
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
51 r_branch for r_branch in branches \
52 if r_branch.strip().startswith('remotes/'+remote) or \
53 r_branch.strip().startswith(remote)]
64 print 'Base is not specified, '\
65 'will use %s as comparasion base for linting' % remote
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')]
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)
82 return pyfiles, others
84 def do_cpp_lint(changeset, repo, args):
85 # Try to import cpplint from depot_tools first
89 TryAddDepotToolsToPythonPath()
93 import cpplint_chromium
96 sys.stderr.write("Can't find cpplint, please add your depot_tools "\
97 "to PATH or PYTHONPATH\n")
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))
113 origin_error(filename, linenum, category, confidence, message)
114 cpplint.Error = MyError
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.
124 repo_name = origin_FileInfo.RepositoryName(self)
125 if repo == "xwalk" and not repo_name.startswith('xwalk'):
126 return 'xwalk/%s' % repo_name
129 cpplint.FileInfo = MyFileInfo
131 print '_____ do cpp lint'
132 if len(changeset) == 0:
133 print 'changeset is empty except python files'
135 # Following code is referencing depot_tools/gcl.py: CMDlint
136 # Process cpplints arguments if any.
137 filenames = cpplint.ParseArguments(args + changeset)
139 white_list = gcl.GetCodeReviewSetting("LINT_REGEX")
141 white_list = gcl.DEFAULT_LINT_REGEX
142 white_regex = re.compile(white_list)
143 black_list = gcl.GetCodeReviewSetting("LINT_IGNORE_REGEX")
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
155 cpplint.ProcessFile(filename, cpplint_state.verbose_level,
156 extra_check_functions)
158 print "Skipping file %s" % filename
159 print "Total errors found: %d\n" % cpplint_state.error_count
161 def do_py_lint(changeset):
162 print '_____ do python lint'
163 if sys.platform.startswith('win'):
164 pylint_cmd = ['pylint.bat']
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()
172 print 'pylint %s' % pyfile
174 output = GetCommandOutput(pylint_cmd + [py_name]).strip()
178 if not _has_import_error and \
179 'F0401:' in [error[:6] for error in str(e).splitlines()]:
180 _has_import_error = True
182 os.chdir(previous_cwd)
183 if _has_import_error:
184 print 'You have error for python importing, please check your PYTHONPATH'
186 def do_lint(repo, base, args):
187 # dir structure should be src/xwalk for xwalk
188 # src/third_party/WebKit for blink
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])
195 base_repo = os.path.join(src_root, 'xwalk')
196 elif repo == 'chromium':
198 elif repo == 'blink':
199 base_repo = os.path.join(src_root, 'third_party', 'WebKit')
201 raise NotImplementedError('repo must in xwalk, blink and chromium')
202 previous_cwd = os.getcwd()
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)
212 from optparse import OptionParser, BadOptionError
213 class PassThroughOptionParser(OptionParser):
214 def _process_long_opt(self, rargs, values):
216 OptionParser._process_long_opt(self, rargs, values)
217 except BadOptionError, err:
218 self.largs.append(err.opt_str)
220 def _process_short_opts(self, rargs, values):
222 OptionParser._process_short_opts(self, rargs, values)
223 except BadOptionError, err:
224 self.largs.append(err.opt_str)
227 option_parser = PassThroughOptionParser()
229 option_parser.add_option('--repo', default='xwalk',
230 help='The repo to do lint, should be in [xwalk, blink, chromium]\
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')
239 options, args = option_parser.parse_args()
241 sys.exit(do_lint(options.repo, options.base, args))
243 if __name__ == '__main__':