blob: 087188df5ec3545a840648850d041d57c96c50fb [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
tfarina@chromium.orgb6795642014-12-12 00:03:49 +000010# Justifications for each filter:
11#
12# - build/include : Too many; fix in the future.
13# - build/include_order : Not happening; #ifdefed includes.
14# - build/namespace : I'm surprised by how often we violate this rule.
15# - readability/casting : Mistakes a whole bunch of function pointer.
16# - runtime/int : Can be fixed long term; volume of errors too high
17# - runtime/virtual : Broken now, but can be fixed in the future?
18# - whitespace/braces : We have a lot of explicit scoping in chrome code.
19# - readability/inheritance : Temporary, while the OVERRIDE and FINAL fixup
20# is in progress.
21DEFAULT_LINT_FILTERS = [
22 '-build/include',
23 '-build/include_order',
24 '-build/namespace',
25 '-readability/casting',
26 '-runtime/int',
27 '-runtime/virtual',
28 '-whitespace/braces',
29 '-readability/inheritance'
30]
bradnelson@google.com56e48bc2011-03-24 20:51:21 +000031
maruel@chromium.org3410d912009-06-09 20:56:16 +000032### Description checks
33
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000034def CheckChangeHasTestField(input_api, output_api):
35 """Requires that the changelist have a TEST= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000036 if input_api.change.TEST:
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000037 return []
38 else:
39 return [output_api.PresubmitNotifyResult(
jam@chromium.org5c76de92011-01-24 18:19:21 +000040 'If this change requires manual test instructions to QA team, add '
41 'TEST=[instructions].')]
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000042
43
44def CheckChangeHasBugField(input_api, output_api):
45 """Requires that the changelist have a BUG= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000046 if input_api.change.BUG:
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000047 return []
48 else:
49 return [output_api.PresubmitNotifyResult(
jam@chromium.org5c76de92011-01-24 18:19:21 +000050 'If this change has an associated bug, add BUG=[bug number].')]
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000051
52
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053def CheckChangeHasTestedField(input_api, output_api):
54 """Requires that the changelist have a TESTED= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000055 if input_api.change.TESTED:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000056 return []
57 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000058 return [output_api.PresubmitError('Changelist must have a TESTED= field.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000059
60
61def CheckChangeHasQaField(input_api, output_api):
62 """Requires that the changelist have a QA= field."""
63 if input_api.change.QA:
64 return []
65 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000066 return [output_api.PresubmitError('Changelist must have a QA= field.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000067
68
69def CheckDoNotSubmitInDescription(input_api, output_api):
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +000070 """Checks that the user didn't add 'DO NOT ''SUBMIT' to the CL description.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071 """
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +000072 keyword = 'DO NOT ''SUBMIT'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000073 if keyword in input_api.change.DescriptionText():
74 return [output_api.PresubmitError(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000075 keyword + ' is present in the changelist description.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076 else:
77 return []
78
79
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000080def CheckChangeHasDescription(input_api, output_api):
81 """Checks the CL description is not empty."""
82 text = input_api.change.DescriptionText()
83 if text.strip() == '':
84 if input_api.is_committing:
pgervais@chromium.orgbc3b3b52014-06-03 00:53:48 +000085 return [output_api.PresubmitError('Add a description to the CL.')]
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000086 else:
pgervais@chromium.orgbc3b3b52014-06-03 00:53:48 +000087 return [output_api.PresubmitNotifyResult('Add a description to the CL.')]
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000088 return []
89
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +000090
91def CheckChangeWasUploaded(input_api, output_api):
92 """Checks that the issue was uploaded before committing."""
maruel@chromium.orgd587f392011-07-26 00:41:18 +000093 if input_api.is_committing and not input_api.change.issue:
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +000094 return [output_api.PresubmitError(
95 'Issue wasn\'t uploaded. Please upload first.')]
96 return []
97
98
maruel@chromium.org3410d912009-06-09 20:56:16 +000099### Content checks
100
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000101def CheckDoNotSubmitInFiles(input_api, output_api):
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000102 """Checks that the user didn't add 'DO NOT ''SUBMIT' to any files."""
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000103 # We want to check every text file, not just source files.
104 file_filter = lambda x : x
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000105 keyword = 'DO NOT ''SUBMIT'
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000106 errors = _FindNewViolationsOfRule(lambda _, line : keyword not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000107 input_api, file_filter)
108 text = '\n'.join('Found %s in %s' % (keyword, loc) for loc in errors)
109 if text:
110 return [output_api.PresubmitError(text)]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000111 return []
112
113
tfarina@chromium.orgb6795642014-12-12 00:03:49 +0000114def CheckChangeLintsClean(input_api, output_api, source_file_filter=None,
tfarina@chromium.orga62208f2015-02-25 03:23:11 +0000115 lint_filters=None, verbose_level=None):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000116 """Checks that all '.cc' and '.h' files pass cpplint.py."""
erg@google.com26970fa2009-11-17 18:07:32 +0000117 _RE_IS_TEST = input_api.re.compile(r'.*tests?.(cc|h)$')
118 result = []
119
enne@chromium.orge72c5f52013-04-16 00:36:40 +0000120 cpplint = input_api.cpplint
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000121 # Access to a protected member _XX of a client class
122 # pylint: disable=W0212
erg@google.com26970fa2009-11-17 18:07:32 +0000123 cpplint._cpplint_state.ResetErrorCounts()
124
tfarina@chromium.orgb6795642014-12-12 00:03:49 +0000125 lint_filters = lint_filters or DEFAULT_LINT_FILTERS
126 cpplint._SetFilters(','.join(lint_filters))
erg@google.com26970fa2009-11-17 18:07:32 +0000127
128 # We currently are more strict with normal code than unit tests; 4 and 5 are
129 # the verbosity level that would normally be passed to cpplint.py through
130 # --verbose=#. Hopefully, in the future, we can be more verbose.
131 files = [f.AbsoluteLocalPath() for f in
132 input_api.AffectedSourceFiles(source_file_filter)]
133 for file_name in files:
134 if _RE_IS_TEST.match(file_name):
135 level = 5
136 else:
137 level = 4
138
tfarina@chromium.orga62208f2015-02-25 03:23:11 +0000139 verbose_level = verbose_level or level
140 cpplint.ProcessFile(file_name, verbose_level)
erg@google.com26970fa2009-11-17 18:07:32 +0000141
142 if cpplint._cpplint_state.error_count > 0:
143 if input_api.is_committing:
144 res_type = output_api.PresubmitError
145 else:
146 res_type = output_api.PresubmitPromptWarning
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000147 result = [res_type('Changelist failed cpplint.py check.')]
erg@google.com26970fa2009-11-17 18:07:32 +0000148
149 return result
150
151
maruel@chromium.org3410d912009-06-09 20:56:16 +0000152def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000153 """Checks no '\r' (CR) character is in any source files."""
154 cr_files = []
maruel@chromium.org3410d912009-06-09 20:56:16 +0000155 for f in input_api.AffectedSourceFiles(source_file_filter):
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000156 if '\r' in input_api.ReadFile(f, 'rb'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000157 cr_files.append(f.LocalPath())
158 if cr_files:
159 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000160 'Found a CR character in these files:', items=cr_files)]
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000161 return []
162
163
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000164def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None):
165 """Checks for files in svn modified directories.
166
167 They will get submitted on accident because svn commits recursively by
168 default, and that's very dangerous.
169 """
170 if input_api.change.scm != 'svn':
171 return []
172
173 errors = []
174 current_cl_files = input_api.change.GetModifiedFiles()
175 all_modified_files = input_api.change.GetAllModifiedFiles()
176 # Filter out files in the current CL.
177 modified_files = [f for f in all_modified_files if f not in current_cl_files]
178 modified_abspaths = [input_api.os_path.abspath(f) for f in modified_files]
179
sail@chromium.org5538e022011-05-12 17:53:16 +0000180 for f in input_api.AffectedFiles(file_filter=source_file_filter):
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000181 if f.Action() == 'M' and f.IsDirectory():
182 curpath = f.AbsoluteLocalPath()
183 bad_files = []
184 # Check if any of the modified files in other CLs are under curpath.
185 for i in xrange(len(modified_files)):
186 abspath = modified_abspaths[i]
187 if input_api.os_path.commonprefix([curpath, abspath]) == curpath:
188 bad_files.append(modified_files[i])
189 if bad_files:
190 if input_api.is_committing:
191 error_type = output_api.PresubmitPromptWarning
192 else:
193 error_type = output_api.PresubmitNotifyResult
194 errors.append(error_type(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000195 'Potential accidental commits in changelist %s:' % f.LocalPath(),
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000196 items=bad_files))
197 return errors
198
199
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000200def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
201 """Checks the files ends with one and only one \n (LF)."""
202 eof_files = []
203 for f in input_api.AffectedSourceFiles(source_file_filter):
204 contents = input_api.ReadFile(f, 'rb')
205 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000206 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000207 eof_files.append(f.LocalPath())
208
209 if eof_files:
210 return [output_api.PresubmitPromptWarning(
211 'These files should end in one (and only one) newline character:',
212 items=eof_files)]
213 return []
214
215
216def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api,
217 source_file_filter=None):
218 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass.
219
220 It is faster because it is reading the file only once.
221 """
222 cr_files = []
223 eof_files = []
224 for f in input_api.AffectedSourceFiles(source_file_filter):
225 contents = input_api.ReadFile(f, 'rb')
226 if '\r' in contents:
227 cr_files.append(f.LocalPath())
228 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000229 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000230 eof_files.append(f.LocalPath())
231 outputs = []
232 if cr_files:
233 outputs.append(output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000234 'Found a CR character in these files:', items=cr_files))
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000235 if eof_files:
236 outputs.append(output_api.PresubmitPromptWarning(
237 'These files should end in one (and only one) newline character:',
238 items=eof_files))
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000239 return outputs
240
241
chrisha@google.com267d6592012-06-19 19:23:31 +0000242def _ReportErrorFileAndLine(filename, line_num, dummy_line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000243 """Default error formatter for _FindNewViolationsOfRule."""
danakj@chromium.orgc5965ba2013-08-14 00:27:24 +0000244 return '%s:%s' % (filename, line_num)
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000245
246
247def _FindNewViolationsOfRule(callable_rule, input_api, source_file_filter=None,
248 error_formatter=_ReportErrorFileAndLine):
249 """Find all newly introduced violations of a per-line rule (a callable).
250
251 Arguments:
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000252 callable_rule: a callable taking a file extension and line of input and
253 returning True if the rule is satisfied and False if there was a problem.
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000254 input_api: object to enumerate the affected files.
255 source_file_filter: a filter to be passed to the input api.
256 error_formatter: a callable taking (filename, line_number, line) and
257 returning a formatted error string.
258
259 Returns:
260 A list of the newly-introduced violations reported by the rule.
261 """
262 errors = []
sail@chromium.org5538e022011-05-12 17:53:16 +0000263 for f in input_api.AffectedFiles(include_deletes=False,
264 file_filter=source_file_filter):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000265 # For speed, we do two passes, checking first the full file. Shelling out
266 # to the SCM to determine the changed region can be quite expensive on
267 # Win32. Assuming that most files will be kept problem-free, we can
268 # skip the SCM operations most of the time.
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000269 extension = str(f.LocalPath()).rsplit('.', 1)[-1]
270 if all(callable_rule(extension, line) for line in f.NewContents()):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000271 continue # No violation found in full text: can skip considering diff.
272
273 for line_num, line in f.ChangedContents():
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000274 if not callable_rule(extension, line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000275 errors.append(error_formatter(f.LocalPath(), line_num, line))
276
277 return errors
278
279
maruel@chromium.org3410d912009-06-09 20:56:16 +0000280def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000281 """Checks that there are no tab characters in any of the text files to be
282 submitted.
283 """
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000284 # In addition to the filter, make sure that makefiles are blacklisted.
285 if not source_file_filter:
286 # It's the default filter.
287 source_file_filter = input_api.FilterSourceFile
288 def filter_more(affected_file):
cmp@chromium.orgcb5e57c2012-04-06 19:50:15 +0000289 basename = input_api.os_path.basename(affected_file.LocalPath())
290 return (not (basename in ('Makefile', 'makefile') or
291 basename.endswith('.mk')) and
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000292 source_file_filter(affected_file))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000293
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000294 tabs = _FindNewViolationsOfRule(lambda _, line : '\t' not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000295 input_api, filter_more)
296
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000297 if tabs:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000298 return [output_api.PresubmitPromptWarning('Found a tab character in:',
299 long_text='\n'.join(tabs))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000300 return []
301
302
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000303def CheckChangeTodoHasOwner(input_api, output_api, source_file_filter=None):
304 """Checks that the user didn't add TODO(name) without an owner."""
305
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000306 unowned_todo = input_api.re.compile('TO''DO[^(]')
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000307 errors = _FindNewViolationsOfRule(lambda _, x : not unowned_todo.search(x),
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000308 input_api, source_file_filter)
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000309 errors = ['Found TO''DO with no owner in ' + x for x in errors]
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000310 if errors:
311 return [output_api.PresubmitPromptWarning('\n'.join(errors))]
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000312 return []
313
314
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000315def CheckChangeHasNoStrayWhitespace(input_api, output_api,
316 source_file_filter=None):
317 """Checks that there is no stray whitespace at source lines end."""
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000318 errors = _FindNewViolationsOfRule(lambda _, line : line.rstrip() == line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000319 input_api, source_file_filter)
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000320 if errors:
321 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000322 'Found line ending with white spaces in:',
323 long_text='\n'.join(errors))]
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000324 return []
325
326
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000327def CheckLongLines(input_api, output_api, maxlen, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000328 """Checks that there aren't any lines longer than maxlen characters in any of
329 the text files to be submitted.
330 """
dpranke@chromium.orge3b1c3d2012-10-20 22:28:14 +0000331 maxlens = {
332 'java': 100,
torne@chromium.org0ecad9c2013-02-15 16:35:16 +0000333 # This is specifically for Android's handwritten makefiles (Android.mk).
334 'mk': 200,
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000335 '': maxlen,
336 }
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000337
erikchen@google.com12816082013-12-03 02:04:20 +0000338 # Language specific exceptions to max line length.
339 # '.h' is considered an obj-c file extension, since OBJC_EXCEPTIONS are a
340 # superset of CPP_EXCEPTIONS.
341 CPP_FILE_EXTS = ('c', 'cc')
342 CPP_EXCEPTIONS = ('#define', '#endif', '#if', '#include', '#pragma')
343 JAVA_FILE_EXTS = ('java',)
344 JAVA_EXCEPTIONS = ('import ', 'package ')
345 OBJC_FILE_EXTS = ('h', 'm', 'mm')
346 OBJC_EXCEPTIONS = ('#define', '#endif', '#if', '#import', '#include',
347 '#pragma')
348
349 LANGUAGE_EXCEPTIONS = [
350 (CPP_FILE_EXTS, CPP_EXCEPTIONS),
351 (JAVA_FILE_EXTS, JAVA_EXCEPTIONS),
352 (OBJC_FILE_EXTS, OBJC_EXCEPTIONS),
353 ]
sivachandra@chromium.org270db5c2012-10-09 22:38:44 +0000354
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000355 def no_long_lines(file_extension, line):
erikchen@google.com12816082013-12-03 02:04:20 +0000356 # Check for language specific exceptions.
357 if any(file_extension in exts and line.startswith(exceptions)
358 for exts, exceptions in LANGUAGE_EXCEPTIONS):
sivachandra@chromium.org270db5c2012-10-09 22:38:44 +0000359 return True
360
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000361 file_maxlen = maxlens.get(file_extension, maxlens[''])
362 # Stupidly long symbols that needs to be worked around if takes 66% of line.
363 long_symbol = file_maxlen * 2 / 3
364 # Hard line length limit at 50% more.
365 extra_maxlen = file_maxlen * 3 / 2
366
367 line_len = len(line)
368 if line_len <= file_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000369 return True
370
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000371 if line_len > extra_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000372 return False
373
treib@chromium.orgde3ee902014-07-28 14:23:11 +0000374 if any((url in line) for url in ('file://', 'http://', 'https://')):
375 return True
376
377 if 'url(' in line and file_extension == 'css':
378 return True
379
dbeam@chromium.orgb5ccc9b2014-09-23 00:42:22 +0000380 if '<include' in line and file_extension in ('css', 'html', 'js'):
381 return True
382
treib@chromium.orgde3ee902014-07-28 14:23:11 +0000383 return input_api.re.match(
384 r'.*[A-Za-z][A-Za-z_0-9]{%d,}.*' % long_symbol, line)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000385
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000386 def format_error(filename, line_num, line):
387 return '%s, line %s, %s chars' % (filename, line_num, len(line))
388
389 errors = _FindNewViolationsOfRule(no_long_lines, input_api,
390 source_file_filter,
391 error_formatter=format_error)
392 if errors:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000393 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000394 return [output_api.PresubmitPromptWarning(msg, items=errors[:5])]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000395 else:
396 return []
397
398
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000399def CheckLicense(input_api, output_api, license_re, source_file_filter=None,
maruel@chromium.org71626852010-11-03 13:14:25 +0000400 accept_empty_files=True):
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000401 """Verifies the license header.
402 """
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000403 license_re = input_api.re.compile(license_re, input_api.re.MULTILINE)
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000404 bad_files = []
405 for f in input_api.AffectedSourceFiles(source_file_filter):
406 contents = input_api.ReadFile(f, 'rb')
maruel@chromium.org71626852010-11-03 13:14:25 +0000407 if accept_empty_files and not contents:
408 continue
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000409 if not license_re.search(contents):
410 bad_files.append(f.LocalPath())
411 if bad_files:
412 if input_api.is_committing:
413 res_type = output_api.PresubmitPromptWarning
414 else:
415 res_type = output_api.PresubmitNotifyResult
416 return [res_type(
bradnelson@google.com393460b2011-03-29 01:20:26 +0000417 'License must match:\n%s\n' % license_re.pattern +
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000418 'Found a bad license header in these files:', items=bad_files)]
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000419 return []
420
421
maruel@chromium.org1a0e3cb2009-06-10 18:03:04 +0000422def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None):
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000423 """Checks that the source files have svn:eol-style=LF."""
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000424 return CheckSvnProperty(input_api, output_api,
425 'svn:eol-style', 'LF',
426 input_api.AffectedSourceFiles(source_file_filter))
427
428
429def CheckSvnForCommonMimeTypes(input_api, output_api):
430 """Checks that common binary file types have the correct svn:mime-type."""
431 output = []
432 files = input_api.AffectedFiles(include_deletes=False)
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000433 def IsExts(x, exts):
434 path = x.LocalPath()
435 for extension in exts:
436 if path.endswith(extension):
437 return True
438 return False
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000439 def FilterFiles(extension):
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000440 return filter(lambda x: IsExts(x, extension), files)
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000441 def RunCheck(mime_type, files):
442 output.extend(CheckSvnProperty(input_api, output_api, 'svn:mime-type',
443 mime_type, files))
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000444 RunCheck('application/pdf', FilterFiles(['.pdf']))
445 RunCheck('image/bmp', FilterFiles(['.bmp']))
446 RunCheck('image/gif', FilterFiles(['.gif']))
447 RunCheck('image/png', FilterFiles(['.png']))
448 RunCheck('image/jpeg', FilterFiles(['.jpg', '.jpeg', '.jpe']))
449 RunCheck('image/vnd.microsoft.icon', FilterFiles(['.ico']))
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000450 return output
451
452
453def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
454 """Checks that affected_files files have prop=expected."""
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000455 if input_api.change.scm != 'svn':
456 return []
457
458 bad = filter(lambda f: f.Property(prop) != expected, affected_files)
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000459 if bad:
maruel@chromium.org0874d472009-06-10 19:08:33 +0000460 if input_api.is_committing:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000461 res_type = output_api.PresubmitError
maruel@chromium.org0874d472009-06-10 19:08:33 +0000462 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000463 res_type = output_api.PresubmitNotifyResult
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000464 message = 'Run the command: svn pset %s %s \\' % (prop, expected)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000465 return [res_type(message, items=bad)]
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000466 return []
467
468
maruel@chromium.org3410d912009-06-09 20:56:16 +0000469### Other checks
470
471def CheckDoNotSubmit(input_api, output_api):
472 return (
473 CheckDoNotSubmitInDescription(input_api, output_api) +
474 CheckDoNotSubmitInFiles(input_api, output_api)
475 )
476
477
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000478def CheckTreeIsOpen(input_api, output_api,
479 url=None, closed=None, json_url=None):
480 """Check whether to allow commit without prompt.
481
482 Supports two styles:
483 1. Checks that an url's content doesn't match a regexp that would mean that
484 the tree is closed. (old)
485 2. Check the json_url to decide whether to allow commit without prompt.
486 Args:
487 input_api: input related apis.
488 output_api: output related apis.
489 url: url to use for regex based tree status.
490 closed: regex to match for closed status.
491 json_url: url to download json style status.
492 """
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000493 if not input_api.is_committing:
494 return []
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000495 try:
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000496 if json_url:
497 connection = input_api.urllib2.urlopen(json_url)
498 status = input_api.json.loads(connection.read())
499 connection.close()
500 if not status['can_commit_freely']:
501 short_text = 'Tree state is: ' + status['general_state']
502 long_text = status['message'] + '\n' + json_url
503 return [output_api.PresubmitError(short_text, long_text=long_text)]
504 else:
505 # TODO(bradnelson): drop this once all users are gone.
506 connection = input_api.urllib2.urlopen(url)
507 status = connection.read()
508 connection.close()
509 if input_api.re.match(closed, status):
510 long_text = status + '\n' + url
511 return [output_api.PresubmitError('The tree is closed.',
512 long_text=long_text)]
rohitrao@chromium.orgd490ee82012-02-06 19:31:33 +0000513 except IOError as e:
514 return [output_api.PresubmitError('Error fetching tree status.',
515 long_text=str(e))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000516 return []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000517
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000518def GetUnitTestsInDirectory(
smut@google.comac296202014-04-24 21:47:17 +0000519 input_api, output_api, directory, whitelist=None, blacklist=None, env=None):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000520 """Lists all files in a directory and runs them. Doesn't recurse.
521
nick@chromium.orgff526192013-06-10 19:30:26 +0000522 It's mainly a wrapper for RunUnitTests. Use whitelist and blacklist to filter
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000523 tests accordingly.
524 """
525 unit_tests = []
526 test_path = input_api.os_path.abspath(
527 input_api.os_path.join(input_api.PresubmitLocalPath(), directory))
528
529 def check(filename, filters):
530 return any(True for i in filters if input_api.re.match(i, filename))
531
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000532 to_run = found = 0
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000533 for filename in input_api.os_listdir(test_path):
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000534 found += 1
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000535 fullpath = input_api.os_path.join(test_path, filename)
536 if not input_api.os_path.isfile(fullpath):
537 continue
538 if whitelist and not check(filename, whitelist):
539 continue
540 if blacklist and check(filename, blacklist):
541 continue
542 unit_tests.append(input_api.os_path.join(directory, filename))
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000543 to_run += 1
544 input_api.logging.debug('Found %d files, running %d' % (found, to_run))
545 if not to_run:
546 return [
547 output_api.PresubmitPromptWarning(
548 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
549 % (found, whitelist, blacklist, directory))
550 ]
smut@google.comac296202014-04-24 21:47:17 +0000551 return GetUnitTests(input_api, output_api, unit_tests, env)
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000552
553
smut@google.comac296202014-04-24 21:47:17 +0000554def GetUnitTests(input_api, output_api, unit_tests, env=None):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000555 """Runs all unit tests in a directory.
556
557 On Windows, sys.executable is used for unit tests ending with ".py".
558 """
559 # We don't want to hinder users from uploading incomplete patches.
560 if input_api.is_committing:
561 message_type = output_api.PresubmitError
562 else:
563 message_type = output_api.PresubmitPromptWarning
564
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000565 results = []
566 for unit_test in unit_tests:
567 cmd = []
568 if input_api.platform == 'win32' and unit_test.endswith('.py'):
569 # Windows needs some help.
570 cmd = [input_api.python_executable]
571 cmd.append(unit_test)
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000572 if input_api.verbose:
maruel@chromium.org6c7723e2011-04-12 19:04:55 +0000573 cmd.append('--verbose')
smut@google.comac296202014-04-24 21:47:17 +0000574 kwargs = {'cwd': input_api.PresubmitLocalPath()}
575 if env:
576 kwargs['env'] = env
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000577 results.append(input_api.Command(
578 name=unit_test,
579 cmd=cmd,
smut@google.comac296202014-04-24 21:47:17 +0000580 kwargs=kwargs,
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000581 message=message_type))
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000582 return results
583
nick@chromium.orgff526192013-06-10 19:30:26 +0000584
agable@chromium.org40a3d0b2014-05-15 01:59:16 +0000585def GetUnitTestsRecursively(input_api, output_api, directory,
586 whitelist, blacklist):
587 """Gets all files in the directory tree (git repo) that match the whitelist.
588
589 Restricts itself to only find files within the Change's source repo, not
590 dependencies.
591 """
592 def check(filename):
593 return (any(input_api.re.match(f, filename) for f in whitelist) and
594 not any(input_api.re.match(f, filename) for f in blacklist))
595
596 tests = []
597
598 to_run = found = 0
599 for filepath in input_api.change.AllFiles(directory):
600 found += 1
601 if check(filepath):
602 to_run += 1
603 tests.append(filepath)
604 input_api.logging.debug('Found %d files, running %d' % (found, to_run))
605 if not to_run:
606 return [
607 output_api.PresubmitPromptWarning(
608 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
609 % (found, whitelist, blacklist, directory))
610 ]
611
612 return GetUnitTests(input_api, output_api, tests)
613
614
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000615def GetPythonUnitTests(input_api, output_api, unit_tests):
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000616 """Run the unit tests out of process, capture the output and use the result
617 code to determine success.
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000618
619 DEPRECATED.
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000620 """
maruel@chromium.orgd7dccf52009-06-06 18:51:58 +0000621 # We don't want to hinder users from uploading incomplete patches.
622 if input_api.is_committing:
623 message_type = output_api.PresubmitError
624 else:
625 message_type = output_api.PresubmitNotifyResult
maruel@chromium.org0e766052011-04-06 13:32:51 +0000626 results = []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000627 for unit_test in unit_tests:
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000628 # Run the unit tests out of process. This is because some unit tests
629 # stub out base libraries and don't clean up their mess. It's too easy to
630 # get subtle bugs.
631 cwd = None
632 env = None
633 unit_test_name = unit_test
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000634 # 'python -m test.unit_test' doesn't work. We need to change to the right
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000635 # directory instead.
636 if '.' in unit_test:
637 # Tests imported in submodules (subdirectories) assume that the current
638 # directory is in the PYTHONPATH. Manually fix that.
639 unit_test = unit_test.replace('.', '/')
640 cwd = input_api.os_path.dirname(unit_test)
641 unit_test = input_api.os_path.basename(unit_test)
642 env = input_api.environ.copy()
kbr@google.comab318592009-09-04 00:54:55 +0000643 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
644 backpath = [
645 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
646 ]
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000647 if env.get('PYTHONPATH'):
648 backpath.append(env.get('PYTHONPATH'))
ukai@chromium.orga301f1f2009-08-05 10:37:33 +0000649 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000650 cmd = [input_api.python_executable, '-m', '%s' % unit_test]
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000651 results.append(input_api.Command(
652 name=unit_test_name,
653 cmd=cmd,
654 kwargs={'env': env, 'cwd': cwd},
655 message=message_type))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000656 return results
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000657
658
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000659def RunUnitTestsInDirectory(input_api, *args, **kwargs):
660 """Run tests in a directory serially.
661
662 For better performance, use GetUnitTestsInDirectory and then
663 pass to input_api.RunTests.
664 """
665 return input_api.RunTests(
666 GetUnitTestsInDirectory(input_api, *args, **kwargs), False)
667
668
669def RunUnitTests(input_api, *args, **kwargs):
670 """Run tests serially.
671
672 For better performance, use GetUnitTests and then pass to
673 input_api.RunTests.
674 """
675 return input_api.RunTests(GetUnitTests(input_api, *args, **kwargs), False)
676
677
678def RunPythonUnitTests(input_api, *args, **kwargs):
679 """Run python tests in a directory serially.
680
681 DEPRECATED
682 """
683 return input_api.RunTests(
684 GetPythonUnitTests(input_api, *args, **kwargs), False)
685
686
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000687def _FetchAllFiles(input_api, white_list, black_list):
688 """Hack to fetch all files."""
689 # We cannot use AffectedFiles here because we want to test every python
690 # file on each single python change. It's because a change in a python file
691 # can break another unmodified file.
692 # Use code similar to InputApi.FilterSourceFile()
693 def Find(filepath, filters):
694 for item in filters:
695 if input_api.re.match(item, filepath):
696 return True
697 return False
698
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000699 files = []
700 path_len = len(input_api.PresubmitLocalPath())
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000701 for dirpath, dirnames, filenames in input_api.os_walk(
702 input_api.PresubmitLocalPath()):
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000703 # Passes dirnames in black list to speed up search.
704 for item in dirnames[:]:
705 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
706 if Find(filepath, black_list):
707 dirnames.remove(item)
708 for item in filenames:
709 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
710 if Find(filepath, white_list) and not Find(filepath, black_list):
711 files.append(filepath)
712 return files
713
714
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000715def GetPylint(input_api, output_api, white_list=None, black_list=None,
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000716 disabled_warnings=None, extra_paths_list=None):
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000717 """Run pylint on python files.
718
chrisha@google.com267d6592012-06-19 19:23:31 +0000719 The default white_list enforces looking only at *.py files.
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000720 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000721 white_list = tuple(white_list or ('.*\.py$',))
722 black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000723 extra_paths_list = extra_paths_list or []
724
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000725 if input_api.is_committing:
726 error_type = output_api.PresubmitError
727 else:
728 error_type = output_api.PresubmitPromptWarning
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000729
730 # Only trigger if there is at least one python file affected.
ilevy@chromium.org36576332013-01-08 03:16:15 +0000731 def rel_path(regex):
732 """Modifies a regex for a subject to accept paths relative to root."""
chrisha@chromium.org40e5d3d2013-01-18 17:46:57 +0000733 def samefile(a, b):
734 # Default implementation for platforms lacking os.path.samefile
735 # (like Windows).
736 return input_api.os_path.abspath(a) == input_api.os_path.abspath(b)
737 samefile = getattr(input_api.os_path, 'samefile', samefile)
738 if samefile(input_api.PresubmitLocalPath(),
739 input_api.change.RepositoryRoot()):
ilevy@chromium.orgdd4e9312013-01-15 03:22:04 +0000740 return regex
chrisha@chromium.org40e5d3d2013-01-18 17:46:57 +0000741
ilevy@chromium.org36576332013-01-08 03:16:15 +0000742 prefix = input_api.os_path.join(input_api.os_path.relpath(
743 input_api.PresubmitLocalPath(), input_api.change.RepositoryRoot()), '')
744 return input_api.re.escape(prefix) + regex
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000745 src_filter = lambda x: input_api.FilterSourceFile(
746 x, map(rel_path, white_list), map(rel_path, black_list))
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000747 if not input_api.AffectedSourceFiles(src_filter):
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000748 input_api.logging.info('Skipping pylint: no matching changes.')
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000749 return []
750
maruel@chromium.orgff9a2172012-04-24 16:55:32 +0000751 extra_args = ['--rcfile=%s' % input_api.os_path.join(_HERE, 'pylintrc')]
752 if disabled_warnings:
753 extra_args.extend(['-d', ','.join(disabled_warnings)])
754
chrisha@google.com267d6592012-06-19 19:23:31 +0000755 files = _FetchAllFiles(input_api, white_list, black_list)
756 if not files:
maruel@chromium.orgfca53392010-12-21 18:42:57 +0000757 return []
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000758 files.sort()
chrisha@google.com267d6592012-06-19 19:23:31 +0000759
csharp@chromium.org40395342013-02-21 14:57:23 +0000760 input_api.logging.info('Running pylint on %d files', len(files))
761 input_api.logging.debug('Running pylint on: %s', files)
chrisha@google.com267d6592012-06-19 19:23:31 +0000762 # Copy the system path to the environment so pylint can find the right
763 # imports.
764 env = input_api.environ.copy()
765 import sys
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000766 env['PYTHONPATH'] = input_api.os_path.pathsep.join(
robertshield@chromium.orga2873932013-02-20 18:08:46 +0000767 extra_paths_list + sys.path).encode('utf8')
chrisha@google.com267d6592012-06-19 19:23:31 +0000768
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000769 def GetPylintCmd(files):
770 # Windows needs help running python files so we explicitly specify
771 # the interpreter to use. It also has limitations on the size of
772 # the command-line, so we pass arguments via a pipe.
773 if len(files) == 1:
774 description = files[0]
775 else:
776 description = '%s files' % len(files)
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000777
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000778 return input_api.Command(
779 name='Pylint (%s)' % description,
780 cmd=[input_api.python_executable,
781 input_api.os_path.join(_HERE, 'third_party', 'pylint.py'),
782 '--args-on-stdin'],
783 kwargs={'env': env, 'stdin': '\n'.join(files + extra_args)},
784 message=error_type)
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000785
sbc@chromium.org7fc75ca2012-09-21 20:14:21 +0000786 # Always run pylint and pass it all the py files at once.
787 # Passing py files one at time is slower and can produce
788 # different results. input_api.verbose used to be used
789 # to enable this behaviour but differing behaviour in
790 # verbose mode is not desirable.
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000791 # Leave this unreachable code in here so users can make
792 # a quick local edit to diagnose pylint issues more
793 # easily.
sbc@chromium.org7fc75ca2012-09-21 20:14:21 +0000794 if True:
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000795 return [GetPylintCmd(files)]
chrisha@google.com267d6592012-06-19 19:23:31 +0000796 else:
agable@chromium.org40a3d0b2014-05-15 01:59:16 +0000797 return map(lambda x: GetPylintCmd([x]), files)
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000798
799
800def RunPylint(input_api, *args, **kwargs):
801 """Legacy presubmit function.
802
803 For better performance, get all tests and then pass to
804 input_api.RunTests.
805 """
806 return input_api.RunTests(GetPylint(input_api, *args, **kwargs), False)
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000807
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000808
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000809# TODO(dpranke): Get the host_url from the input_api instead
chrisha@google.com267d6592012-06-19 19:23:31 +0000810def CheckRietveldTryJobExecution(dummy_input_api, dummy_output_api,
811 dummy_host_url, dummy_platforms,
812 dummy_owner):
maruel@chromium.org85da74b2011-10-27 17:13:30 +0000813 # Temporarily 'fix' the check while the Rietveld API is being upgraded to
814 # something sensible.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000815 return []
816
817
818def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
819 ignored):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000820 try:
821 connection = input_api.urllib2.urlopen(url)
822 raw_data = connection.read()
823 connection.close()
824 except IOError:
825 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)]
826
827 try:
828 data = input_api.json.loads(raw_data)
829 except ValueError:
830 return [output_api.PresubmitNotifyResult('Received malformed json while '
831 'looking up buildbot status')]
832
833 out = []
834 for (builder_name, builder) in data.iteritems():
835 if builder_name in ignored:
836 continue
maruel@chromium.orgcf1982c2010-10-04 15:08:28 +0000837 if builder.get('state', '') == 'offline':
838 continue
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000839 pending_builds_len = len(builder.get('pending_builds', []))
840 if pending_builds_len > max_pendings:
841 out.append('%s has %d build(s) pending' %
842 (builder_name, pending_builds_len))
843 if out:
844 return [output_api.PresubmitPromptWarning(
845 'Build(s) pending. It is suggested to wait that no more than %d '
846 'builds are pending.' % max_pendings,
847 long_text='\n'.join(out))]
848 return []
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000849
850
dpranke@chromium.orgbce925d2015-01-16 22:38:38 +0000851def CheckOwners(input_api, output_api, source_file_filter=None):
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000852 if input_api.is_committing:
853 if input_api.tbr:
854 return [output_api.PresubmitNotifyResult(
855 '--tbr was specified, skipping OWNERS check')]
856 if not input_api.change.issue:
857 return [output_api.PresubmitError("OWNERS check failed: this change has "
858 "no Rietveld issue number, so we can't check it for approvals.")]
859 needed = 'LGTM from an OWNER'
860 output = output_api.PresubmitError
861 else:
862 needed = 'OWNER reviewers'
863 output = output_api.PresubmitNotifyResult
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000864
dpranke@chromium.orgadd5df42011-03-08 23:04:01 +0000865 affected_files = set([f.LocalPath() for f in
sail@chromium.org5538e022011-05-12 17:53:16 +0000866 input_api.change.AffectedFiles(file_filter=source_file_filter)])
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000867
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000868 owners_db = input_api.owners_db
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000869 owner_email, reviewers = _RietveldOwnerAndReviewers(
870 input_api,
871 owners_db.email_regexp,
872 approval_needed=input_api.is_committing)
873
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000874 owner_email = owner_email or input_api.change.author_email
875
dpranke@chromium.orgbce925d2015-01-16 22:38:38 +0000876 if owner_email:
dpranke@chromium.org4c7ce992013-03-06 17:15:40 +0000877 reviewers_plus_owner = set([owner_email]).union(reviewers)
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000878 missing_files = owners_db.files_not_covered_by(affected_files,
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000879 reviewers_plus_owner)
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000880 else:
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000881 missing_files = owners_db.files_not_covered_by(affected_files, reviewers)
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000882
dpranke@chromium.org6b1e3ee2013-02-23 00:06:38 +0000883 if missing_files:
zork@chromium.org046e1752012-05-07 05:56:12 +0000884 output_list = [
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000885 output('Missing %s for these files:\n %s' %
bauerb@chromium.orgb3b52012013-04-18 19:28:04 +0000886 (needed, '\n '.join(sorted(missing_files))))]
zork@chromium.org046e1752012-05-07 05:56:12 +0000887 if not input_api.is_committing:
dpranke@chromium.org31b42c02013-09-19 19:24:15 +0000888 suggested_owners = owners_db.reviewers_for(missing_files, owner_email)
scheib@chromium.org93276ab2013-10-14 23:55:32 +0000889 output_list.append(output('Suggested OWNERS: ' +
890 '(Use "git-cl owners" to interactively select owners.)\n %s' %
891 ('\n '.join(suggested_owners or []))))
zork@chromium.org046e1752012-05-07 05:56:12 +0000892 return output_list
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000893
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000894 if input_api.is_committing and not reviewers:
895 return [output('Missing LGTM from someone other than %s' % owner_email)]
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000896 return []
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000897
898
ilevy@chromium.org1fa5cb92012-12-05 04:04:42 +0000899def _GetRietveldIssueProps(input_api, messages):
900 """Gets the issue properties from rietveld."""
901 issue = input_api.change.issue
902 if issue and input_api.rietveld:
903 return input_api.rietveld.get_issue_properties(
904 issue=int(issue), messages=messages)
905
906
isherman@chromium.orgb5cded62014-03-25 17:47:57 +0000907def _ReviewersFromChange(change):
908 """Return the reviewers specified in the |change|, if any."""
909 reviewers = set()
910 if change.R:
911 reviewers.update(set([r.strip() for r in change.R.split(',')]))
912 if change.TBR:
913 reviewers.update(set([r.strip() for r in change.TBR.split(',')]))
isherman@chromium.org103ae032014-04-09 09:06:19 +0000914
915 # Drop reviewers that aren't specified in email address format.
916 return set(reviewer for reviewer in reviewers if '@' in reviewer)
isherman@chromium.orgb5cded62014-03-25 17:47:57 +0000917
918
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000919def _RietveldOwnerAndReviewers(input_api, email_regexp, approval_needed=False):
920 """Return the owner and reviewers of a change, if any.
921
922 If approval_needed is True, only reviewers who have approved the change
923 will be returned.
924 """
ilevy@chromium.org1fa5cb92012-12-05 04:04:42 +0000925 issue_props = _GetRietveldIssueProps(input_api, True)
926 if not issue_props:
isherman@chromium.orgb5cded62014-03-25 17:47:57 +0000927 reviewers = set()
928 if not approval_needed:
929 reviewers = _ReviewersFromChange(input_api.change)
930 return None, reviewers
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000931
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000932 if not approval_needed:
933 return issue_props['owner_email'], set(issue_props['reviewers'])
934
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000935 owner_email = issue_props['owner_email']
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000936
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000937 def match_reviewer(r):
maruel@chromium.org80941c22011-05-30 20:14:18 +0000938 return email_regexp.match(r) and r != owner_email
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000939
maruel@chromium.org80941c22011-05-30 20:14:18 +0000940 messages = issue_props.get('messages', [])
941 approvers = set(
942 m['sender'] for m in messages
943 if m.get('approval') and match_reviewer(m['sender']))
944
945 return owner_email, approvers
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000946
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000947
948def _CheckConstNSObject(input_api, output_api, source_file_filter):
949 """Checks to make sure no objective-c files have |const NSSomeClass*|."""
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000950 pattern = input_api.re.compile(
951 r'const\s+NS(?!(Point|Range|Rect|Size)\s*\*)\w*\s*\*')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000952
953 def objective_c_filter(f):
954 return (source_file_filter(f) and
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000955 input_api.os_path.splitext(f.LocalPath())[1] in ('.h', '.m', '.mm'))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000956
957 files = []
958 for f in input_api.AffectedSourceFiles(objective_c_filter):
959 contents = input_api.ReadFile(f)
960 if pattern.search(contents):
961 files.append(f)
962
963 if files:
964 if input_api.is_committing:
965 res_type = output_api.PresubmitPromptWarning
966 else:
967 res_type = output_api.PresubmitNotifyResult
968 return [ res_type('|const NSClass*| is wrong, see ' +
969 'http://dev.chromium.org/developers/clang-mac',
970 files) ]
971 return []
972
973
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000974def CheckSingletonInHeaders(input_api, output_api, source_file_filter=None):
glider@chromium.orgc3617f32015-02-19 16:33:33 +0000975 """Deprecated, must be removed."""
976 return [
977 output_api.PresubmitNotifyResult(
978 'CheckSingletonInHeaders is deprecated, please remove it.')
979 ]
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000980
981
982def PanProjectChecks(input_api, output_api,
983 excluded_paths=None, text_files=None,
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000984 license_header=None, project_name=None,
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000985 owners_check=True, maxlen=80):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000986 """Checks that ALL chromium orbit projects should use.
987
988 These are checks to be run on all Chromium orbit project, including:
989 Chromium
990 Native Client
991 V8
992 When you update this function, please take this broad scope into account.
993 Args:
994 input_api: Bag of input related interfaces.
995 output_api: Bag of output related interfaces.
996 excluded_paths: Don't include these paths in common checks.
997 text_files: Which file are to be treated as documentation text files.
998 license_header: What license header should be on files.
999 project_name: What is the name of the project as it appears in the license.
1000 Returns:
1001 A list of warning or error objects.
1002 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +00001003 excluded_paths = tuple(excluded_paths or [])
1004 text_files = tuple(text_files or (
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +00001005 r'.+\.txt$',
1006 r'.+\.json$',
maruel@chromium.org69eaecb2011-06-14 13:09:13 +00001007 ))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001008 project_name = project_name or 'Chromium'
mark@chromium.org2cc66422012-08-07 21:22:32 +00001009
1010 # Accept any year number from 2006 to the current year, or the special
rvargas@chromium.org02652602014-03-14 19:43:20 +00001011 # 2006-20xx string used on the oldest files. 2006-20xx is deprecated, but
1012 # tolerated on old files.
mark@chromium.org2cc66422012-08-07 21:22:32 +00001013 current_year = int(input_api.time.strftime('%Y'))
1014 allowed_years = (str(s) for s in reversed(xrange(2006, current_year + 1)))
rvargas@chromium.org02652602014-03-14 19:43:20 +00001015 years_re = '(' + '|'.join(allowed_years) + '|2006-2008|2006-2009|2006-2010)'
mark@chromium.org2cc66422012-08-07 21:22:32 +00001016
1017 # The (c) is deprecated, but tolerate it until it's removed from all files.
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001018 license_header = license_header or (
mark@chromium.org2cc66422012-08-07 21:22:32 +00001019 r'.*? Copyright (\(c\) )?%(year)s The %(project)s Authors\. '
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001020 r'All rights reserved\.\n'
1021 r'.*? Use of this source code is governed by a BSD-style license that '
1022 r'can be\n'
dbeam@chromium.orgb2312102012-02-15 02:01:55 +00001023 r'.*? found in the LICENSE file\.(?: \*/)?\n'
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001024 ) % {
mark@chromium.org2cc66422012-08-07 21:22:32 +00001025 'year': years_re,
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001026 'project': project_name,
1027 }
1028
1029 results = []
1030 # This code loads the default black list (e.g. third_party, experimental, etc)
1031 # and add our black list (breakpad, skia and v8 are still not following
1032 # google style and are not really living this repository).
1033 # See presubmit_support.py InputApi.FilterSourceFile for the (simple) usage.
1034 black_list = input_api.DEFAULT_BLACK_LIST + excluded_paths
1035 white_list = input_api.DEFAULT_WHITE_LIST + text_files
1036 sources = lambda x: input_api.FilterSourceFile(x, black_list=black_list)
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +00001037 text_files = lambda x: input_api.FilterSourceFile(
1038 x, black_list=black_list, white_list=white_list)
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001039
1040 snapshot_memory = []
1041 def snapshot(msg):
1042 """Measures & prints performance warning if a rule is running slow."""
1043 dt2 = input_api.time.clock()
1044 if snapshot_memory:
1045 delta_ms = int(1000*(dt2 - snapshot_memory[0]))
1046 if delta_ms > 500:
1047 print " %s took a long time: %dms" % (snapshot_memory[1], delta_ms)
1048 snapshot_memory[:] = (dt2, msg)
1049
bradnelson@google.comd57771d2011-03-31 19:18:32 +00001050 if owners_check:
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001051 snapshot("checking owners")
bradnelson@google.comd57771d2011-03-31 19:18:32 +00001052 results.extend(input_api.canned_checks.CheckOwners(
dpranke@chromium.org751797a2011-06-07 18:46:27 +00001053 input_api, output_api, source_file_filter=None))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001054
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001055 snapshot("checking long lines")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001056 results.extend(input_api.canned_checks.CheckLongLines(
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +00001057 input_api, output_api, maxlen, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001058 snapshot( "checking tabs")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001059 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
1060 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001061 snapshot( "checking stray whitespace")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001062 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
1063 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001064 snapshot("checking nsobjects")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001065 results.extend(_CheckConstNSObject(
1066 input_api, output_api, source_file_filter=sources))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +00001067
1068 # The following checks are only done on commit, since the commit bot will
1069 # auto-fix most of these.
1070 if input_api.is_committing:
maruel@chromium.org8571dac2011-05-10 18:10:13 +00001071 snapshot("checking eol style")
1072 results.extend(input_api.canned_checks.CheckChangeSvnEolStyle(
1073 input_api, output_api, source_file_filter=text_files))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +00001074 snapshot("checking svn mime types")
1075 results.extend(input_api.canned_checks.CheckSvnForCommonMimeTypes(
1076 input_api, output_api))
1077 snapshot("checking license")
1078 results.extend(input_api.canned_checks.CheckLicense(
1079 input_api, output_api, license_header, source_file_filter=sources))
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +00001080 snapshot("checking was uploaded")
1081 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
1082 input_api, output_api))
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +00001083 snapshot("checking description")
1084 results.extend(input_api.canned_checks.CheckChangeHasDescription(
1085 input_api, output_api))
1086 results.extend(input_api.canned_checks.CheckDoNotSubmitInDescription(
1087 input_api, output_api))
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +00001088 snapshot("checking do not submit in files")
1089 results.extend(input_api.canned_checks.CheckDoNotSubmitInFiles(
1090 input_api, output_api))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001091 snapshot("done")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001092 return results
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001093
1094
1095def CheckPatchFormatted(input_api, output_api):
1096 import git_cl
enne@chromium.org555cfe42014-01-29 18:21:39 +00001097 cmd = ['cl', 'format', '--dry-run', input_api.PresubmitLocalPath()]
1098 code, _ = git_cl.RunGitWithCode(cmd, suppress_stderr=True)
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001099 if code == 2:
1100 return [output_api.PresubmitPromptWarning(
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00001101 'The %s directory requires source formatting. '
enne@chromium.org7b7b5b22014-10-16 19:23:07 +00001102 'Please run git cl format %s' %
1103 (input_api.basename(input_api.PresubmitLocalPath()),
1104 input_api.basename(input_api.PresubmitLocalPath())))]
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001105 # As this is just a warning, ignore all other errors if the user
1106 # happens to have a broken clang-format, doesn't use git, etc etc.
1107 return []
scottmg@chromium.orgd05ab352014-12-05 17:24:26 +00001108
1109
1110def CheckGNFormatted(input_api, output_api):
1111 import gn
1112 affected_files = input_api.AffectedFiles(
1113 include_deletes=False,
1114 file_filter=lambda x: x.LocalPath().endswith('.gn') or
1115 x.LocalPath().endswith('.gni'))
1116 warnings = []
1117 for f in affected_files:
1118 cmd = ['gn', 'format', '--dry-run', f.AbsoluteLocalPath()]
1119 rc = gn.main(cmd)
1120 if rc == 2:
1121 warnings.append(output_api.PresubmitPromptWarning(
1122 '%s requires formatting. Please run `gn format --in-place %s`.' % (
1123 f.AbsoluteLocalPath(), f.LocalPath())))
1124 # It's just a warning, so ignore other types of failures assuming they'll be
1125 # caught elsewhere.
1126 return warnings