blob: 1e2d1ab62e39e4dba0dfe3b7ff935b4f6790ed6b [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,
115 lint_filters=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
139 cpplint.ProcessFile(file_name, level)
140
141 if cpplint._cpplint_state.error_count > 0:
142 if input_api.is_committing:
143 res_type = output_api.PresubmitError
144 else:
145 res_type = output_api.PresubmitPromptWarning
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000146 result = [res_type('Changelist failed cpplint.py check.')]
erg@google.com26970fa2009-11-17 18:07:32 +0000147
148 return result
149
150
maruel@chromium.org3410d912009-06-09 20:56:16 +0000151def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000152 """Checks no '\r' (CR) character is in any source files."""
153 cr_files = []
maruel@chromium.org3410d912009-06-09 20:56:16 +0000154 for f in input_api.AffectedSourceFiles(source_file_filter):
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000155 if '\r' in input_api.ReadFile(f, 'rb'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000156 cr_files.append(f.LocalPath())
157 if cr_files:
158 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000159 'Found a CR character in these files:', items=cr_files)]
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000160 return []
161
162
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000163def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None):
164 """Checks for files in svn modified directories.
165
166 They will get submitted on accident because svn commits recursively by
167 default, and that's very dangerous.
168 """
169 if input_api.change.scm != 'svn':
170 return []
171
172 errors = []
173 current_cl_files = input_api.change.GetModifiedFiles()
174 all_modified_files = input_api.change.GetAllModifiedFiles()
175 # Filter out files in the current CL.
176 modified_files = [f for f in all_modified_files if f not in current_cl_files]
177 modified_abspaths = [input_api.os_path.abspath(f) for f in modified_files]
178
sail@chromium.org5538e022011-05-12 17:53:16 +0000179 for f in input_api.AffectedFiles(file_filter=source_file_filter):
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000180 if f.Action() == 'M' and f.IsDirectory():
181 curpath = f.AbsoluteLocalPath()
182 bad_files = []
183 # Check if any of the modified files in other CLs are under curpath.
184 for i in xrange(len(modified_files)):
185 abspath = modified_abspaths[i]
186 if input_api.os_path.commonprefix([curpath, abspath]) == curpath:
187 bad_files.append(modified_files[i])
188 if bad_files:
189 if input_api.is_committing:
190 error_type = output_api.PresubmitPromptWarning
191 else:
192 error_type = output_api.PresubmitNotifyResult
193 errors.append(error_type(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000194 'Potential accidental commits in changelist %s:' % f.LocalPath(),
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000195 items=bad_files))
196 return errors
197
198
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000199def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
200 """Checks the files ends with one and only one \n (LF)."""
201 eof_files = []
202 for f in input_api.AffectedSourceFiles(source_file_filter):
203 contents = input_api.ReadFile(f, 'rb')
204 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000205 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000206 eof_files.append(f.LocalPath())
207
208 if eof_files:
209 return [output_api.PresubmitPromptWarning(
210 'These files should end in one (and only one) newline character:',
211 items=eof_files)]
212 return []
213
214
215def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api,
216 source_file_filter=None):
217 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass.
218
219 It is faster because it is reading the file only once.
220 """
221 cr_files = []
222 eof_files = []
223 for f in input_api.AffectedSourceFiles(source_file_filter):
224 contents = input_api.ReadFile(f, 'rb')
225 if '\r' in contents:
226 cr_files.append(f.LocalPath())
227 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000228 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000229 eof_files.append(f.LocalPath())
230 outputs = []
231 if cr_files:
232 outputs.append(output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000233 'Found a CR character in these files:', items=cr_files))
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000234 if eof_files:
235 outputs.append(output_api.PresubmitPromptWarning(
236 'These files should end in one (and only one) newline character:',
237 items=eof_files))
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000238 return outputs
239
240
chrisha@google.com267d6592012-06-19 19:23:31 +0000241def _ReportErrorFileAndLine(filename, line_num, dummy_line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000242 """Default error formatter for _FindNewViolationsOfRule."""
danakj@chromium.orgc5965ba2013-08-14 00:27:24 +0000243 return '%s:%s' % (filename, line_num)
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000244
245
246def _FindNewViolationsOfRule(callable_rule, input_api, source_file_filter=None,
247 error_formatter=_ReportErrorFileAndLine):
248 """Find all newly introduced violations of a per-line rule (a callable).
249
250 Arguments:
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000251 callable_rule: a callable taking a file extension and line of input and
252 returning True if the rule is satisfied and False if there was a problem.
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000253 input_api: object to enumerate the affected files.
254 source_file_filter: a filter to be passed to the input api.
255 error_formatter: a callable taking (filename, line_number, line) and
256 returning a formatted error string.
257
258 Returns:
259 A list of the newly-introduced violations reported by the rule.
260 """
261 errors = []
sail@chromium.org5538e022011-05-12 17:53:16 +0000262 for f in input_api.AffectedFiles(include_deletes=False,
263 file_filter=source_file_filter):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000264 # For speed, we do two passes, checking first the full file. Shelling out
265 # to the SCM to determine the changed region can be quite expensive on
266 # Win32. Assuming that most files will be kept problem-free, we can
267 # skip the SCM operations most of the time.
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000268 extension = str(f.LocalPath()).rsplit('.', 1)[-1]
269 if all(callable_rule(extension, line) for line in f.NewContents()):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000270 continue # No violation found in full text: can skip considering diff.
271
272 for line_num, line in f.ChangedContents():
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000273 if not callable_rule(extension, line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000274 errors.append(error_formatter(f.LocalPath(), line_num, line))
275
276 return errors
277
278
maruel@chromium.org3410d912009-06-09 20:56:16 +0000279def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000280 """Checks that there are no tab characters in any of the text files to be
281 submitted.
282 """
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000283 # In addition to the filter, make sure that makefiles are blacklisted.
284 if not source_file_filter:
285 # It's the default filter.
286 source_file_filter = input_api.FilterSourceFile
287 def filter_more(affected_file):
cmp@chromium.orgcb5e57c2012-04-06 19:50:15 +0000288 basename = input_api.os_path.basename(affected_file.LocalPath())
289 return (not (basename in ('Makefile', 'makefile') or
290 basename.endswith('.mk')) and
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000291 source_file_filter(affected_file))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000292
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000293 tabs = _FindNewViolationsOfRule(lambda _, line : '\t' not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000294 input_api, filter_more)
295
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000296 if tabs:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000297 return [output_api.PresubmitPromptWarning('Found a tab character in:',
298 long_text='\n'.join(tabs))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000299 return []
300
301
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000302def CheckChangeTodoHasOwner(input_api, output_api, source_file_filter=None):
303 """Checks that the user didn't add TODO(name) without an owner."""
304
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000305 unowned_todo = input_api.re.compile('TO''DO[^(]')
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000306 errors = _FindNewViolationsOfRule(lambda _, x : not unowned_todo.search(x),
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000307 input_api, source_file_filter)
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000308 errors = ['Found TO''DO with no owner in ' + x for x in errors]
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000309 if errors:
310 return [output_api.PresubmitPromptWarning('\n'.join(errors))]
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000311 return []
312
313
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000314def CheckChangeHasNoStrayWhitespace(input_api, output_api,
315 source_file_filter=None):
316 """Checks that there is no stray whitespace at source lines end."""
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000317 errors = _FindNewViolationsOfRule(lambda _, line : line.rstrip() == line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000318 input_api, source_file_filter)
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000319 if errors:
320 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000321 'Found line ending with white spaces in:',
322 long_text='\n'.join(errors))]
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000323 return []
324
325
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000326def CheckLongLines(input_api, output_api, maxlen, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000327 """Checks that there aren't any lines longer than maxlen characters in any of
328 the text files to be submitted.
329 """
dpranke@chromium.orge3b1c3d2012-10-20 22:28:14 +0000330 maxlens = {
331 'java': 100,
torne@chromium.org0ecad9c2013-02-15 16:35:16 +0000332 # This is specifically for Android's handwritten makefiles (Android.mk).
333 'mk': 200,
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000334 '': maxlen,
335 }
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000336
erikchen@google.com12816082013-12-03 02:04:20 +0000337 # Language specific exceptions to max line length.
338 # '.h' is considered an obj-c file extension, since OBJC_EXCEPTIONS are a
339 # superset of CPP_EXCEPTIONS.
340 CPP_FILE_EXTS = ('c', 'cc')
341 CPP_EXCEPTIONS = ('#define', '#endif', '#if', '#include', '#pragma')
342 JAVA_FILE_EXTS = ('java',)
343 JAVA_EXCEPTIONS = ('import ', 'package ')
344 OBJC_FILE_EXTS = ('h', 'm', 'mm')
345 OBJC_EXCEPTIONS = ('#define', '#endif', '#if', '#import', '#include',
346 '#pragma')
347
348 LANGUAGE_EXCEPTIONS = [
349 (CPP_FILE_EXTS, CPP_EXCEPTIONS),
350 (JAVA_FILE_EXTS, JAVA_EXCEPTIONS),
351 (OBJC_FILE_EXTS, OBJC_EXCEPTIONS),
352 ]
sivachandra@chromium.org270db5c2012-10-09 22:38:44 +0000353
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000354 def no_long_lines(file_extension, line):
erikchen@google.com12816082013-12-03 02:04:20 +0000355 # Check for language specific exceptions.
356 if any(file_extension in exts and line.startswith(exceptions)
357 for exts, exceptions in LANGUAGE_EXCEPTIONS):
sivachandra@chromium.org270db5c2012-10-09 22:38:44 +0000358 return True
359
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000360 file_maxlen = maxlens.get(file_extension, maxlens[''])
361 # Stupidly long symbols that needs to be worked around if takes 66% of line.
362 long_symbol = file_maxlen * 2 / 3
363 # Hard line length limit at 50% more.
364 extra_maxlen = file_maxlen * 3 / 2
365
366 line_len = len(line)
367 if line_len <= file_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000368 return True
369
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000370 if line_len > extra_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000371 return False
372
treib@chromium.orgde3ee902014-07-28 14:23:11 +0000373 if any((url in line) for url in ('file://', 'http://', 'https://')):
374 return True
375
376 if 'url(' in line and file_extension == 'css':
377 return True
378
dbeam@chromium.orgb5ccc9b2014-09-23 00:42:22 +0000379 if '<include' in line and file_extension in ('css', 'html', 'js'):
380 return True
381
treib@chromium.orgde3ee902014-07-28 14:23:11 +0000382 return input_api.re.match(
383 r'.*[A-Za-z][A-Za-z_0-9]{%d,}.*' % long_symbol, line)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000384
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000385 def format_error(filename, line_num, line):
386 return '%s, line %s, %s chars' % (filename, line_num, len(line))
387
388 errors = _FindNewViolationsOfRule(no_long_lines, input_api,
389 source_file_filter,
390 error_formatter=format_error)
391 if errors:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000392 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000393 return [output_api.PresubmitPromptWarning(msg, items=errors[:5])]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000394 else:
395 return []
396
397
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000398def CheckLicense(input_api, output_api, license_re, source_file_filter=None,
maruel@chromium.org71626852010-11-03 13:14:25 +0000399 accept_empty_files=True):
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000400 """Verifies the license header.
401 """
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000402 license_re = input_api.re.compile(license_re, input_api.re.MULTILINE)
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000403 bad_files = []
404 for f in input_api.AffectedSourceFiles(source_file_filter):
405 contents = input_api.ReadFile(f, 'rb')
maruel@chromium.org71626852010-11-03 13:14:25 +0000406 if accept_empty_files and not contents:
407 continue
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000408 if not license_re.search(contents):
409 bad_files.append(f.LocalPath())
410 if bad_files:
411 if input_api.is_committing:
412 res_type = output_api.PresubmitPromptWarning
413 else:
414 res_type = output_api.PresubmitNotifyResult
415 return [res_type(
bradnelson@google.com393460b2011-03-29 01:20:26 +0000416 'License must match:\n%s\n' % license_re.pattern +
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000417 'Found a bad license header in these files:', items=bad_files)]
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000418 return []
419
420
maruel@chromium.org1a0e3cb2009-06-10 18:03:04 +0000421def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None):
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000422 """Checks that the source files have svn:eol-style=LF."""
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000423 return CheckSvnProperty(input_api, output_api,
424 'svn:eol-style', 'LF',
425 input_api.AffectedSourceFiles(source_file_filter))
426
427
428def CheckSvnForCommonMimeTypes(input_api, output_api):
429 """Checks that common binary file types have the correct svn:mime-type."""
430 output = []
431 files = input_api.AffectedFiles(include_deletes=False)
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000432 def IsExts(x, exts):
433 path = x.LocalPath()
434 for extension in exts:
435 if path.endswith(extension):
436 return True
437 return False
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000438 def FilterFiles(extension):
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000439 return filter(lambda x: IsExts(x, extension), files)
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000440 def RunCheck(mime_type, files):
441 output.extend(CheckSvnProperty(input_api, output_api, 'svn:mime-type',
442 mime_type, files))
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000443 RunCheck('application/pdf', FilterFiles(['.pdf']))
444 RunCheck('image/bmp', FilterFiles(['.bmp']))
445 RunCheck('image/gif', FilterFiles(['.gif']))
446 RunCheck('image/png', FilterFiles(['.png']))
447 RunCheck('image/jpeg', FilterFiles(['.jpg', '.jpeg', '.jpe']))
448 RunCheck('image/vnd.microsoft.icon', FilterFiles(['.ico']))
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000449 return output
450
451
452def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
453 """Checks that affected_files files have prop=expected."""
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000454 if input_api.change.scm != 'svn':
455 return []
456
457 bad = filter(lambda f: f.Property(prop) != expected, affected_files)
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000458 if bad:
maruel@chromium.org0874d472009-06-10 19:08:33 +0000459 if input_api.is_committing:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000460 res_type = output_api.PresubmitError
maruel@chromium.org0874d472009-06-10 19:08:33 +0000461 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000462 res_type = output_api.PresubmitNotifyResult
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000463 message = 'Run the command: svn pset %s %s \\' % (prop, expected)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000464 return [res_type(message, items=bad)]
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000465 return []
466
467
maruel@chromium.org3410d912009-06-09 20:56:16 +0000468### Other checks
469
470def CheckDoNotSubmit(input_api, output_api):
471 return (
472 CheckDoNotSubmitInDescription(input_api, output_api) +
473 CheckDoNotSubmitInFiles(input_api, output_api)
474 )
475
476
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000477def CheckTreeIsOpen(input_api, output_api,
478 url=None, closed=None, json_url=None):
479 """Check whether to allow commit without prompt.
480
481 Supports two styles:
482 1. Checks that an url's content doesn't match a regexp that would mean that
483 the tree is closed. (old)
484 2. Check the json_url to decide whether to allow commit without prompt.
485 Args:
486 input_api: input related apis.
487 output_api: output related apis.
488 url: url to use for regex based tree status.
489 closed: regex to match for closed status.
490 json_url: url to download json style status.
491 """
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000492 if not input_api.is_committing:
493 return []
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000494 try:
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000495 if json_url:
496 connection = input_api.urllib2.urlopen(json_url)
497 status = input_api.json.loads(connection.read())
498 connection.close()
499 if not status['can_commit_freely']:
500 short_text = 'Tree state is: ' + status['general_state']
501 long_text = status['message'] + '\n' + json_url
502 return [output_api.PresubmitError(short_text, long_text=long_text)]
503 else:
504 # TODO(bradnelson): drop this once all users are gone.
505 connection = input_api.urllib2.urlopen(url)
506 status = connection.read()
507 connection.close()
508 if input_api.re.match(closed, status):
509 long_text = status + '\n' + url
510 return [output_api.PresubmitError('The tree is closed.',
511 long_text=long_text)]
rohitrao@chromium.orgd490ee82012-02-06 19:31:33 +0000512 except IOError as e:
513 return [output_api.PresubmitError('Error fetching tree status.',
514 long_text=str(e))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000515 return []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000516
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000517def GetUnitTestsInDirectory(
smut@google.comac296202014-04-24 21:47:17 +0000518 input_api, output_api, directory, whitelist=None, blacklist=None, env=None):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000519 """Lists all files in a directory and runs them. Doesn't recurse.
520
nick@chromium.orgff526192013-06-10 19:30:26 +0000521 It's mainly a wrapper for RunUnitTests. Use whitelist and blacklist to filter
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000522 tests accordingly.
523 """
524 unit_tests = []
525 test_path = input_api.os_path.abspath(
526 input_api.os_path.join(input_api.PresubmitLocalPath(), directory))
527
528 def check(filename, filters):
529 return any(True for i in filters if input_api.re.match(i, filename))
530
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000531 to_run = found = 0
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000532 for filename in input_api.os_listdir(test_path):
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000533 found += 1
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000534 fullpath = input_api.os_path.join(test_path, filename)
535 if not input_api.os_path.isfile(fullpath):
536 continue
537 if whitelist and not check(filename, whitelist):
538 continue
539 if blacklist and check(filename, blacklist):
540 continue
541 unit_tests.append(input_api.os_path.join(directory, filename))
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000542 to_run += 1
543 input_api.logging.debug('Found %d files, running %d' % (found, to_run))
544 if not to_run:
545 return [
546 output_api.PresubmitPromptWarning(
547 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
548 % (found, whitelist, blacklist, directory))
549 ]
smut@google.comac296202014-04-24 21:47:17 +0000550 return GetUnitTests(input_api, output_api, unit_tests, env)
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000551
552
smut@google.comac296202014-04-24 21:47:17 +0000553def GetUnitTests(input_api, output_api, unit_tests, env=None):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000554 """Runs all unit tests in a directory.
555
556 On Windows, sys.executable is used for unit tests ending with ".py".
557 """
558 # We don't want to hinder users from uploading incomplete patches.
559 if input_api.is_committing:
560 message_type = output_api.PresubmitError
561 else:
562 message_type = output_api.PresubmitPromptWarning
563
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000564 results = []
565 for unit_test in unit_tests:
566 cmd = []
567 if input_api.platform == 'win32' and unit_test.endswith('.py'):
568 # Windows needs some help.
569 cmd = [input_api.python_executable]
570 cmd.append(unit_test)
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000571 if input_api.verbose:
maruel@chromium.org6c7723e2011-04-12 19:04:55 +0000572 cmd.append('--verbose')
smut@google.comac296202014-04-24 21:47:17 +0000573 kwargs = {'cwd': input_api.PresubmitLocalPath()}
574 if env:
575 kwargs['env'] = env
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000576 results.append(input_api.Command(
577 name=unit_test,
578 cmd=cmd,
smut@google.comac296202014-04-24 21:47:17 +0000579 kwargs=kwargs,
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000580 message=message_type))
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000581 return results
582
nick@chromium.orgff526192013-06-10 19:30:26 +0000583
agable@chromium.org40a3d0b2014-05-15 01:59:16 +0000584def GetUnitTestsRecursively(input_api, output_api, directory,
585 whitelist, blacklist):
586 """Gets all files in the directory tree (git repo) that match the whitelist.
587
588 Restricts itself to only find files within the Change's source repo, not
589 dependencies.
590 """
591 def check(filename):
592 return (any(input_api.re.match(f, filename) for f in whitelist) and
593 not any(input_api.re.match(f, filename) for f in blacklist))
594
595 tests = []
596
597 to_run = found = 0
598 for filepath in input_api.change.AllFiles(directory):
599 found += 1
600 if check(filepath):
601 to_run += 1
602 tests.append(filepath)
603 input_api.logging.debug('Found %d files, running %d' % (found, to_run))
604 if not to_run:
605 return [
606 output_api.PresubmitPromptWarning(
607 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
608 % (found, whitelist, blacklist, directory))
609 ]
610
611 return GetUnitTests(input_api, output_api, tests)
612
613
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000614def GetPythonUnitTests(input_api, output_api, unit_tests):
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000615 """Run the unit tests out of process, capture the output and use the result
616 code to determine success.
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000617
618 DEPRECATED.
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000619 """
maruel@chromium.orgd7dccf52009-06-06 18:51:58 +0000620 # We don't want to hinder users from uploading incomplete patches.
621 if input_api.is_committing:
622 message_type = output_api.PresubmitError
623 else:
624 message_type = output_api.PresubmitNotifyResult
maruel@chromium.org0e766052011-04-06 13:32:51 +0000625 results = []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000626 for unit_test in unit_tests:
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000627 # Run the unit tests out of process. This is because some unit tests
628 # stub out base libraries and don't clean up their mess. It's too easy to
629 # get subtle bugs.
630 cwd = None
631 env = None
632 unit_test_name = unit_test
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000633 # 'python -m test.unit_test' doesn't work. We need to change to the right
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000634 # directory instead.
635 if '.' in unit_test:
636 # Tests imported in submodules (subdirectories) assume that the current
637 # directory is in the PYTHONPATH. Manually fix that.
638 unit_test = unit_test.replace('.', '/')
639 cwd = input_api.os_path.dirname(unit_test)
640 unit_test = input_api.os_path.basename(unit_test)
641 env = input_api.environ.copy()
kbr@google.comab318592009-09-04 00:54:55 +0000642 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
643 backpath = [
644 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
645 ]
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000646 if env.get('PYTHONPATH'):
647 backpath.append(env.get('PYTHONPATH'))
ukai@chromium.orga301f1f2009-08-05 10:37:33 +0000648 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000649 cmd = [input_api.python_executable, '-m', '%s' % unit_test]
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000650 results.append(input_api.Command(
651 name=unit_test_name,
652 cmd=cmd,
653 kwargs={'env': env, 'cwd': cwd},
654 message=message_type))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000655 return results
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000656
657
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000658def RunUnitTestsInDirectory(input_api, *args, **kwargs):
659 """Run tests in a directory serially.
660
661 For better performance, use GetUnitTestsInDirectory and then
662 pass to input_api.RunTests.
663 """
664 return input_api.RunTests(
665 GetUnitTestsInDirectory(input_api, *args, **kwargs), False)
666
667
668def RunUnitTests(input_api, *args, **kwargs):
669 """Run tests serially.
670
671 For better performance, use GetUnitTests and then pass to
672 input_api.RunTests.
673 """
674 return input_api.RunTests(GetUnitTests(input_api, *args, **kwargs), False)
675
676
677def RunPythonUnitTests(input_api, *args, **kwargs):
678 """Run python tests in a directory serially.
679
680 DEPRECATED
681 """
682 return input_api.RunTests(
683 GetPythonUnitTests(input_api, *args, **kwargs), False)
684
685
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000686def _FetchAllFiles(input_api, white_list, black_list):
687 """Hack to fetch all files."""
688 # We cannot use AffectedFiles here because we want to test every python
689 # file on each single python change. It's because a change in a python file
690 # can break another unmodified file.
691 # Use code similar to InputApi.FilterSourceFile()
692 def Find(filepath, filters):
693 for item in filters:
694 if input_api.re.match(item, filepath):
695 return True
696 return False
697
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000698 files = []
699 path_len = len(input_api.PresubmitLocalPath())
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000700 for dirpath, dirnames, filenames in input_api.os_walk(
701 input_api.PresubmitLocalPath()):
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000702 # Passes dirnames in black list to speed up search.
703 for item in dirnames[:]:
704 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
705 if Find(filepath, black_list):
706 dirnames.remove(item)
707 for item in filenames:
708 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
709 if Find(filepath, white_list) and not Find(filepath, black_list):
710 files.append(filepath)
711 return files
712
713
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000714def GetPylint(input_api, output_api, white_list=None, black_list=None,
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000715 disabled_warnings=None, extra_paths_list=None):
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000716 """Run pylint on python files.
717
chrisha@google.com267d6592012-06-19 19:23:31 +0000718 The default white_list enforces looking only at *.py files.
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000719 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000720 white_list = tuple(white_list or ('.*\.py$',))
721 black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000722 extra_paths_list = extra_paths_list or []
723
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000724 if input_api.is_committing:
725 error_type = output_api.PresubmitError
726 else:
727 error_type = output_api.PresubmitPromptWarning
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000728
729 # Only trigger if there is at least one python file affected.
ilevy@chromium.org36576332013-01-08 03:16:15 +0000730 def rel_path(regex):
731 """Modifies a regex for a subject to accept paths relative to root."""
chrisha@chromium.org40e5d3d2013-01-18 17:46:57 +0000732 def samefile(a, b):
733 # Default implementation for platforms lacking os.path.samefile
734 # (like Windows).
735 return input_api.os_path.abspath(a) == input_api.os_path.abspath(b)
736 samefile = getattr(input_api.os_path, 'samefile', samefile)
737 if samefile(input_api.PresubmitLocalPath(),
738 input_api.change.RepositoryRoot()):
ilevy@chromium.orgdd4e9312013-01-15 03:22:04 +0000739 return regex
chrisha@chromium.org40e5d3d2013-01-18 17:46:57 +0000740
ilevy@chromium.org36576332013-01-08 03:16:15 +0000741 prefix = input_api.os_path.join(input_api.os_path.relpath(
742 input_api.PresubmitLocalPath(), input_api.change.RepositoryRoot()), '')
743 return input_api.re.escape(prefix) + regex
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000744 src_filter = lambda x: input_api.FilterSourceFile(
745 x, map(rel_path, white_list), map(rel_path, black_list))
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000746 if not input_api.AffectedSourceFiles(src_filter):
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000747 input_api.logging.info('Skipping pylint: no matching changes.')
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000748 return []
749
maruel@chromium.orgff9a2172012-04-24 16:55:32 +0000750 extra_args = ['--rcfile=%s' % input_api.os_path.join(_HERE, 'pylintrc')]
751 if disabled_warnings:
752 extra_args.extend(['-d', ','.join(disabled_warnings)])
753
chrisha@google.com267d6592012-06-19 19:23:31 +0000754 files = _FetchAllFiles(input_api, white_list, black_list)
755 if not files:
maruel@chromium.orgfca53392010-12-21 18:42:57 +0000756 return []
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000757 files.sort()
chrisha@google.com267d6592012-06-19 19:23:31 +0000758
csharp@chromium.org40395342013-02-21 14:57:23 +0000759 input_api.logging.info('Running pylint on %d files', len(files))
760 input_api.logging.debug('Running pylint on: %s', files)
chrisha@google.com267d6592012-06-19 19:23:31 +0000761 # Copy the system path to the environment so pylint can find the right
762 # imports.
763 env = input_api.environ.copy()
764 import sys
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000765 env['PYTHONPATH'] = input_api.os_path.pathsep.join(
robertshield@chromium.orga2873932013-02-20 18:08:46 +0000766 extra_paths_list + sys.path).encode('utf8')
chrisha@google.com267d6592012-06-19 19:23:31 +0000767
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000768 def GetPylintCmd(files):
769 # Windows needs help running python files so we explicitly specify
770 # the interpreter to use. It also has limitations on the size of
771 # the command-line, so we pass arguments via a pipe.
772 if len(files) == 1:
773 description = files[0]
774 else:
775 description = '%s files' % len(files)
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000776
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000777 return input_api.Command(
778 name='Pylint (%s)' % description,
779 cmd=[input_api.python_executable,
780 input_api.os_path.join(_HERE, 'third_party', 'pylint.py'),
781 '--args-on-stdin'],
782 kwargs={'env': env, 'stdin': '\n'.join(files + extra_args)},
783 message=error_type)
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000784
sbc@chromium.org7fc75ca2012-09-21 20:14:21 +0000785 # Always run pylint and pass it all the py files at once.
786 # Passing py files one at time is slower and can produce
787 # different results. input_api.verbose used to be used
788 # to enable this behaviour but differing behaviour in
789 # verbose mode is not desirable.
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000790 # Leave this unreachable code in here so users can make
791 # a quick local edit to diagnose pylint issues more
792 # easily.
sbc@chromium.org7fc75ca2012-09-21 20:14:21 +0000793 if True:
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000794 return [GetPylintCmd(files)]
chrisha@google.com267d6592012-06-19 19:23:31 +0000795 else:
agable@chromium.org40a3d0b2014-05-15 01:59:16 +0000796 return map(lambda x: GetPylintCmd([x]), files)
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000797
798
799def RunPylint(input_api, *args, **kwargs):
800 """Legacy presubmit function.
801
802 For better performance, get all tests and then pass to
803 input_api.RunTests.
804 """
805 return input_api.RunTests(GetPylint(input_api, *args, **kwargs), False)
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000806
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000807
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000808# TODO(dpranke): Get the host_url from the input_api instead
chrisha@google.com267d6592012-06-19 19:23:31 +0000809def CheckRietveldTryJobExecution(dummy_input_api, dummy_output_api,
810 dummy_host_url, dummy_platforms,
811 dummy_owner):
maruel@chromium.org85da74b2011-10-27 17:13:30 +0000812 # Temporarily 'fix' the check while the Rietveld API is being upgraded to
813 # something sensible.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000814 return []
815
816
817def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
818 ignored):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000819 try:
820 connection = input_api.urllib2.urlopen(url)
821 raw_data = connection.read()
822 connection.close()
823 except IOError:
824 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)]
825
826 try:
827 data = input_api.json.loads(raw_data)
828 except ValueError:
829 return [output_api.PresubmitNotifyResult('Received malformed json while '
830 'looking up buildbot status')]
831
832 out = []
833 for (builder_name, builder) in data.iteritems():
834 if builder_name in ignored:
835 continue
maruel@chromium.orgcf1982c2010-10-04 15:08:28 +0000836 if builder.get('state', '') == 'offline':
837 continue
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000838 pending_builds_len = len(builder.get('pending_builds', []))
839 if pending_builds_len > max_pendings:
840 out.append('%s has %d build(s) pending' %
841 (builder_name, pending_builds_len))
842 if out:
843 return [output_api.PresubmitPromptWarning(
844 'Build(s) pending. It is suggested to wait that no more than %d '
845 'builds are pending.' % max_pendings,
846 long_text='\n'.join(out))]
847 return []
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000848
849
dpranke@chromium.orgbce925d2015-01-16 22:38:38 +0000850def CheckOwners(input_api, output_api, source_file_filter=None):
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000851 if input_api.is_committing:
852 if input_api.tbr:
853 return [output_api.PresubmitNotifyResult(
854 '--tbr was specified, skipping OWNERS check')]
855 if not input_api.change.issue:
856 return [output_api.PresubmitError("OWNERS check failed: this change has "
857 "no Rietveld issue number, so we can't check it for approvals.")]
858 needed = 'LGTM from an OWNER'
859 output = output_api.PresubmitError
860 else:
861 needed = 'OWNER reviewers'
862 output = output_api.PresubmitNotifyResult
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000863
dpranke@chromium.orgadd5df42011-03-08 23:04:01 +0000864 affected_files = set([f.LocalPath() for f in
sail@chromium.org5538e022011-05-12 17:53:16 +0000865 input_api.change.AffectedFiles(file_filter=source_file_filter)])
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000866
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000867 owners_db = input_api.owners_db
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000868 owner_email, reviewers = _RietveldOwnerAndReviewers(
869 input_api,
870 owners_db.email_regexp,
871 approval_needed=input_api.is_committing)
872
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000873 owner_email = owner_email or input_api.change.author_email
874
dpranke@chromium.orgbce925d2015-01-16 22:38:38 +0000875 if owner_email:
dpranke@chromium.org4c7ce992013-03-06 17:15:40 +0000876 reviewers_plus_owner = set([owner_email]).union(reviewers)
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000877 missing_files = owners_db.files_not_covered_by(affected_files,
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000878 reviewers_plus_owner)
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000879 else:
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000880 missing_files = owners_db.files_not_covered_by(affected_files, reviewers)
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000881
dpranke@chromium.org6b1e3ee2013-02-23 00:06:38 +0000882 if missing_files:
zork@chromium.org046e1752012-05-07 05:56:12 +0000883 output_list = [
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000884 output('Missing %s for these files:\n %s' %
bauerb@chromium.orgb3b52012013-04-18 19:28:04 +0000885 (needed, '\n '.join(sorted(missing_files))))]
zork@chromium.org046e1752012-05-07 05:56:12 +0000886 if not input_api.is_committing:
dpranke@chromium.org31b42c02013-09-19 19:24:15 +0000887 suggested_owners = owners_db.reviewers_for(missing_files, owner_email)
scheib@chromium.org93276ab2013-10-14 23:55:32 +0000888 output_list.append(output('Suggested OWNERS: ' +
889 '(Use "git-cl owners" to interactively select owners.)\n %s' %
890 ('\n '.join(suggested_owners or []))))
zork@chromium.org046e1752012-05-07 05:56:12 +0000891 return output_list
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000892
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000893 if input_api.is_committing and not reviewers:
894 return [output('Missing LGTM from someone other than %s' % owner_email)]
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000895 return []
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000896
897
ilevy@chromium.org1fa5cb92012-12-05 04:04:42 +0000898def _GetRietveldIssueProps(input_api, messages):
899 """Gets the issue properties from rietveld."""
900 issue = input_api.change.issue
901 if issue and input_api.rietveld:
902 return input_api.rietveld.get_issue_properties(
903 issue=int(issue), messages=messages)
904
905
isherman@chromium.orgb5cded62014-03-25 17:47:57 +0000906def _ReviewersFromChange(change):
907 """Return the reviewers specified in the |change|, if any."""
908 reviewers = set()
909 if change.R:
910 reviewers.update(set([r.strip() for r in change.R.split(',')]))
911 if change.TBR:
912 reviewers.update(set([r.strip() for r in change.TBR.split(',')]))
isherman@chromium.org103ae032014-04-09 09:06:19 +0000913
914 # Drop reviewers that aren't specified in email address format.
915 return set(reviewer for reviewer in reviewers if '@' in reviewer)
isherman@chromium.orgb5cded62014-03-25 17:47:57 +0000916
917
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000918def _RietveldOwnerAndReviewers(input_api, email_regexp, approval_needed=False):
919 """Return the owner and reviewers of a change, if any.
920
921 If approval_needed is True, only reviewers who have approved the change
922 will be returned.
923 """
ilevy@chromium.org1fa5cb92012-12-05 04:04:42 +0000924 issue_props = _GetRietveldIssueProps(input_api, True)
925 if not issue_props:
isherman@chromium.orgb5cded62014-03-25 17:47:57 +0000926 reviewers = set()
927 if not approval_needed:
928 reviewers = _ReviewersFromChange(input_api.change)
929 return None, reviewers
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000930
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000931 if not approval_needed:
932 return issue_props['owner_email'], set(issue_props['reviewers'])
933
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000934 owner_email = issue_props['owner_email']
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000935
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000936 def match_reviewer(r):
maruel@chromium.org80941c22011-05-30 20:14:18 +0000937 return email_regexp.match(r) and r != owner_email
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000938
maruel@chromium.org80941c22011-05-30 20:14:18 +0000939 messages = issue_props.get('messages', [])
940 approvers = set(
941 m['sender'] for m in messages
942 if m.get('approval') and match_reviewer(m['sender']))
943
944 return owner_email, approvers
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000945
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000946
947def _CheckConstNSObject(input_api, output_api, source_file_filter):
948 """Checks to make sure no objective-c files have |const NSSomeClass*|."""
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000949 pattern = input_api.re.compile(
950 r'const\s+NS(?!(Point|Range|Rect|Size)\s*\*)\w*\s*\*')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000951
952 def objective_c_filter(f):
953 return (source_file_filter(f) and
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000954 input_api.os_path.splitext(f.LocalPath())[1] in ('.h', '.m', '.mm'))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000955
956 files = []
957 for f in input_api.AffectedSourceFiles(objective_c_filter):
958 contents = input_api.ReadFile(f)
959 if pattern.search(contents):
960 files.append(f)
961
962 if files:
963 if input_api.is_committing:
964 res_type = output_api.PresubmitPromptWarning
965 else:
966 res_type = output_api.PresubmitNotifyResult
967 return [ res_type('|const NSClass*| is wrong, see ' +
968 'http://dev.chromium.org/developers/clang-mac',
969 files) ]
970 return []
971
972
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000973def CheckSingletonInHeaders(input_api, output_api, source_file_filter=None):
glider@chromium.orgc3617f32015-02-19 16:33:33 +0000974 """Deprecated, must be removed."""
975 return [
976 output_api.PresubmitNotifyResult(
977 'CheckSingletonInHeaders is deprecated, please remove it.')
978 ]
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000979
980
981def PanProjectChecks(input_api, output_api,
982 excluded_paths=None, text_files=None,
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000983 license_header=None, project_name=None,
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000984 owners_check=True, maxlen=80):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000985 """Checks that ALL chromium orbit projects should use.
986
987 These are checks to be run on all Chromium orbit project, including:
988 Chromium
989 Native Client
990 V8
991 When you update this function, please take this broad scope into account.
992 Args:
993 input_api: Bag of input related interfaces.
994 output_api: Bag of output related interfaces.
995 excluded_paths: Don't include these paths in common checks.
996 text_files: Which file are to be treated as documentation text files.
997 license_header: What license header should be on files.
998 project_name: What is the name of the project as it appears in the license.
999 Returns:
1000 A list of warning or error objects.
1001 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +00001002 excluded_paths = tuple(excluded_paths or [])
1003 text_files = tuple(text_files or (
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +00001004 r'.+\.txt$',
1005 r'.+\.json$',
maruel@chromium.org69eaecb2011-06-14 13:09:13 +00001006 ))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001007 project_name = project_name or 'Chromium'
mark@chromium.org2cc66422012-08-07 21:22:32 +00001008
1009 # Accept any year number from 2006 to the current year, or the special
rvargas@chromium.org02652602014-03-14 19:43:20 +00001010 # 2006-20xx string used on the oldest files. 2006-20xx is deprecated, but
1011 # tolerated on old files.
mark@chromium.org2cc66422012-08-07 21:22:32 +00001012 current_year = int(input_api.time.strftime('%Y'))
1013 allowed_years = (str(s) for s in reversed(xrange(2006, current_year + 1)))
rvargas@chromium.org02652602014-03-14 19:43:20 +00001014 years_re = '(' + '|'.join(allowed_years) + '|2006-2008|2006-2009|2006-2010)'
mark@chromium.org2cc66422012-08-07 21:22:32 +00001015
1016 # The (c) is deprecated, but tolerate it until it's removed from all files.
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001017 license_header = license_header or (
mark@chromium.org2cc66422012-08-07 21:22:32 +00001018 r'.*? Copyright (\(c\) )?%(year)s The %(project)s Authors\. '
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001019 r'All rights reserved\.\n'
1020 r'.*? Use of this source code is governed by a BSD-style license that '
1021 r'can be\n'
dbeam@chromium.orgb2312102012-02-15 02:01:55 +00001022 r'.*? found in the LICENSE file\.(?: \*/)?\n'
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001023 ) % {
mark@chromium.org2cc66422012-08-07 21:22:32 +00001024 'year': years_re,
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001025 'project': project_name,
1026 }
1027
1028 results = []
1029 # This code loads the default black list (e.g. third_party, experimental, etc)
1030 # and add our black list (breakpad, skia and v8 are still not following
1031 # google style and are not really living this repository).
1032 # See presubmit_support.py InputApi.FilterSourceFile for the (simple) usage.
1033 black_list = input_api.DEFAULT_BLACK_LIST + excluded_paths
1034 white_list = input_api.DEFAULT_WHITE_LIST + text_files
1035 sources = lambda x: input_api.FilterSourceFile(x, black_list=black_list)
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +00001036 text_files = lambda x: input_api.FilterSourceFile(
1037 x, black_list=black_list, white_list=white_list)
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001038
1039 snapshot_memory = []
1040 def snapshot(msg):
1041 """Measures & prints performance warning if a rule is running slow."""
1042 dt2 = input_api.time.clock()
1043 if snapshot_memory:
1044 delta_ms = int(1000*(dt2 - snapshot_memory[0]))
1045 if delta_ms > 500:
1046 print " %s took a long time: %dms" % (snapshot_memory[1], delta_ms)
1047 snapshot_memory[:] = (dt2, msg)
1048
bradnelson@google.comd57771d2011-03-31 19:18:32 +00001049 if owners_check:
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001050 snapshot("checking owners")
bradnelson@google.comd57771d2011-03-31 19:18:32 +00001051 results.extend(input_api.canned_checks.CheckOwners(
dpranke@chromium.org751797a2011-06-07 18:46:27 +00001052 input_api, output_api, source_file_filter=None))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001053
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001054 snapshot("checking long lines")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001055 results.extend(input_api.canned_checks.CheckLongLines(
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +00001056 input_api, output_api, maxlen, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001057 snapshot( "checking tabs")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001058 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
1059 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001060 snapshot( "checking stray whitespace")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001061 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
1062 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001063 snapshot("checking nsobjects")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001064 results.extend(_CheckConstNSObject(
1065 input_api, output_api, source_file_filter=sources))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +00001066
1067 # The following checks are only done on commit, since the commit bot will
1068 # auto-fix most of these.
1069 if input_api.is_committing:
maruel@chromium.org8571dac2011-05-10 18:10:13 +00001070 snapshot("checking eol style")
1071 results.extend(input_api.canned_checks.CheckChangeSvnEolStyle(
1072 input_api, output_api, source_file_filter=text_files))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +00001073 snapshot("checking svn mime types")
1074 results.extend(input_api.canned_checks.CheckSvnForCommonMimeTypes(
1075 input_api, output_api))
1076 snapshot("checking license")
1077 results.extend(input_api.canned_checks.CheckLicense(
1078 input_api, output_api, license_header, source_file_filter=sources))
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +00001079 snapshot("checking was uploaded")
1080 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
1081 input_api, output_api))
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +00001082 snapshot("checking description")
1083 results.extend(input_api.canned_checks.CheckChangeHasDescription(
1084 input_api, output_api))
1085 results.extend(input_api.canned_checks.CheckDoNotSubmitInDescription(
1086 input_api, output_api))
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +00001087 snapshot("checking do not submit in files")
1088 results.extend(input_api.canned_checks.CheckDoNotSubmitInFiles(
1089 input_api, output_api))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001090 snapshot("done")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001091 return results
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001092
1093
1094def CheckPatchFormatted(input_api, output_api):
1095 import git_cl
enne@chromium.org555cfe42014-01-29 18:21:39 +00001096 cmd = ['cl', 'format', '--dry-run', input_api.PresubmitLocalPath()]
1097 code, _ = git_cl.RunGitWithCode(cmd, suppress_stderr=True)
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001098 if code == 2:
1099 return [output_api.PresubmitPromptWarning(
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00001100 'The %s directory requires source formatting. '
enne@chromium.org7b7b5b22014-10-16 19:23:07 +00001101 'Please run git cl format %s' %
1102 (input_api.basename(input_api.PresubmitLocalPath()),
1103 input_api.basename(input_api.PresubmitLocalPath())))]
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001104 # As this is just a warning, ignore all other errors if the user
1105 # happens to have a broken clang-format, doesn't use git, etc etc.
1106 return []
scottmg@chromium.orgd05ab352014-12-05 17:24:26 +00001107
1108
1109def CheckGNFormatted(input_api, output_api):
1110 import gn
1111 affected_files = input_api.AffectedFiles(
1112 include_deletes=False,
1113 file_filter=lambda x: x.LocalPath().endswith('.gn') or
1114 x.LocalPath().endswith('.gni'))
1115 warnings = []
1116 for f in affected_files:
1117 cmd = ['gn', 'format', '--dry-run', f.AbsoluteLocalPath()]
1118 rc = gn.main(cmd)
1119 if rc == 2:
1120 warnings.append(output_api.PresubmitPromptWarning(
1121 '%s requires formatting. Please run `gn format --in-place %s`.' % (
1122 f.AbsoluteLocalPath(), f.LocalPath())))
1123 # It's just a warning, so ignore other types of failures assuming they'll be
1124 # caught elsewhere.
1125 return warnings