blob: 878ee70a68ff71b12bed82dd3082a90cae7219fd [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):
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +000049 """Checks that the user didn't add 'DO NOT ''SUBMIT' to the CL description.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000050 """
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +000051 keyword = 'DO NOT ''SUBMIT'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000052 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):
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +000081 """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
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +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
enne@chromium.orge72c5f52013-04-16 00:36:40 +000098 cpplint = input_api.cpplint
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +000099 # Access to a protected member _XX of a client class
100 # pylint: disable=W0212
erg@google.com26970fa2009-11-17 18:07:32 +0000101 cpplint._cpplint_state.ResetErrorCounts()
102
103 # Justifications for each filter:
104 #
105 # - build/include : Too many; fix in the future.
106 # - build/include_order : Not happening; #ifdefed includes.
107 # - build/namespace : I'm surprised by how often we violate this rule.
108 # - readability/casting : Mistakes a whole bunch of function pointer.
109 # - runtime/int : Can be fixed long term; volume of errors too high
110 # - runtime/virtual : Broken now, but can be fixed in the future?
111 # - whitespace/braces : We have a lot of explicit scoping in chrome code.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000112 cpplint._SetFilters('-build/include,-build/include_order,-build/namespace,'
113 '-readability/casting,-runtime/int,-runtime/virtual,'
114 '-whitespace/braces')
erg@google.com26970fa2009-11-17 18:07:32 +0000115
116 # We currently are more strict with normal code than unit tests; 4 and 5 are
117 # the verbosity level that would normally be passed to cpplint.py through
118 # --verbose=#. Hopefully, in the future, we can be more verbose.
119 files = [f.AbsoluteLocalPath() for f in
120 input_api.AffectedSourceFiles(source_file_filter)]
121 for file_name in files:
122 if _RE_IS_TEST.match(file_name):
123 level = 5
124 else:
125 level = 4
126
127 cpplint.ProcessFile(file_name, level)
128
129 if cpplint._cpplint_state.error_count > 0:
130 if input_api.is_committing:
131 res_type = output_api.PresubmitError
132 else:
133 res_type = output_api.PresubmitPromptWarning
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000134 result = [res_type('Changelist failed cpplint.py check.')]
erg@google.com26970fa2009-11-17 18:07:32 +0000135
136 return result
137
138
maruel@chromium.org3410d912009-06-09 20:56:16 +0000139def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000140 """Checks no '\r' (CR) character is in any source files."""
141 cr_files = []
maruel@chromium.org3410d912009-06-09 20:56:16 +0000142 for f in input_api.AffectedSourceFiles(source_file_filter):
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000143 if '\r' in input_api.ReadFile(f, 'rb'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000144 cr_files.append(f.LocalPath())
145 if cr_files:
146 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000147 'Found a CR character in these files:', items=cr_files)]
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000148 return []
149
150
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000151def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None):
152 """Checks for files in svn modified directories.
153
154 They will get submitted on accident because svn commits recursively by
155 default, and that's very dangerous.
156 """
157 if input_api.change.scm != 'svn':
158 return []
159
160 errors = []
161 current_cl_files = input_api.change.GetModifiedFiles()
162 all_modified_files = input_api.change.GetAllModifiedFiles()
163 # Filter out files in the current CL.
164 modified_files = [f for f in all_modified_files if f not in current_cl_files]
165 modified_abspaths = [input_api.os_path.abspath(f) for f in modified_files]
166
sail@chromium.org5538e022011-05-12 17:53:16 +0000167 for f in input_api.AffectedFiles(file_filter=source_file_filter):
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000168 if f.Action() == 'M' and f.IsDirectory():
169 curpath = f.AbsoluteLocalPath()
170 bad_files = []
171 # Check if any of the modified files in other CLs are under curpath.
172 for i in xrange(len(modified_files)):
173 abspath = modified_abspaths[i]
174 if input_api.os_path.commonprefix([curpath, abspath]) == curpath:
175 bad_files.append(modified_files[i])
176 if bad_files:
177 if input_api.is_committing:
178 error_type = output_api.PresubmitPromptWarning
179 else:
180 error_type = output_api.PresubmitNotifyResult
181 errors.append(error_type(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000182 'Potential accidental commits in changelist %s:' % f.LocalPath(),
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000183 items=bad_files))
184 return errors
185
186
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000187def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
188 """Checks the files ends with one and only one \n (LF)."""
189 eof_files = []
190 for f in input_api.AffectedSourceFiles(source_file_filter):
191 contents = input_api.ReadFile(f, 'rb')
192 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000193 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000194 eof_files.append(f.LocalPath())
195
196 if eof_files:
197 return [output_api.PresubmitPromptWarning(
198 'These files should end in one (and only one) newline character:',
199 items=eof_files)]
200 return []
201
202
203def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api,
204 source_file_filter=None):
205 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass.
206
207 It is faster because it is reading the file only once.
208 """
209 cr_files = []
210 eof_files = []
211 for f in input_api.AffectedSourceFiles(source_file_filter):
212 contents = input_api.ReadFile(f, 'rb')
213 if '\r' in contents:
214 cr_files.append(f.LocalPath())
215 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000216 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000217 eof_files.append(f.LocalPath())
218 outputs = []
219 if cr_files:
220 outputs.append(output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000221 'Found a CR character in these files:', items=cr_files))
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000222 if eof_files:
223 outputs.append(output_api.PresubmitPromptWarning(
224 'These files should end in one (and only one) newline character:',
225 items=eof_files))
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000226 return outputs
227
228
chrisha@google.com267d6592012-06-19 19:23:31 +0000229def _ReportErrorFileAndLine(filename, line_num, dummy_line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000230 """Default error formatter for _FindNewViolationsOfRule."""
danakj@chromium.orgc5965ba2013-08-14 00:27:24 +0000231 return '%s:%s' % (filename, line_num)
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000232
233
234def _FindNewViolationsOfRule(callable_rule, input_api, source_file_filter=None,
235 error_formatter=_ReportErrorFileAndLine):
236 """Find all newly introduced violations of a per-line rule (a callable).
237
238 Arguments:
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000239 callable_rule: a callable taking a file extension and line of input and
240 returning True if the rule is satisfied and False if there was a problem.
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000241 input_api: object to enumerate the affected files.
242 source_file_filter: a filter to be passed to the input api.
243 error_formatter: a callable taking (filename, line_number, line) and
244 returning a formatted error string.
245
246 Returns:
247 A list of the newly-introduced violations reported by the rule.
248 """
249 errors = []
sail@chromium.org5538e022011-05-12 17:53:16 +0000250 for f in input_api.AffectedFiles(include_deletes=False,
251 file_filter=source_file_filter):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000252 # For speed, we do two passes, checking first the full file. Shelling out
253 # to the SCM to determine the changed region can be quite expensive on
254 # Win32. Assuming that most files will be kept problem-free, we can
255 # skip the SCM operations most of the time.
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000256 extension = str(f.LocalPath()).rsplit('.', 1)[-1]
257 if all(callable_rule(extension, line) for line in f.NewContents()):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000258 continue # No violation found in full text: can skip considering diff.
259
260 for line_num, line in f.ChangedContents():
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000261 if not callable_rule(extension, line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000262 errors.append(error_formatter(f.LocalPath(), line_num, line))
263
264 return errors
265
266
maruel@chromium.org3410d912009-06-09 20:56:16 +0000267def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000268 """Checks that there are no tab characters in any of the text files to be
269 submitted.
270 """
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000271 # In addition to the filter, make sure that makefiles are blacklisted.
272 if not source_file_filter:
273 # It's the default filter.
274 source_file_filter = input_api.FilterSourceFile
275 def filter_more(affected_file):
cmp@chromium.orgcb5e57c2012-04-06 19:50:15 +0000276 basename = input_api.os_path.basename(affected_file.LocalPath())
277 return (not (basename in ('Makefile', 'makefile') or
278 basename.endswith('.mk')) and
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000279 source_file_filter(affected_file))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000280
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000281 tabs = _FindNewViolationsOfRule(lambda _, line : '\t' not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000282 input_api, filter_more)
283
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000284 if tabs:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000285 return [output_api.PresubmitPromptWarning('Found a tab character in:',
286 long_text='\n'.join(tabs))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000287 return []
288
289
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000290def CheckChangeTodoHasOwner(input_api, output_api, source_file_filter=None):
291 """Checks that the user didn't add TODO(name) without an owner."""
292
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000293 unowned_todo = input_api.re.compile('TO''DO[^(]')
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000294 errors = _FindNewViolationsOfRule(lambda _, x : not unowned_todo.search(x),
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000295 input_api, source_file_filter)
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000296 errors = ['Found TO''DO with no owner in ' + x for x in errors]
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000297 if errors:
298 return [output_api.PresubmitPromptWarning('\n'.join(errors))]
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000299 return []
300
301
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000302def CheckChangeHasNoStrayWhitespace(input_api, output_api,
303 source_file_filter=None):
304 """Checks that there is no stray whitespace at source lines end."""
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000305 errors = _FindNewViolationsOfRule(lambda _, line : line.rstrip() == line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000306 input_api, source_file_filter)
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000307 if errors:
308 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000309 'Found line ending with white spaces in:',
310 long_text='\n'.join(errors))]
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000311 return []
312
313
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000314def CheckLongLines(input_api, output_api, maxlen, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000315 """Checks that there aren't any lines longer than maxlen characters in any of
316 the text files to be submitted.
317 """
dpranke@chromium.orge3b1c3d2012-10-20 22:28:14 +0000318 maxlens = {
319 'java': 100,
torne@chromium.org0ecad9c2013-02-15 16:35:16 +0000320 # This is specifically for Android's handwritten makefiles (Android.mk).
321 'mk': 200,
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000322 '': maxlen,
323 }
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000324
erikchen@google.com12816082013-12-03 02:04:20 +0000325 # Language specific exceptions to max line length.
326 # '.h' is considered an obj-c file extension, since OBJC_EXCEPTIONS are a
327 # superset of CPP_EXCEPTIONS.
328 CPP_FILE_EXTS = ('c', 'cc')
329 CPP_EXCEPTIONS = ('#define', '#endif', '#if', '#include', '#pragma')
330 JAVA_FILE_EXTS = ('java',)
331 JAVA_EXCEPTIONS = ('import ', 'package ')
332 OBJC_FILE_EXTS = ('h', 'm', 'mm')
333 OBJC_EXCEPTIONS = ('#define', '#endif', '#if', '#import', '#include',
334 '#pragma')
335
336 LANGUAGE_EXCEPTIONS = [
337 (CPP_FILE_EXTS, CPP_EXCEPTIONS),
338 (JAVA_FILE_EXTS, JAVA_EXCEPTIONS),
339 (OBJC_FILE_EXTS, OBJC_EXCEPTIONS),
340 ]
sivachandra@chromium.org270db5c2012-10-09 22:38:44 +0000341
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000342 def no_long_lines(file_extension, line):
erikchen@google.com12816082013-12-03 02:04:20 +0000343 # Check for language specific exceptions.
344 if any(file_extension in exts and line.startswith(exceptions)
345 for exts, exceptions in LANGUAGE_EXCEPTIONS):
sivachandra@chromium.org270db5c2012-10-09 22:38:44 +0000346 return True
347
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000348 file_maxlen = maxlens.get(file_extension, maxlens[''])
349 # Stupidly long symbols that needs to be worked around if takes 66% of line.
350 long_symbol = file_maxlen * 2 / 3
351 # Hard line length limit at 50% more.
352 extra_maxlen = file_maxlen * 3 / 2
353
354 line_len = len(line)
355 if line_len <= file_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000356 return True
357
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000358 if line_len > extra_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000359 return False
360
361 return (
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000362 any((url in line) for url in ('http://', 'https://')) or
363 input_api.re.match(
364 r'.*[A-Za-z][A-Za-z_0-9]{%d,}.*' % long_symbol, line))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000365
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000366 def format_error(filename, line_num, line):
367 return '%s, line %s, %s chars' % (filename, line_num, len(line))
368
369 errors = _FindNewViolationsOfRule(no_long_lines, input_api,
370 source_file_filter,
371 error_formatter=format_error)
372 if errors:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000373 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000374 return [output_api.PresubmitPromptWarning(msg, items=errors[:5])]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000375 else:
376 return []
377
378
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000379def CheckLicense(input_api, output_api, license_re, source_file_filter=None,
maruel@chromium.org71626852010-11-03 13:14:25 +0000380 accept_empty_files=True):
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000381 """Verifies the license header.
382 """
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000383 license_re = input_api.re.compile(license_re, input_api.re.MULTILINE)
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000384 bad_files = []
385 for f in input_api.AffectedSourceFiles(source_file_filter):
386 contents = input_api.ReadFile(f, 'rb')
maruel@chromium.org71626852010-11-03 13:14:25 +0000387 if accept_empty_files and not contents:
388 continue
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000389 if not license_re.search(contents):
390 bad_files.append(f.LocalPath())
391 if bad_files:
392 if input_api.is_committing:
393 res_type = output_api.PresubmitPromptWarning
394 else:
395 res_type = output_api.PresubmitNotifyResult
396 return [res_type(
bradnelson@google.com393460b2011-03-29 01:20:26 +0000397 'License must match:\n%s\n' % license_re.pattern +
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000398 'Found a bad license header in these files:', items=bad_files)]
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000399 return []
400
401
maruel@chromium.org1a0e3cb2009-06-10 18:03:04 +0000402def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None):
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000403 """Checks that the source files have svn:eol-style=LF."""
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000404 return CheckSvnProperty(input_api, output_api,
405 'svn:eol-style', 'LF',
406 input_api.AffectedSourceFiles(source_file_filter))
407
408
409def CheckSvnForCommonMimeTypes(input_api, output_api):
410 """Checks that common binary file types have the correct svn:mime-type."""
411 output = []
412 files = input_api.AffectedFiles(include_deletes=False)
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000413 def IsExts(x, exts):
414 path = x.LocalPath()
415 for extension in exts:
416 if path.endswith(extension):
417 return True
418 return False
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000419 def FilterFiles(extension):
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000420 return filter(lambda x: IsExts(x, extension), files)
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000421 def RunCheck(mime_type, files):
422 output.extend(CheckSvnProperty(input_api, output_api, 'svn:mime-type',
423 mime_type, files))
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000424 RunCheck('application/pdf', FilterFiles(['.pdf']))
425 RunCheck('image/bmp', FilterFiles(['.bmp']))
426 RunCheck('image/gif', FilterFiles(['.gif']))
427 RunCheck('image/png', FilterFiles(['.png']))
428 RunCheck('image/jpeg', FilterFiles(['.jpg', '.jpeg', '.jpe']))
429 RunCheck('image/vnd.microsoft.icon', FilterFiles(['.ico']))
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000430 return output
431
432
433def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
434 """Checks that affected_files files have prop=expected."""
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000435 if input_api.change.scm != 'svn':
436 return []
437
438 bad = filter(lambda f: f.Property(prop) != expected, affected_files)
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000439 if bad:
maruel@chromium.org0874d472009-06-10 19:08:33 +0000440 if input_api.is_committing:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000441 res_type = output_api.PresubmitError
maruel@chromium.org0874d472009-06-10 19:08:33 +0000442 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000443 res_type = output_api.PresubmitNotifyResult
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000444 message = 'Run the command: svn pset %s %s \\' % (prop, expected)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000445 return [res_type(message, items=bad)]
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000446 return []
447
448
maruel@chromium.org3410d912009-06-09 20:56:16 +0000449### Other checks
450
451def CheckDoNotSubmit(input_api, output_api):
452 return (
453 CheckDoNotSubmitInDescription(input_api, output_api) +
454 CheckDoNotSubmitInFiles(input_api, output_api)
455 )
456
457
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000458def CheckTreeIsOpen(input_api, output_api,
459 url=None, closed=None, json_url=None):
460 """Check whether to allow commit without prompt.
461
462 Supports two styles:
463 1. Checks that an url's content doesn't match a regexp that would mean that
464 the tree is closed. (old)
465 2. Check the json_url to decide whether to allow commit without prompt.
466 Args:
467 input_api: input related apis.
468 output_api: output related apis.
469 url: url to use for regex based tree status.
470 closed: regex to match for closed status.
471 json_url: url to download json style status.
472 """
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000473 if not input_api.is_committing:
474 return []
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000475 try:
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000476 if json_url:
477 connection = input_api.urllib2.urlopen(json_url)
478 status = input_api.json.loads(connection.read())
479 connection.close()
480 if not status['can_commit_freely']:
481 short_text = 'Tree state is: ' + status['general_state']
482 long_text = status['message'] + '\n' + json_url
483 return [output_api.PresubmitError(short_text, long_text=long_text)]
484 else:
485 # TODO(bradnelson): drop this once all users are gone.
486 connection = input_api.urllib2.urlopen(url)
487 status = connection.read()
488 connection.close()
489 if input_api.re.match(closed, status):
490 long_text = status + '\n' + url
491 return [output_api.PresubmitError('The tree is closed.',
492 long_text=long_text)]
rohitrao@chromium.orgd490ee82012-02-06 19:31:33 +0000493 except IOError as e:
494 return [output_api.PresubmitError('Error fetching tree status.',
495 long_text=str(e))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000496 return []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000497
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000498def GetUnitTestsInDirectory(
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000499 input_api, output_api, directory, whitelist=None, blacklist=None):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000500 """Lists all files in a directory and runs them. Doesn't recurse.
501
nick@chromium.orgff526192013-06-10 19:30:26 +0000502 It's mainly a wrapper for RunUnitTests. Use whitelist and blacklist to filter
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000503 tests accordingly.
504 """
505 unit_tests = []
506 test_path = input_api.os_path.abspath(
507 input_api.os_path.join(input_api.PresubmitLocalPath(), directory))
508
509 def check(filename, filters):
510 return any(True for i in filters if input_api.re.match(i, filename))
511
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000512 to_run = found = 0
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000513 for filename in input_api.os_listdir(test_path):
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000514 found += 1
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000515 fullpath = input_api.os_path.join(test_path, filename)
516 if not input_api.os_path.isfile(fullpath):
517 continue
518 if whitelist and not check(filename, whitelist):
519 continue
520 if blacklist and check(filename, blacklist):
521 continue
522 unit_tests.append(input_api.os_path.join(directory, filename))
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000523 to_run += 1
524 input_api.logging.debug('Found %d files, running %d' % (found, to_run))
525 if not to_run:
526 return [
527 output_api.PresubmitPromptWarning(
528 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
529 % (found, whitelist, blacklist, directory))
530 ]
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000531 return GetUnitTests(input_api, output_api, unit_tests)
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000532
533
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000534def GetUnitTests(input_api, output_api, unit_tests):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000535 """Runs all unit tests in a directory.
536
537 On Windows, sys.executable is used for unit tests ending with ".py".
538 """
539 # We don't want to hinder users from uploading incomplete patches.
540 if input_api.is_committing:
541 message_type = output_api.PresubmitError
542 else:
543 message_type = output_api.PresubmitPromptWarning
544
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000545 results = []
546 for unit_test in unit_tests:
547 cmd = []
548 if input_api.platform == 'win32' and unit_test.endswith('.py'):
549 # Windows needs some help.
550 cmd = [input_api.python_executable]
551 cmd.append(unit_test)
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000552 if input_api.verbose:
maruel@chromium.org6c7723e2011-04-12 19:04:55 +0000553 cmd.append('--verbose')
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000554 results.append(input_api.Command(
555 name=unit_test,
556 cmd=cmd,
557 kwargs={'cwd': input_api.PresubmitLocalPath()},
558 message=message_type))
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000559 return results
560
nick@chromium.orgff526192013-06-10 19:30:26 +0000561
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000562def GetPythonUnitTests(input_api, output_api, unit_tests):
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000563 """Run the unit tests out of process, capture the output and use the result
564 code to determine success.
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000565
566 DEPRECATED.
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000567 """
maruel@chromium.orgd7dccf52009-06-06 18:51:58 +0000568 # We don't want to hinder users from uploading incomplete patches.
569 if input_api.is_committing:
570 message_type = output_api.PresubmitError
571 else:
572 message_type = output_api.PresubmitNotifyResult
maruel@chromium.org0e766052011-04-06 13:32:51 +0000573 results = []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000574 for unit_test in unit_tests:
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000575 # Run the unit tests out of process. This is because some unit tests
576 # stub out base libraries and don't clean up their mess. It's too easy to
577 # get subtle bugs.
578 cwd = None
579 env = None
580 unit_test_name = unit_test
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000581 # 'python -m test.unit_test' doesn't work. We need to change to the right
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000582 # directory instead.
583 if '.' in unit_test:
584 # Tests imported in submodules (subdirectories) assume that the current
585 # directory is in the PYTHONPATH. Manually fix that.
586 unit_test = unit_test.replace('.', '/')
587 cwd = input_api.os_path.dirname(unit_test)
588 unit_test = input_api.os_path.basename(unit_test)
589 env = input_api.environ.copy()
kbr@google.comab318592009-09-04 00:54:55 +0000590 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
591 backpath = [
592 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
593 ]
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000594 if env.get('PYTHONPATH'):
595 backpath.append(env.get('PYTHONPATH'))
ukai@chromium.orga301f1f2009-08-05 10:37:33 +0000596 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000597 cmd = [input_api.python_executable, '-m', '%s' % unit_test]
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000598 results.append(input_api.Command(
599 name=unit_test_name,
600 cmd=cmd,
601 kwargs={'env': env, 'cwd': cwd},
602 message=message_type))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000603 return results
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000604
605
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000606def RunUnitTestsInDirectory(input_api, *args, **kwargs):
607 """Run tests in a directory serially.
608
609 For better performance, use GetUnitTestsInDirectory and then
610 pass to input_api.RunTests.
611 """
612 return input_api.RunTests(
613 GetUnitTestsInDirectory(input_api, *args, **kwargs), False)
614
615
616def RunUnitTests(input_api, *args, **kwargs):
617 """Run tests serially.
618
619 For better performance, use GetUnitTests and then pass to
620 input_api.RunTests.
621 """
622 return input_api.RunTests(GetUnitTests(input_api, *args, **kwargs), False)
623
624
625def RunPythonUnitTests(input_api, *args, **kwargs):
626 """Run python tests in a directory serially.
627
628 DEPRECATED
629 """
630 return input_api.RunTests(
631 GetPythonUnitTests(input_api, *args, **kwargs), False)
632
633
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000634def _FetchAllFiles(input_api, white_list, black_list):
635 """Hack to fetch all files."""
636 # We cannot use AffectedFiles here because we want to test every python
637 # file on each single python change. It's because a change in a python file
638 # can break another unmodified file.
639 # Use code similar to InputApi.FilterSourceFile()
640 def Find(filepath, filters):
641 for item in filters:
642 if input_api.re.match(item, filepath):
643 return True
644 return False
645
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000646 files = []
647 path_len = len(input_api.PresubmitLocalPath())
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000648 for dirpath, dirnames, filenames in input_api.os_walk(
649 input_api.PresubmitLocalPath()):
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000650 # Passes dirnames in black list to speed up search.
651 for item in dirnames[:]:
652 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
653 if Find(filepath, black_list):
654 dirnames.remove(item)
655 for item in filenames:
656 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
657 if Find(filepath, white_list) and not Find(filepath, black_list):
658 files.append(filepath)
659 return files
660
661
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000662def GetPylint(input_api, output_api, white_list=None, black_list=None,
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000663 disabled_warnings=None, extra_paths_list=None):
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000664 """Run pylint on python files.
665
chrisha@google.com267d6592012-06-19 19:23:31 +0000666 The default white_list enforces looking only at *.py files.
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000667 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000668 white_list = tuple(white_list or ('.*\.py$',))
669 black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000670 extra_paths_list = extra_paths_list or []
671
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000672 if input_api.is_committing:
673 error_type = output_api.PresubmitError
674 else:
675 error_type = output_api.PresubmitPromptWarning
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000676
677 # Only trigger if there is at least one python file affected.
ilevy@chromium.org36576332013-01-08 03:16:15 +0000678 def rel_path(regex):
679 """Modifies a regex for a subject to accept paths relative to root."""
chrisha@chromium.org40e5d3d2013-01-18 17:46:57 +0000680 def samefile(a, b):
681 # Default implementation for platforms lacking os.path.samefile
682 # (like Windows).
683 return input_api.os_path.abspath(a) == input_api.os_path.abspath(b)
684 samefile = getattr(input_api.os_path, 'samefile', samefile)
685 if samefile(input_api.PresubmitLocalPath(),
686 input_api.change.RepositoryRoot()):
ilevy@chromium.orgdd4e9312013-01-15 03:22:04 +0000687 return regex
chrisha@chromium.org40e5d3d2013-01-18 17:46:57 +0000688
ilevy@chromium.org36576332013-01-08 03:16:15 +0000689 prefix = input_api.os_path.join(input_api.os_path.relpath(
690 input_api.PresubmitLocalPath(), input_api.change.RepositoryRoot()), '')
691 return input_api.re.escape(prefix) + regex
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000692 src_filter = lambda x: input_api.FilterSourceFile(
693 x, map(rel_path, white_list), map(rel_path, black_list))
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000694 if not input_api.AffectedSourceFiles(src_filter):
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000695 input_api.logging.info('Skipping pylint: no matching changes.')
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000696 return []
697
maruel@chromium.orgff9a2172012-04-24 16:55:32 +0000698 extra_args = ['--rcfile=%s' % input_api.os_path.join(_HERE, 'pylintrc')]
699 if disabled_warnings:
700 extra_args.extend(['-d', ','.join(disabled_warnings)])
701
chrisha@google.com267d6592012-06-19 19:23:31 +0000702 files = _FetchAllFiles(input_api, white_list, black_list)
703 if not files:
maruel@chromium.orgfca53392010-12-21 18:42:57 +0000704 return []
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000705 files.sort()
chrisha@google.com267d6592012-06-19 19:23:31 +0000706
csharp@chromium.org40395342013-02-21 14:57:23 +0000707 input_api.logging.info('Running pylint on %d files', len(files))
708 input_api.logging.debug('Running pylint on: %s', files)
chrisha@google.com267d6592012-06-19 19:23:31 +0000709 # Copy the system path to the environment so pylint can find the right
710 # imports.
711 env = input_api.environ.copy()
712 import sys
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000713 env['PYTHONPATH'] = input_api.os_path.pathsep.join(
robertshield@chromium.orga2873932013-02-20 18:08:46 +0000714 extra_paths_list + sys.path).encode('utf8')
chrisha@google.com267d6592012-06-19 19:23:31 +0000715
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000716 def GetPylintCmd(files):
717 # Windows needs help running python files so we explicitly specify
718 # the interpreter to use. It also has limitations on the size of
719 # the command-line, so we pass arguments via a pipe.
720 if len(files) == 1:
721 description = files[0]
722 else:
723 description = '%s files' % len(files)
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000724
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000725 return input_api.Command(
726 name='Pylint (%s)' % description,
727 cmd=[input_api.python_executable,
728 input_api.os_path.join(_HERE, 'third_party', 'pylint.py'),
729 '--args-on-stdin'],
730 kwargs={'env': env, 'stdin': '\n'.join(files + extra_args)},
731 message=error_type)
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000732
sbc@chromium.org7fc75ca2012-09-21 20:14:21 +0000733 # Always run pylint and pass it all the py files at once.
734 # Passing py files one at time is slower and can produce
735 # different results. input_api.verbose used to be used
736 # to enable this behaviour but differing behaviour in
737 # verbose mode is not desirable.
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000738 # Leave this unreachable code in here so users can make
739 # a quick local edit to diagnose pylint issues more
740 # easily.
sbc@chromium.org7fc75ca2012-09-21 20:14:21 +0000741 if True:
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000742 return [GetPylintCmd(files)]
chrisha@google.com267d6592012-06-19 19:23:31 +0000743 else:
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000744 return map(GetPylintCmd, files)
745
746
747def RunPylint(input_api, *args, **kwargs):
748 """Legacy presubmit function.
749
750 For better performance, get all tests and then pass to
751 input_api.RunTests.
752 """
753 return input_api.RunTests(GetPylint(input_api, *args, **kwargs), False)
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000754
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000755
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000756# TODO(dpranke): Get the host_url from the input_api instead
chrisha@google.com267d6592012-06-19 19:23:31 +0000757def CheckRietveldTryJobExecution(dummy_input_api, dummy_output_api,
758 dummy_host_url, dummy_platforms,
759 dummy_owner):
maruel@chromium.org85da74b2011-10-27 17:13:30 +0000760 # Temporarily 'fix' the check while the Rietveld API is being upgraded to
761 # something sensible.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000762 return []
763
764
765def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
766 ignored):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000767 try:
768 connection = input_api.urllib2.urlopen(url)
769 raw_data = connection.read()
770 connection.close()
771 except IOError:
772 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)]
773
774 try:
775 data = input_api.json.loads(raw_data)
776 except ValueError:
777 return [output_api.PresubmitNotifyResult('Received malformed json while '
778 'looking up buildbot status')]
779
780 out = []
781 for (builder_name, builder) in data.iteritems():
782 if builder_name in ignored:
783 continue
maruel@chromium.orgcf1982c2010-10-04 15:08:28 +0000784 if builder.get('state', '') == 'offline':
785 continue
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000786 pending_builds_len = len(builder.get('pending_builds', []))
787 if pending_builds_len > max_pendings:
788 out.append('%s has %d build(s) pending' %
789 (builder_name, pending_builds_len))
790 if out:
791 return [output_api.PresubmitPromptWarning(
792 'Build(s) pending. It is suggested to wait that no more than %d '
793 'builds are pending.' % max_pendings,
794 long_text='\n'.join(out))]
795 return []
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000796
797
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000798def CheckOwners(input_api, output_api, source_file_filter=None,
799 author_counts_as_owner=True):
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000800 if input_api.is_committing:
801 if input_api.tbr:
802 return [output_api.PresubmitNotifyResult(
803 '--tbr was specified, skipping OWNERS check')]
804 if not input_api.change.issue:
805 return [output_api.PresubmitError("OWNERS check failed: this change has "
806 "no Rietveld issue number, so we can't check it for approvals.")]
807 needed = 'LGTM from an OWNER'
808 output = output_api.PresubmitError
809 else:
810 needed = 'OWNER reviewers'
811 output = output_api.PresubmitNotifyResult
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000812
dpranke@chromium.orgadd5df42011-03-08 23:04:01 +0000813 affected_files = set([f.LocalPath() for f in
sail@chromium.org5538e022011-05-12 17:53:16 +0000814 input_api.change.AffectedFiles(file_filter=source_file_filter)])
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000815
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000816 owners_db = input_api.owners_db
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000817 owner_email, reviewers = _RietveldOwnerAndReviewers(
818 input_api,
819 owners_db.email_regexp,
820 approval_needed=input_api.is_committing)
821
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000822 owner_email = owner_email or input_api.change.author_email
823
824 if author_counts_as_owner and owner_email:
dpranke@chromium.org4c7ce992013-03-06 17:15:40 +0000825 reviewers_plus_owner = set([owner_email]).union(reviewers)
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000826 missing_files = owners_db.files_not_covered_by(affected_files,
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000827 reviewers_plus_owner)
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000828 else:
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000829 missing_files = owners_db.files_not_covered_by(affected_files, reviewers)
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000830
dpranke@chromium.org6b1e3ee2013-02-23 00:06:38 +0000831 if missing_files:
zork@chromium.org046e1752012-05-07 05:56:12 +0000832 output_list = [
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000833 output('Missing %s for these files:\n %s' %
bauerb@chromium.orgb3b52012013-04-18 19:28:04 +0000834 (needed, '\n '.join(sorted(missing_files))))]
zork@chromium.org046e1752012-05-07 05:56:12 +0000835 if not input_api.is_committing:
dpranke@chromium.org31b42c02013-09-19 19:24:15 +0000836 suggested_owners = owners_db.reviewers_for(missing_files, owner_email)
scheib@chromium.org93276ab2013-10-14 23:55:32 +0000837 output_list.append(output('Suggested OWNERS: ' +
838 '(Use "git-cl owners" to interactively select owners.)\n %s' %
839 ('\n '.join(suggested_owners or []))))
zork@chromium.org046e1752012-05-07 05:56:12 +0000840 return output_list
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000841
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000842 if input_api.is_committing and not reviewers:
843 return [output('Missing LGTM from someone other than %s' % owner_email)]
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000844 return []
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000845
846
ilevy@chromium.org1fa5cb92012-12-05 04:04:42 +0000847def _GetRietveldIssueProps(input_api, messages):
848 """Gets the issue properties from rietveld."""
849 issue = input_api.change.issue
850 if issue and input_api.rietveld:
851 return input_api.rietveld.get_issue_properties(
852 issue=int(issue), messages=messages)
853
854
isherman@chromium.orgb5cded62014-03-25 17:47:57 +0000855def _ReviewersFromChange(change):
856 """Return the reviewers specified in the |change|, if any."""
857 reviewers = set()
858 if change.R:
859 reviewers.update(set([r.strip() for r in change.R.split(',')]))
860 if change.TBR:
861 reviewers.update(set([r.strip() for r in change.TBR.split(',')]))
isherman@chromium.org103ae032014-04-09 09:06:19 +0000862
863 # Drop reviewers that aren't specified in email address format.
864 return set(reviewer for reviewer in reviewers if '@' in reviewer)
isherman@chromium.orgb5cded62014-03-25 17:47:57 +0000865
866
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000867def _RietveldOwnerAndReviewers(input_api, email_regexp, approval_needed=False):
868 """Return the owner and reviewers of a change, if any.
869
870 If approval_needed is True, only reviewers who have approved the change
871 will be returned.
872 """
ilevy@chromium.org1fa5cb92012-12-05 04:04:42 +0000873 issue_props = _GetRietveldIssueProps(input_api, True)
874 if not issue_props:
isherman@chromium.orgb5cded62014-03-25 17:47:57 +0000875 reviewers = set()
876 if not approval_needed:
877 reviewers = _ReviewersFromChange(input_api.change)
878 return None, reviewers
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000879
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000880 if not approval_needed:
881 return issue_props['owner_email'], set(issue_props['reviewers'])
882
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000883 owner_email = issue_props['owner_email']
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000884
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000885 def match_reviewer(r):
maruel@chromium.org80941c22011-05-30 20:14:18 +0000886 return email_regexp.match(r) and r != owner_email
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000887
maruel@chromium.org80941c22011-05-30 20:14:18 +0000888 messages = issue_props.get('messages', [])
889 approvers = set(
890 m['sender'] for m in messages
891 if m.get('approval') and match_reviewer(m['sender']))
892
893 return owner_email, approvers
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000894
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000895
896def _CheckConstNSObject(input_api, output_api, source_file_filter):
897 """Checks to make sure no objective-c files have |const NSSomeClass*|."""
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000898 pattern = input_api.re.compile(
899 r'const\s+NS(?!(Point|Range|Rect|Size)\s*\*)\w*\s*\*')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000900
901 def objective_c_filter(f):
902 return (source_file_filter(f) and
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000903 input_api.os_path.splitext(f.LocalPath())[1] in ('.h', '.m', '.mm'))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000904
905 files = []
906 for f in input_api.AffectedSourceFiles(objective_c_filter):
907 contents = input_api.ReadFile(f)
908 if pattern.search(contents):
909 files.append(f)
910
911 if files:
912 if input_api.is_committing:
913 res_type = output_api.PresubmitPromptWarning
914 else:
915 res_type = output_api.PresubmitNotifyResult
916 return [ res_type('|const NSClass*| is wrong, see ' +
917 'http://dev.chromium.org/developers/clang-mac',
918 files) ]
919 return []
920
921
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000922def CheckSingletonInHeaders(input_api, output_api, source_file_filter=None):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000923 """Checks to make sure no header files have |Singleton<|."""
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000924 pattern = input_api.re.compile(r'(?<!class\s)Singleton\s*<')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000925 files = []
926 for f in input_api.AffectedSourceFiles(source_file_filter):
927 if (f.LocalPath().endswith('.h') or f.LocalPath().endswith('.hxx') or
928 f.LocalPath().endswith('.hpp') or f.LocalPath().endswith('.inl')):
929 contents = input_api.ReadFile(f)
dilmah@chromium.orgd22b9702011-08-26 13:53:49 +0000930 for line in contents.splitlines(False):
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000931 if (not input_api.re.match(r'//', line) and # Strip C++ comment.
932 pattern.search(line)):
dilmah@chromium.orgd22b9702011-08-26 13:53:49 +0000933 files.append(f)
934 break
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000935
936 if files:
937 return [ output_api.PresubmitError(
938 'Found Singleton<T> in the following header files.\n' +
939 'Please move them to an appropriate source file so that the ' +
940 'template gets instantiated in a single compilation unit.',
941 files) ]
942 return []
943
944
945def PanProjectChecks(input_api, output_api,
946 excluded_paths=None, text_files=None,
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000947 license_header=None, project_name=None,
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000948 owners_check=True, maxlen=80):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000949 """Checks that ALL chromium orbit projects should use.
950
951 These are checks to be run on all Chromium orbit project, including:
952 Chromium
953 Native Client
954 V8
955 When you update this function, please take this broad scope into account.
956 Args:
957 input_api: Bag of input related interfaces.
958 output_api: Bag of output related interfaces.
959 excluded_paths: Don't include these paths in common checks.
960 text_files: Which file are to be treated as documentation text files.
961 license_header: What license header should be on files.
962 project_name: What is the name of the project as it appears in the license.
963 Returns:
964 A list of warning or error objects.
965 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000966 excluded_paths = tuple(excluded_paths or [])
967 text_files = tuple(text_files or (
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +0000968 r'.+\.txt$',
969 r'.+\.json$',
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000970 ))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000971 project_name = project_name or 'Chromium'
mark@chromium.org2cc66422012-08-07 21:22:32 +0000972
973 # Accept any year number from 2006 to the current year, or the special
rvargas@chromium.org02652602014-03-14 19:43:20 +0000974 # 2006-20xx string used on the oldest files. 2006-20xx is deprecated, but
975 # tolerated on old files.
mark@chromium.org2cc66422012-08-07 21:22:32 +0000976 current_year = int(input_api.time.strftime('%Y'))
977 allowed_years = (str(s) for s in reversed(xrange(2006, current_year + 1)))
rvargas@chromium.org02652602014-03-14 19:43:20 +0000978 years_re = '(' + '|'.join(allowed_years) + '|2006-2008|2006-2009|2006-2010)'
mark@chromium.org2cc66422012-08-07 21:22:32 +0000979
980 # The (c) is deprecated, but tolerate it until it's removed from all files.
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000981 license_header = license_header or (
mark@chromium.org2cc66422012-08-07 21:22:32 +0000982 r'.*? Copyright (\(c\) )?%(year)s The %(project)s Authors\. '
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000983 r'All rights reserved\.\n'
984 r'.*? Use of this source code is governed by a BSD-style license that '
985 r'can be\n'
dbeam@chromium.orgb2312102012-02-15 02:01:55 +0000986 r'.*? found in the LICENSE file\.(?: \*/)?\n'
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000987 ) % {
mark@chromium.org2cc66422012-08-07 21:22:32 +0000988 'year': years_re,
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000989 'project': project_name,
990 }
991
992 results = []
993 # This code loads the default black list (e.g. third_party, experimental, etc)
994 # and add our black list (breakpad, skia and v8 are still not following
995 # google style and are not really living this repository).
996 # See presubmit_support.py InputApi.FilterSourceFile for the (simple) usage.
997 black_list = input_api.DEFAULT_BLACK_LIST + excluded_paths
998 white_list = input_api.DEFAULT_WHITE_LIST + text_files
999 sources = lambda x: input_api.FilterSourceFile(x, black_list=black_list)
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +00001000 text_files = lambda x: input_api.FilterSourceFile(
1001 x, black_list=black_list, white_list=white_list)
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001002
1003 snapshot_memory = []
1004 def snapshot(msg):
1005 """Measures & prints performance warning if a rule is running slow."""
1006 dt2 = input_api.time.clock()
1007 if snapshot_memory:
1008 delta_ms = int(1000*(dt2 - snapshot_memory[0]))
1009 if delta_ms > 500:
1010 print " %s took a long time: %dms" % (snapshot_memory[1], delta_ms)
1011 snapshot_memory[:] = (dt2, msg)
1012
bradnelson@google.comd57771d2011-03-31 19:18:32 +00001013 if owners_check:
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001014 snapshot("checking owners")
bradnelson@google.comd57771d2011-03-31 19:18:32 +00001015 results.extend(input_api.canned_checks.CheckOwners(
dpranke@chromium.org751797a2011-06-07 18:46:27 +00001016 input_api, output_api, source_file_filter=None))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001017
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001018 snapshot("checking long lines")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001019 results.extend(input_api.canned_checks.CheckLongLines(
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +00001020 input_api, output_api, maxlen, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001021 snapshot( "checking tabs")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001022 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
1023 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001024 snapshot( "checking stray whitespace")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001025 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
1026 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001027 snapshot("checking nsobjects")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001028 results.extend(_CheckConstNSObject(
1029 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001030 snapshot("checking singletons")
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +00001031 results.extend(CheckSingletonInHeaders(
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001032 input_api, output_api, source_file_filter=sources))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +00001033
1034 # The following checks are only done on commit, since the commit bot will
1035 # auto-fix most of these.
1036 if input_api.is_committing:
maruel@chromium.org8571dac2011-05-10 18:10:13 +00001037 snapshot("checking eol style")
1038 results.extend(input_api.canned_checks.CheckChangeSvnEolStyle(
1039 input_api, output_api, source_file_filter=text_files))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +00001040 snapshot("checking svn mime types")
1041 results.extend(input_api.canned_checks.CheckSvnForCommonMimeTypes(
1042 input_api, output_api))
1043 snapshot("checking license")
1044 results.extend(input_api.canned_checks.CheckLicense(
1045 input_api, output_api, license_header, source_file_filter=sources))
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +00001046 snapshot("checking was uploaded")
1047 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
1048 input_api, output_api))
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +00001049 snapshot("checking description")
1050 results.extend(input_api.canned_checks.CheckChangeHasDescription(
1051 input_api, output_api))
1052 results.extend(input_api.canned_checks.CheckDoNotSubmitInDescription(
1053 input_api, output_api))
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +00001054 snapshot("checking do not submit in files")
1055 results.extend(input_api.canned_checks.CheckDoNotSubmitInFiles(
1056 input_api, output_api))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001057 snapshot("done")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001058 return results
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001059
1060
1061def CheckPatchFormatted(input_api, output_api):
1062 import git_cl
enne@chromium.org555cfe42014-01-29 18:21:39 +00001063 cmd = ['cl', 'format', '--dry-run', input_api.PresubmitLocalPath()]
1064 code, _ = git_cl.RunGitWithCode(cmd, suppress_stderr=True)
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001065 if code == 2:
1066 return [output_api.PresubmitPromptWarning(
1067 'Your patch is not formatted, please run git cl format.')]
1068 # As this is just a warning, ignore all other errors if the user
1069 # happens to have a broken clang-format, doesn't use git, etc etc.
1070 return []