blob: 271753611edd1d3b1b34b2cdca75ddf641aed411 [file] [log] [blame]
estade@chromium.orgae7af922012-01-27 14:51:13 +00001# Copyright (c) 2012 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.orgcc73ad62011-07-06 17:39:26 +000066
67def CheckChangeWasUploaded(input_api, output_api):
68 """Checks that the issue was uploaded before committing."""
maruel@chromium.orgd587f392011-07-26 00:41:18 +000069 if input_api.is_committing and not input_api.change.issue:
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +000070 return [output_api.PresubmitError(
71 'Issue wasn\'t uploaded. Please upload first.')]
72 return []
73
74
maruel@chromium.org3410d912009-06-09 20:56:16 +000075### Content checks
76
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000077def CheckDoNotSubmitInFiles(input_api, output_api):
78 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to any files."""
nick@chromium.orge06cb4e2011-04-23 01:20:38 +000079 # We want to check every text file, not just source files.
80 file_filter = lambda x : x
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081 keyword = 'DO NOT ' + 'SUBMIT'
bulach@chromium.orgbfffd452012-02-22 01:13:29 +000082 errors = _FindNewViolationsOfRule(lambda _, line : keyword not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +000083 input_api, file_filter)
84 text = '\n'.join('Found %s in %s' % (keyword, loc) for loc in errors)
85 if text:
86 return [output_api.PresubmitError(text)]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000087 return []
88
89
erg@google.com26970fa2009-11-17 18:07:32 +000090def CheckChangeLintsClean(input_api, output_api, source_file_filter=None):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000091 """Checks that all '.cc' and '.h' files pass cpplint.py."""
erg@google.com26970fa2009-11-17 18:07:32 +000092 _RE_IS_TEST = input_api.re.compile(r'.*tests?.(cc|h)$')
93 result = []
94
95 # Initialize cpplint.
96 import cpplint
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +000097 # Access to a protected member _XX of a client class
98 # pylint: disable=W0212
erg@google.com26970fa2009-11-17 18:07:32 +000099 cpplint._cpplint_state.ResetErrorCounts()
100
101 # Justifications for each filter:
102 #
103 # - build/include : Too many; fix in the future.
104 # - build/include_order : Not happening; #ifdefed includes.
105 # - build/namespace : I'm surprised by how often we violate this rule.
106 # - readability/casting : Mistakes a whole bunch of function pointer.
107 # - runtime/int : Can be fixed long term; volume of errors too high
108 # - runtime/virtual : Broken now, but can be fixed in the future?
109 # - whitespace/braces : We have a lot of explicit scoping in chrome code.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000110 cpplint._SetFilters('-build/include,-build/include_order,-build/namespace,'
111 '-readability/casting,-runtime/int,-runtime/virtual,'
112 '-whitespace/braces')
erg@google.com26970fa2009-11-17 18:07:32 +0000113
114 # We currently are more strict with normal code than unit tests; 4 and 5 are
115 # the verbosity level that would normally be passed to cpplint.py through
116 # --verbose=#. Hopefully, in the future, we can be more verbose.
117 files = [f.AbsoluteLocalPath() for f in
118 input_api.AffectedSourceFiles(source_file_filter)]
119 for file_name in files:
120 if _RE_IS_TEST.match(file_name):
121 level = 5
122 else:
123 level = 4
124
125 cpplint.ProcessFile(file_name, level)
126
127 if cpplint._cpplint_state.error_count > 0:
128 if input_api.is_committing:
129 res_type = output_api.PresubmitError
130 else:
131 res_type = output_api.PresubmitPromptWarning
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000132 result = [res_type('Changelist failed cpplint.py check.')]
erg@google.com26970fa2009-11-17 18:07:32 +0000133
134 return result
135
136
maruel@chromium.org3410d912009-06-09 20:56:16 +0000137def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000138 """Checks no '\r' (CR) character is in any source files."""
139 cr_files = []
maruel@chromium.org3410d912009-06-09 20:56:16 +0000140 for f in input_api.AffectedSourceFiles(source_file_filter):
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000141 if '\r' in input_api.ReadFile(f, 'rb'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000142 cr_files.append(f.LocalPath())
143 if cr_files:
144 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000145 'Found a CR character in these files:', items=cr_files)]
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000146 return []
147
148
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000149def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None):
150 """Checks for files in svn modified directories.
151
152 They will get submitted on accident because svn commits recursively by
153 default, and that's very dangerous.
154 """
155 if input_api.change.scm != 'svn':
156 return []
157
158 errors = []
159 current_cl_files = input_api.change.GetModifiedFiles()
160 all_modified_files = input_api.change.GetAllModifiedFiles()
161 # Filter out files in the current CL.
162 modified_files = [f for f in all_modified_files if f not in current_cl_files]
163 modified_abspaths = [input_api.os_path.abspath(f) for f in modified_files]
164
sail@chromium.org5538e022011-05-12 17:53:16 +0000165 for f in input_api.AffectedFiles(file_filter=source_file_filter):
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000166 if f.Action() == 'M' and f.IsDirectory():
167 curpath = f.AbsoluteLocalPath()
168 bad_files = []
169 # Check if any of the modified files in other CLs are under curpath.
170 for i in xrange(len(modified_files)):
171 abspath = modified_abspaths[i]
172 if input_api.os_path.commonprefix([curpath, abspath]) == curpath:
173 bad_files.append(modified_files[i])
174 if bad_files:
175 if input_api.is_committing:
176 error_type = output_api.PresubmitPromptWarning
177 else:
178 error_type = output_api.PresubmitNotifyResult
179 errors.append(error_type(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000180 'Potential accidental commits in changelist %s:' % f.LocalPath(),
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000181 items=bad_files))
182 return errors
183
184
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000185def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
186 """Checks the files ends with one and only one \n (LF)."""
187 eof_files = []
188 for f in input_api.AffectedSourceFiles(source_file_filter):
189 contents = input_api.ReadFile(f, 'rb')
190 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000191 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000192 eof_files.append(f.LocalPath())
193
194 if eof_files:
195 return [output_api.PresubmitPromptWarning(
196 'These files should end in one (and only one) newline character:',
197 items=eof_files)]
198 return []
199
200
201def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api,
202 source_file_filter=None):
203 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass.
204
205 It is faster because it is reading the file only once.
206 """
207 cr_files = []
208 eof_files = []
209 for f in input_api.AffectedSourceFiles(source_file_filter):
210 contents = input_api.ReadFile(f, 'rb')
211 if '\r' in contents:
212 cr_files.append(f.LocalPath())
213 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000214 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000215 eof_files.append(f.LocalPath())
216 outputs = []
217 if cr_files:
218 outputs.append(output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000219 'Found a CR character in these files:', items=cr_files))
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000220 if eof_files:
221 outputs.append(output_api.PresubmitPromptWarning(
222 'These files should end in one (and only one) newline character:',
223 items=eof_files))
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000224 return outputs
225
226
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000227def _ReportErrorFileAndLine(filename, line_num, line):
228 """Default error formatter for _FindNewViolationsOfRule."""
229 return '%s, line %s' % (filename, line_num)
230
231
232def _FindNewViolationsOfRule(callable_rule, input_api, source_file_filter=None,
233 error_formatter=_ReportErrorFileAndLine):
234 """Find all newly introduced violations of a per-line rule (a callable).
235
236 Arguments:
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000237 callable_rule: a callable taking a file extension and line of input and
238 returning True if the rule is satisfied and False if there was a problem.
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000239 input_api: object to enumerate the affected files.
240 source_file_filter: a filter to be passed to the input api.
241 error_formatter: a callable taking (filename, line_number, line) and
242 returning a formatted error string.
243
244 Returns:
245 A list of the newly-introduced violations reported by the rule.
246 """
247 errors = []
sail@chromium.org5538e022011-05-12 17:53:16 +0000248 for f in input_api.AffectedFiles(include_deletes=False,
249 file_filter=source_file_filter):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000250 # For speed, we do two passes, checking first the full file. Shelling out
251 # to the SCM to determine the changed region can be quite expensive on
252 # Win32. Assuming that most files will be kept problem-free, we can
253 # skip the SCM operations most of the time.
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000254 extension = str(f.LocalPath()).rsplit('.', 1)[-1]
255 if all(callable_rule(extension, line) for line in f.NewContents()):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000256 continue # No violation found in full text: can skip considering diff.
257
258 for line_num, line in f.ChangedContents():
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000259 if not callable_rule(extension, line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000260 errors.append(error_formatter(f.LocalPath(), line_num, line))
261
262 return errors
263
264
maruel@chromium.org3410d912009-06-09 20:56:16 +0000265def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000266 """Checks that there are no tab characters in any of the text files to be
267 submitted.
268 """
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000269 # In addition to the filter, make sure that makefiles are blacklisted.
270 if not source_file_filter:
271 # It's the default filter.
272 source_file_filter = input_api.FilterSourceFile
273 def filter_more(affected_file):
274 return (not input_api.os_path.basename(affected_file.LocalPath()) in
275 ('Makefile', 'makefile') and
276 source_file_filter(affected_file))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000277
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000278 tabs = _FindNewViolationsOfRule(lambda _, line : '\t' not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000279 input_api, filter_more)
280
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000281 if tabs:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000282 return [output_api.PresubmitPromptWarning('Found a tab character in:',
283 long_text='\n'.join(tabs))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000284 return []
285
286
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000287def CheckChangeTodoHasOwner(input_api, output_api, source_file_filter=None):
288 """Checks that the user didn't add TODO(name) without an owner."""
289
maruel@chromium.org07ab60e2011-02-08 21:54:00 +0000290 unowned_todo = input_api.re.compile('TO' + 'DO[^(]')
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000291 errors = _FindNewViolationsOfRule(lambda _, x : not unowned_todo.search(x),
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000292 input_api, source_file_filter)
293 errors = ['Found TO' + 'DO with no owner in ' + x for x in errors]
294 if errors:
295 return [output_api.PresubmitPromptWarning('\n'.join(errors))]
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000296 return []
297
298
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000299def CheckChangeHasNoStrayWhitespace(input_api, output_api,
300 source_file_filter=None):
301 """Checks that there is no stray whitespace at source lines end."""
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000302 errors = _FindNewViolationsOfRule(lambda _, line : line.rstrip() == line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000303 input_api, source_file_filter)
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000304 if errors:
305 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000306 'Found line ending with white spaces in:',
307 long_text='\n'.join(errors))]
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000308 return []
309
310
maruel@chromium.org3410d912009-06-09 20:56:16 +0000311def CheckLongLines(input_api, output_api, maxlen=80, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000312 """Checks that there aren't any lines longer than maxlen characters in any of
313 the text files to be submitted.
314 """
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000315 maxlens = {
316 'java': 100,
317 '': maxlen,
318 }
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000319 # Note: these are C++ specific but processed on all languages. :(
320 MACROS = ('#define', '#include', '#import', '#pragma', '#if', '#endif')
321
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000322 def no_long_lines(file_extension, line):
323 file_maxlen = maxlens.get(file_extension, maxlens[''])
324 # Stupidly long symbols that needs to be worked around if takes 66% of line.
325 long_symbol = file_maxlen * 2 / 3
326 # Hard line length limit at 50% more.
327 extra_maxlen = file_maxlen * 3 / 2
328
329 line_len = len(line)
330 if line_len <= file_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000331 return True
332
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000333 if line_len > extra_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000334 return False
335
336 return (
337 line.startswith(MACROS) or
338 any((url in line) for url in ('http://', 'https://')) or
339 input_api.re.match(
340 r'.*[A-Za-z][A-Za-z_0-9]{%d,}.*' % long_symbol, line))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000341
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000342 def format_error(filename, line_num, line):
343 return '%s, line %s, %s chars' % (filename, line_num, len(line))
344
345 errors = _FindNewViolationsOfRule(no_long_lines, input_api,
346 source_file_filter,
347 error_formatter=format_error)
348 if errors:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000349 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000350 return [output_api.PresubmitPromptWarning(msg, items=errors[:5])]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000351 else:
352 return []
353
354
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000355def CheckLicense(input_api, output_api, license_re, source_file_filter=None,
maruel@chromium.org71626852010-11-03 13:14:25 +0000356 accept_empty_files=True):
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000357 """Verifies the license header.
358 """
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000359 license_re = input_api.re.compile(license_re, input_api.re.MULTILINE)
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000360 bad_files = []
361 for f in input_api.AffectedSourceFiles(source_file_filter):
362 contents = input_api.ReadFile(f, 'rb')
maruel@chromium.org71626852010-11-03 13:14:25 +0000363 if accept_empty_files and not contents:
364 continue
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000365 if not license_re.search(contents):
366 bad_files.append(f.LocalPath())
367 if bad_files:
368 if input_api.is_committing:
369 res_type = output_api.PresubmitPromptWarning
370 else:
371 res_type = output_api.PresubmitNotifyResult
372 return [res_type(
bradnelson@google.com393460b2011-03-29 01:20:26 +0000373 'License must match:\n%s\n' % license_re.pattern +
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000374 'Found a bad license header in these files:', items=bad_files)]
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000375 return []
376
377
maruel@chromium.org1a0e3cb2009-06-10 18:03:04 +0000378def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None):
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000379 """Checks that the source files have svn:eol-style=LF."""
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000380 return CheckSvnProperty(input_api, output_api,
381 'svn:eol-style', 'LF',
382 input_api.AffectedSourceFiles(source_file_filter))
383
384
385def CheckSvnForCommonMimeTypes(input_api, output_api):
386 """Checks that common binary file types have the correct svn:mime-type."""
387 output = []
388 files = input_api.AffectedFiles(include_deletes=False)
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000389 def IsExts(x, exts):
390 path = x.LocalPath()
391 for extension in exts:
392 if path.endswith(extension):
393 return True
394 return False
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000395 def FilterFiles(extension):
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000396 return filter(lambda x: IsExts(x, extension), files)
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000397 def RunCheck(mime_type, files):
398 output.extend(CheckSvnProperty(input_api, output_api, 'svn:mime-type',
399 mime_type, files))
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000400 RunCheck('application/pdf', FilterFiles(['.pdf']))
401 RunCheck('image/bmp', FilterFiles(['.bmp']))
402 RunCheck('image/gif', FilterFiles(['.gif']))
403 RunCheck('image/png', FilterFiles(['.png']))
404 RunCheck('image/jpeg', FilterFiles(['.jpg', '.jpeg', '.jpe']))
405 RunCheck('image/vnd.microsoft.icon', FilterFiles(['.ico']))
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000406 return output
407
408
409def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
410 """Checks that affected_files files have prop=expected."""
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000411 if input_api.change.scm != 'svn':
412 return []
413
414 bad = filter(lambda f: f.Property(prop) != expected, affected_files)
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000415 if bad:
maruel@chromium.org0874d472009-06-10 19:08:33 +0000416 if input_api.is_committing:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000417 res_type = output_api.PresubmitError
maruel@chromium.org0874d472009-06-10 19:08:33 +0000418 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000419 res_type = output_api.PresubmitNotifyResult
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000420 message = 'Run the command: svn pset %s %s \\' % (prop, expected)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000421 return [res_type(message, items=bad)]
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000422 return []
423
424
maruel@chromium.org3410d912009-06-09 20:56:16 +0000425### Other checks
426
427def CheckDoNotSubmit(input_api, output_api):
428 return (
429 CheckDoNotSubmitInDescription(input_api, output_api) +
430 CheckDoNotSubmitInFiles(input_api, output_api)
431 )
432
433
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000434def CheckTreeIsOpen(input_api, output_api,
435 url=None, closed=None, json_url=None):
436 """Check whether to allow commit without prompt.
437
438 Supports two styles:
439 1. Checks that an url's content doesn't match a regexp that would mean that
440 the tree is closed. (old)
441 2. Check the json_url to decide whether to allow commit without prompt.
442 Args:
443 input_api: input related apis.
444 output_api: output related apis.
445 url: url to use for regex based tree status.
446 closed: regex to match for closed status.
447 json_url: url to download json style status.
448 """
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000449 if not input_api.is_committing:
450 return []
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000451 try:
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000452 if json_url:
453 connection = input_api.urllib2.urlopen(json_url)
454 status = input_api.json.loads(connection.read())
455 connection.close()
456 if not status['can_commit_freely']:
457 short_text = 'Tree state is: ' + status['general_state']
458 long_text = status['message'] + '\n' + json_url
459 return [output_api.PresubmitError(short_text, long_text=long_text)]
460 else:
461 # TODO(bradnelson): drop this once all users are gone.
462 connection = input_api.urllib2.urlopen(url)
463 status = connection.read()
464 connection.close()
465 if input_api.re.match(closed, status):
466 long_text = status + '\n' + url
467 return [output_api.PresubmitError('The tree is closed.',
468 long_text=long_text)]
rohitrao@chromium.orgd490ee82012-02-06 19:31:33 +0000469 except IOError as e:
470 return [output_api.PresubmitError('Error fetching tree status.',
471 long_text=str(e))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000472 return []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000473
474
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000475def RunUnitTestsInDirectory(
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000476 input_api, output_api, directory, whitelist=None, blacklist=None):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000477 """Lists all files in a directory and runs them. Doesn't recurse.
478
479 It's mainly a wrapper for RunUnitTests. USe whitelist and blacklist to filter
480 tests accordingly.
481 """
482 unit_tests = []
483 test_path = input_api.os_path.abspath(
484 input_api.os_path.join(input_api.PresubmitLocalPath(), directory))
485
486 def check(filename, filters):
487 return any(True for i in filters if input_api.re.match(i, filename))
488
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000489 to_run = found = 0
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000490 for filename in input_api.os_listdir(test_path):
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000491 found += 1
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000492 fullpath = input_api.os_path.join(test_path, filename)
493 if not input_api.os_path.isfile(fullpath):
494 continue
495 if whitelist and not check(filename, whitelist):
496 continue
497 if blacklist and check(filename, blacklist):
498 continue
499 unit_tests.append(input_api.os_path.join(directory, filename))
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000500 to_run += 1
501 input_api.logging.debug('Found %d files, running %d' % (found, to_run))
502 if not to_run:
503 return [
504 output_api.PresubmitPromptWarning(
505 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
506 % (found, whitelist, blacklist, directory))
507 ]
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000508 return RunUnitTests(input_api, output_api, unit_tests)
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000509
510
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000511def RunUnitTests(input_api, output_api, unit_tests):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000512 """Runs all unit tests in a directory.
513
514 On Windows, sys.executable is used for unit tests ending with ".py".
515 """
516 # We don't want to hinder users from uploading incomplete patches.
517 if input_api.is_committing:
518 message_type = output_api.PresubmitError
519 else:
520 message_type = output_api.PresubmitPromptWarning
521
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000522 results = []
523 for unit_test in unit_tests:
524 cmd = []
525 if input_api.platform == 'win32' and unit_test.endswith('.py'):
526 # Windows needs some help.
527 cmd = [input_api.python_executable]
528 cmd.append(unit_test)
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000529 if input_api.verbose:
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000530 print('Running %s' % unit_test)
maruel@chromium.org6c7723e2011-04-12 19:04:55 +0000531 cmd.append('--verbose')
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000532 try:
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000533 if input_api.verbose:
maruel@chromium.org0e766052011-04-06 13:32:51 +0000534 input_api.subprocess.check_call(cmd, cwd=input_api.PresubmitLocalPath())
535 else:
536 input_api.subprocess.check_output(
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000537 cmd,
538 stderr=input_api.subprocess.STDOUT,
539 cwd=input_api.PresubmitLocalPath())
maruel@chromium.org0e766052011-04-06 13:32:51 +0000540 except (OSError, input_api.subprocess.CalledProcessError), e:
541 results.append(message_type('%s failed!\n%s' % (unit_test, e)))
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000542 return results
543
544
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000545def RunPythonUnitTests(input_api, output_api, unit_tests):
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000546 """Run the unit tests out of process, capture the output and use the result
547 code to determine success.
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000548
549 DEPRECATED.
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000550 """
maruel@chromium.orgd7dccf52009-06-06 18:51:58 +0000551 # We don't want to hinder users from uploading incomplete patches.
552 if input_api.is_committing:
553 message_type = output_api.PresubmitError
554 else:
555 message_type = output_api.PresubmitNotifyResult
maruel@chromium.org0e766052011-04-06 13:32:51 +0000556 results = []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000557 for unit_test in unit_tests:
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000558 # Run the unit tests out of process. This is because some unit tests
559 # stub out base libraries and don't clean up their mess. It's too easy to
560 # get subtle bugs.
561 cwd = None
562 env = None
563 unit_test_name = unit_test
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000564 # 'python -m test.unit_test' doesn't work. We need to change to the right
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000565 # directory instead.
566 if '.' in unit_test:
567 # Tests imported in submodules (subdirectories) assume that the current
568 # directory is in the PYTHONPATH. Manually fix that.
569 unit_test = unit_test.replace('.', '/')
570 cwd = input_api.os_path.dirname(unit_test)
571 unit_test = input_api.os_path.basename(unit_test)
572 env = input_api.environ.copy()
kbr@google.comab318592009-09-04 00:54:55 +0000573 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
574 backpath = [
575 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
576 ]
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000577 if env.get('PYTHONPATH'):
578 backpath.append(env.get('PYTHONPATH'))
ukai@chromium.orga301f1f2009-08-05 10:37:33 +0000579 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000580 cmd = [input_api.python_executable, '-m', '%s' % unit_test]
581 try:
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000582 input_api.subprocess.check_output(
583 cmd, stderr=input_api.subprocess.STDOUT, cwd=cwd, env=env)
maruel@chromium.org0e766052011-04-06 13:32:51 +0000584 except (OSError, input_api.subprocess.CalledProcessError), e:
585 results.append(message_type('%s failed!\n%s' % (unit_test_name, e)))
586 return results
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000587
588
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000589def _FetchAllFiles(input_api, white_list, black_list):
590 """Hack to fetch all files."""
591 # We cannot use AffectedFiles here because we want to test every python
592 # file on each single python change. It's because a change in a python file
593 # can break another unmodified file.
594 # Use code similar to InputApi.FilterSourceFile()
595 def Find(filepath, filters):
596 for item in filters:
597 if input_api.re.match(item, filepath):
598 return True
599 return False
600
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000601 files = []
602 path_len = len(input_api.PresubmitLocalPath())
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000603 for dirpath, dirnames, filenames in input_api.os_walk(
604 input_api.PresubmitLocalPath()):
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000605 # Passes dirnames in black list to speed up search.
606 for item in dirnames[:]:
607 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
608 if Find(filepath, black_list):
609 dirnames.remove(item)
610 for item in filenames:
611 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
612 if Find(filepath, white_list) and not Find(filepath, black_list):
613 files.append(filepath)
614 return files
615
616
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000617def RunPylint(input_api, output_api, white_list=None, black_list=None):
618 """Run pylint on python files.
619
620 The default white_list enforces looking only a *.py files.
621 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000622 white_list = tuple(white_list or ('.*\.py$',))
623 black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000624 if input_api.is_committing:
625 error_type = output_api.PresubmitError
626 else:
627 error_type = output_api.PresubmitPromptWarning
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000628
629 # Only trigger if there is at least one python file affected.
630 src_filter = lambda x: input_api.FilterSourceFile(x, white_list, black_list)
631 if not input_api.AffectedSourceFiles(src_filter):
632 return []
633
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000634 # On certain pylint/python version combination, running pylint throws a lot of
635 # warning messages.
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000636 import warnings
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000637 warnings.filterwarnings('ignore', category=DeprecationWarning)
638 try:
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000639 files = _FetchAllFiles(input_api, white_list, black_list)
640 if not files:
641 return []
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000642 # Now that at least one python file was modified and all the python files
643 # were listed, try to run pylint.
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000644 try:
645 from pylint import lint
maruel@chromium.orgce71e672011-10-23 00:35:58 +0000646 from pylint.utils import UnknownMessage
maruel@chromium.org6fba34d2011-06-02 13:45:12 +0000647 input_api.logging.debug(
648 'Using pylint v%s from %s' % (lint.version, lint.__file__))
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000649 except ImportError:
650 if input_api.platform == 'win32':
651 return [output_api.PresubmitNotifyResult(
652 'Warning: Can\'t run pylint because it is not installed. Please '
653 'install manually\n'
654 'Cannot do static analysis of python files.')]
655 return [output_api.PresubmitError(
656 'Please install pylint with "sudo apt-get install python-setuptools; '
657 'sudo easy_install pylint"\n'
maruel@chromium.org725f1c32011-04-01 20:24:54 +0000658 'or visit http://pypi.python.org/pypi/setuptools.\n'
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000659 'Cannot do static analysis of python files.')]
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000660
661 def run_lint(files):
662 try:
663 lint.Run(files)
664 assert False
665 except SystemExit, e:
666 # pylint has the bad habit of calling sys.exit(), trap it here.
667 return e.code
maruel@chromium.orgce71e672011-10-23 00:35:58 +0000668 except UnknownMessage, e:
669 return 'Please upgrade pylint: %s' % e
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000670
671 result = None
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000672 if not input_api.verbose:
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000673 result = run_lint(sorted(files))
674 else:
675 for filename in sorted(files):
676 print('Running pylint on %s' % filename)
maruel@chromium.orgce71e672011-10-23 00:35:58 +0000677 result = run_lint([filename]) or result
678 if isinstance(result, basestring):
679 return [error_type(result)]
680 elif result:
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000681 return [error_type('Fix pylint errors first.')]
maruel@chromium.orgfca53392010-12-21 18:42:57 +0000682 return []
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000683 finally:
684 warnings.filterwarnings('default', category=DeprecationWarning)
685
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000686
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000687# TODO(dpranke): Get the host_url from the input_api instead
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000688def CheckRietveldTryJobExecution(input_api, output_api, host_url, platforms,
689 owner):
maruel@chromium.org85da74b2011-10-27 17:13:30 +0000690 # Temporarily 'fix' the check while the Rietveld API is being upgraded to
691 # something sensible.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000692 return []
693
694
695def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
696 ignored):
697 if not input_api.json:
698 return [output_api.PresubmitPromptWarning(
699 'Please install simplejson or upgrade to python 2.6+')]
700 try:
701 connection = input_api.urllib2.urlopen(url)
702 raw_data = connection.read()
703 connection.close()
704 except IOError:
705 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)]
706
707 try:
708 data = input_api.json.loads(raw_data)
709 except ValueError:
710 return [output_api.PresubmitNotifyResult('Received malformed json while '
711 'looking up buildbot status')]
712
713 out = []
714 for (builder_name, builder) in data.iteritems():
715 if builder_name in ignored:
716 continue
maruel@chromium.orgcf1982c2010-10-04 15:08:28 +0000717 if builder.get('state', '') == 'offline':
718 continue
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000719 pending_builds_len = len(builder.get('pending_builds', []))
720 if pending_builds_len > max_pendings:
721 out.append('%s has %d build(s) pending' %
722 (builder_name, pending_builds_len))
723 if out:
724 return [output_api.PresubmitPromptWarning(
725 'Build(s) pending. It is suggested to wait that no more than %d '
726 'builds are pending.' % max_pendings,
727 long_text='\n'.join(out))]
728 return []
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000729
730
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000731def CheckOwners(input_api, output_api, source_file_filter=None):
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000732 if input_api.is_committing:
733 if input_api.tbr:
734 return [output_api.PresubmitNotifyResult(
735 '--tbr was specified, skipping OWNERS check')]
736 if not input_api.change.issue:
737 return [output_api.PresubmitError("OWNERS check failed: this change has "
738 "no Rietveld issue number, so we can't check it for approvals.")]
739 needed = 'LGTM from an OWNER'
740 output = output_api.PresubmitError
741 else:
742 needed = 'OWNER reviewers'
743 output = output_api.PresubmitNotifyResult
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000744
dpranke@chromium.orgadd5df42011-03-08 23:04:01 +0000745 affected_files = set([f.LocalPath() for f in
sail@chromium.org5538e022011-05-12 17:53:16 +0000746 input_api.change.AffectedFiles(file_filter=source_file_filter)])
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000747
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000748 owners_db = input_api.owners_db
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000749 owner_email, reviewers = _RietveldOwnerAndReviewers(
750 input_api,
751 owners_db.email_regexp,
752 approval_needed=input_api.is_committing)
753
754 if owner_email:
755 reviewers_plus_owner = reviewers.union(set([owner_email]))
756 elif input_api.is_committing:
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000757 return [output_api.PresubmitWarning(
758 'The issue was not uploaded so you have no OWNER approval.')]
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000759 else:
760 owner_email = ''
761 reviewers_plus_owner = set()
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000762
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000763 missing_directories = owners_db.directories_not_covered_by(affected_files,
764 reviewers_plus_owner)
765 if missing_directories:
766 return [output('Missing %s for files in these directories:\n %s' %
767 (needed, '\n '.join(missing_directories)))]
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000768
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000769 if input_api.is_committing and not reviewers:
770 return [output('Missing LGTM from someone other than %s' % owner_email)]
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000771 return []
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000772
773
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000774def _RietveldOwnerAndReviewers(input_api, email_regexp, approval_needed=False):
775 """Return the owner and reviewers of a change, if any.
776
777 If approval_needed is True, only reviewers who have approved the change
778 will be returned.
779 """
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000780 if not input_api.change.issue:
781 return None, None
782
783 issue_props = input_api.rietveld.get_issue_properties(
784 int(input_api.change.issue), True)
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000785 if not approval_needed:
786 return issue_props['owner_email'], set(issue_props['reviewers'])
787
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000788 owner_email = issue_props['owner_email']
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000789
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000790 def match_reviewer(r):
maruel@chromium.org80941c22011-05-30 20:14:18 +0000791 return email_regexp.match(r) and r != owner_email
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000792
maruel@chromium.org80941c22011-05-30 20:14:18 +0000793 messages = issue_props.get('messages', [])
794 approvers = set(
795 m['sender'] for m in messages
796 if m.get('approval') and match_reviewer(m['sender']))
797
798 return owner_email, approvers
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000799
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000800
801def _CheckConstNSObject(input_api, output_api, source_file_filter):
802 """Checks to make sure no objective-c files have |const NSSomeClass*|."""
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000803 pattern = input_api.re.compile(
804 r'const\s+NS(?!(Point|Range|Rect|Size)\s*\*)\w*\s*\*')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000805
806 def objective_c_filter(f):
807 return (source_file_filter(f) and
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000808 input_api.os_path.splitext(f.LocalPath())[1] in ('.h', '.m', '.mm'))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000809
810 files = []
811 for f in input_api.AffectedSourceFiles(objective_c_filter):
812 contents = input_api.ReadFile(f)
813 if pattern.search(contents):
814 files.append(f)
815
816 if files:
817 if input_api.is_committing:
818 res_type = output_api.PresubmitPromptWarning
819 else:
820 res_type = output_api.PresubmitNotifyResult
821 return [ res_type('|const NSClass*| is wrong, see ' +
822 'http://dev.chromium.org/developers/clang-mac',
823 files) ]
824 return []
825
826
827def _CheckSingletonInHeaders(input_api, output_api, source_file_filter):
828 """Checks to make sure no header files have |Singleton<|."""
dilmah@chromium.orgd22b9702011-08-26 13:53:49 +0000829 pattern = input_api.re.compile(r'Singleton\s*<')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000830 files = []
831 for f in input_api.AffectedSourceFiles(source_file_filter):
832 if (f.LocalPath().endswith('.h') or f.LocalPath().endswith('.hxx') or
833 f.LocalPath().endswith('.hpp') or f.LocalPath().endswith('.inl')):
834 contents = input_api.ReadFile(f)
dilmah@chromium.orgd22b9702011-08-26 13:53:49 +0000835 for line in contents.splitlines(False):
836 line = input_api.re.sub(r'//.*$', '', line) # Strip C++ comment.
837 if pattern.search(line):
838 files.append(f)
839 break
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000840
841 if files:
842 return [ output_api.PresubmitError(
843 'Found Singleton<T> in the following header files.\n' +
844 'Please move them to an appropriate source file so that the ' +
845 'template gets instantiated in a single compilation unit.',
846 files) ]
847 return []
848
849
850def PanProjectChecks(input_api, output_api,
851 excluded_paths=None, text_files=None,
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000852 license_header=None, project_name=None,
853 owners_check=True):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000854 """Checks that ALL chromium orbit projects should use.
855
856 These are checks to be run on all Chromium orbit project, including:
857 Chromium
858 Native Client
859 V8
860 When you update this function, please take this broad scope into account.
861 Args:
862 input_api: Bag of input related interfaces.
863 output_api: Bag of output related interfaces.
864 excluded_paths: Don't include these paths in common checks.
865 text_files: Which file are to be treated as documentation text files.
866 license_header: What license header should be on files.
867 project_name: What is the name of the project as it appears in the license.
868 Returns:
869 A list of warning or error objects.
870 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000871 excluded_paths = tuple(excluded_paths or [])
872 text_files = tuple(text_files or (
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +0000873 r'.+\.txt$',
874 r'.+\.json$',
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000875 ))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000876 project_name = project_name or 'Chromium'
877 license_header = license_header or (
878 r'.*? Copyright \(c\) %(year)s The %(project)s Authors\. '
879 r'All rights reserved\.\n'
880 r'.*? Use of this source code is governed by a BSD-style license that '
881 r'can be\n'
dbeam@chromium.orgb2312102012-02-15 02:01:55 +0000882 r'.*? found in the LICENSE file\.(?: \*/)?\n'
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000883 ) % {
dpranke@chromium.org0d1bdea2011-03-24 22:54:38 +0000884 'year': input_api.time.strftime('%Y'),
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000885 'project': project_name,
886 }
887
888 results = []
889 # This code loads the default black list (e.g. third_party, experimental, etc)
890 # and add our black list (breakpad, skia and v8 are still not following
891 # google style and are not really living this repository).
892 # See presubmit_support.py InputApi.FilterSourceFile for the (simple) usage.
893 black_list = input_api.DEFAULT_BLACK_LIST + excluded_paths
894 white_list = input_api.DEFAULT_WHITE_LIST + text_files
895 sources = lambda x: input_api.FilterSourceFile(x, black_list=black_list)
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +0000896 text_files = lambda x: input_api.FilterSourceFile(
897 x, black_list=black_list, white_list=white_list)
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000898
899 snapshot_memory = []
900 def snapshot(msg):
901 """Measures & prints performance warning if a rule is running slow."""
902 dt2 = input_api.time.clock()
903 if snapshot_memory:
904 delta_ms = int(1000*(dt2 - snapshot_memory[0]))
905 if delta_ms > 500:
906 print " %s took a long time: %dms" % (snapshot_memory[1], delta_ms)
907 snapshot_memory[:] = (dt2, msg)
908
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000909 if owners_check:
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000910 snapshot("checking owners")
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000911 results.extend(input_api.canned_checks.CheckOwners(
dpranke@chromium.org751797a2011-06-07 18:46:27 +0000912 input_api, output_api, source_file_filter=None))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000913
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000914 snapshot("checking long lines")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000915 results.extend(input_api.canned_checks.CheckLongLines(
916 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000917 snapshot( "checking tabs")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000918 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
919 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000920 snapshot( "checking stray whitespace")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000921 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
922 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000923 snapshot("checking nsobjects")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000924 results.extend(_CheckConstNSObject(
925 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000926 snapshot("checking singletons")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000927 results.extend(_CheckSingletonInHeaders(
928 input_api, output_api, source_file_filter=sources))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +0000929
930 # The following checks are only done on commit, since the commit bot will
931 # auto-fix most of these.
932 if input_api.is_committing:
maruel@chromium.org8571dac2011-05-10 18:10:13 +0000933 snapshot("checking eol style")
934 results.extend(input_api.canned_checks.CheckChangeSvnEolStyle(
935 input_api, output_api, source_file_filter=text_files))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +0000936 snapshot("checking svn mime types")
937 results.extend(input_api.canned_checks.CheckSvnForCommonMimeTypes(
938 input_api, output_api))
939 snapshot("checking license")
940 results.extend(input_api.canned_checks.CheckLicense(
941 input_api, output_api, license_header, source_file_filter=sources))
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +0000942 snapshot("checking was uploaded")
943 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
944 input_api, output_api))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000945 snapshot("done")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000946 return results