blob: 68b981908938e48685012fb5644c72b5b36d35e8 [file] [log] [blame]
estade@chromium.orgae7af922012-01-27 14:51:13 +00001# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Generic presubmit checks that can be reused by other presubmit checks."""
6
maruel@chromium.orgff9a2172012-04-24 16:55:32 +00007import os as _os
8_HERE = _os.path.dirname(_os.path.abspath(__file__))
9
bradnelson@google.com56e48bc2011-03-24 20:51:21 +000010
maruel@chromium.org3410d912009-06-09 20:56:16 +000011### Description checks
12
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000013def CheckChangeHasTestField(input_api, output_api):
14 """Requires that the changelist have a TEST= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000015 if input_api.change.TEST:
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000016 return []
17 else:
18 return [output_api.PresubmitNotifyResult(
jam@chromium.org5c76de92011-01-24 18:19:21 +000019 'If this change requires manual test instructions to QA team, add '
20 'TEST=[instructions].')]
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000021
22
23def CheckChangeHasBugField(input_api, output_api):
24 """Requires that the changelist have a BUG= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000025 if input_api.change.BUG:
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000026 return []
27 else:
28 return [output_api.PresubmitNotifyResult(
jam@chromium.org5c76de92011-01-24 18:19:21 +000029 'If this change has an associated bug, add BUG=[bug number].')]
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000030
31
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000032def CheckChangeHasTestedField(input_api, output_api):
33 """Requires that the changelist have a TESTED= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000034 if input_api.change.TESTED:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000035 return []
36 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000037 return [output_api.PresubmitError('Changelist must have a TESTED= field.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000038
39
40def CheckChangeHasQaField(input_api, output_api):
41 """Requires that the changelist have a QA= field."""
42 if input_api.change.QA:
43 return []
44 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000045 return [output_api.PresubmitError('Changelist must have a QA= field.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000046
47
48def CheckDoNotSubmitInDescription(input_api, output_api):
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +000049 """Checks that the user didn't add 'DO NOT ''SUBMIT' to the CL description.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000050 """
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +000051 keyword = 'DO NOT ''SUBMIT'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000052 if keyword in input_api.change.DescriptionText():
53 return [output_api.PresubmitError(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000054 keyword + ' is present in the changelist description.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000055 else:
56 return []
57
58
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000059def CheckChangeHasDescription(input_api, output_api):
60 """Checks the CL description is not empty."""
61 text = input_api.change.DescriptionText()
62 if text.strip() == '':
63 if input_api.is_committing:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000064 return [output_api.PresubmitError('Add a description.')]
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000065 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000066 return [output_api.PresubmitNotifyResult('Add a description.')]
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000067 return []
68
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +000069
70def CheckChangeWasUploaded(input_api, output_api):
71 """Checks that the issue was uploaded before committing."""
maruel@chromium.orgd587f392011-07-26 00:41:18 +000072 if input_api.is_committing and not input_api.change.issue:
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +000073 return [output_api.PresubmitError(
74 'Issue wasn\'t uploaded. Please upload first.')]
75 return []
76
77
maruel@chromium.org3410d912009-06-09 20:56:16 +000078### Content checks
79
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080def CheckDoNotSubmitInFiles(input_api, output_api):
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +000081 """Checks that the user didn't add 'DO NOT ''SUBMIT' to any files."""
nick@chromium.orge06cb4e2011-04-23 01:20:38 +000082 # We want to check every text file, not just source files.
83 file_filter = lambda x : x
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +000084 keyword = 'DO NOT ''SUBMIT'
bulach@chromium.orgbfffd452012-02-22 01:13:29 +000085 errors = _FindNewViolationsOfRule(lambda _, line : keyword not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +000086 input_api, file_filter)
87 text = '\n'.join('Found %s in %s' % (keyword, loc) for loc in errors)
88 if text:
89 return [output_api.PresubmitError(text)]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000090 return []
91
92
erg@google.com26970fa2009-11-17 18:07:32 +000093def CheckChangeLintsClean(input_api, output_api, source_file_filter=None):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000094 """Checks that all '.cc' and '.h' files pass cpplint.py."""
erg@google.com26970fa2009-11-17 18:07:32 +000095 _RE_IS_TEST = input_api.re.compile(r'.*tests?.(cc|h)$')
96 result = []
97
enne@chromium.orge72c5f52013-04-16 00:36:40 +000098 cpplint = input_api.cpplint
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +000099 # Access to a protected member _XX of a client class
100 # pylint: disable=W0212
erg@google.com26970fa2009-11-17 18:07:32 +0000101 cpplint._cpplint_state.ResetErrorCounts()
102
103 # Justifications for each filter:
104 #
105 # - build/include : Too many; fix in the future.
106 # - build/include_order : Not happening; #ifdefed includes.
107 # - build/namespace : I'm surprised by how often we violate this rule.
108 # - readability/casting : Mistakes a whole bunch of function pointer.
109 # - runtime/int : Can be fixed long term; volume of errors too high
110 # - runtime/virtual : Broken now, but can be fixed in the future?
111 # - whitespace/braces : We have a lot of explicit scoping in chrome code.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000112 cpplint._SetFilters('-build/include,-build/include_order,-build/namespace,'
113 '-readability/casting,-runtime/int,-runtime/virtual,'
114 '-whitespace/braces')
erg@google.com26970fa2009-11-17 18:07:32 +0000115
116 # We currently are more strict with normal code than unit tests; 4 and 5 are
117 # the verbosity level that would normally be passed to cpplint.py through
118 # --verbose=#. Hopefully, in the future, we can be more verbose.
119 files = [f.AbsoluteLocalPath() for f in
120 input_api.AffectedSourceFiles(source_file_filter)]
121 for file_name in files:
122 if _RE_IS_TEST.match(file_name):
123 level = 5
124 else:
125 level = 4
126
127 cpplint.ProcessFile(file_name, level)
128
129 if cpplint._cpplint_state.error_count > 0:
130 if input_api.is_committing:
131 res_type = output_api.PresubmitError
132 else:
133 res_type = output_api.PresubmitPromptWarning
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000134 result = [res_type('Changelist failed cpplint.py check.')]
erg@google.com26970fa2009-11-17 18:07:32 +0000135
136 return result
137
138
maruel@chromium.org3410d912009-06-09 20:56:16 +0000139def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000140 """Checks no '\r' (CR) character is in any source files."""
141 cr_files = []
maruel@chromium.org3410d912009-06-09 20:56:16 +0000142 for f in input_api.AffectedSourceFiles(source_file_filter):
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000143 if '\r' in input_api.ReadFile(f, 'rb'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000144 cr_files.append(f.LocalPath())
145 if cr_files:
146 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000147 'Found a CR character in these files:', items=cr_files)]
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000148 return []
149
150
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000151def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None):
152 """Checks for files in svn modified directories.
153
154 They will get submitted on accident because svn commits recursively by
155 default, and that's very dangerous.
156 """
157 if input_api.change.scm != 'svn':
158 return []
159
160 errors = []
161 current_cl_files = input_api.change.GetModifiedFiles()
162 all_modified_files = input_api.change.GetAllModifiedFiles()
163 # Filter out files in the current CL.
164 modified_files = [f for f in all_modified_files if f not in current_cl_files]
165 modified_abspaths = [input_api.os_path.abspath(f) for f in modified_files]
166
sail@chromium.org5538e022011-05-12 17:53:16 +0000167 for f in input_api.AffectedFiles(file_filter=source_file_filter):
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000168 if f.Action() == 'M' and f.IsDirectory():
169 curpath = f.AbsoluteLocalPath()
170 bad_files = []
171 # Check if any of the modified files in other CLs are under curpath.
172 for i in xrange(len(modified_files)):
173 abspath = modified_abspaths[i]
174 if input_api.os_path.commonprefix([curpath, abspath]) == curpath:
175 bad_files.append(modified_files[i])
176 if bad_files:
177 if input_api.is_committing:
178 error_type = output_api.PresubmitPromptWarning
179 else:
180 error_type = output_api.PresubmitNotifyResult
181 errors.append(error_type(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000182 'Potential accidental commits in changelist %s:' % f.LocalPath(),
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000183 items=bad_files))
184 return errors
185
186
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000187def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
188 """Checks the files ends with one and only one \n (LF)."""
189 eof_files = []
190 for f in input_api.AffectedSourceFiles(source_file_filter):
191 contents = input_api.ReadFile(f, 'rb')
192 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000193 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000194 eof_files.append(f.LocalPath())
195
196 if eof_files:
197 return [output_api.PresubmitPromptWarning(
198 'These files should end in one (and only one) newline character:',
199 items=eof_files)]
200 return []
201
202
203def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api,
204 source_file_filter=None):
205 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass.
206
207 It is faster because it is reading the file only once.
208 """
209 cr_files = []
210 eof_files = []
211 for f in input_api.AffectedSourceFiles(source_file_filter):
212 contents = input_api.ReadFile(f, 'rb')
213 if '\r' in contents:
214 cr_files.append(f.LocalPath())
215 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000216 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000217 eof_files.append(f.LocalPath())
218 outputs = []
219 if cr_files:
220 outputs.append(output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000221 'Found a CR character in these files:', items=cr_files))
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000222 if eof_files:
223 outputs.append(output_api.PresubmitPromptWarning(
224 'These files should end in one (and only one) newline character:',
225 items=eof_files))
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000226 return outputs
227
228
chrisha@google.com267d6592012-06-19 19:23:31 +0000229def _ReportErrorFileAndLine(filename, line_num, dummy_line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000230 """Default error formatter for _FindNewViolationsOfRule."""
231 return '%s, line %s' % (filename, line_num)
232
233
234def _FindNewViolationsOfRule(callable_rule, input_api, source_file_filter=None,
235 error_formatter=_ReportErrorFileAndLine):
236 """Find all newly introduced violations of a per-line rule (a callable).
237
238 Arguments:
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000239 callable_rule: a callable taking a file extension and line of input and
240 returning True if the rule is satisfied and False if there was a problem.
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000241 input_api: object to enumerate the affected files.
242 source_file_filter: a filter to be passed to the input api.
243 error_formatter: a callable taking (filename, line_number, line) and
244 returning a formatted error string.
245
246 Returns:
247 A list of the newly-introduced violations reported by the rule.
248 """
249 errors = []
sail@chromium.org5538e022011-05-12 17:53:16 +0000250 for f in input_api.AffectedFiles(include_deletes=False,
251 file_filter=source_file_filter):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000252 # For speed, we do two passes, checking first the full file. Shelling out
253 # to the SCM to determine the changed region can be quite expensive on
254 # Win32. Assuming that most files will be kept problem-free, we can
255 # skip the SCM operations most of the time.
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000256 extension = str(f.LocalPath()).rsplit('.', 1)[-1]
257 if all(callable_rule(extension, line) for line in f.NewContents()):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000258 continue # No violation found in full text: can skip considering diff.
259
260 for line_num, line in f.ChangedContents():
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000261 if not callable_rule(extension, line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000262 errors.append(error_formatter(f.LocalPath(), line_num, line))
263
264 return errors
265
266
maruel@chromium.org3410d912009-06-09 20:56:16 +0000267def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000268 """Checks that there are no tab characters in any of the text files to be
269 submitted.
270 """
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000271 # In addition to the filter, make sure that makefiles are blacklisted.
272 if not source_file_filter:
273 # It's the default filter.
274 source_file_filter = input_api.FilterSourceFile
275 def filter_more(affected_file):
cmp@chromium.orgcb5e57c2012-04-06 19:50:15 +0000276 basename = input_api.os_path.basename(affected_file.LocalPath())
277 return (not (basename in ('Makefile', 'makefile') or
278 basename.endswith('.mk')) and
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000279 source_file_filter(affected_file))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000280
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000281 tabs = _FindNewViolationsOfRule(lambda _, line : '\t' not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000282 input_api, filter_more)
283
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000284 if tabs:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000285 return [output_api.PresubmitPromptWarning('Found a tab character in:',
286 long_text='\n'.join(tabs))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000287 return []
288
289
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000290def CheckChangeTodoHasOwner(input_api, output_api, source_file_filter=None):
291 """Checks that the user didn't add TODO(name) without an owner."""
292
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000293 unowned_todo = input_api.re.compile('TO''DO[^(]')
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000294 errors = _FindNewViolationsOfRule(lambda _, x : not unowned_todo.search(x),
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000295 input_api, source_file_filter)
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000296 errors = ['Found TO''DO with no owner in ' + x for x in errors]
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000297 if errors:
298 return [output_api.PresubmitPromptWarning('\n'.join(errors))]
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000299 return []
300
301
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000302def CheckChangeHasNoStrayWhitespace(input_api, output_api,
303 source_file_filter=None):
304 """Checks that there is no stray whitespace at source lines end."""
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000305 errors = _FindNewViolationsOfRule(lambda _, line : line.rstrip() == line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000306 input_api, source_file_filter)
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000307 if errors:
308 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000309 'Found line ending with white spaces in:',
310 long_text='\n'.join(errors))]
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000311 return []
312
313
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000314def CheckLongLines(input_api, output_api, maxlen, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000315 """Checks that there aren't any lines longer than maxlen characters in any of
316 the text files to be submitted.
317 """
dpranke@chromium.orge3b1c3d2012-10-20 22:28:14 +0000318 maxlens = {
319 'java': 100,
torne@chromium.org0ecad9c2013-02-15 16:35:16 +0000320 # This is specifically for Android's handwritten makefiles (Android.mk).
321 'mk': 200,
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000322 '': maxlen,
323 }
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000324 # Note: these are C++ specific but processed on all languages. :(
325 MACROS = ('#define', '#include', '#import', '#pragma', '#if', '#endif')
326
sivachandra@chromium.org270db5c2012-10-09 22:38:44 +0000327 # Special java statements.
328 SPECIAL_JAVA_STARTS = ('package ', 'import ')
329
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000330 def no_long_lines(file_extension, line):
nick@chromium.orgff526192013-06-10 19:30:26 +0000331 # Allow special java statements to be as long as necessary.
sivachandra@chromium.org270db5c2012-10-09 22:38:44 +0000332 if file_extension == 'java' and line.startswith(SPECIAL_JAVA_STARTS):
333 return True
334
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000335 file_maxlen = maxlens.get(file_extension, maxlens[''])
336 # Stupidly long symbols that needs to be worked around if takes 66% of line.
337 long_symbol = file_maxlen * 2 / 3
338 # Hard line length limit at 50% more.
339 extra_maxlen = file_maxlen * 3 / 2
340
341 line_len = len(line)
342 if line_len <= file_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000343 return True
344
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000345 if line_len > extra_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000346 return False
347
348 return (
349 line.startswith(MACROS) or
350 any((url in line) for url in ('http://', 'https://')) or
351 input_api.re.match(
352 r'.*[A-Za-z][A-Za-z_0-9]{%d,}.*' % long_symbol, line))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000353
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000354 def format_error(filename, line_num, line):
355 return '%s, line %s, %s chars' % (filename, line_num, len(line))
356
357 errors = _FindNewViolationsOfRule(no_long_lines, input_api,
358 source_file_filter,
359 error_formatter=format_error)
360 if errors:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000361 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000362 return [output_api.PresubmitPromptWarning(msg, items=errors[:5])]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000363 else:
364 return []
365
366
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000367def CheckLicense(input_api, output_api, license_re, source_file_filter=None,
maruel@chromium.org71626852010-11-03 13:14:25 +0000368 accept_empty_files=True):
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000369 """Verifies the license header.
370 """
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000371 license_re = input_api.re.compile(license_re, input_api.re.MULTILINE)
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000372 bad_files = []
373 for f in input_api.AffectedSourceFiles(source_file_filter):
374 contents = input_api.ReadFile(f, 'rb')
maruel@chromium.org71626852010-11-03 13:14:25 +0000375 if accept_empty_files and not contents:
376 continue
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000377 if not license_re.search(contents):
378 bad_files.append(f.LocalPath())
379 if bad_files:
380 if input_api.is_committing:
381 res_type = output_api.PresubmitPromptWarning
382 else:
383 res_type = output_api.PresubmitNotifyResult
384 return [res_type(
bradnelson@google.com393460b2011-03-29 01:20:26 +0000385 'License must match:\n%s\n' % license_re.pattern +
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000386 'Found a bad license header in these files:', items=bad_files)]
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000387 return []
388
389
maruel@chromium.org1a0e3cb2009-06-10 18:03:04 +0000390def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None):
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000391 """Checks that the source files have svn:eol-style=LF."""
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000392 return CheckSvnProperty(input_api, output_api,
393 'svn:eol-style', 'LF',
394 input_api.AffectedSourceFiles(source_file_filter))
395
396
397def CheckSvnForCommonMimeTypes(input_api, output_api):
398 """Checks that common binary file types have the correct svn:mime-type."""
399 output = []
400 files = input_api.AffectedFiles(include_deletes=False)
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000401 def IsExts(x, exts):
402 path = x.LocalPath()
403 for extension in exts:
404 if path.endswith(extension):
405 return True
406 return False
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000407 def FilterFiles(extension):
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000408 return filter(lambda x: IsExts(x, extension), files)
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000409 def RunCheck(mime_type, files):
410 output.extend(CheckSvnProperty(input_api, output_api, 'svn:mime-type',
411 mime_type, files))
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000412 RunCheck('application/pdf', FilterFiles(['.pdf']))
413 RunCheck('image/bmp', FilterFiles(['.bmp']))
414 RunCheck('image/gif', FilterFiles(['.gif']))
415 RunCheck('image/png', FilterFiles(['.png']))
416 RunCheck('image/jpeg', FilterFiles(['.jpg', '.jpeg', '.jpe']))
417 RunCheck('image/vnd.microsoft.icon', FilterFiles(['.ico']))
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000418 return output
419
420
421def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
422 """Checks that affected_files files have prop=expected."""
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000423 if input_api.change.scm != 'svn':
424 return []
425
426 bad = filter(lambda f: f.Property(prop) != expected, affected_files)
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000427 if bad:
maruel@chromium.org0874d472009-06-10 19:08:33 +0000428 if input_api.is_committing:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000429 res_type = output_api.PresubmitError
maruel@chromium.org0874d472009-06-10 19:08:33 +0000430 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000431 res_type = output_api.PresubmitNotifyResult
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000432 message = 'Run the command: svn pset %s %s \\' % (prop, expected)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000433 return [res_type(message, items=bad)]
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000434 return []
435
436
maruel@chromium.org3410d912009-06-09 20:56:16 +0000437### Other checks
438
439def CheckDoNotSubmit(input_api, output_api):
440 return (
441 CheckDoNotSubmitInDescription(input_api, output_api) +
442 CheckDoNotSubmitInFiles(input_api, output_api)
443 )
444
445
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000446def CheckTreeIsOpen(input_api, output_api,
447 url=None, closed=None, json_url=None):
448 """Check whether to allow commit without prompt.
449
450 Supports two styles:
451 1. Checks that an url's content doesn't match a regexp that would mean that
452 the tree is closed. (old)
453 2. Check the json_url to decide whether to allow commit without prompt.
454 Args:
455 input_api: input related apis.
456 output_api: output related apis.
457 url: url to use for regex based tree status.
458 closed: regex to match for closed status.
459 json_url: url to download json style status.
460 """
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000461 if not input_api.is_committing:
462 return []
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000463 try:
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000464 if json_url:
465 connection = input_api.urllib2.urlopen(json_url)
466 status = input_api.json.loads(connection.read())
467 connection.close()
468 if not status['can_commit_freely']:
469 short_text = 'Tree state is: ' + status['general_state']
470 long_text = status['message'] + '\n' + json_url
471 return [output_api.PresubmitError(short_text, long_text=long_text)]
472 else:
473 # TODO(bradnelson): drop this once all users are gone.
474 connection = input_api.urllib2.urlopen(url)
475 status = connection.read()
476 connection.close()
477 if input_api.re.match(closed, status):
478 long_text = status + '\n' + url
479 return [output_api.PresubmitError('The tree is closed.',
480 long_text=long_text)]
rohitrao@chromium.orgd490ee82012-02-06 19:31:33 +0000481 except IOError as e:
482 return [output_api.PresubmitError('Error fetching tree status.',
483 long_text=str(e))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000484 return []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000485
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000486def GetUnitTestsInDirectory(
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000487 input_api, output_api, directory, whitelist=None, blacklist=None):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000488 """Lists all files in a directory and runs them. Doesn't recurse.
489
nick@chromium.orgff526192013-06-10 19:30:26 +0000490 It's mainly a wrapper for RunUnitTests. Use whitelist and blacklist to filter
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000491 tests accordingly.
492 """
493 unit_tests = []
494 test_path = input_api.os_path.abspath(
495 input_api.os_path.join(input_api.PresubmitLocalPath(), directory))
496
497 def check(filename, filters):
498 return any(True for i in filters if input_api.re.match(i, filename))
499
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000500 to_run = found = 0
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000501 for filename in input_api.os_listdir(test_path):
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000502 found += 1
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000503 fullpath = input_api.os_path.join(test_path, filename)
504 if not input_api.os_path.isfile(fullpath):
505 continue
506 if whitelist and not check(filename, whitelist):
507 continue
508 if blacklist and check(filename, blacklist):
509 continue
510 unit_tests.append(input_api.os_path.join(directory, filename))
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000511 to_run += 1
512 input_api.logging.debug('Found %d files, running %d' % (found, to_run))
513 if not to_run:
514 return [
515 output_api.PresubmitPromptWarning(
516 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
517 % (found, whitelist, blacklist, directory))
518 ]
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000519 return GetUnitTests(input_api, output_api, unit_tests)
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000520
521
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000522def GetUnitTests(input_api, output_api, unit_tests):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000523 """Runs all unit tests in a directory.
524
525 On Windows, sys.executable is used for unit tests ending with ".py".
526 """
527 # We don't want to hinder users from uploading incomplete patches.
528 if input_api.is_committing:
529 message_type = output_api.PresubmitError
530 else:
531 message_type = output_api.PresubmitPromptWarning
532
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000533 results = []
534 for unit_test in unit_tests:
535 cmd = []
536 if input_api.platform == 'win32' and unit_test.endswith('.py'):
537 # Windows needs some help.
538 cmd = [input_api.python_executable]
539 cmd.append(unit_test)
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000540 if input_api.verbose:
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000541 print('Running %s' % unit_test)
maruel@chromium.org6c7723e2011-04-12 19:04:55 +0000542 cmd.append('--verbose')
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000543 results.append(input_api.Command(
544 name=unit_test,
545 cmd=cmd,
546 kwargs={'cwd': input_api.PresubmitLocalPath()},
547 message=message_type))
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000548 return results
549
nick@chromium.orgff526192013-06-10 19:30:26 +0000550
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000551def GetPythonUnitTests(input_api, output_api, unit_tests):
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000552 """Run the unit tests out of process, capture the output and use the result
553 code to determine success.
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000554
555 DEPRECATED.
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000556 """
maruel@chromium.orgd7dccf52009-06-06 18:51:58 +0000557 # We don't want to hinder users from uploading incomplete patches.
558 if input_api.is_committing:
559 message_type = output_api.PresubmitError
560 else:
561 message_type = output_api.PresubmitNotifyResult
maruel@chromium.org0e766052011-04-06 13:32:51 +0000562 results = []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000563 for unit_test in unit_tests:
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000564 # Run the unit tests out of process. This is because some unit tests
565 # stub out base libraries and don't clean up their mess. It's too easy to
566 # get subtle bugs.
567 cwd = None
568 env = None
569 unit_test_name = unit_test
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000570 # 'python -m test.unit_test' doesn't work. We need to change to the right
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000571 # directory instead.
572 if '.' in unit_test:
573 # Tests imported in submodules (subdirectories) assume that the current
574 # directory is in the PYTHONPATH. Manually fix that.
575 unit_test = unit_test.replace('.', '/')
576 cwd = input_api.os_path.dirname(unit_test)
577 unit_test = input_api.os_path.basename(unit_test)
578 env = input_api.environ.copy()
kbr@google.comab318592009-09-04 00:54:55 +0000579 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
580 backpath = [
581 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
582 ]
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000583 if env.get('PYTHONPATH'):
584 backpath.append(env.get('PYTHONPATH'))
ukai@chromium.orga301f1f2009-08-05 10:37:33 +0000585 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000586 cmd = [input_api.python_executable, '-m', '%s' % unit_test]
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000587 results.append(input_api.Command(
588 name=unit_test_name,
589 cmd=cmd,
590 kwargs={'env': env, 'cwd': cwd},
591 message=message_type))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000592 return results
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000593
594
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000595def RunUnitTestsInDirectory(input_api, *args, **kwargs):
596 """Run tests in a directory serially.
597
598 For better performance, use GetUnitTestsInDirectory and then
599 pass to input_api.RunTests.
600 """
601 return input_api.RunTests(
602 GetUnitTestsInDirectory(input_api, *args, **kwargs), False)
603
604
605def RunUnitTests(input_api, *args, **kwargs):
606 """Run tests serially.
607
608 For better performance, use GetUnitTests and then pass to
609 input_api.RunTests.
610 """
611 return input_api.RunTests(GetUnitTests(input_api, *args, **kwargs), False)
612
613
614def RunPythonUnitTests(input_api, *args, **kwargs):
615 """Run python tests in a directory serially.
616
617 DEPRECATED
618 """
619 return input_api.RunTests(
620 GetPythonUnitTests(input_api, *args, **kwargs), False)
621
622
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000623def _FetchAllFiles(input_api, white_list, black_list):
624 """Hack to fetch all files."""
625 # We cannot use AffectedFiles here because we want to test every python
626 # file on each single python change. It's because a change in a python file
627 # can break another unmodified file.
628 # Use code similar to InputApi.FilterSourceFile()
629 def Find(filepath, filters):
630 for item in filters:
631 if input_api.re.match(item, filepath):
632 return True
633 return False
634
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000635 files = []
636 path_len = len(input_api.PresubmitLocalPath())
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000637 for dirpath, dirnames, filenames in input_api.os_walk(
638 input_api.PresubmitLocalPath()):
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000639 # Passes dirnames in black list to speed up search.
640 for item in dirnames[:]:
641 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
642 if Find(filepath, black_list):
643 dirnames.remove(item)
644 for item in filenames:
645 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
646 if Find(filepath, white_list) and not Find(filepath, black_list):
647 files.append(filepath)
648 return files
649
650
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000651def GetPylint(input_api, output_api, white_list=None, black_list=None,
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000652 disabled_warnings=None, extra_paths_list=None):
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000653 """Run pylint on python files.
654
chrisha@google.com267d6592012-06-19 19:23:31 +0000655 The default white_list enforces looking only at *.py files.
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000656 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000657 white_list = tuple(white_list or ('.*\.py$',))
658 black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000659 extra_paths_list = extra_paths_list or []
660
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000661 if input_api.is_committing:
662 error_type = output_api.PresubmitError
663 else:
664 error_type = output_api.PresubmitPromptWarning
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000665
666 # Only trigger if there is at least one python file affected.
ilevy@chromium.org36576332013-01-08 03:16:15 +0000667 def rel_path(regex):
668 """Modifies a regex for a subject to accept paths relative to root."""
chrisha@chromium.org40e5d3d2013-01-18 17:46:57 +0000669 def samefile(a, b):
670 # Default implementation for platforms lacking os.path.samefile
671 # (like Windows).
672 return input_api.os_path.abspath(a) == input_api.os_path.abspath(b)
673 samefile = getattr(input_api.os_path, 'samefile', samefile)
674 if samefile(input_api.PresubmitLocalPath(),
675 input_api.change.RepositoryRoot()):
ilevy@chromium.orgdd4e9312013-01-15 03:22:04 +0000676 return regex
chrisha@chromium.org40e5d3d2013-01-18 17:46:57 +0000677
ilevy@chromium.org36576332013-01-08 03:16:15 +0000678 prefix = input_api.os_path.join(input_api.os_path.relpath(
679 input_api.PresubmitLocalPath(), input_api.change.RepositoryRoot()), '')
680 return input_api.re.escape(prefix) + regex
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000681 src_filter = lambda x: input_api.FilterSourceFile(
682 x, map(rel_path, white_list), map(rel_path, black_list))
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000683 if not input_api.AffectedSourceFiles(src_filter):
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000684 input_api.logging.info('Skipping pylint: no matching changes.')
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000685 return []
686
maruel@chromium.orgff9a2172012-04-24 16:55:32 +0000687 extra_args = ['--rcfile=%s' % input_api.os_path.join(_HERE, 'pylintrc')]
688 if disabled_warnings:
689 extra_args.extend(['-d', ','.join(disabled_warnings)])
690
chrisha@google.com267d6592012-06-19 19:23:31 +0000691 files = _FetchAllFiles(input_api, white_list, black_list)
692 if not files:
maruel@chromium.orgfca53392010-12-21 18:42:57 +0000693 return []
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000694 files.sort()
chrisha@google.com267d6592012-06-19 19:23:31 +0000695
csharp@chromium.org40395342013-02-21 14:57:23 +0000696 input_api.logging.info('Running pylint on %d files', len(files))
697 input_api.logging.debug('Running pylint on: %s', files)
chrisha@google.com267d6592012-06-19 19:23:31 +0000698 # Copy the system path to the environment so pylint can find the right
699 # imports.
700 env = input_api.environ.copy()
701 import sys
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000702 env['PYTHONPATH'] = input_api.os_path.pathsep.join(
robertshield@chromium.orga2873932013-02-20 18:08:46 +0000703 extra_paths_list + sys.path).encode('utf8')
chrisha@google.com267d6592012-06-19 19:23:31 +0000704
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000705 def GetPylintCmd(files):
706 # Windows needs help running python files so we explicitly specify
707 # the interpreter to use. It also has limitations on the size of
708 # the command-line, so we pass arguments via a pipe.
709 if len(files) == 1:
710 description = files[0]
711 else:
712 description = '%s files' % len(files)
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000713
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000714 return input_api.Command(
715 name='Pylint (%s)' % description,
716 cmd=[input_api.python_executable,
717 input_api.os_path.join(_HERE, 'third_party', 'pylint.py'),
718 '--args-on-stdin'],
719 kwargs={'env': env, 'stdin': '\n'.join(files + extra_args)},
720 message=error_type)
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000721
sbc@chromium.org7fc75ca2012-09-21 20:14:21 +0000722 # Always run pylint and pass it all the py files at once.
723 # Passing py files one at time is slower and can produce
724 # different results. input_api.verbose used to be used
725 # to enable this behaviour but differing behaviour in
726 # verbose mode is not desirable.
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000727 # Leave this unreachable code in here so users can make
728 # a quick local edit to diagnose pylint issues more
729 # easily.
sbc@chromium.org7fc75ca2012-09-21 20:14:21 +0000730 if True:
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000731 return [GetPylintCmd(files)]
chrisha@google.com267d6592012-06-19 19:23:31 +0000732 else:
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000733 return map(GetPylintCmd, files)
734
735
736def RunPylint(input_api, *args, **kwargs):
737 """Legacy presubmit function.
738
739 For better performance, get all tests and then pass to
740 input_api.RunTests.
741 """
742 return input_api.RunTests(GetPylint(input_api, *args, **kwargs), False)
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000743
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000744
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000745# TODO(dpranke): Get the host_url from the input_api instead
chrisha@google.com267d6592012-06-19 19:23:31 +0000746def CheckRietveldTryJobExecution(dummy_input_api, dummy_output_api,
747 dummy_host_url, dummy_platforms,
748 dummy_owner):
maruel@chromium.org85da74b2011-10-27 17:13:30 +0000749 # Temporarily 'fix' the check while the Rietveld API is being upgraded to
750 # something sensible.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000751 return []
752
753
754def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
755 ignored):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000756 try:
757 connection = input_api.urllib2.urlopen(url)
758 raw_data = connection.read()
759 connection.close()
760 except IOError:
761 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)]
762
763 try:
764 data = input_api.json.loads(raw_data)
765 except ValueError:
766 return [output_api.PresubmitNotifyResult('Received malformed json while '
767 'looking up buildbot status')]
768
769 out = []
770 for (builder_name, builder) in data.iteritems():
771 if builder_name in ignored:
772 continue
maruel@chromium.orgcf1982c2010-10-04 15:08:28 +0000773 if builder.get('state', '') == 'offline':
774 continue
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000775 pending_builds_len = len(builder.get('pending_builds', []))
776 if pending_builds_len > max_pendings:
777 out.append('%s has %d build(s) pending' %
778 (builder_name, pending_builds_len))
779 if out:
780 return [output_api.PresubmitPromptWarning(
781 'Build(s) pending. It is suggested to wait that no more than %d '
782 'builds are pending.' % max_pendings,
783 long_text='\n'.join(out))]
784 return []
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000785
786
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000787def CheckOwners(input_api, output_api, source_file_filter=None,
788 author_counts_as_owner=True):
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000789 if input_api.is_committing:
790 if input_api.tbr:
791 return [output_api.PresubmitNotifyResult(
792 '--tbr was specified, skipping OWNERS check')]
793 if not input_api.change.issue:
794 return [output_api.PresubmitError("OWNERS check failed: this change has "
795 "no Rietveld issue number, so we can't check it for approvals.")]
796 needed = 'LGTM from an OWNER'
797 output = output_api.PresubmitError
798 else:
799 needed = 'OWNER reviewers'
800 output = output_api.PresubmitNotifyResult
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000801
dpranke@chromium.orgadd5df42011-03-08 23:04:01 +0000802 affected_files = set([f.LocalPath() for f in
sail@chromium.org5538e022011-05-12 17:53:16 +0000803 input_api.change.AffectedFiles(file_filter=source_file_filter)])
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000804
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000805 owners_db = input_api.owners_db
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000806 owner_email, reviewers = _RietveldOwnerAndReviewers(
807 input_api,
808 owners_db.email_regexp,
809 approval_needed=input_api.is_committing)
810
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000811 owner_email = owner_email or input_api.change.author_email
812
813 if author_counts_as_owner and owner_email:
dpranke@chromium.org4c7ce992013-03-06 17:15:40 +0000814 reviewers_plus_owner = set([owner_email]).union(reviewers)
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000815 missing_files = owners_db.files_not_covered_by(affected_files,
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000816 reviewers_plus_owner)
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000817 else:
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000818 missing_files = owners_db.files_not_covered_by(affected_files, reviewers)
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000819
dpranke@chromium.org6b1e3ee2013-02-23 00:06:38 +0000820 if missing_files:
zork@chromium.org046e1752012-05-07 05:56:12 +0000821 output_list = [
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000822 output('Missing %s for these files:\n %s' %
bauerb@chromium.orgb3b52012013-04-18 19:28:04 +0000823 (needed, '\n '.join(sorted(missing_files))))]
zork@chromium.org046e1752012-05-07 05:56:12 +0000824 if not input_api.is_committing:
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000825 suggested_owners = owners_db.reviewers_for(affected_files, owner_email)
zork@chromium.org046e1752012-05-07 05:56:12 +0000826 output_list.append(output('Suggested OWNERS:\n %s' %
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000827 ('\n '.join(suggested_owners or []))))
zork@chromium.org046e1752012-05-07 05:56:12 +0000828 return output_list
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000829
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000830 if input_api.is_committing and not reviewers:
831 return [output('Missing LGTM from someone other than %s' % owner_email)]
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000832 return []
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000833
834
ilevy@chromium.org1fa5cb92012-12-05 04:04:42 +0000835def _GetRietveldIssueProps(input_api, messages):
836 """Gets the issue properties from rietveld."""
837 issue = input_api.change.issue
838 if issue and input_api.rietveld:
839 return input_api.rietveld.get_issue_properties(
840 issue=int(issue), messages=messages)
841
842
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000843def _RietveldOwnerAndReviewers(input_api, email_regexp, approval_needed=False):
844 """Return the owner and reviewers of a change, if any.
845
846 If approval_needed is True, only reviewers who have approved the change
847 will be returned.
848 """
ilevy@chromium.org1fa5cb92012-12-05 04:04:42 +0000849 issue_props = _GetRietveldIssueProps(input_api, True)
850 if not issue_props:
dpranke@chromium.org4c7ce992013-03-06 17:15:40 +0000851 return None, set()
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000852
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000853 if not approval_needed:
854 return issue_props['owner_email'], set(issue_props['reviewers'])
855
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000856 owner_email = issue_props['owner_email']
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000857
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000858 def match_reviewer(r):
maruel@chromium.org80941c22011-05-30 20:14:18 +0000859 return email_regexp.match(r) and r != owner_email
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000860
maruel@chromium.org80941c22011-05-30 20:14:18 +0000861 messages = issue_props.get('messages', [])
862 approvers = set(
863 m['sender'] for m in messages
864 if m.get('approval') and match_reviewer(m['sender']))
865
866 return owner_email, approvers
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000867
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000868
869def _CheckConstNSObject(input_api, output_api, source_file_filter):
870 """Checks to make sure no objective-c files have |const NSSomeClass*|."""
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000871 pattern = input_api.re.compile(
872 r'const\s+NS(?!(Point|Range|Rect|Size)\s*\*)\w*\s*\*')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000873
874 def objective_c_filter(f):
875 return (source_file_filter(f) and
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000876 input_api.os_path.splitext(f.LocalPath())[1] in ('.h', '.m', '.mm'))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000877
878 files = []
879 for f in input_api.AffectedSourceFiles(objective_c_filter):
880 contents = input_api.ReadFile(f)
881 if pattern.search(contents):
882 files.append(f)
883
884 if files:
885 if input_api.is_committing:
886 res_type = output_api.PresubmitPromptWarning
887 else:
888 res_type = output_api.PresubmitNotifyResult
889 return [ res_type('|const NSClass*| is wrong, see ' +
890 'http://dev.chromium.org/developers/clang-mac',
891 files) ]
892 return []
893
894
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000895def CheckSingletonInHeaders(input_api, output_api, source_file_filter=None):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000896 """Checks to make sure no header files have |Singleton<|."""
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000897 pattern = input_api.re.compile(r'(?<!class\s)Singleton\s*<')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000898 files = []
899 for f in input_api.AffectedSourceFiles(source_file_filter):
900 if (f.LocalPath().endswith('.h') or f.LocalPath().endswith('.hxx') or
901 f.LocalPath().endswith('.hpp') or f.LocalPath().endswith('.inl')):
902 contents = input_api.ReadFile(f)
dilmah@chromium.orgd22b9702011-08-26 13:53:49 +0000903 for line in contents.splitlines(False):
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000904 if (not input_api.re.match(r'//', line) and # Strip C++ comment.
905 pattern.search(line)):
dilmah@chromium.orgd22b9702011-08-26 13:53:49 +0000906 files.append(f)
907 break
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000908
909 if files:
910 return [ output_api.PresubmitError(
911 'Found Singleton<T> in the following header files.\n' +
912 'Please move them to an appropriate source file so that the ' +
913 'template gets instantiated in a single compilation unit.',
914 files) ]
915 return []
916
917
918def PanProjectChecks(input_api, output_api,
919 excluded_paths=None, text_files=None,
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000920 license_header=None, project_name=None,
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000921 owners_check=True, maxlen=80):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000922 """Checks that ALL chromium orbit projects should use.
923
924 These are checks to be run on all Chromium orbit project, including:
925 Chromium
926 Native Client
927 V8
928 When you update this function, please take this broad scope into account.
929 Args:
930 input_api: Bag of input related interfaces.
931 output_api: Bag of output related interfaces.
932 excluded_paths: Don't include these paths in common checks.
933 text_files: Which file are to be treated as documentation text files.
934 license_header: What license header should be on files.
935 project_name: What is the name of the project as it appears in the license.
936 Returns:
937 A list of warning or error objects.
938 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000939 excluded_paths = tuple(excluded_paths or [])
940 text_files = tuple(text_files or (
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +0000941 r'.+\.txt$',
942 r'.+\.json$',
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000943 ))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000944 project_name = project_name or 'Chromium'
mark@chromium.org2cc66422012-08-07 21:22:32 +0000945
946 # Accept any year number from 2006 to the current year, or the special
947 # 2006-2008 string used on the oldest files. 2006-2008 is deprecated, but
948 # tolerate it until it's removed from all files.
949 current_year = int(input_api.time.strftime('%Y'))
950 allowed_years = (str(s) for s in reversed(xrange(2006, current_year + 1)))
951 years_re = '(' + '|'.join(allowed_years) + '|2006-2008)'
952
953 # The (c) is deprecated, but tolerate it until it's removed from all files.
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000954 license_header = license_header or (
mark@chromium.org2cc66422012-08-07 21:22:32 +0000955 r'.*? Copyright (\(c\) )?%(year)s The %(project)s Authors\. '
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000956 r'All rights reserved\.\n'
957 r'.*? Use of this source code is governed by a BSD-style license that '
958 r'can be\n'
dbeam@chromium.orgb2312102012-02-15 02:01:55 +0000959 r'.*? found in the LICENSE file\.(?: \*/)?\n'
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000960 ) % {
mark@chromium.org2cc66422012-08-07 21:22:32 +0000961 'year': years_re,
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000962 'project': project_name,
963 }
964
965 results = []
966 # This code loads the default black list (e.g. third_party, experimental, etc)
967 # and add our black list (breakpad, skia and v8 are still not following
968 # google style and are not really living this repository).
969 # See presubmit_support.py InputApi.FilterSourceFile for the (simple) usage.
970 black_list = input_api.DEFAULT_BLACK_LIST + excluded_paths
971 white_list = input_api.DEFAULT_WHITE_LIST + text_files
972 sources = lambda x: input_api.FilterSourceFile(x, black_list=black_list)
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +0000973 text_files = lambda x: input_api.FilterSourceFile(
974 x, black_list=black_list, white_list=white_list)
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000975
976 snapshot_memory = []
977 def snapshot(msg):
978 """Measures & prints performance warning if a rule is running slow."""
979 dt2 = input_api.time.clock()
980 if snapshot_memory:
981 delta_ms = int(1000*(dt2 - snapshot_memory[0]))
982 if delta_ms > 500:
983 print " %s took a long time: %dms" % (snapshot_memory[1], delta_ms)
984 snapshot_memory[:] = (dt2, msg)
985
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000986 if owners_check:
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000987 snapshot("checking owners")
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000988 results.extend(input_api.canned_checks.CheckOwners(
dpranke@chromium.org751797a2011-06-07 18:46:27 +0000989 input_api, output_api, source_file_filter=None))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000990
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000991 snapshot("checking long lines")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000992 results.extend(input_api.canned_checks.CheckLongLines(
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000993 input_api, output_api, maxlen, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000994 snapshot( "checking tabs")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000995 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
996 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000997 snapshot( "checking stray whitespace")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000998 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
999 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001000 snapshot("checking nsobjects")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001001 results.extend(_CheckConstNSObject(
1002 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001003 snapshot("checking singletons")
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +00001004 results.extend(CheckSingletonInHeaders(
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001005 input_api, output_api, source_file_filter=sources))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +00001006
1007 # The following checks are only done on commit, since the commit bot will
1008 # auto-fix most of these.
1009 if input_api.is_committing:
maruel@chromium.org8571dac2011-05-10 18:10:13 +00001010 snapshot("checking eol style")
1011 results.extend(input_api.canned_checks.CheckChangeSvnEolStyle(
1012 input_api, output_api, source_file_filter=text_files))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +00001013 snapshot("checking svn mime types")
1014 results.extend(input_api.canned_checks.CheckSvnForCommonMimeTypes(
1015 input_api, output_api))
1016 snapshot("checking license")
1017 results.extend(input_api.canned_checks.CheckLicense(
1018 input_api, output_api, license_header, source_file_filter=sources))
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +00001019 snapshot("checking was uploaded")
1020 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
1021 input_api, output_api))
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +00001022 snapshot("checking description")
1023 results.extend(input_api.canned_checks.CheckChangeHasDescription(
1024 input_api, output_api))
1025 results.extend(input_api.canned_checks.CheckDoNotSubmitInDescription(
1026 input_api, output_api))
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +00001027 snapshot("checking do not submit in files")
1028 results.extend(input_api.canned_checks.CheckDoNotSubmitInFiles(
1029 input_api, output_api))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001030 snapshot("done")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001031 return results