blob: e4ce0418c04ff0cc4645d9fbc2ba961710b53666 [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
98 # Initialize cpplint.
99 import cpplint
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000100 # Access to a protected member _XX of a client class
101 # pylint: disable=W0212
erg@google.com26970fa2009-11-17 18:07:32 +0000102 cpplint._cpplint_state.ResetErrorCounts()
103
104 # Justifications for each filter:
105 #
106 # - build/include : Too many; fix in the future.
107 # - build/include_order : Not happening; #ifdefed includes.
108 # - build/namespace : I'm surprised by how often we violate this rule.
109 # - readability/casting : Mistakes a whole bunch of function pointer.
110 # - runtime/int : Can be fixed long term; volume of errors too high
111 # - runtime/virtual : Broken now, but can be fixed in the future?
112 # - whitespace/braces : We have a lot of explicit scoping in chrome code.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000113 cpplint._SetFilters('-build/include,-build/include_order,-build/namespace,'
114 '-readability/casting,-runtime/int,-runtime/virtual,'
115 '-whitespace/braces')
erg@google.com26970fa2009-11-17 18:07:32 +0000116
117 # We currently are more strict with normal code than unit tests; 4 and 5 are
118 # the verbosity level that would normally be passed to cpplint.py through
119 # --verbose=#. Hopefully, in the future, we can be more verbose.
120 files = [f.AbsoluteLocalPath() for f in
121 input_api.AffectedSourceFiles(source_file_filter)]
122 for file_name in files:
123 if _RE_IS_TEST.match(file_name):
124 level = 5
125 else:
126 level = 4
127
128 cpplint.ProcessFile(file_name, level)
129
130 if cpplint._cpplint_state.error_count > 0:
131 if input_api.is_committing:
132 res_type = output_api.PresubmitError
133 else:
134 res_type = output_api.PresubmitPromptWarning
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000135 result = [res_type('Changelist failed cpplint.py check.')]
erg@google.com26970fa2009-11-17 18:07:32 +0000136
137 return result
138
139
maruel@chromium.org3410d912009-06-09 20:56:16 +0000140def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000141 """Checks no '\r' (CR) character is in any source files."""
142 cr_files = []
maruel@chromium.org3410d912009-06-09 20:56:16 +0000143 for f in input_api.AffectedSourceFiles(source_file_filter):
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000144 if '\r' in input_api.ReadFile(f, 'rb'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000145 cr_files.append(f.LocalPath())
146 if cr_files:
147 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000148 'Found a CR character in these files:', items=cr_files)]
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000149 return []
150
151
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000152def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None):
153 """Checks for files in svn modified directories.
154
155 They will get submitted on accident because svn commits recursively by
156 default, and that's very dangerous.
157 """
158 if input_api.change.scm != 'svn':
159 return []
160
161 errors = []
162 current_cl_files = input_api.change.GetModifiedFiles()
163 all_modified_files = input_api.change.GetAllModifiedFiles()
164 # Filter out files in the current CL.
165 modified_files = [f for f in all_modified_files if f not in current_cl_files]
166 modified_abspaths = [input_api.os_path.abspath(f) for f in modified_files]
167
sail@chromium.org5538e022011-05-12 17:53:16 +0000168 for f in input_api.AffectedFiles(file_filter=source_file_filter):
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000169 if f.Action() == 'M' and f.IsDirectory():
170 curpath = f.AbsoluteLocalPath()
171 bad_files = []
172 # Check if any of the modified files in other CLs are under curpath.
173 for i in xrange(len(modified_files)):
174 abspath = modified_abspaths[i]
175 if input_api.os_path.commonprefix([curpath, abspath]) == curpath:
176 bad_files.append(modified_files[i])
177 if bad_files:
178 if input_api.is_committing:
179 error_type = output_api.PresubmitPromptWarning
180 else:
181 error_type = output_api.PresubmitNotifyResult
182 errors.append(error_type(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000183 'Potential accidental commits in changelist %s:' % f.LocalPath(),
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000184 items=bad_files))
185 return errors
186
187
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000188def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
189 """Checks the files ends with one and only one \n (LF)."""
190 eof_files = []
191 for f in input_api.AffectedSourceFiles(source_file_filter):
192 contents = input_api.ReadFile(f, 'rb')
193 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000194 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000195 eof_files.append(f.LocalPath())
196
197 if eof_files:
198 return [output_api.PresubmitPromptWarning(
199 'These files should end in one (and only one) newline character:',
200 items=eof_files)]
201 return []
202
203
204def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api,
205 source_file_filter=None):
206 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass.
207
208 It is faster because it is reading the file only once.
209 """
210 cr_files = []
211 eof_files = []
212 for f in input_api.AffectedSourceFiles(source_file_filter):
213 contents = input_api.ReadFile(f, 'rb')
214 if '\r' in contents:
215 cr_files.append(f.LocalPath())
216 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000217 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000218 eof_files.append(f.LocalPath())
219 outputs = []
220 if cr_files:
221 outputs.append(output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000222 'Found a CR character in these files:', items=cr_files))
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000223 if eof_files:
224 outputs.append(output_api.PresubmitPromptWarning(
225 'These files should end in one (and only one) newline character:',
226 items=eof_files))
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000227 return outputs
228
229
chrisha@google.com267d6592012-06-19 19:23:31 +0000230def _ReportErrorFileAndLine(filename, line_num, dummy_line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000231 """Default error formatter for _FindNewViolationsOfRule."""
232 return '%s, line %s' % (filename, line_num)
233
234
235def _FindNewViolationsOfRule(callable_rule, input_api, source_file_filter=None,
236 error_formatter=_ReportErrorFileAndLine):
237 """Find all newly introduced violations of a per-line rule (a callable).
238
239 Arguments:
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000240 callable_rule: a callable taking a file extension and line of input and
241 returning True if the rule is satisfied and False if there was a problem.
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000242 input_api: object to enumerate the affected files.
243 source_file_filter: a filter to be passed to the input api.
244 error_formatter: a callable taking (filename, line_number, line) and
245 returning a formatted error string.
246
247 Returns:
248 A list of the newly-introduced violations reported by the rule.
249 """
250 errors = []
sail@chromium.org5538e022011-05-12 17:53:16 +0000251 for f in input_api.AffectedFiles(include_deletes=False,
252 file_filter=source_file_filter):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000253 # For speed, we do two passes, checking first the full file. Shelling out
254 # to the SCM to determine the changed region can be quite expensive on
255 # Win32. Assuming that most files will be kept problem-free, we can
256 # skip the SCM operations most of the time.
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000257 extension = str(f.LocalPath()).rsplit('.', 1)[-1]
258 if all(callable_rule(extension, line) for line in f.NewContents()):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000259 continue # No violation found in full text: can skip considering diff.
260
261 for line_num, line in f.ChangedContents():
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000262 if not callable_rule(extension, line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000263 errors.append(error_formatter(f.LocalPath(), line_num, line))
264
265 return errors
266
267
maruel@chromium.org3410d912009-06-09 20:56:16 +0000268def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000269 """Checks that there are no tab characters in any of the text files to be
270 submitted.
271 """
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000272 # In addition to the filter, make sure that makefiles are blacklisted.
273 if not source_file_filter:
274 # It's the default filter.
275 source_file_filter = input_api.FilterSourceFile
276 def filter_more(affected_file):
cmp@chromium.orgcb5e57c2012-04-06 19:50:15 +0000277 basename = input_api.os_path.basename(affected_file.LocalPath())
278 return (not (basename in ('Makefile', 'makefile') or
279 basename.endswith('.mk')) and
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000280 source_file_filter(affected_file))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000281
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000282 tabs = _FindNewViolationsOfRule(lambda _, line : '\t' not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000283 input_api, filter_more)
284
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000285 if tabs:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000286 return [output_api.PresubmitPromptWarning('Found a tab character in:',
287 long_text='\n'.join(tabs))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000288 return []
289
290
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000291def CheckChangeTodoHasOwner(input_api, output_api, source_file_filter=None):
292 """Checks that the user didn't add TODO(name) without an owner."""
293
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000294 unowned_todo = input_api.re.compile('TO''DO[^(]')
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000295 errors = _FindNewViolationsOfRule(lambda _, x : not unowned_todo.search(x),
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000296 input_api, source_file_filter)
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000297 errors = ['Found TO''DO with no owner in ' + x for x in errors]
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000298 if errors:
299 return [output_api.PresubmitPromptWarning('\n'.join(errors))]
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000300 return []
301
302
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000303def CheckChangeHasNoStrayWhitespace(input_api, output_api,
304 source_file_filter=None):
305 """Checks that there is no stray whitespace at source lines end."""
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000306 errors = _FindNewViolationsOfRule(lambda _, line : line.rstrip() == line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000307 input_api, source_file_filter)
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000308 if errors:
309 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000310 'Found line ending with white spaces in:',
311 long_text='\n'.join(errors))]
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000312 return []
313
314
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000315def CheckLongLines(input_api, output_api, maxlen, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000316 """Checks that there aren't any lines longer than maxlen characters in any of
317 the text files to be submitted.
318 """
dpranke@chromium.orge3b1c3d2012-10-20 22:28:14 +0000319 maxlens = {
320 'java': 100,
torne@chromium.org0ecad9c2013-02-15 16:35:16 +0000321 # This is specifically for Android's handwritten makefiles (Android.mk).
322 'mk': 200,
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000323 '': maxlen,
324 }
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000325 # Note: these are C++ specific but processed on all languages. :(
326 MACROS = ('#define', '#include', '#import', '#pragma', '#if', '#endif')
327
sivachandra@chromium.org270db5c2012-10-09 22:38:44 +0000328 # Special java statements.
329 SPECIAL_JAVA_STARTS = ('package ', 'import ')
330
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000331 def no_long_lines(file_extension, line):
sivachandra@chromium.org270db5c2012-10-09 22:38:44 +0000332 # Allow special java statements to be as long as neccessary.
333 if file_extension == 'java' and line.startswith(SPECIAL_JAVA_STARTS):
334 return True
335
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000336 file_maxlen = maxlens.get(file_extension, maxlens[''])
337 # Stupidly long symbols that needs to be worked around if takes 66% of line.
338 long_symbol = file_maxlen * 2 / 3
339 # Hard line length limit at 50% more.
340 extra_maxlen = file_maxlen * 3 / 2
341
342 line_len = len(line)
343 if line_len <= file_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000344 return True
345
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000346 if line_len > extra_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000347 return False
348
349 return (
350 line.startswith(MACROS) or
351 any((url in line) for url in ('http://', 'https://')) or
352 input_api.re.match(
353 r'.*[A-Za-z][A-Za-z_0-9]{%d,}.*' % long_symbol, line))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000354
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000355 def format_error(filename, line_num, line):
356 return '%s, line %s, %s chars' % (filename, line_num, len(line))
357
358 errors = _FindNewViolationsOfRule(no_long_lines, input_api,
359 source_file_filter,
360 error_formatter=format_error)
361 if errors:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000362 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000363 return [output_api.PresubmitPromptWarning(msg, items=errors[:5])]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000364 else:
365 return []
366
367
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000368def CheckLicense(input_api, output_api, license_re, source_file_filter=None,
maruel@chromium.org71626852010-11-03 13:14:25 +0000369 accept_empty_files=True):
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000370 """Verifies the license header.
371 """
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000372 license_re = input_api.re.compile(license_re, input_api.re.MULTILINE)
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000373 bad_files = []
374 for f in input_api.AffectedSourceFiles(source_file_filter):
375 contents = input_api.ReadFile(f, 'rb')
maruel@chromium.org71626852010-11-03 13:14:25 +0000376 if accept_empty_files and not contents:
377 continue
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000378 if not license_re.search(contents):
379 bad_files.append(f.LocalPath())
380 if bad_files:
381 if input_api.is_committing:
382 res_type = output_api.PresubmitPromptWarning
383 else:
384 res_type = output_api.PresubmitNotifyResult
385 return [res_type(
bradnelson@google.com393460b2011-03-29 01:20:26 +0000386 'License must match:\n%s\n' % license_re.pattern +
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000387 'Found a bad license header in these files:', items=bad_files)]
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000388 return []
389
390
maruel@chromium.org1a0e3cb2009-06-10 18:03:04 +0000391def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None):
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000392 """Checks that the source files have svn:eol-style=LF."""
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000393 return CheckSvnProperty(input_api, output_api,
394 'svn:eol-style', 'LF',
395 input_api.AffectedSourceFiles(source_file_filter))
396
397
398def CheckSvnForCommonMimeTypes(input_api, output_api):
399 """Checks that common binary file types have the correct svn:mime-type."""
400 output = []
401 files = input_api.AffectedFiles(include_deletes=False)
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000402 def IsExts(x, exts):
403 path = x.LocalPath()
404 for extension in exts:
405 if path.endswith(extension):
406 return True
407 return False
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000408 def FilterFiles(extension):
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000409 return filter(lambda x: IsExts(x, extension), files)
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000410 def RunCheck(mime_type, files):
411 output.extend(CheckSvnProperty(input_api, output_api, 'svn:mime-type',
412 mime_type, files))
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000413 RunCheck('application/pdf', FilterFiles(['.pdf']))
414 RunCheck('image/bmp', FilterFiles(['.bmp']))
415 RunCheck('image/gif', FilterFiles(['.gif']))
416 RunCheck('image/png', FilterFiles(['.png']))
417 RunCheck('image/jpeg', FilterFiles(['.jpg', '.jpeg', '.jpe']))
418 RunCheck('image/vnd.microsoft.icon', FilterFiles(['.ico']))
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000419 return output
420
421
422def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
423 """Checks that affected_files files have prop=expected."""
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000424 if input_api.change.scm != 'svn':
425 return []
426
427 bad = filter(lambda f: f.Property(prop) != expected, affected_files)
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000428 if bad:
maruel@chromium.org0874d472009-06-10 19:08:33 +0000429 if input_api.is_committing:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000430 res_type = output_api.PresubmitError
maruel@chromium.org0874d472009-06-10 19:08:33 +0000431 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000432 res_type = output_api.PresubmitNotifyResult
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000433 message = 'Run the command: svn pset %s %s \\' % (prop, expected)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000434 return [res_type(message, items=bad)]
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000435 return []
436
437
maruel@chromium.org3410d912009-06-09 20:56:16 +0000438### Other checks
439
440def CheckDoNotSubmit(input_api, output_api):
441 return (
442 CheckDoNotSubmitInDescription(input_api, output_api) +
443 CheckDoNotSubmitInFiles(input_api, output_api)
444 )
445
446
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000447def CheckTreeIsOpen(input_api, output_api,
448 url=None, closed=None, json_url=None):
449 """Check whether to allow commit without prompt.
450
451 Supports two styles:
452 1. Checks that an url's content doesn't match a regexp that would mean that
453 the tree is closed. (old)
454 2. Check the json_url to decide whether to allow commit without prompt.
455 Args:
456 input_api: input related apis.
457 output_api: output related apis.
458 url: url to use for regex based tree status.
459 closed: regex to match for closed status.
460 json_url: url to download json style status.
461 """
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000462 if not input_api.is_committing:
463 return []
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000464 try:
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000465 if json_url:
466 connection = input_api.urllib2.urlopen(json_url)
467 status = input_api.json.loads(connection.read())
468 connection.close()
469 if not status['can_commit_freely']:
470 short_text = 'Tree state is: ' + status['general_state']
471 long_text = status['message'] + '\n' + json_url
472 return [output_api.PresubmitError(short_text, long_text=long_text)]
473 else:
474 # TODO(bradnelson): drop this once all users are gone.
475 connection = input_api.urllib2.urlopen(url)
476 status = connection.read()
477 connection.close()
478 if input_api.re.match(closed, status):
479 long_text = status + '\n' + url
480 return [output_api.PresubmitError('The tree is closed.',
481 long_text=long_text)]
rohitrao@chromium.orgd490ee82012-02-06 19:31:33 +0000482 except IOError as e:
483 return [output_api.PresubmitError('Error fetching tree status.',
484 long_text=str(e))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000485 return []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000486
487
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000488def RunUnitTestsInDirectory(
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000489 input_api, output_api, directory, whitelist=None, blacklist=None):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000490 """Lists all files in a directory and runs them. Doesn't recurse.
491
492 It's mainly a wrapper for RunUnitTests. USe whitelist and blacklist to filter
493 tests accordingly.
494 """
495 unit_tests = []
496 test_path = input_api.os_path.abspath(
497 input_api.os_path.join(input_api.PresubmitLocalPath(), directory))
498
499 def check(filename, filters):
500 return any(True for i in filters if input_api.re.match(i, filename))
501
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000502 to_run = found = 0
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000503 for filename in input_api.os_listdir(test_path):
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000504 found += 1
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000505 fullpath = input_api.os_path.join(test_path, filename)
506 if not input_api.os_path.isfile(fullpath):
507 continue
508 if whitelist and not check(filename, whitelist):
509 continue
510 if blacklist and check(filename, blacklist):
511 continue
512 unit_tests.append(input_api.os_path.join(directory, filename))
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000513 to_run += 1
514 input_api.logging.debug('Found %d files, running %d' % (found, to_run))
515 if not to_run:
516 return [
517 output_api.PresubmitPromptWarning(
518 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
519 % (found, whitelist, blacklist, directory))
520 ]
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000521 return RunUnitTests(input_api, output_api, unit_tests)
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000522
523
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000524def RunUnitTests(input_api, output_api, unit_tests):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000525 """Runs all unit tests in a directory.
526
527 On Windows, sys.executable is used for unit tests ending with ".py".
528 """
529 # We don't want to hinder users from uploading incomplete patches.
530 if input_api.is_committing:
531 message_type = output_api.PresubmitError
532 else:
533 message_type = output_api.PresubmitPromptWarning
534
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000535 results = []
536 for unit_test in unit_tests:
537 cmd = []
538 if input_api.platform == 'win32' and unit_test.endswith('.py'):
539 # Windows needs some help.
540 cmd = [input_api.python_executable]
541 cmd.append(unit_test)
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000542 if input_api.verbose:
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000543 print('Running %s' % unit_test)
maruel@chromium.org6c7723e2011-04-12 19:04:55 +0000544 cmd.append('--verbose')
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000545 try:
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000546 if input_api.verbose:
maruel@chromium.org0e766052011-04-06 13:32:51 +0000547 input_api.subprocess.check_call(cmd, cwd=input_api.PresubmitLocalPath())
548 else:
549 input_api.subprocess.check_output(
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000550 cmd,
551 stderr=input_api.subprocess.STDOUT,
552 cwd=input_api.PresubmitLocalPath())
maruel@chromium.org0e766052011-04-06 13:32:51 +0000553 except (OSError, input_api.subprocess.CalledProcessError), e:
554 results.append(message_type('%s failed!\n%s' % (unit_test, e)))
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000555 return results
556
557
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000558def RunPythonUnitTests(input_api, output_api, unit_tests):
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000559 """Run the unit tests out of process, capture the output and use the result
560 code to determine success.
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000561
562 DEPRECATED.
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000563 """
maruel@chromium.orgd7dccf52009-06-06 18:51:58 +0000564 # We don't want to hinder users from uploading incomplete patches.
565 if input_api.is_committing:
566 message_type = output_api.PresubmitError
567 else:
568 message_type = output_api.PresubmitNotifyResult
maruel@chromium.org0e766052011-04-06 13:32:51 +0000569 results = []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000570 for unit_test in unit_tests:
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000571 # Run the unit tests out of process. This is because some unit tests
572 # stub out base libraries and don't clean up their mess. It's too easy to
573 # get subtle bugs.
574 cwd = None
575 env = None
576 unit_test_name = unit_test
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000577 # 'python -m test.unit_test' doesn't work. We need to change to the right
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000578 # directory instead.
579 if '.' in unit_test:
580 # Tests imported in submodules (subdirectories) assume that the current
581 # directory is in the PYTHONPATH. Manually fix that.
582 unit_test = unit_test.replace('.', '/')
583 cwd = input_api.os_path.dirname(unit_test)
584 unit_test = input_api.os_path.basename(unit_test)
585 env = input_api.environ.copy()
kbr@google.comab318592009-09-04 00:54:55 +0000586 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
587 backpath = [
588 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
589 ]
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000590 if env.get('PYTHONPATH'):
591 backpath.append(env.get('PYTHONPATH'))
ukai@chromium.orga301f1f2009-08-05 10:37:33 +0000592 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000593 cmd = [input_api.python_executable, '-m', '%s' % unit_test]
594 try:
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000595 input_api.subprocess.check_output(
596 cmd, stderr=input_api.subprocess.STDOUT, cwd=cwd, env=env)
maruel@chromium.org0e766052011-04-06 13:32:51 +0000597 except (OSError, input_api.subprocess.CalledProcessError), e:
598 results.append(message_type('%s failed!\n%s' % (unit_test_name, e)))
599 return results
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000600
601
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000602def _FetchAllFiles(input_api, white_list, black_list):
603 """Hack to fetch all files."""
604 # We cannot use AffectedFiles here because we want to test every python
605 # file on each single python change. It's because a change in a python file
606 # can break another unmodified file.
607 # Use code similar to InputApi.FilterSourceFile()
608 def Find(filepath, filters):
609 for item in filters:
610 if input_api.re.match(item, filepath):
611 return True
612 return False
613
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000614 files = []
615 path_len = len(input_api.PresubmitLocalPath())
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000616 for dirpath, dirnames, filenames in input_api.os_walk(
617 input_api.PresubmitLocalPath()):
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000618 # Passes dirnames in black list to speed up search.
619 for item in dirnames[:]:
620 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
621 if Find(filepath, black_list):
622 dirnames.remove(item)
623 for item in filenames:
624 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
625 if Find(filepath, white_list) and not Find(filepath, black_list):
626 files.append(filepath)
627 return files
628
629
maruel@chromium.orgff9a2172012-04-24 16:55:32 +0000630def RunPylint(input_api, output_api, white_list=None, black_list=None,
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000631 disabled_warnings=None, extra_paths_list=None):
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000632 """Run pylint on python files.
633
chrisha@google.com267d6592012-06-19 19:23:31 +0000634 The default white_list enforces looking only at *.py files.
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000635 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000636 white_list = tuple(white_list or ('.*\.py$',))
637 black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000638 extra_paths_list = extra_paths_list or []
639
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000640 if input_api.is_committing:
641 error_type = output_api.PresubmitError
642 else:
643 error_type = output_api.PresubmitPromptWarning
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000644
645 # Only trigger if there is at least one python file affected.
ilevy@chromium.org36576332013-01-08 03:16:15 +0000646 def rel_path(regex):
647 """Modifies a regex for a subject to accept paths relative to root."""
chrisha@chromium.org40e5d3d2013-01-18 17:46:57 +0000648 def samefile(a, b):
649 # Default implementation for platforms lacking os.path.samefile
650 # (like Windows).
651 return input_api.os_path.abspath(a) == input_api.os_path.abspath(b)
652 samefile = getattr(input_api.os_path, 'samefile', samefile)
653 if samefile(input_api.PresubmitLocalPath(),
654 input_api.change.RepositoryRoot()):
ilevy@chromium.orgdd4e9312013-01-15 03:22:04 +0000655 return regex
chrisha@chromium.org40e5d3d2013-01-18 17:46:57 +0000656
ilevy@chromium.org36576332013-01-08 03:16:15 +0000657 prefix = input_api.os_path.join(input_api.os_path.relpath(
658 input_api.PresubmitLocalPath(), input_api.change.RepositoryRoot()), '')
659 return input_api.re.escape(prefix) + regex
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000660 src_filter = lambda x: input_api.FilterSourceFile(
661 x, map(rel_path, white_list), map(rel_path, black_list))
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000662 if not input_api.AffectedSourceFiles(src_filter):
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000663 input_api.logging.info('Skipping pylint: no matching changes.')
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000664 return []
665
maruel@chromium.orgff9a2172012-04-24 16:55:32 +0000666 extra_args = ['--rcfile=%s' % input_api.os_path.join(_HERE, 'pylintrc')]
667 if disabled_warnings:
668 extra_args.extend(['-d', ','.join(disabled_warnings)])
669
chrisha@google.com267d6592012-06-19 19:23:31 +0000670 files = _FetchAllFiles(input_api, white_list, black_list)
671 if not files:
maruel@chromium.orgfca53392010-12-21 18:42:57 +0000672 return []
chrisha@google.com267d6592012-06-19 19:23:31 +0000673
csharp@chromium.org40395342013-02-21 14:57:23 +0000674 input_api.logging.info('Running pylint on %d files', len(files))
675 input_api.logging.debug('Running pylint on: %s', files)
chrisha@google.com267d6592012-06-19 19:23:31 +0000676 # Copy the system path to the environment so pylint can find the right
677 # imports.
678 env = input_api.environ.copy()
679 import sys
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000680 env['PYTHONPATH'] = input_api.os_path.pathsep.join(
robertshield@chromium.orga2873932013-02-20 18:08:46 +0000681 extra_paths_list + sys.path).encode('utf8')
chrisha@google.com267d6592012-06-19 19:23:31 +0000682
683 def run_lint(files):
684 # We can't import pylint directly due to licensing issues, so we run
685 # it in another process. Windows needs help running python files so we
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000686 # explicitly specify the interpreter to use. It also has limitations on
687 # the size of the command-line, so we pass arguments via a pipe.
chrisha@google.com267d6592012-06-19 19:23:31 +0000688 command = [input_api.python_executable,
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000689 input_api.os_path.join(_HERE, 'third_party', 'pylint.py'),
690 '--args-on-stdin']
chrisha@google.com267d6592012-06-19 19:23:31 +0000691 try:
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000692 child = input_api.subprocess.Popen(command, env=env,
693 stdin=input_api.subprocess.PIPE)
694
695 # Dump the arguments to the child process via a pipe.
696 for filename in files:
697 child.stdin.write(filename + '\n')
698 for arg in extra_args:
699 child.stdin.write(arg + '\n')
700 child.stdin.close()
701
702 child.communicate()
703 return child.returncode
chrisha@google.com267d6592012-06-19 19:23:31 +0000704 except OSError:
705 return 'Pylint failed!'
706
707 result = None
sbc@chromium.org7fc75ca2012-09-21 20:14:21 +0000708 # Always run pylint and pass it all the py files at once.
709 # Passing py files one at time is slower and can produce
710 # different results. input_api.verbose used to be used
711 # to enable this behaviour but differing behaviour in
712 # verbose mode is not desirable.
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000713 # Leave this unreachable code in here so users can make
714 # a quick local edit to diagnose pylint issues more
715 # easily.
sbc@chromium.org7fc75ca2012-09-21 20:14:21 +0000716 if True:
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000717 print('Running pylint on %d files.' % len(files))
chrisha@google.com267d6592012-06-19 19:23:31 +0000718 result = run_lint(sorted(files))
719 else:
720 for filename in sorted(files):
721 print('Running pylint on %s' % filename)
722 result = run_lint([filename]) or result
723 if isinstance(result, basestring):
724 return [error_type(result)]
725 elif result:
726 return [error_type('Fix pylint errors first.')]
727 return []
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000728
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000729
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000730# TODO(dpranke): Get the host_url from the input_api instead
chrisha@google.com267d6592012-06-19 19:23:31 +0000731def CheckRietveldTryJobExecution(dummy_input_api, dummy_output_api,
732 dummy_host_url, dummy_platforms,
733 dummy_owner):
maruel@chromium.org85da74b2011-10-27 17:13:30 +0000734 # Temporarily 'fix' the check while the Rietveld API is being upgraded to
735 # something sensible.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000736 return []
737
738
739def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
740 ignored):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000741 try:
742 connection = input_api.urllib2.urlopen(url)
743 raw_data = connection.read()
744 connection.close()
745 except IOError:
746 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)]
747
748 try:
749 data = input_api.json.loads(raw_data)
750 except ValueError:
751 return [output_api.PresubmitNotifyResult('Received malformed json while '
752 'looking up buildbot status')]
753
754 out = []
755 for (builder_name, builder) in data.iteritems():
756 if builder_name in ignored:
757 continue
maruel@chromium.orgcf1982c2010-10-04 15:08:28 +0000758 if builder.get('state', '') == 'offline':
759 continue
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000760 pending_builds_len = len(builder.get('pending_builds', []))
761 if pending_builds_len > max_pendings:
762 out.append('%s has %d build(s) pending' %
763 (builder_name, pending_builds_len))
764 if out:
765 return [output_api.PresubmitPromptWarning(
766 'Build(s) pending. It is suggested to wait that no more than %d '
767 'builds are pending.' % max_pendings,
768 long_text='\n'.join(out))]
769 return []
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000770
771
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000772def CheckOwners(input_api, output_api, source_file_filter=None,
773 author_counts_as_owner=True):
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000774 if input_api.is_committing:
775 if input_api.tbr:
776 return [output_api.PresubmitNotifyResult(
777 '--tbr was specified, skipping OWNERS check')]
778 if not input_api.change.issue:
779 return [output_api.PresubmitError("OWNERS check failed: this change has "
780 "no Rietveld issue number, so we can't check it for approvals.")]
781 needed = 'LGTM from an OWNER'
782 output = output_api.PresubmitError
783 else:
784 needed = 'OWNER reviewers'
785 output = output_api.PresubmitNotifyResult
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000786
dpranke@chromium.orgadd5df42011-03-08 23:04:01 +0000787 affected_files = set([f.LocalPath() for f in
sail@chromium.org5538e022011-05-12 17:53:16 +0000788 input_api.change.AffectedFiles(file_filter=source_file_filter)])
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000789
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000790 owners_db = input_api.owners_db
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000791 owner_email, reviewers = _RietveldOwnerAndReviewers(
792 input_api,
793 owners_db.email_regexp,
794 approval_needed=input_api.is_committing)
795
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000796 owner_email = owner_email or input_api.change.author_email
797
798 if author_counts_as_owner and owner_email:
dpranke@chromium.org4c7ce992013-03-06 17:15:40 +0000799 reviewers_plus_owner = set([owner_email]).union(reviewers)
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000800 missing_files = owners_db.files_not_covered_by(affected_files,
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000801 reviewers_plus_owner)
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000802 else:
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000803 missing_files = owners_db.files_not_covered_by(affected_files, reviewers)
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000804
dpranke@chromium.org6b1e3ee2013-02-23 00:06:38 +0000805 if missing_files:
zork@chromium.org046e1752012-05-07 05:56:12 +0000806 output_list = [
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000807 output('Missing %s for these files:\n %s' %
808 (needed, '\n '.join(missing_files)))]
zork@chromium.org046e1752012-05-07 05:56:12 +0000809 if not input_api.is_committing:
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000810 suggested_owners = owners_db.reviewers_for(affected_files, owner_email)
zork@chromium.org046e1752012-05-07 05:56:12 +0000811 output_list.append(output('Suggested OWNERS:\n %s' %
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000812 ('\n '.join(suggested_owners or []))))
zork@chromium.org046e1752012-05-07 05:56:12 +0000813 return output_list
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000814
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000815 if input_api.is_committing and not reviewers:
816 return [output('Missing LGTM from someone other than %s' % owner_email)]
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000817 return []
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000818
819
ilevy@chromium.org1fa5cb92012-12-05 04:04:42 +0000820def _GetRietveldIssueProps(input_api, messages):
821 """Gets the issue properties from rietveld."""
822 issue = input_api.change.issue
823 if issue and input_api.rietveld:
824 return input_api.rietveld.get_issue_properties(
825 issue=int(issue), messages=messages)
826
827
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000828def _RietveldOwnerAndReviewers(input_api, email_regexp, approval_needed=False):
829 """Return the owner and reviewers of a change, if any.
830
831 If approval_needed is True, only reviewers who have approved the change
832 will be returned.
833 """
ilevy@chromium.org1fa5cb92012-12-05 04:04:42 +0000834 issue_props = _GetRietveldIssueProps(input_api, True)
835 if not issue_props:
dpranke@chromium.org4c7ce992013-03-06 17:15:40 +0000836 return None, set()
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000837
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000838 if not approval_needed:
839 return issue_props['owner_email'], set(issue_props['reviewers'])
840
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000841 owner_email = issue_props['owner_email']
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000842
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000843 def match_reviewer(r):
maruel@chromium.org80941c22011-05-30 20:14:18 +0000844 return email_regexp.match(r) and r != owner_email
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000845
maruel@chromium.org80941c22011-05-30 20:14:18 +0000846 messages = issue_props.get('messages', [])
847 approvers = set(
848 m['sender'] for m in messages
849 if m.get('approval') and match_reviewer(m['sender']))
850
851 return owner_email, approvers
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000852
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000853
854def _CheckConstNSObject(input_api, output_api, source_file_filter):
855 """Checks to make sure no objective-c files have |const NSSomeClass*|."""
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000856 pattern = input_api.re.compile(
857 r'const\s+NS(?!(Point|Range|Rect|Size)\s*\*)\w*\s*\*')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000858
859 def objective_c_filter(f):
860 return (source_file_filter(f) and
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000861 input_api.os_path.splitext(f.LocalPath())[1] in ('.h', '.m', '.mm'))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000862
863 files = []
864 for f in input_api.AffectedSourceFiles(objective_c_filter):
865 contents = input_api.ReadFile(f)
866 if pattern.search(contents):
867 files.append(f)
868
869 if files:
870 if input_api.is_committing:
871 res_type = output_api.PresubmitPromptWarning
872 else:
873 res_type = output_api.PresubmitNotifyResult
874 return [ res_type('|const NSClass*| is wrong, see ' +
875 'http://dev.chromium.org/developers/clang-mac',
876 files) ]
877 return []
878
879
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000880def CheckSingletonInHeaders(input_api, output_api, source_file_filter=None):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000881 """Checks to make sure no header files have |Singleton<|."""
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000882 pattern = input_api.re.compile(r'(?<!class\s)Singleton\s*<')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000883 files = []
884 for f in input_api.AffectedSourceFiles(source_file_filter):
885 if (f.LocalPath().endswith('.h') or f.LocalPath().endswith('.hxx') or
886 f.LocalPath().endswith('.hpp') or f.LocalPath().endswith('.inl')):
887 contents = input_api.ReadFile(f)
dilmah@chromium.orgd22b9702011-08-26 13:53:49 +0000888 for line in contents.splitlines(False):
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000889 if (not input_api.re.match(r'//', line) and # Strip C++ comment.
890 pattern.search(line)):
dilmah@chromium.orgd22b9702011-08-26 13:53:49 +0000891 files.append(f)
892 break
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000893
894 if files:
895 return [ output_api.PresubmitError(
896 'Found Singleton<T> in the following header files.\n' +
897 'Please move them to an appropriate source file so that the ' +
898 'template gets instantiated in a single compilation unit.',
899 files) ]
900 return []
901
902
903def PanProjectChecks(input_api, output_api,
904 excluded_paths=None, text_files=None,
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000905 license_header=None, project_name=None,
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000906 owners_check=True, maxlen=80):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000907 """Checks that ALL chromium orbit projects should use.
908
909 These are checks to be run on all Chromium orbit project, including:
910 Chromium
911 Native Client
912 V8
913 When you update this function, please take this broad scope into account.
914 Args:
915 input_api: Bag of input related interfaces.
916 output_api: Bag of output related interfaces.
917 excluded_paths: Don't include these paths in common checks.
918 text_files: Which file are to be treated as documentation text files.
919 license_header: What license header should be on files.
920 project_name: What is the name of the project as it appears in the license.
921 Returns:
922 A list of warning or error objects.
923 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000924 excluded_paths = tuple(excluded_paths or [])
925 text_files = tuple(text_files or (
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +0000926 r'.+\.txt$',
927 r'.+\.json$',
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000928 ))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000929 project_name = project_name or 'Chromium'
mark@chromium.org2cc66422012-08-07 21:22:32 +0000930
931 # Accept any year number from 2006 to the current year, or the special
932 # 2006-2008 string used on the oldest files. 2006-2008 is deprecated, but
933 # tolerate it until it's removed from all files.
934 current_year = int(input_api.time.strftime('%Y'))
935 allowed_years = (str(s) for s in reversed(xrange(2006, current_year + 1)))
936 years_re = '(' + '|'.join(allowed_years) + '|2006-2008)'
937
938 # The (c) is deprecated, but tolerate it until it's removed from all files.
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000939 license_header = license_header or (
mark@chromium.org2cc66422012-08-07 21:22:32 +0000940 r'.*? Copyright (\(c\) )?%(year)s The %(project)s Authors\. '
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000941 r'All rights reserved\.\n'
942 r'.*? Use of this source code is governed by a BSD-style license that '
943 r'can be\n'
dbeam@chromium.orgb2312102012-02-15 02:01:55 +0000944 r'.*? found in the LICENSE file\.(?: \*/)?\n'
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000945 ) % {
mark@chromium.org2cc66422012-08-07 21:22:32 +0000946 'year': years_re,
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000947 'project': project_name,
948 }
949
950 results = []
951 # This code loads the default black list (e.g. third_party, experimental, etc)
952 # and add our black list (breakpad, skia and v8 are still not following
953 # google style and are not really living this repository).
954 # See presubmit_support.py InputApi.FilterSourceFile for the (simple) usage.
955 black_list = input_api.DEFAULT_BLACK_LIST + excluded_paths
956 white_list = input_api.DEFAULT_WHITE_LIST + text_files
957 sources = lambda x: input_api.FilterSourceFile(x, black_list=black_list)
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +0000958 text_files = lambda x: input_api.FilterSourceFile(
959 x, black_list=black_list, white_list=white_list)
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000960
961 snapshot_memory = []
962 def snapshot(msg):
963 """Measures & prints performance warning if a rule is running slow."""
964 dt2 = input_api.time.clock()
965 if snapshot_memory:
966 delta_ms = int(1000*(dt2 - snapshot_memory[0]))
967 if delta_ms > 500:
968 print " %s took a long time: %dms" % (snapshot_memory[1], delta_ms)
969 snapshot_memory[:] = (dt2, msg)
970
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000971 if owners_check:
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000972 snapshot("checking owners")
bradnelson@google.comd57771d2011-03-31 19:18:32 +0000973 results.extend(input_api.canned_checks.CheckOwners(
dpranke@chromium.org751797a2011-06-07 18:46:27 +0000974 input_api, output_api, source_file_filter=None))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000975
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000976 snapshot("checking long lines")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000977 results.extend(input_api.canned_checks.CheckLongLines(
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000978 input_api, output_api, maxlen, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000979 snapshot( "checking tabs")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000980 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
981 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000982 snapshot( "checking stray whitespace")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000983 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
984 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000985 snapshot("checking nsobjects")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000986 results.extend(_CheckConstNSObject(
987 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000988 snapshot("checking singletons")
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +0000989 results.extend(CheckSingletonInHeaders(
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000990 input_api, output_api, source_file_filter=sources))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +0000991
992 # The following checks are only done on commit, since the commit bot will
993 # auto-fix most of these.
994 if input_api.is_committing:
maruel@chromium.org8571dac2011-05-10 18:10:13 +0000995 snapshot("checking eol style")
996 results.extend(input_api.canned_checks.CheckChangeSvnEolStyle(
997 input_api, output_api, source_file_filter=text_files))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +0000998 snapshot("checking svn mime types")
999 results.extend(input_api.canned_checks.CheckSvnForCommonMimeTypes(
1000 input_api, output_api))
1001 snapshot("checking license")
1002 results.extend(input_api.canned_checks.CheckLicense(
1003 input_api, output_api, license_header, source_file_filter=sources))
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +00001004 snapshot("checking was uploaded")
1005 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
1006 input_api, output_api))
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +00001007 snapshot("checking description")
1008 results.extend(input_api.canned_checks.CheckChangeHasDescription(
1009 input_api, output_api))
1010 results.extend(input_api.canned_checks.CheckDoNotSubmitInDescription(
1011 input_api, output_api))
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +00001012 snapshot("checking do not submit in files")
1013 results.extend(input_api.canned_checks.CheckDoNotSubmitInFiles(
1014 input_api, output_api))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001015 snapshot("done")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001016 return results