blob: 11cdeb1570a60cb68aedde54eb7405d9a8275333 [file] [log] [blame]
maruel@chromium.orgba551772010-02-03 18:21:42 +00001# Copyright (c) 2010 The Chromium Authors. All rights reserved.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Generic presubmit checks that can be reused by other presubmit checks."""
6
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00007
maruel@chromium.org3410d912009-06-09 20:56:16 +00008### Description checks
9
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000010def CheckChangeHasTestField(input_api, output_api):
11 """Requires that the changelist have a TEST= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000012 if input_api.change.TEST:
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000013 return []
14 else:
15 return [output_api.PresubmitNotifyResult(
jam@chromium.org5c76de92011-01-24 18:19:21 +000016 'If this change requires manual test instructions to QA team, add '
17 'TEST=[instructions].')]
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000018
19
20def CheckChangeHasBugField(input_api, output_api):
21 """Requires that the changelist have a BUG= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000022 if input_api.change.BUG:
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000023 return []
24 else:
25 return [output_api.PresubmitNotifyResult(
jam@chromium.org5c76de92011-01-24 18:19:21 +000026 'If this change has an associated bug, add BUG=[bug number].')]
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000027
28
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000029def CheckChangeHasTestedField(input_api, output_api):
30 """Requires that the changelist have a TESTED= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000031 if input_api.change.TESTED:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000032 return []
33 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000034 return [output_api.PresubmitError('Changelist must have a TESTED= field.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000035
36
37def CheckChangeHasQaField(input_api, output_api):
38 """Requires that the changelist have a QA= field."""
39 if input_api.change.QA:
40 return []
41 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000042 return [output_api.PresubmitError('Changelist must have a QA= field.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000043
44
45def CheckDoNotSubmitInDescription(input_api, output_api):
46 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to the CL description.
47 """
48 keyword = 'DO NOT ' + 'SUBMIT'
49 if keyword in input_api.change.DescriptionText():
50 return [output_api.PresubmitError(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000051 keyword + ' is present in the changelist description.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000052 else:
53 return []
54
55
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000056def CheckChangeHasDescription(input_api, output_api):
57 """Checks the CL description is not empty."""
58 text = input_api.change.DescriptionText()
59 if text.strip() == '':
60 if input_api.is_committing:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000061 return [output_api.PresubmitError('Add a description.')]
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000062 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000063 return [output_api.PresubmitNotifyResult('Add a description.')]
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000064 return []
65
maruel@chromium.org3410d912009-06-09 20:56:16 +000066### Content checks
67
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000068def CheckDoNotSubmitInFiles(input_api, output_api):
69 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to any files."""
nick@chromium.orge06cb4e2011-04-23 01:20:38 +000070 # We want to check every text file, not just source files.
71 file_filter = lambda x : x
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000072 keyword = 'DO NOT ' + 'SUBMIT'
nick@chromium.orge06cb4e2011-04-23 01:20:38 +000073 errors = _FindNewViolationsOfRule(lambda line : keyword not in line,
74 input_api, file_filter)
75 text = '\n'.join('Found %s in %s' % (keyword, loc) for loc in errors)
76 if text:
77 return [output_api.PresubmitError(text)]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078 return []
79
80
erg@google.com26970fa2009-11-17 18:07:32 +000081def CheckChangeLintsClean(input_api, output_api, source_file_filter=None):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000082 """Checks that all '.cc' and '.h' files pass cpplint.py."""
erg@google.com26970fa2009-11-17 18:07:32 +000083 _RE_IS_TEST = input_api.re.compile(r'.*tests?.(cc|h)$')
84 result = []
85
86 # Initialize cpplint.
87 import cpplint
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +000088 # Access to a protected member _XX of a client class
89 # pylint: disable=W0212
erg@google.com26970fa2009-11-17 18:07:32 +000090 cpplint._cpplint_state.ResetErrorCounts()
91
92 # Justifications for each filter:
93 #
94 # - build/include : Too many; fix in the future.
95 # - build/include_order : Not happening; #ifdefed includes.
96 # - build/namespace : I'm surprised by how often we violate this rule.
97 # - readability/casting : Mistakes a whole bunch of function pointer.
98 # - runtime/int : Can be fixed long term; volume of errors too high
99 # - runtime/virtual : Broken now, but can be fixed in the future?
100 # - whitespace/braces : We have a lot of explicit scoping in chrome code.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000101 cpplint._SetFilters('-build/include,-build/include_order,-build/namespace,'
102 '-readability/casting,-runtime/int,-runtime/virtual,'
103 '-whitespace/braces')
erg@google.com26970fa2009-11-17 18:07:32 +0000104
105 # We currently are more strict with normal code than unit tests; 4 and 5 are
106 # the verbosity level that would normally be passed to cpplint.py through
107 # --verbose=#. Hopefully, in the future, we can be more verbose.
108 files = [f.AbsoluteLocalPath() for f in
109 input_api.AffectedSourceFiles(source_file_filter)]
110 for file_name in files:
111 if _RE_IS_TEST.match(file_name):
112 level = 5
113 else:
114 level = 4
115
116 cpplint.ProcessFile(file_name, level)
117
118 if cpplint._cpplint_state.error_count > 0:
119 if input_api.is_committing:
120 res_type = output_api.PresubmitError
121 else:
122 res_type = output_api.PresubmitPromptWarning
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000123 result = [res_type('Changelist failed cpplint.py check.')]
erg@google.com26970fa2009-11-17 18:07:32 +0000124
125 return result
126
127
maruel@chromium.org3410d912009-06-09 20:56:16 +0000128def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000129 """Checks no '\r' (CR) character is in any source files."""
130 cr_files = []
maruel@chromium.org3410d912009-06-09 20:56:16 +0000131 for f in input_api.AffectedSourceFiles(source_file_filter):
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000132 if '\r' in input_api.ReadFile(f, 'rb'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000133 cr_files.append(f.LocalPath())
134 if cr_files:
135 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000136 'Found a CR character in these files:', items=cr_files)]
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000137 return []
138
139
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000140def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None):
141 """Checks for files in svn modified directories.
142
143 They will get submitted on accident because svn commits recursively by
144 default, and that's very dangerous.
145 """
146 if input_api.change.scm != 'svn':
147 return []
148
149 errors = []
150 current_cl_files = input_api.change.GetModifiedFiles()
151 all_modified_files = input_api.change.GetAllModifiedFiles()
152 # Filter out files in the current CL.
153 modified_files = [f for f in all_modified_files if f not in current_cl_files]
154 modified_abspaths = [input_api.os_path.abspath(f) for f in modified_files]
155
sail@chromium.org5538e022011-05-12 17:53:16 +0000156 for f in input_api.AffectedFiles(file_filter=source_file_filter):
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000157 if f.Action() == 'M' and f.IsDirectory():
158 curpath = f.AbsoluteLocalPath()
159 bad_files = []
160 # Check if any of the modified files in other CLs are under curpath.
161 for i in xrange(len(modified_files)):
162 abspath = modified_abspaths[i]
163 if input_api.os_path.commonprefix([curpath, abspath]) == curpath:
164 bad_files.append(modified_files[i])
165 if bad_files:
166 if input_api.is_committing:
167 error_type = output_api.PresubmitPromptWarning
168 else:
169 error_type = output_api.PresubmitNotifyResult
170 errors.append(error_type(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000171 'Potential accidental commits in changelist %s:' % f.LocalPath(),
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000172 items=bad_files))
173 return errors
174
175
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000176def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
177 """Checks the files ends with one and only one \n (LF)."""
178 eof_files = []
179 for f in input_api.AffectedSourceFiles(source_file_filter):
180 contents = input_api.ReadFile(f, 'rb')
181 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000182 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000183 eof_files.append(f.LocalPath())
184
185 if eof_files:
186 return [output_api.PresubmitPromptWarning(
187 'These files should end in one (and only one) newline character:',
188 items=eof_files)]
189 return []
190
191
192def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api,
193 source_file_filter=None):
194 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass.
195
196 It is faster because it is reading the file only once.
197 """
198 cr_files = []
199 eof_files = []
200 for f in input_api.AffectedSourceFiles(source_file_filter):
201 contents = input_api.ReadFile(f, 'rb')
202 if '\r' in contents:
203 cr_files.append(f.LocalPath())
204 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000205 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000206 eof_files.append(f.LocalPath())
207 outputs = []
208 if cr_files:
209 outputs.append(output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000210 'Found a CR character in these files:', items=cr_files))
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000211 if eof_files:
212 outputs.append(output_api.PresubmitPromptWarning(
213 'These files should end in one (and only one) newline character:',
214 items=eof_files))
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000215 return outputs
216
217
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000218def _ReportErrorFileAndLine(filename, line_num, line):
219 """Default error formatter for _FindNewViolationsOfRule."""
220 return '%s, line %s' % (filename, line_num)
221
222
223def _FindNewViolationsOfRule(callable_rule, input_api, source_file_filter=None,
224 error_formatter=_ReportErrorFileAndLine):
225 """Find all newly introduced violations of a per-line rule (a callable).
226
227 Arguments:
228 callable_rule: a callable taking a line of input and returning True
229 if the rule is satisfied and False if there was a problem.
230 input_api: object to enumerate the affected files.
231 source_file_filter: a filter to be passed to the input api.
232 error_formatter: a callable taking (filename, line_number, line) and
233 returning a formatted error string.
234
235 Returns:
236 A list of the newly-introduced violations reported by the rule.
237 """
238 errors = []
sail@chromium.org5538e022011-05-12 17:53:16 +0000239 for f in input_api.AffectedFiles(include_deletes=False,
240 file_filter=source_file_filter):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000241 # For speed, we do two passes, checking first the full file. Shelling out
242 # to the SCM to determine the changed region can be quite expensive on
243 # Win32. Assuming that most files will be kept problem-free, we can
244 # skip the SCM operations most of the time.
245 if all(callable_rule(line) for line in f.NewContents()):
246 continue # No violation found in full text: can skip considering diff.
247
248 for line_num, line in f.ChangedContents():
249 if not callable_rule(line):
250 errors.append(error_formatter(f.LocalPath(), line_num, line))
251
252 return errors
253
254
maruel@chromium.org3410d912009-06-09 20:56:16 +0000255def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000256 """Checks that there are no tab characters in any of the text files to be
257 submitted.
258 """
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000259 # In addition to the filter, make sure that makefiles are blacklisted.
260 if not source_file_filter:
261 # It's the default filter.
262 source_file_filter = input_api.FilterSourceFile
263 def filter_more(affected_file):
264 return (not input_api.os_path.basename(affected_file.LocalPath()) in
265 ('Makefile', 'makefile') and
266 source_file_filter(affected_file))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000267
268 tabs = _FindNewViolationsOfRule(lambda line : '\t' not in line,
269 input_api, filter_more)
270
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000271 if tabs:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000272 return [output_api.PresubmitPromptWarning('Found a tab character in:',
273 long_text='\n'.join(tabs))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000274 return []
275
276
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000277def CheckChangeTodoHasOwner(input_api, output_api, source_file_filter=None):
278 """Checks that the user didn't add TODO(name) without an owner."""
279
maruel@chromium.org07ab60e2011-02-08 21:54:00 +0000280 unowned_todo = input_api.re.compile('TO' + 'DO[^(]')
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000281 errors = _FindNewViolationsOfRule(lambda x : not unowned_todo.search(x),
282 input_api, source_file_filter)
283 errors = ['Found TO' + 'DO with no owner in ' + x for x in errors]
284 if errors:
285 return [output_api.PresubmitPromptWarning('\n'.join(errors))]
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000286 return []
287
288
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000289def CheckChangeHasNoStrayWhitespace(input_api, output_api,
290 source_file_filter=None):
291 """Checks that there is no stray whitespace at source lines end."""
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000292 errors = _FindNewViolationsOfRule(lambda line : line.rstrip() == line,
293 input_api, source_file_filter)
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000294 if errors:
295 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000296 'Found line ending with white spaces in:',
297 long_text='\n'.join(errors))]
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000298 return []
299
300
maruel@chromium.org3410d912009-06-09 20:56:16 +0000301def CheckLongLines(input_api, output_api, maxlen=80, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000302 """Checks that there aren't any lines longer than maxlen characters in any of
303 the text files to be submitted.
304 """
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000305 def no_long_lines(line):
maruel@chromium.org5c2720e2009-06-09 14:04:08 +0000306 # Allow lines with http://, https:// and #define/#pragma/#include/#if/#endif
307 # to exceed the maxlen rule.
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000308 return (len(line) <= maxlen or
309 any((url in line) for url in ('http://', 'https://')) or
310 line.startswith(('#define', '#include', '#import', '#pragma',
311 '#if', '#endif')))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000312
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000313 def format_error(filename, line_num, line):
314 return '%s, line %s, %s chars' % (filename, line_num, len(line))
315
316 errors = _FindNewViolationsOfRule(no_long_lines, input_api,
317 source_file_filter,
318 error_formatter=format_error)
319 if errors:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000320 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000321 return [output_api.PresubmitPromptWarning(msg, items=errors[:5])]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000322 else:
323 return []
324
325
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000326def CheckLicense(input_api, output_api, license_re, source_file_filter=None,
maruel@chromium.org71626852010-11-03 13:14:25 +0000327 accept_empty_files=True):
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000328 """Verifies the license header.
329 """
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000330 license_re = input_api.re.compile(license_re, input_api.re.MULTILINE)
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000331 bad_files = []
332 for f in input_api.AffectedSourceFiles(source_file_filter):
333 contents = input_api.ReadFile(f, 'rb')
maruel@chromium.org71626852010-11-03 13:14:25 +0000334 if accept_empty_files and not contents:
335 continue
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000336 if not license_re.search(contents):
337 bad_files.append(f.LocalPath())
338 if bad_files:
339 if input_api.is_committing:
340 res_type = output_api.PresubmitPromptWarning
341 else:
342 res_type = output_api.PresubmitNotifyResult
343 return [res_type(
bradnelson@google.com393460b2011-03-29 01:20:26 +0000344 'License must match:\n%s\n' % license_re.pattern +
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000345 'Found a bad license header in these files:', items=bad_files)]
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000346 return []
347
348
maruel@chromium.org1a0e3cb2009-06-10 18:03:04 +0000349def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None):
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000350 """Checks that the source files have svn:eol-style=LF."""
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000351 return CheckSvnProperty(input_api, output_api,
352 'svn:eol-style', 'LF',
353 input_api.AffectedSourceFiles(source_file_filter))
354
355
356def CheckSvnForCommonMimeTypes(input_api, output_api):
357 """Checks that common binary file types have the correct svn:mime-type."""
358 output = []
359 files = input_api.AffectedFiles(include_deletes=False)
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000360 def IsExts(x, exts):
361 path = x.LocalPath()
362 for extension in exts:
363 if path.endswith(extension):
364 return True
365 return False
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000366 def FilterFiles(extension):
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000367 return filter(lambda x: IsExts(x, extension), files)
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000368 def RunCheck(mime_type, files):
369 output.extend(CheckSvnProperty(input_api, output_api, 'svn:mime-type',
370 mime_type, files))
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000371 RunCheck('application/pdf', FilterFiles(['.pdf']))
372 RunCheck('image/bmp', FilterFiles(['.bmp']))
373 RunCheck('image/gif', FilterFiles(['.gif']))
374 RunCheck('image/png', FilterFiles(['.png']))
375 RunCheck('image/jpeg', FilterFiles(['.jpg', '.jpeg', '.jpe']))
376 RunCheck('image/vnd.microsoft.icon', FilterFiles(['.ico']))
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000377 return output
378
379
380def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
381 """Checks that affected_files files have prop=expected."""
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000382 if input_api.change.scm != 'svn':
383 return []
384
385 bad = filter(lambda f: f.Property(prop) != expected, affected_files)
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000386 if bad:
maruel@chromium.org0874d472009-06-10 19:08:33 +0000387 if input_api.is_committing:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000388 res_type = output_api.PresubmitError
maruel@chromium.org0874d472009-06-10 19:08:33 +0000389 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000390 res_type = output_api.PresubmitNotifyResult
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000391 message = 'Run the command: svn pset %s %s \\' % (prop, expected)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000392 return [res_type(message, items=bad)]
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000393 return []
394
395
maruel@chromium.org3410d912009-06-09 20:56:16 +0000396### Other checks
397
398def CheckDoNotSubmit(input_api, output_api):
399 return (
400 CheckDoNotSubmitInDescription(input_api, output_api) +
401 CheckDoNotSubmitInFiles(input_api, output_api)
402 )
403
404
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000405def CheckTreeIsOpen(input_api, output_api,
406 url=None, closed=None, json_url=None):
407 """Check whether to allow commit without prompt.
408
409 Supports two styles:
410 1. Checks that an url's content doesn't match a regexp that would mean that
411 the tree is closed. (old)
412 2. Check the json_url to decide whether to allow commit without prompt.
413 Args:
414 input_api: input related apis.
415 output_api: output related apis.
416 url: url to use for regex based tree status.
417 closed: regex to match for closed status.
418 json_url: url to download json style status.
419 """
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000420 if not input_api.is_committing:
421 return []
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000422 try:
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000423 if json_url:
424 connection = input_api.urllib2.urlopen(json_url)
425 status = input_api.json.loads(connection.read())
426 connection.close()
427 if not status['can_commit_freely']:
428 short_text = 'Tree state is: ' + status['general_state']
429 long_text = status['message'] + '\n' + json_url
430 return [output_api.PresubmitError(short_text, long_text=long_text)]
431 else:
432 # TODO(bradnelson): drop this once all users are gone.
433 connection = input_api.urllib2.urlopen(url)
434 status = connection.read()
435 connection.close()
436 if input_api.re.match(closed, status):
437 long_text = status + '\n' + url
438 return [output_api.PresubmitError('The tree is closed.',
439 long_text=long_text)]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000440 except IOError:
441 pass
442 return []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000443
444
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000445def RunUnitTestsInDirectory(
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000446 input_api, output_api, directory, whitelist=None, blacklist=None):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000447 """Lists all files in a directory and runs them. Doesn't recurse.
448
449 It's mainly a wrapper for RunUnitTests. USe whitelist and blacklist to filter
450 tests accordingly.
451 """
452 unit_tests = []
453 test_path = input_api.os_path.abspath(
454 input_api.os_path.join(input_api.PresubmitLocalPath(), directory))
455
456 def check(filename, filters):
457 return any(True for i in filters if input_api.re.match(i, filename))
458
459 for filename in input_api.os_listdir(test_path):
460 fullpath = input_api.os_path.join(test_path, filename)
461 if not input_api.os_path.isfile(fullpath):
462 continue
463 if whitelist and not check(filename, whitelist):
464 continue
465 if blacklist and check(filename, blacklist):
466 continue
467 unit_tests.append(input_api.os_path.join(directory, filename))
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000468 return RunUnitTests(input_api, output_api, unit_tests)
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000469
470
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000471def RunUnitTests(input_api, output_api, unit_tests):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000472 """Runs all unit tests in a directory.
473
474 On Windows, sys.executable is used for unit tests ending with ".py".
475 """
476 # We don't want to hinder users from uploading incomplete patches.
477 if input_api.is_committing:
478 message_type = output_api.PresubmitError
479 else:
480 message_type = output_api.PresubmitPromptWarning
481
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000482 results = []
483 for unit_test in unit_tests:
484 cmd = []
485 if input_api.platform == 'win32' and unit_test.endswith('.py'):
486 # Windows needs some help.
487 cmd = [input_api.python_executable]
488 cmd.append(unit_test)
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000489 if input_api.verbose:
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000490 print('Running %s' % unit_test)
maruel@chromium.org6c7723e2011-04-12 19:04:55 +0000491 cmd.append('--verbose')
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000492 try:
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000493 if input_api.verbose:
maruel@chromium.org0e766052011-04-06 13:32:51 +0000494 input_api.subprocess.check_call(cmd, cwd=input_api.PresubmitLocalPath())
495 else:
496 input_api.subprocess.check_output(
497 cmd, cwd=input_api.PresubmitLocalPath())
498 except (OSError, input_api.subprocess.CalledProcessError), e:
499 results.append(message_type('%s failed!\n%s' % (unit_test, e)))
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000500 return results
501
502
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000503def RunPythonUnitTests(input_api, output_api, unit_tests):
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000504 """Run the unit tests out of process, capture the output and use the result
505 code to determine success.
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000506
507 DEPRECATED.
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000508 """
maruel@chromium.orgd7dccf52009-06-06 18:51:58 +0000509 # We don't want to hinder users from uploading incomplete patches.
510 if input_api.is_committing:
511 message_type = output_api.PresubmitError
512 else:
513 message_type = output_api.PresubmitNotifyResult
maruel@chromium.org0e766052011-04-06 13:32:51 +0000514 results = []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000515 for unit_test in unit_tests:
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000516 # Run the unit tests out of process. This is because some unit tests
517 # stub out base libraries and don't clean up their mess. It's too easy to
518 # get subtle bugs.
519 cwd = None
520 env = None
521 unit_test_name = unit_test
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000522 # 'python -m test.unit_test' doesn't work. We need to change to the right
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000523 # directory instead.
524 if '.' in unit_test:
525 # Tests imported in submodules (subdirectories) assume that the current
526 # directory is in the PYTHONPATH. Manually fix that.
527 unit_test = unit_test.replace('.', '/')
528 cwd = input_api.os_path.dirname(unit_test)
529 unit_test = input_api.os_path.basename(unit_test)
530 env = input_api.environ.copy()
kbr@google.comab318592009-09-04 00:54:55 +0000531 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
532 backpath = [
533 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
534 ]
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000535 if env.get('PYTHONPATH'):
536 backpath.append(env.get('PYTHONPATH'))
ukai@chromium.orga301f1f2009-08-05 10:37:33 +0000537 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000538 cmd = [input_api.python_executable, '-m', '%s' % unit_test]
539 try:
540 input_api.subprocess.check_output(cmd, cwd=cwd, env=env)
541 except (OSError, input_api.subprocess.CalledProcessError), e:
542 results.append(message_type('%s failed!\n%s' % (unit_test_name, e)))
543 return results
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000544
545
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000546def _FetchAllFiles(input_api, white_list, black_list):
547 """Hack to fetch all files."""
548 # We cannot use AffectedFiles here because we want to test every python
549 # file on each single python change. It's because a change in a python file
550 # can break another unmodified file.
551 # Use code similar to InputApi.FilterSourceFile()
552 def Find(filepath, filters):
553 for item in filters:
554 if input_api.re.match(item, filepath):
555 return True
556 return False
557
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000558 files = []
559 path_len = len(input_api.PresubmitLocalPath())
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000560 for dirpath, dirnames, filenames in input_api.os_walk(
561 input_api.PresubmitLocalPath()):
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000562 # Passes dirnames in black list to speed up search.
563 for item in dirnames[:]:
564 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
565 if Find(filepath, black_list):
566 dirnames.remove(item)
567 for item in filenames:
568 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
569 if Find(filepath, white_list) and not Find(filepath, black_list):
570 files.append(filepath)
571 return files
572
573
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000574def RunPylint(input_api, output_api, white_list=None, black_list=None):
575 """Run pylint on python files.
576
577 The default white_list enforces looking only a *.py files.
578 """
579 white_list = white_list or ['.*\.py$']
580 black_list = black_list or input_api.DEFAULT_BLACK_LIST
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000581 if input_api.is_committing:
582 error_type = output_api.PresubmitError
583 else:
584 error_type = output_api.PresubmitPromptWarning
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000585
586 # Only trigger if there is at least one python file affected.
587 src_filter = lambda x: input_api.FilterSourceFile(x, white_list, black_list)
588 if not input_api.AffectedSourceFiles(src_filter):
589 return []
590
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000591 # On certain pylint/python version combination, running pylint throws a lot of
592 # warning messages.
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000593 import warnings
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000594 warnings.filterwarnings('ignore', category=DeprecationWarning)
595 try:
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000596 files = _FetchAllFiles(input_api, white_list, black_list)
597 if not files:
598 return []
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000599 # Now that at least one python file was modified and all the python files
600 # were listed, try to run pylint.
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000601 try:
602 from pylint import lint
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000603 except ImportError:
604 if input_api.platform == 'win32':
605 return [output_api.PresubmitNotifyResult(
606 'Warning: Can\'t run pylint because it is not installed. Please '
607 'install manually\n'
608 'Cannot do static analysis of python files.')]
609 return [output_api.PresubmitError(
610 'Please install pylint with "sudo apt-get install python-setuptools; '
611 'sudo easy_install pylint"\n'
maruel@chromium.org725f1c32011-04-01 20:24:54 +0000612 'or visit http://pypi.python.org/pypi/setuptools.\n'
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000613 'Cannot do static analysis of python files.')]
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000614
615 def run_lint(files):
616 try:
617 lint.Run(files)
618 assert False
619 except SystemExit, e:
620 # pylint has the bad habit of calling sys.exit(), trap it here.
621 return e.code
622
623 result = None
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000624 if not input_api.verbose:
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000625 result = run_lint(sorted(files))
626 else:
627 for filename in sorted(files):
628 print('Running pylint on %s' % filename)
629 out = run_lint([filename])
630 if out:
631 result = out
maruel@chromium.orgfca53392010-12-21 18:42:57 +0000632 if result:
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000633 return [error_type('Fix pylint errors first.')]
maruel@chromium.orgfca53392010-12-21 18:42:57 +0000634 return []
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000635 finally:
636 warnings.filterwarnings('default', category=DeprecationWarning)
637
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000638
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000639# TODO(dpranke): Get the host_url from the input_api instead
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000640def CheckRietveldTryJobExecution(input_api, output_api, host_url, platforms,
641 owner):
642 if not input_api.is_committing:
643 return []
644 if not input_api.change.issue or not input_api.change.patchset:
645 return []
646 url = '%s/%d/get_build_results/%d' % (
647 host_url, input_api.change.issue, input_api.change.patchset)
648 try:
649 connection = input_api.urllib2.urlopen(url)
650 # platform|status|url
651 values = [item.split('|', 2) for item in connection.read().splitlines()]
652 connection.close()
653 except input_api.urllib2.HTTPError, e:
654 if e.code == 404:
655 # Fallback to no try job.
656 return [output_api.PresubmitPromptWarning(
657 'You should try the patch first.')]
658 else:
659 # Another HTTP error happened, warn the user.
660 return [output_api.PresubmitPromptWarning(
661 'Got %s while looking for try job status.' % str(e))]
662
663 if not values:
664 # It returned an empty list. Probably a private review.
665 return []
666 # Reformat as an dict of platform: [status, url]
667 values = dict([[v[0], [v[1], v[2]]] for v in values if len(v) == 3])
668 if not values:
669 # It returned useless data.
670 return [output_api.PresubmitNotifyResult('Failed to parse try job results')]
671
672 for platform in platforms:
673 values.setdefault(platform, ['not started', ''])
674 message = None
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000675 non_success = [k.upper() for k, v in values.iteritems() if v[0] != 'success']
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000676 if 'failure' in [v[0] for v in values.itervalues()]:
677 message = 'Try job failures on %s!\n' % ', '.join(non_success)
678 elif non_success:
679 message = ('Unfinished (or not even started) try jobs on '
680 '%s.\n') % ', '.join(non_success)
681 if message:
682 message += (
683 'Is try server wrong or broken? Please notify %s. '
684 'Thanks.\n' % owner)
685 return [output_api.PresubmitPromptWarning(message=message)]
686 return []
687
688
689def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
690 ignored):
691 if not input_api.json:
692 return [output_api.PresubmitPromptWarning(
693 'Please install simplejson or upgrade to python 2.6+')]
694 try:
695 connection = input_api.urllib2.urlopen(url)
696 raw_data = connection.read()
697 connection.close()
698 except IOError:
699 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)]
700
701 try:
702 data = input_api.json.loads(raw_data)
703 except ValueError:
704 return [output_api.PresubmitNotifyResult('Received malformed json while '
705 'looking up buildbot status')]
706
707 out = []
708 for (builder_name, builder) in data.iteritems():
709 if builder_name in ignored:
710 continue
maruel@chromium.orgcf1982c2010-10-04 15:08:28 +0000711 if builder.get('state', '') == 'offline':
712 continue
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000713 pending_builds_len = len(builder.get('pending_builds', []))
714 if pending_builds_len > max_pendings:
715 out.append('%s has %d build(s) pending' %
716 (builder_name, pending_builds_len))
717 if out:
718 return [output_api.PresubmitPromptWarning(
719 'Build(s) pending. It is suggested to wait that no more than %d '
720 'builds are pending.' % max_pendings,
721 long_text='\n'.join(out))]
722 return []
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000723
724
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000725def CheckOwners(input_api, output_api, source_file_filter=None):
726 if not input_api.is_committing:
727 return []
728 if input_api.tbr:
dpranke@chromium.org0a2bb372011-03-25 01:16:22 +0000729 return [output_api.PresubmitNotifyResult(
730 '--tbr was specified, skipping OWNERS check')]
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000731 if not input_api.change.issue:
dpranke@chromium.org0a2bb372011-03-25 01:16:22 +0000732 return [output_api.PresubmitError(
733 "OWNERS check failed: this change has no Rietveld issue number, so "
734 "we can't check it for approvals.")]
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000735
dpranke@chromium.orgadd5df42011-03-08 23:04:01 +0000736 affected_files = set([f.LocalPath() for f in
sail@chromium.org5538e022011-05-12 17:53:16 +0000737 input_api.change.AffectedFiles(file_filter=source_file_filter)])
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000738
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000739 owners_db = input_api.owners_db
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000740 owner_email, approvers = _RietveldOwnerAndApprovers(input_api,
741 owners_db.email_regexp)
742 approvers_plus_owner = approvers.union(set([owner_email]))
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000743
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000744 missing_files = owners_db.files_not_covered_by(affected_files,
745 approvers_plus_owner)
746 if missing_files:
747 return [output_api.PresubmitError('Missing LGTM from an OWNER for: %s' %
748 ','.join(missing_files))]
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000749
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000750 if not approvers:
751 return [output_api.PresubmitError('Missing LGTM from someone other than %s'
752 % owner_email)]
753 return []
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000754
755
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000756def _RietveldOwnerAndApprovers(input_api, email_regexp):
757 """Return the owner and approvers of a change, if any."""
dpranke@chromium.org1b98c432011-03-17 21:11:49 +0000758 # TODO(dpranke): Should figure out if input_api.host_url is supposed to
759 # be a host or a scheme+host and normalize it there.
760 host = input_api.host_url
761 if not host.startswith('http://') and not host.startswith('https://'):
762 host = 'http://' + host
763 url = '%s/api/%s?messages=true' % (host, input_api.change.issue)
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000764
765 f = input_api.urllib2.urlopen(url)
766 issue_props = input_api.json.load(f)
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000767 messages = issue_props.get('messages', [])
768 owner_email = issue_props['owner_email']
769 owner_regexp = input_api.re.compile(input_api.re.escape(owner_email))
770 approvers = _GetApprovers(messages, email_regexp, owner_regexp)
771
772 return (owner_email, set(approvers))
773
774
775def _IsApprovingComment(text):
776 """Implements the logic for parsing a change comment for approval."""
777
778 # Any comment that contains a non-quoted line containing an 'lgtm' is an
779 # approval.
780 #
781 # TODO(dpranke): this differs from the logic used inside Google in a few
782 # ways. Inside Google,
783 #
784 # 1) the approving phrase must appear at the beginning of the first non
785 # quoted-line in the comment.'
786 # 2) "LG", "Looks Good", and "Looks Good to Me" are also acceptable.
787 # 3) Subsequent comments from the reviewer can rescind approval, unless
788 # the phrase "LGTM++" was used.
789 # We should consider implementing some or all of this here.
790 for l in text.splitlines():
791 l = l.strip().lower()
792 if l.startswith('>'):
793 continue
794
795 if 'lgtm' in l:
796 return True
797 return False
798
799
800def _GetApprovers(messages, email_regexp, owner_regexp):
801 """Returns the set of approvers for a change given the owner and messages.
802
803 Messages should be a list of dicts containing 'sender' and 'text' keys."""
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000804
805 # TODO(dpranke): This mimics the logic in
806 # /tools/commit-queue/verifiers/reviewer_lgtm.py
807 # We should share the code and/or remove the check there where it is
808 # redundant (since the commit queue also enforces the presubmit checks).
809 def match_reviewer(r):
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000810 return email_regexp.match(r) and not owner_regexp.match(r)
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000811
812 approvers = []
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000813 for m in messages:
814 sender = m['sender']
815 if _IsApprovingComment(m['text']) and match_reviewer(sender):
816 approvers.append(sender)
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000817 return set(approvers)
818
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000819
820def _CheckConstNSObject(input_api, output_api, source_file_filter):
821 """Checks to make sure no objective-c files have |const NSSomeClass*|."""
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000822 pattern = input_api.re.compile(
823 r'const\s+NS(?!(Point|Range|Rect|Size)\s*\*)\w*\s*\*')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000824
825 def objective_c_filter(f):
826 return (source_file_filter(f) and
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000827 input_api.os_path.splitext(f.LocalPath())[1] in ('.h', '.m', '.mm'))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000828
829 files = []
830 for f in input_api.AffectedSourceFiles(objective_c_filter):
831 contents = input_api.ReadFile(f)
832 if pattern.search(contents):
833 files.append(f)
834
835 if files:
836 if input_api.is_committing:
837 res_type = output_api.PresubmitPromptWarning
838 else:
839 res_type = output_api.PresubmitNotifyResult
840 return [ res_type('|const NSClass*| is wrong, see ' +
841 'http://dev.chromium.org/developers/clang-mac',
842 files) ]
843 return []
844
845
846def _CheckSingletonInHeaders(input_api, output_api, source_file_filter):
847 """Checks to make sure no header files have |Singleton<|."""
848 pattern = input_api.re.compile(r'Singleton<')
849 files = []
850 for f in input_api.AffectedSourceFiles(source_file_filter):
851 if (f.LocalPath().endswith('.h') or f.LocalPath().endswith('.hxx') or
852 f.LocalPath().endswith('.hpp') or f.LocalPath().endswith('.inl')):
853 contents = input_api.ReadFile(f)
854 if pattern.search(contents):
855 files.append(f)
856
857 if files:
858 return [ output_api.PresubmitError(
859 'Found Singleton<T> in the following header files.\n' +
860 'Please move them to an appropriate source file so that the ' +
861 'template gets instantiated in a single compilation unit.',
862 files) ]
863 return []
864
865
866def PanProjectChecks(input_api, output_api,
867 excluded_paths=None, text_files=None,
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000868 license_header=None, project_name=None,
869 owners_check=True):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000870 """Checks that ALL chromium orbit projects should use.
871
872 These are checks to be run on all Chromium orbit project, including:
873 Chromium
874 Native Client
875 V8
876 When you update this function, please take this broad scope into account.
877 Args:
878 input_api: Bag of input related interfaces.
879 output_api: Bag of output related interfaces.
880 excluded_paths: Don't include these paths in common checks.
881 text_files: Which file are to be treated as documentation text files.
882 license_header: What license header should be on files.
883 project_name: What is the name of the project as it appears in the license.
884 Returns:
885 A list of warning or error objects.
886 """
887 excluded_paths = excluded_paths or tuple()
888 text_files = text_files or (
889 r'.*\.txt',
890 r'.*\.json',
891 )
892 project_name = project_name or 'Chromium'
893 license_header = license_header or (
894 r'.*? Copyright \(c\) %(year)s The %(project)s Authors\. '
895 r'All rights reserved\.\n'
896 r'.*? Use of this source code is governed by a BSD-style license that '
897 r'can be\n'
898 r'.*? found in the LICENSE file\.\n'
899 ) % {
dpranke@chromium.org0d1bdea2011-03-24 22:54:38 +0000900 'year': input_api.time.strftime('%Y'),
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000901 'project': project_name,
902 }
903
904 results = []
905 # This code loads the default black list (e.g. third_party, experimental, etc)
906 # and add our black list (breakpad, skia and v8 are still not following
907 # google style and are not really living this repository).
908 # See presubmit_support.py InputApi.FilterSourceFile for the (simple) usage.
909 black_list = input_api.DEFAULT_BLACK_LIST + excluded_paths
910 white_list = input_api.DEFAULT_WHITE_LIST + text_files
911 sources = lambda x: input_api.FilterSourceFile(x, black_list=black_list)
912 text_files = lambda x: input_api.FilterSourceFile(x, black_list=black_list,
913 white_list=white_list)
914
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000915
916 snapshot_memory = []
917 def snapshot(msg):
918 """Measures & prints performance warning if a rule is running slow."""
919 dt2 = input_api.time.clock()
920 if snapshot_memory:
921 delta_ms = int(1000*(dt2 - snapshot_memory[0]))
922 if delta_ms > 500:
923 print " %s took a long time: %dms" % (snapshot_memory[1], delta_ms)
924 snapshot_memory[:] = (dt2, msg)
925
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000926 if owners_check:
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000927 snapshot("checking owners")
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000928 results.extend(input_api.canned_checks.CheckOwners(
929 input_api, output_api, source_file_filter=sources))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000930
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000931 snapshot("checking long lines")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000932 results.extend(input_api.canned_checks.CheckLongLines(
933 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000934 snapshot( "checking tabs")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000935 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
936 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000937 snapshot( "checking stray whitespace")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000938 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
939 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000940 snapshot("checking nsobjects")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000941 results.extend(_CheckConstNSObject(
942 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000943 snapshot("checking singletons")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000944 results.extend(_CheckSingletonInHeaders(
945 input_api, output_api, source_file_filter=sources))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +0000946
947 # The following checks are only done on commit, since the commit bot will
948 # auto-fix most of these.
949 if input_api.is_committing:
maruel@chromium.org8571dac2011-05-10 18:10:13 +0000950 snapshot("checking eol style")
951 results.extend(input_api.canned_checks.CheckChangeSvnEolStyle(
952 input_api, output_api, source_file_filter=text_files))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +0000953 snapshot("checking svn mime types")
954 results.extend(input_api.canned_checks.CheckSvnForCommonMimeTypes(
955 input_api, output_api))
956 snapshot("checking license")
957 results.extend(input_api.canned_checks.CheckLicense(
958 input_api, output_api, license_header, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000959 snapshot("done")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000960 return results