blob: 6b9383e913c760f4eb5b298ecc22b3e299d27a3d [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
maruel@chromium.orgff9a2172012-04-24 16:55:32 +00007import os as _os
8_HERE = _os.path.dirname(_os.path.abspath(__file__))
9
bradnelson@google.com56e48bc2011-03-24 20:51:21 +000010
maruel@chromium.org3410d912009-06-09 20:56:16 +000011### Description checks
12
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000013def CheckChangeHasTestField(input_api, output_api):
14 """Requires that the changelist have a TEST= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000015 if input_api.change.TEST:
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000016 return []
17 else:
18 return [output_api.PresubmitNotifyResult(
jam@chromium.org5c76de92011-01-24 18:19:21 +000019 'If this change requires manual test instructions to QA team, add '
20 'TEST=[instructions].')]
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000021
22
23def CheckChangeHasBugField(input_api, output_api):
24 """Requires that the changelist have a BUG= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000025 if input_api.change.BUG:
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000026 return []
27 else:
28 return [output_api.PresubmitNotifyResult(
jam@chromium.org5c76de92011-01-24 18:19:21 +000029 'If this change has an associated bug, add BUG=[bug number].')]
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000030
31
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000032def CheckChangeHasTestedField(input_api, output_api):
33 """Requires that the changelist have a TESTED= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000034 if input_api.change.TESTED:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000035 return []
36 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000037 return [output_api.PresubmitError('Changelist must have a TESTED= field.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000038
39
40def CheckChangeHasQaField(input_api, output_api):
41 """Requires that the changelist have a QA= field."""
42 if input_api.change.QA:
43 return []
44 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000045 return [output_api.PresubmitError('Changelist must have a QA= field.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000046
47
48def CheckDoNotSubmitInDescription(input_api, output_api):
49 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to the CL description.
50 """
51 keyword = 'DO NOT ' + 'SUBMIT'
52 if keyword in input_api.change.DescriptionText():
53 return [output_api.PresubmitError(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000054 keyword + ' is present in the changelist description.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000055 else:
56 return []
57
58
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000059def CheckChangeHasDescription(input_api, output_api):
60 """Checks the CL description is not empty."""
61 text = input_api.change.DescriptionText()
62 if text.strip() == '':
63 if input_api.is_committing:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000064 return [output_api.PresubmitError('Add a description.')]
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000065 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000066 return [output_api.PresubmitNotifyResult('Add a description.')]
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000067 return []
68
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +000069
70def CheckChangeWasUploaded(input_api, output_api):
71 """Checks that the issue was uploaded before committing."""
maruel@chromium.orgd587f392011-07-26 00:41:18 +000072 if input_api.is_committing and not input_api.change.issue:
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +000073 return [output_api.PresubmitError(
74 'Issue wasn\'t uploaded. Please upload first.')]
75 return []
76
77
maruel@chromium.org3410d912009-06-09 20:56:16 +000078### Content checks
79
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080def CheckDoNotSubmitInFiles(input_api, output_api):
81 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to any files."""
nick@chromium.orge06cb4e2011-04-23 01:20:38 +000082 # We want to check every text file, not just source files.
83 file_filter = lambda x : x
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084 keyword = 'DO NOT ' + 'SUBMIT'
bulach@chromium.orgbfffd452012-02-22 01:13:29 +000085 errors = _FindNewViolationsOfRule(lambda _, line : keyword not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +000086 input_api, file_filter)
87 text = '\n'.join('Found %s in %s' % (keyword, loc) for loc in errors)
88 if text:
89 return [output_api.PresubmitError(text)]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000090 return []
91
92
erg@google.com26970fa2009-11-17 18:07:32 +000093def CheckChangeLintsClean(input_api, output_api, source_file_filter=None):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000094 """Checks that all '.cc' and '.h' files pass cpplint.py."""
erg@google.com26970fa2009-11-17 18:07:32 +000095 _RE_IS_TEST = input_api.re.compile(r'.*tests?.(cc|h)$')
96 result = []
97
98 # Initialize cpplint.
99 import cpplint
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000100 # Access to a protected member _XX of a client class
101 # pylint: disable=W0212
erg@google.com26970fa2009-11-17 18:07:32 +0000102 cpplint._cpplint_state.ResetErrorCounts()
103
104 # Justifications for each filter:
105 #
106 # - build/include : Too many; fix in the future.
107 # - build/include_order : Not happening; #ifdefed includes.
108 # - build/namespace : I'm surprised by how often we violate this rule.
109 # - readability/casting : Mistakes a whole bunch of function pointer.
110 # - runtime/int : Can be fixed long term; volume of errors too high
111 # - runtime/virtual : Broken now, but can be fixed in the future?
112 # - whitespace/braces : We have a lot of explicit scoping in chrome code.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000113 cpplint._SetFilters('-build/include,-build/include_order,-build/namespace,'
114 '-readability/casting,-runtime/int,-runtime/virtual,'
115 '-whitespace/braces')
erg@google.com26970fa2009-11-17 18:07:32 +0000116
117 # We currently are more strict with normal code than unit tests; 4 and 5 are
118 # the verbosity level that would normally be passed to cpplint.py through
119 # --verbose=#. Hopefully, in the future, we can be more verbose.
120 files = [f.AbsoluteLocalPath() for f in
121 input_api.AffectedSourceFiles(source_file_filter)]
122 for file_name in files:
123 if _RE_IS_TEST.match(file_name):
124 level = 5
125 else:
126 level = 4
127
128 cpplint.ProcessFile(file_name, level)
129
130 if cpplint._cpplint_state.error_count > 0:
131 if input_api.is_committing:
132 res_type = output_api.PresubmitError
133 else:
134 res_type = output_api.PresubmitPromptWarning
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000135 result = [res_type('Changelist failed cpplint.py check.')]
erg@google.com26970fa2009-11-17 18:07:32 +0000136
137 return result
138
139
maruel@chromium.org3410d912009-06-09 20:56:16 +0000140def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000141 """Checks no '\r' (CR) character is in any source files."""
142 cr_files = []
maruel@chromium.org3410d912009-06-09 20:56:16 +0000143 for f in input_api.AffectedSourceFiles(source_file_filter):
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000144 if '\r' in input_api.ReadFile(f, 'rb'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000145 cr_files.append(f.LocalPath())
146 if cr_files:
147 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000148 'Found a CR character in these files:', items=cr_files)]
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000149 return []
150
151
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000152def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None):
153 """Checks for files in svn modified directories.
154
155 They will get submitted on accident because svn commits recursively by
156 default, and that's very dangerous.
157 """
158 if input_api.change.scm != 'svn':
159 return []
160
161 errors = []
162 current_cl_files = input_api.change.GetModifiedFiles()
163 all_modified_files = input_api.change.GetAllModifiedFiles()
164 # Filter out files in the current CL.
165 modified_files = [f for f in all_modified_files if f not in current_cl_files]
166 modified_abspaths = [input_api.os_path.abspath(f) for f in modified_files]
167
sail@chromium.org5538e022011-05-12 17:53:16 +0000168 for f in input_api.AffectedFiles(file_filter=source_file_filter):
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000169 if f.Action() == 'M' and f.IsDirectory():
170 curpath = f.AbsoluteLocalPath()
171 bad_files = []
172 # Check if any of the modified files in other CLs are under curpath.
173 for i in xrange(len(modified_files)):
174 abspath = modified_abspaths[i]
175 if input_api.os_path.commonprefix([curpath, abspath]) == curpath:
176 bad_files.append(modified_files[i])
177 if bad_files:
178 if input_api.is_committing:
179 error_type = output_api.PresubmitPromptWarning
180 else:
181 error_type = output_api.PresubmitNotifyResult
182 errors.append(error_type(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000183 'Potential accidental commits in changelist %s:' % f.LocalPath(),
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000184 items=bad_files))
185 return errors
186
187
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000188def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
189 """Checks the files ends with one and only one \n (LF)."""
190 eof_files = []
191 for f in input_api.AffectedSourceFiles(source_file_filter):
192 contents = input_api.ReadFile(f, 'rb')
193 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000194 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000195 eof_files.append(f.LocalPath())
196
197 if eof_files:
198 return [output_api.PresubmitPromptWarning(
199 'These files should end in one (and only one) newline character:',
200 items=eof_files)]
201 return []
202
203
204def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api,
205 source_file_filter=None):
206 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass.
207
208 It is faster because it is reading the file only once.
209 """
210 cr_files = []
211 eof_files = []
212 for f in input_api.AffectedSourceFiles(source_file_filter):
213 contents = input_api.ReadFile(f, 'rb')
214 if '\r' in contents:
215 cr_files.append(f.LocalPath())
216 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000217 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000218 eof_files.append(f.LocalPath())
219 outputs = []
220 if cr_files:
221 outputs.append(output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000222 'Found a CR character in these files:', items=cr_files))
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000223 if eof_files:
224 outputs.append(output_api.PresubmitPromptWarning(
225 'These files should end in one (and only one) newline character:',
226 items=eof_files))
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000227 return outputs
228
229
chrisha@google.com267d6592012-06-19 19:23:31 +0000230def _ReportErrorFileAndLine(filename, line_num, dummy_line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000231 """Default error formatter for _FindNewViolationsOfRule."""
232 return '%s, line %s' % (filename, line_num)
233
234
235def _FindNewViolationsOfRule(callable_rule, input_api, source_file_filter=None,
236 error_formatter=_ReportErrorFileAndLine):
237 """Find all newly introduced violations of a per-line rule (a callable).
238
239 Arguments:
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000240 callable_rule: a callable taking a file extension and line of input and
241 returning True if the rule is satisfied and False if there was a problem.
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000242 input_api: object to enumerate the affected files.
243 source_file_filter: a filter to be passed to the input api.
244 error_formatter: a callable taking (filename, line_number, line) and
245 returning a formatted error string.
246
247 Returns:
248 A list of the newly-introduced violations reported by the rule.
249 """
250 errors = []
sail@chromium.org5538e022011-05-12 17:53:16 +0000251 for f in input_api.AffectedFiles(include_deletes=False,
252 file_filter=source_file_filter):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000253 # For speed, we do two passes, checking first the full file. Shelling out
254 # to the SCM to determine the changed region can be quite expensive on
255 # Win32. Assuming that most files will be kept problem-free, we can
256 # skip the SCM operations most of the time.
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000257 extension = str(f.LocalPath()).rsplit('.', 1)[-1]
258 if all(callable_rule(extension, line) for line in f.NewContents()):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000259 continue # No violation found in full text: can skip considering diff.
260
261 for line_num, line in f.ChangedContents():
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000262 if not callable_rule(extension, line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000263 errors.append(error_formatter(f.LocalPath(), line_num, line))
264
265 return errors
266
267
maruel@chromium.org3410d912009-06-09 20:56:16 +0000268def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000269 """Checks that there are no tab characters in any of the text files to be
270 submitted.
271 """
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000272 # In addition to the filter, make sure that makefiles are blacklisted.
273 if not source_file_filter:
274 # It's the default filter.
275 source_file_filter = input_api.FilterSourceFile
276 def filter_more(affected_file):
cmp@chromium.orgcb5e57c2012-04-06 19:50:15 +0000277 basename = input_api.os_path.basename(affected_file.LocalPath())
278 return (not (basename in ('Makefile', 'makefile') or
279 basename.endswith('.mk')) and
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000280 source_file_filter(affected_file))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000281
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000282 tabs = _FindNewViolationsOfRule(lambda _, line : '\t' not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000283 input_api, filter_more)
284
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000285 if tabs:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000286 return [output_api.PresubmitPromptWarning('Found a tab character in:',
287 long_text='\n'.join(tabs))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000288 return []
289
290
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000291def CheckChangeTodoHasOwner(input_api, output_api, source_file_filter=None):
292 """Checks that the user didn't add TODO(name) without an owner."""
293
maruel@chromium.org07ab60e2011-02-08 21:54:00 +0000294 unowned_todo = input_api.re.compile('TO' + 'DO[^(]')
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000295 errors = _FindNewViolationsOfRule(lambda _, x : not unowned_todo.search(x),
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000296 input_api, source_file_filter)
297 errors = ['Found TO' + 'DO with no owner in ' + x for x in errors]
298 if errors:
299 return [output_api.PresubmitPromptWarning('\n'.join(errors))]
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000300 return []
301
302
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000303def CheckChangeHasNoStrayWhitespace(input_api, output_api,
304 source_file_filter=None):
305 """Checks that there is no stray whitespace at source lines end."""
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000306 errors = _FindNewViolationsOfRule(lambda _, line : line.rstrip() == line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000307 input_api, source_file_filter)
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000308 if errors:
309 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000310 'Found line ending with white spaces in:',
311 long_text='\n'.join(errors))]
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000312 return []
313
314
maruel@chromium.org3410d912009-06-09 20:56:16 +0000315def CheckLongLines(input_api, output_api, maxlen=80, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000316 """Checks that there aren't any lines longer than maxlen characters in any of
317 the text files to be submitted.
318 """
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000319 maxlens = {
320 'java': 100,
321 '': maxlen,
322 }
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000323 # Note: these are C++ specific but processed on all languages. :(
324 MACROS = ('#define', '#include', '#import', '#pragma', '#if', '#endif')
325
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000326 def no_long_lines(file_extension, line):
327 file_maxlen = maxlens.get(file_extension, maxlens[''])
328 # Stupidly long symbols that needs to be worked around if takes 66% of line.
329 long_symbol = file_maxlen * 2 / 3
330 # Hard line length limit at 50% more.
331 extra_maxlen = file_maxlen * 3 / 2
332
333 line_len = len(line)
334 if line_len <= file_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000335 return True
336
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000337 if line_len > extra_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000338 return False
339
340 return (
341 line.startswith(MACROS) or
342 any((url in line) for url in ('http://', 'https://')) or
343 input_api.re.match(
344 r'.*[A-Za-z][A-Za-z_0-9]{%d,}.*' % long_symbol, line))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000345
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000346 def format_error(filename, line_num, line):
347 return '%s, line %s, %s chars' % (filename, line_num, len(line))
348
349 errors = _FindNewViolationsOfRule(no_long_lines, input_api,
350 source_file_filter,
351 error_formatter=format_error)
352 if errors:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000353 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000354 return [output_api.PresubmitPromptWarning(msg, items=errors[:5])]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000355 else:
356 return []
357
358
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000359def CheckLicense(input_api, output_api, license_re, source_file_filter=None,
maruel@chromium.org71626852010-11-03 13:14:25 +0000360 accept_empty_files=True):
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000361 """Verifies the license header.
362 """
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000363 license_re = input_api.re.compile(license_re, input_api.re.MULTILINE)
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000364 bad_files = []
365 for f in input_api.AffectedSourceFiles(source_file_filter):
366 contents = input_api.ReadFile(f, 'rb')
maruel@chromium.org71626852010-11-03 13:14:25 +0000367 if accept_empty_files and not contents:
368 continue
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000369 if not license_re.search(contents):
370 bad_files.append(f.LocalPath())
371 if bad_files:
372 if input_api.is_committing:
373 res_type = output_api.PresubmitPromptWarning
374 else:
375 res_type = output_api.PresubmitNotifyResult
376 return [res_type(
bradnelson@google.com393460b2011-03-29 01:20:26 +0000377 'License must match:\n%s\n' % license_re.pattern +
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000378 'Found a bad license header in these files:', items=bad_files)]
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000379 return []
380
381
maruel@chromium.org1a0e3cb2009-06-10 18:03:04 +0000382def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None):
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000383 """Checks that the source files have svn:eol-style=LF."""
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000384 return CheckSvnProperty(input_api, output_api,
385 'svn:eol-style', 'LF',
386 input_api.AffectedSourceFiles(source_file_filter))
387
388
389def CheckSvnForCommonMimeTypes(input_api, output_api):
390 """Checks that common binary file types have the correct svn:mime-type."""
391 output = []
392 files = input_api.AffectedFiles(include_deletes=False)
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000393 def IsExts(x, exts):
394 path = x.LocalPath()
395 for extension in exts:
396 if path.endswith(extension):
397 return True
398 return False
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000399 def FilterFiles(extension):
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000400 return filter(lambda x: IsExts(x, extension), files)
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000401 def RunCheck(mime_type, files):
402 output.extend(CheckSvnProperty(input_api, output_api, 'svn:mime-type',
403 mime_type, files))
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000404 RunCheck('application/pdf', FilterFiles(['.pdf']))
405 RunCheck('image/bmp', FilterFiles(['.bmp']))
406 RunCheck('image/gif', FilterFiles(['.gif']))
407 RunCheck('image/png', FilterFiles(['.png']))
408 RunCheck('image/jpeg', FilterFiles(['.jpg', '.jpeg', '.jpe']))
409 RunCheck('image/vnd.microsoft.icon', FilterFiles(['.ico']))
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000410 return output
411
412
413def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
414 """Checks that affected_files files have prop=expected."""
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000415 if input_api.change.scm != 'svn':
416 return []
417
418 bad = filter(lambda f: f.Property(prop) != expected, affected_files)
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000419 if bad:
maruel@chromium.org0874d472009-06-10 19:08:33 +0000420 if input_api.is_committing:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000421 res_type = output_api.PresubmitError
maruel@chromium.org0874d472009-06-10 19:08:33 +0000422 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000423 res_type = output_api.PresubmitNotifyResult
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000424 message = 'Run the command: svn pset %s %s \\' % (prop, expected)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000425 return [res_type(message, items=bad)]
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000426 return []
427
428
maruel@chromium.org3410d912009-06-09 20:56:16 +0000429### Other checks
430
431def CheckDoNotSubmit(input_api, output_api):
432 return (
433 CheckDoNotSubmitInDescription(input_api, output_api) +
434 CheckDoNotSubmitInFiles(input_api, output_api)
435 )
436
437
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000438def CheckTreeIsOpen(input_api, output_api,
439 url=None, closed=None, json_url=None):
440 """Check whether to allow commit without prompt.
441
442 Supports two styles:
443 1. Checks that an url's content doesn't match a regexp that would mean that
444 the tree is closed. (old)
445 2. Check the json_url to decide whether to allow commit without prompt.
446 Args:
447 input_api: input related apis.
448 output_api: output related apis.
449 url: url to use for regex based tree status.
450 closed: regex to match for closed status.
451 json_url: url to download json style status.
452 """
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000453 if not input_api.is_committing:
454 return []
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000455 try:
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000456 if json_url:
457 connection = input_api.urllib2.urlopen(json_url)
458 status = input_api.json.loads(connection.read())
459 connection.close()
460 if not status['can_commit_freely']:
461 short_text = 'Tree state is: ' + status['general_state']
462 long_text = status['message'] + '\n' + json_url
463 return [output_api.PresubmitError(short_text, long_text=long_text)]
464 else:
465 # TODO(bradnelson): drop this once all users are gone.
466 connection = input_api.urllib2.urlopen(url)
467 status = connection.read()
468 connection.close()
469 if input_api.re.match(closed, status):
470 long_text = status + '\n' + url
471 return [output_api.PresubmitError('The tree is closed.',
472 long_text=long_text)]
rohitrao@chromium.orgd490ee82012-02-06 19:31:33 +0000473 except IOError as e:
474 return [output_api.PresubmitError('Error fetching tree status.',
475 long_text=str(e))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000476 return []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000477
478
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000479def RunUnitTestsInDirectory(
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000480 input_api, output_api, directory, whitelist=None, blacklist=None):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000481 """Lists all files in a directory and runs them. Doesn't recurse.
482
483 It's mainly a wrapper for RunUnitTests. USe whitelist and blacklist to filter
484 tests accordingly.
485 """
486 unit_tests = []
487 test_path = input_api.os_path.abspath(
488 input_api.os_path.join(input_api.PresubmitLocalPath(), directory))
489
490 def check(filename, filters):
491 return any(True for i in filters if input_api.re.match(i, filename))
492
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000493 to_run = found = 0
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000494 for filename in input_api.os_listdir(test_path):
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000495 found += 1
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000496 fullpath = input_api.os_path.join(test_path, filename)
497 if not input_api.os_path.isfile(fullpath):
498 continue
499 if whitelist and not check(filename, whitelist):
500 continue
501 if blacklist and check(filename, blacklist):
502 continue
503 unit_tests.append(input_api.os_path.join(directory, filename))
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000504 to_run += 1
505 input_api.logging.debug('Found %d files, running %d' % (found, to_run))
506 if not to_run:
507 return [
508 output_api.PresubmitPromptWarning(
509 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
510 % (found, whitelist, blacklist, directory))
511 ]
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000512 return RunUnitTests(input_api, output_api, unit_tests)
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000513
514
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000515def RunUnitTests(input_api, output_api, unit_tests):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000516 """Runs all unit tests in a directory.
517
518 On Windows, sys.executable is used for unit tests ending with ".py".
519 """
520 # We don't want to hinder users from uploading incomplete patches.
521 if input_api.is_committing:
522 message_type = output_api.PresubmitError
523 else:
524 message_type = output_api.PresubmitPromptWarning
525
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000526 results = []
527 for unit_test in unit_tests:
528 cmd = []
529 if input_api.platform == 'win32' and unit_test.endswith('.py'):
530 # Windows needs some help.
531 cmd = [input_api.python_executable]
532 cmd.append(unit_test)
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000533 if input_api.verbose:
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000534 print('Running %s' % unit_test)
maruel@chromium.org6c7723e2011-04-12 19:04:55 +0000535 cmd.append('--verbose')
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000536 try:
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000537 if input_api.verbose:
maruel@chromium.org0e766052011-04-06 13:32:51 +0000538 input_api.subprocess.check_call(cmd, cwd=input_api.PresubmitLocalPath())
539 else:
540 input_api.subprocess.check_output(
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000541 cmd,
542 stderr=input_api.subprocess.STDOUT,
543 cwd=input_api.PresubmitLocalPath())
maruel@chromium.org0e766052011-04-06 13:32:51 +0000544 except (OSError, input_api.subprocess.CalledProcessError), e:
545 results.append(message_type('%s failed!\n%s' % (unit_test, e)))
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000546 return results
547
548
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000549def RunPythonUnitTests(input_api, output_api, unit_tests):
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000550 """Run the unit tests out of process, capture the output and use the result
551 code to determine success.
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000552
553 DEPRECATED.
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000554 """
maruel@chromium.orgd7dccf52009-06-06 18:51:58 +0000555 # We don't want to hinder users from uploading incomplete patches.
556 if input_api.is_committing:
557 message_type = output_api.PresubmitError
558 else:
559 message_type = output_api.PresubmitNotifyResult
maruel@chromium.org0e766052011-04-06 13:32:51 +0000560 results = []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000561 for unit_test in unit_tests:
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000562 # Run the unit tests out of process. This is because some unit tests
563 # stub out base libraries and don't clean up their mess. It's too easy to
564 # get subtle bugs.
565 cwd = None
566 env = None
567 unit_test_name = unit_test
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000568 # 'python -m test.unit_test' doesn't work. We need to change to the right
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000569 # directory instead.
570 if '.' in unit_test:
571 # Tests imported in submodules (subdirectories) assume that the current
572 # directory is in the PYTHONPATH. Manually fix that.
573 unit_test = unit_test.replace('.', '/')
574 cwd = input_api.os_path.dirname(unit_test)
575 unit_test = input_api.os_path.basename(unit_test)
576 env = input_api.environ.copy()
kbr@google.comab318592009-09-04 00:54:55 +0000577 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
578 backpath = [
579 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
580 ]
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000581 if env.get('PYTHONPATH'):
582 backpath.append(env.get('PYTHONPATH'))
ukai@chromium.orga301f1f2009-08-05 10:37:33 +0000583 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000584 cmd = [input_api.python_executable, '-m', '%s' % unit_test]
585 try:
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000586 input_api.subprocess.check_output(
587 cmd, stderr=input_api.subprocess.STDOUT, cwd=cwd, env=env)
maruel@chromium.org0e766052011-04-06 13:32:51 +0000588 except (OSError, input_api.subprocess.CalledProcessError), e:
589 results.append(message_type('%s failed!\n%s' % (unit_test_name, e)))
590 return results
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000591
592
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000593def _FetchAllFiles(input_api, white_list, black_list):
594 """Hack to fetch all files."""
595 # We cannot use AffectedFiles here because we want to test every python
596 # file on each single python change. It's because a change in a python file
597 # can break another unmodified file.
598 # Use code similar to InputApi.FilterSourceFile()
599 def Find(filepath, filters):
600 for item in filters:
601 if input_api.re.match(item, filepath):
602 return True
603 return False
604
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000605 files = []
606 path_len = len(input_api.PresubmitLocalPath())
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000607 for dirpath, dirnames, filenames in input_api.os_walk(
608 input_api.PresubmitLocalPath()):
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000609 # Passes dirnames in black list to speed up search.
610 for item in dirnames[:]:
611 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
612 if Find(filepath, black_list):
613 dirnames.remove(item)
614 for item in filenames:
615 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
616 if Find(filepath, white_list) and not Find(filepath, black_list):
617 files.append(filepath)
618 return files
619
620
maruel@chromium.orgff9a2172012-04-24 16:55:32 +0000621def RunPylint(input_api, output_api, white_list=None, black_list=None,
622 disabled_warnings=None):
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000623 """Run pylint on python files.
624
chrisha@google.com267d6592012-06-19 19:23:31 +0000625 The default white_list enforces looking only at *.py files.
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000626 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000627 white_list = tuple(white_list or ('.*\.py$',))
628 black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000629 if input_api.is_committing:
630 error_type = output_api.PresubmitError
631 else:
632 error_type = output_api.PresubmitPromptWarning
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000633
634 # Only trigger if there is at least one python file affected.
635 src_filter = lambda x: input_api.FilterSourceFile(x, white_list, black_list)
636 if not input_api.AffectedSourceFiles(src_filter):
637 return []
638
maruel@chromium.orgff9a2172012-04-24 16:55:32 +0000639 extra_args = ['--rcfile=%s' % input_api.os_path.join(_HERE, 'pylintrc')]
640 if disabled_warnings:
641 extra_args.extend(['-d', ','.join(disabled_warnings)])
642
chrisha@google.com267d6592012-06-19 19:23:31 +0000643 files = _FetchAllFiles(input_api, white_list, black_list)
644 if not files:
maruel@chromium.orgfca53392010-12-21 18:42:57 +0000645 return []
chrisha@google.com267d6592012-06-19 19:23:31 +0000646
647 # Copy the system path to the environment so pylint can find the right
648 # imports.
649 env = input_api.environ.copy()
650 import sys
651 env['PYTHONPATH'] = input_api.os_path.pathsep.join(sys.path)
652
653 def run_lint(files):
654 # We can't import pylint directly due to licensing issues, so we run
655 # it in another process. Windows needs help running python files so we
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000656 # explicitly specify the interpreter to use. It also has limitations on
657 # the size of the command-line, so we pass arguments via a pipe.
chrisha@google.com267d6592012-06-19 19:23:31 +0000658 command = [input_api.python_executable,
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000659 input_api.os_path.join(_HERE, 'third_party', 'pylint.py'),
660 '--args-on-stdin']
chrisha@google.com267d6592012-06-19 19:23:31 +0000661 try:
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000662 child = input_api.subprocess.Popen(command, env=env,
663 stdin=input_api.subprocess.PIPE)
664
665 # Dump the arguments to the child process via a pipe.
666 for filename in files:
667 child.stdin.write(filename + '\n')
668 for arg in extra_args:
669 child.stdin.write(arg + '\n')
670 child.stdin.close()
671
672 child.communicate()
673 return child.returncode
chrisha@google.com267d6592012-06-19 19:23:31 +0000674 except OSError:
675 return 'Pylint failed!'
676
677 result = None
678 if not input_api.verbose:
679 result = run_lint(sorted(files))
680 else:
681 for filename in sorted(files):
682 print('Running pylint on %s' % filename)
683 result = run_lint([filename]) or result
684 if isinstance(result, basestring):
685 return [error_type(result)]
686 elif result:
687 return [error_type('Fix pylint errors first.')]
688 return []
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000689
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000690
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000691# TODO(dpranke): Get the host_url from the input_api instead
chrisha@google.com267d6592012-06-19 19:23:31 +0000692def CheckRietveldTryJobExecution(dummy_input_api, dummy_output_api,
693 dummy_host_url, dummy_platforms,
694 dummy_owner):
maruel@chromium.org85da74b2011-10-27 17:13:30 +0000695 # Temporarily 'fix' the check while the Rietveld API is being upgraded to
696 # something sensible.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000697 return []
698
699
700def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
701 ignored):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000702 try:
703 connection = input_api.urllib2.urlopen(url)
704 raw_data = connection.read()
705 connection.close()
706 except IOError:
707 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)]
708
709 try:
710 data = input_api.json.loads(raw_data)
711 except ValueError:
712 return [output_api.PresubmitNotifyResult('Received malformed json while '
713 'looking up buildbot status')]
714
715 out = []
716 for (builder_name, builder) in data.iteritems():
717 if builder_name in ignored:
718 continue
maruel@chromium.orgcf1982c2010-10-04 15:08:28 +0000719 if builder.get('state', '') == 'offline':
720 continue
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000721 pending_builds_len = len(builder.get('pending_builds', []))
722 if pending_builds_len > max_pendings:
723 out.append('%s has %d build(s) pending' %
724 (builder_name, pending_builds_len))
725 if out:
726 return [output_api.PresubmitPromptWarning(
727 'Build(s) pending. It is suggested to wait that no more than %d '
728 'builds are pending.' % max_pendings,
729 long_text='\n'.join(out))]
730 return []
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000731
732
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000733def CheckOwners(input_api, output_api, source_file_filter=None):
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000734 if input_api.is_committing:
735 if input_api.tbr:
736 return [output_api.PresubmitNotifyResult(
737 '--tbr was specified, skipping OWNERS check')]
738 if not input_api.change.issue:
739 return [output_api.PresubmitError("OWNERS check failed: this change has "
740 "no Rietveld issue number, so we can't check it for approvals.")]
741 needed = 'LGTM from an OWNER'
742 output = output_api.PresubmitError
743 else:
744 needed = 'OWNER reviewers'
745 output = output_api.PresubmitNotifyResult
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000746
dpranke@chromium.orgadd5df42011-03-08 23:04:01 +0000747 affected_files = set([f.LocalPath() for f in
sail@chromium.org5538e022011-05-12 17:53:16 +0000748 input_api.change.AffectedFiles(file_filter=source_file_filter)])
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000749
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000750 owners_db = input_api.owners_db
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000751 owner_email, reviewers = _RietveldOwnerAndReviewers(
752 input_api,
753 owners_db.email_regexp,
754 approval_needed=input_api.is_committing)
755
756 if owner_email:
pam@chromium.orgaba67762012-03-15 12:57:44 +0000757 message = ''
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000758 reviewers_plus_owner = reviewers.union(set([owner_email]))
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000759 else:
pam@chromium.orgaba67762012-03-15 12:57:44 +0000760 message = ('\nUntil the issue is uploaded, this list will include '
761 'directories for which you \nare an OWNER.')
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000762 owner_email = ''
763 reviewers_plus_owner = set()
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000764
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000765 missing_directories = owners_db.directories_not_covered_by(affected_files,
766 reviewers_plus_owner)
767 if missing_directories:
zork@chromium.org046e1752012-05-07 05:56:12 +0000768 output_list = [
769 output('Missing %s for files in these directories:\n %s%s' %
770 (needed, '\n '.join(missing_directories), message))]
771 if not input_api.is_committing:
772 suggested_owners = owners_db.reviewers_for(affected_files)
773 output_list.append(output('Suggested OWNERS:\n %s' %
774 ('\n '.join(suggested_owners))))
775 return output_list
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000776
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000777 if input_api.is_committing and not reviewers:
778 return [output('Missing LGTM from someone other than %s' % owner_email)]
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000779 return []
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000780
781
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000782def _RietveldOwnerAndReviewers(input_api, email_regexp, approval_needed=False):
783 """Return the owner and reviewers of a change, if any.
784
785 If approval_needed is True, only reviewers who have approved the change
786 will be returned.
787 """
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000788 if not input_api.change.issue:
789 return None, None
790
791 issue_props = input_api.rietveld.get_issue_properties(
792 int(input_api.change.issue), True)
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000793 if not approval_needed:
794 return issue_props['owner_email'], set(issue_props['reviewers'])
795
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000796 owner_email = issue_props['owner_email']
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000797
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000798 def match_reviewer(r):
maruel@chromium.org80941c22011-05-30 20:14:18 +0000799 return email_regexp.match(r) and r != owner_email
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000800
maruel@chromium.org80941c22011-05-30 20:14:18 +0000801 messages = issue_props.get('messages', [])
802 approvers = set(
803 m['sender'] for m in messages
804 if m.get('approval') and match_reviewer(m['sender']))
805
806 return owner_email, approvers
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000807
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000808
809def _CheckConstNSObject(input_api, output_api, source_file_filter):
810 """Checks to make sure no objective-c files have |const NSSomeClass*|."""
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000811 pattern = input_api.re.compile(
812 r'const\s+NS(?!(Point|Range|Rect|Size)\s*\*)\w*\s*\*')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000813
814 def objective_c_filter(f):
815 return (source_file_filter(f) and
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000816 input_api.os_path.splitext(f.LocalPath())[1] in ('.h', '.m', '.mm'))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000817
818 files = []
819 for f in input_api.AffectedSourceFiles(objective_c_filter):
820 contents = input_api.ReadFile(f)
821 if pattern.search(contents):
822 files.append(f)
823
824 if files:
825 if input_api.is_committing:
826 res_type = output_api.PresubmitPromptWarning
827 else:
828 res_type = output_api.PresubmitNotifyResult
829 return [ res_type('|const NSClass*| is wrong, see ' +
830 'http://dev.chromium.org/developers/clang-mac',
831 files) ]
832 return []
833
834
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000835def CheckSingletonInHeaders(input_api, output_api, source_file_filter=None):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000836 """Checks to make sure no header files have |Singleton<|."""
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000837 pattern = input_api.re.compile(r'(?<!class\s)Singleton\s*<')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000838 files = []
839 for f in input_api.AffectedSourceFiles(source_file_filter):
840 if (f.LocalPath().endswith('.h') or f.LocalPath().endswith('.hxx') or
841 f.LocalPath().endswith('.hpp') or f.LocalPath().endswith('.inl')):
842 contents = input_api.ReadFile(f)
dilmah@chromium.orgd22b9702011-08-26 13:53:49 +0000843 for line in contents.splitlines(False):
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000844 if (not input_api.re.match(r'//', line) and # Strip C++ comment.
845 pattern.search(line)):
dilmah@chromium.orgd22b9702011-08-26 13:53:49 +0000846 files.append(f)
847 break
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000848
849 if files:
850 return [ output_api.PresubmitError(
851 'Found Singleton<T> in the following header files.\n' +
852 'Please move them to an appropriate source file so that the ' +
853 'template gets instantiated in a single compilation unit.',
854 files) ]
855 return []
856
857
858def PanProjectChecks(input_api, output_api,
859 excluded_paths=None, text_files=None,
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000860 license_header=None, project_name=None,
861 owners_check=True):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000862 """Checks that ALL chromium orbit projects should use.
863
864 These are checks to be run on all Chromium orbit project, including:
865 Chromium
866 Native Client
867 V8
868 When you update this function, please take this broad scope into account.
869 Args:
870 input_api: Bag of input related interfaces.
871 output_api: Bag of output related interfaces.
872 excluded_paths: Don't include these paths in common checks.
873 text_files: Which file are to be treated as documentation text files.
874 license_header: What license header should be on files.
875 project_name: What is the name of the project as it appears in the license.
876 Returns:
877 A list of warning or error objects.
878 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000879 excluded_paths = tuple(excluded_paths or [])
880 text_files = tuple(text_files or (
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +0000881 r'.+\.txt$',
882 r'.+\.json$',
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000883 ))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000884 project_name = project_name or 'Chromium'
885 license_header = license_header or (
886 r'.*? Copyright \(c\) %(year)s The %(project)s Authors\. '
887 r'All rights reserved\.\n'
888 r'.*? Use of this source code is governed by a BSD-style license that '
889 r'can be\n'
dbeam@chromium.orgb2312102012-02-15 02:01:55 +0000890 r'.*? found in the LICENSE file\.(?: \*/)?\n'
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000891 ) % {
dpranke@chromium.org0d1bdea2011-03-24 22:54:38 +0000892 'year': input_api.time.strftime('%Y'),
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000893 'project': project_name,
894 }
895
896 results = []
897 # This code loads the default black list (e.g. third_party, experimental, etc)
898 # and add our black list (breakpad, skia and v8 are still not following
899 # google style and are not really living this repository).
900 # See presubmit_support.py InputApi.FilterSourceFile for the (simple) usage.
901 black_list = input_api.DEFAULT_BLACK_LIST + excluded_paths
902 white_list = input_api.DEFAULT_WHITE_LIST + text_files
903 sources = lambda x: input_api.FilterSourceFile(x, black_list=black_list)
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +0000904 text_files = lambda x: input_api.FilterSourceFile(
905 x, black_list=black_list, white_list=white_list)
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000906
907 snapshot_memory = []
908 def snapshot(msg):
909 """Measures & prints performance warning if a rule is running slow."""
910 dt2 = input_api.time.clock()
911 if snapshot_memory:
912 delta_ms = int(1000*(dt2 - snapshot_memory[0]))
913 if delta_ms > 500:
914 print " %s took a long time: %dms" % (snapshot_memory[1], delta_ms)
915 snapshot_memory[:] = (dt2, msg)
916
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000917 if owners_check:
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000918 snapshot("checking owners")
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000919 results.extend(input_api.canned_checks.CheckOwners(
dpranke@chromium.org751797a2011-06-07 18:46:27 +0000920 input_api, output_api, source_file_filter=None))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000921
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000922 snapshot("checking long lines")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000923 results.extend(input_api.canned_checks.CheckLongLines(
924 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000925 snapshot( "checking tabs")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000926 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
927 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000928 snapshot( "checking stray whitespace")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000929 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
930 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000931 snapshot("checking nsobjects")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000932 results.extend(_CheckConstNSObject(
933 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000934 snapshot("checking singletons")
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000935 results.extend(CheckSingletonInHeaders(
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000936 input_api, output_api, source_file_filter=sources))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +0000937
938 # The following checks are only done on commit, since the commit bot will
939 # auto-fix most of these.
940 if input_api.is_committing:
maruel@chromium.org8571dac2011-05-10 18:10:13 +0000941 snapshot("checking eol style")
942 results.extend(input_api.canned_checks.CheckChangeSvnEolStyle(
943 input_api, output_api, source_file_filter=text_files))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +0000944 snapshot("checking svn mime types")
945 results.extend(input_api.canned_checks.CheckSvnForCommonMimeTypes(
946 input_api, output_api))
947 snapshot("checking license")
948 results.extend(input_api.canned_checks.CheckLicense(
949 input_api, output_api, license_header, source_file_filter=sources))
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +0000950 snapshot("checking was uploaded")
951 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
952 input_api, output_api))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000953 snapshot("done")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000954 return results