blob: d6434ede538ef4daa26b10f1b6efc371a6dd2239 [file] [log] [blame]
estade@chromium.orgae7af922012-01-27 14:51:13 +00001# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Generic presubmit checks that can be reused by other presubmit checks."""
6
maruel@chromium.orgff9a2172012-04-24 16:55:32 +00007import os as _os
8_HERE = _os.path.dirname(_os.path.abspath(__file__))
9
tfarina@chromium.orgb6795642014-12-12 00:03:49 +000010# Justifications for each filter:
11#
12# - build/include : Too many; fix in the future.
13# - build/include_order : Not happening; #ifdefed includes.
14# - build/namespace : I'm surprised by how often we violate this rule.
15# - readability/casting : Mistakes a whole bunch of function pointer.
16# - runtime/int : Can be fixed long term; volume of errors too high
17# - runtime/virtual : Broken now, but can be fixed in the future?
18# - whitespace/braces : We have a lot of explicit scoping in chrome code.
tfarina@chromium.orgb6795642014-12-12 00:03:49 +000019DEFAULT_LINT_FILTERS = [
20 '-build/include',
21 '-build/include_order',
22 '-build/namespace',
23 '-readability/casting',
24 '-runtime/int',
25 '-runtime/virtual',
26 '-whitespace/braces',
tfarina@chromium.orgb6795642014-12-12 00:03:49 +000027]
bradnelson@google.com56e48bc2011-03-24 20:51:21 +000028
danakj@chromium.org0ae71222016-01-11 19:37:11 +000029# These filters will always be removed, even if the caller specifies a filter
30# set, as they are problematic or broken in some way.
31#
32# Justifications for each filter:
33# - build/c++11 : Rvalue ref checks are unreliable (false positives),
34# include file and feature blacklists are
35# google3-specific.
36BLACKLIST_LINT_FILTERS = [
37 '-build/c++11',
38]
39
maruel@chromium.org3410d912009-06-09 20:56:16 +000040### Description checks
41
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000042def CheckChangeHasTestField(input_api, output_api):
43 """Requires that the changelist have a TEST= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000044 if input_api.change.TEST:
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000045 return []
46 else:
47 return [output_api.PresubmitNotifyResult(
jam@chromium.org5c76de92011-01-24 18:19:21 +000048 'If this change requires manual test instructions to QA team, add '
49 'TEST=[instructions].')]
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000050
51
52def CheckChangeHasBugField(input_api, output_api):
53 """Requires that the changelist have a BUG= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000054 if input_api.change.BUG:
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000055 return []
56 else:
57 return [output_api.PresubmitNotifyResult(
jam@chromium.org5c76de92011-01-24 18:19:21 +000058 'If this change has an associated bug, add BUG=[bug number].')]
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000059
60
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061def CheckChangeHasTestedField(input_api, output_api):
62 """Requires that the changelist have a TESTED= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000063 if input_api.change.TESTED:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000064 return []
65 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000066 return [output_api.PresubmitError('Changelist must have a TESTED= field.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000067
68
69def CheckChangeHasQaField(input_api, output_api):
70 """Requires that the changelist have a QA= field."""
71 if input_api.change.QA:
72 return []
73 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000074 return [output_api.PresubmitError('Changelist must have a QA= field.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000075
76
77def CheckDoNotSubmitInDescription(input_api, output_api):
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +000078 """Checks that the user didn't add 'DO NOT ''SUBMIT' to the CL description.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000079 """
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +000080 keyword = 'DO NOT ''SUBMIT'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081 if keyword in input_api.change.DescriptionText():
82 return [output_api.PresubmitError(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000083 keyword + ' is present in the changelist description.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084 else:
85 return []
86
87
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000088def CheckChangeHasDescription(input_api, output_api):
89 """Checks the CL description is not empty."""
90 text = input_api.change.DescriptionText()
91 if text.strip() == '':
92 if input_api.is_committing:
pgervais@chromium.orgbc3b3b52014-06-03 00:53:48 +000093 return [output_api.PresubmitError('Add a description to the CL.')]
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000094 else:
pgervais@chromium.orgbc3b3b52014-06-03 00:53:48 +000095 return [output_api.PresubmitNotifyResult('Add a description to the CL.')]
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000096 return []
97
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +000098
99def CheckChangeWasUploaded(input_api, output_api):
100 """Checks that the issue was uploaded before committing."""
maruel@chromium.orgd587f392011-07-26 00:41:18 +0000101 if input_api.is_committing and not input_api.change.issue:
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +0000102 return [output_api.PresubmitError(
103 'Issue wasn\'t uploaded. Please upload first.')]
104 return []
105
106
maruel@chromium.org3410d912009-06-09 20:56:16 +0000107### Content checks
108
Michael Achenbachc850b962016-12-05 15:40:17 +0100109def CheckAuthorizedAuthor(input_api, output_api):
110 """For non-googler/chromites committers, verify the author's email address is
111 in AUTHORS.
112 """
113 author = input_api.change.author_email
114 if not author:
115 input_api.logging.info('No author, skipping AUTHOR check')
116 return []
117 authors_path = input_api.os_path.join(
118 input_api.PresubmitLocalPath(), 'AUTHORS')
119 valid_authors = (
120 input_api.re.match(r'[^#]+\s+\<(.+?)\>\s*$', line)
121 for line in open(authors_path))
122 valid_authors = [item.group(1).lower() for item in valid_authors if item]
123 if not any(input_api.fnmatch.fnmatch(author.lower(), valid)
124 for valid in valid_authors):
125 input_api.logging.info('Valid authors are %s', ', '.join(valid_authors))
126 return [output_api.PresubmitPromptWarning(
127 ('%s is not in AUTHORS file. If you are a new contributor, please visit'
128 '\n'
129 'http://www.chromium.org/developers/contributing-code and read the '
130 '"Legal" section\n'
131 'If you are a chromite, verify the contributor signed the CLA.') %
132 author)]
133 return []
134
135
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000136def CheckDoNotSubmitInFiles(input_api, output_api):
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000137 """Checks that the user didn't add 'DO NOT ''SUBMIT' to any files."""
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000138 # We want to check every text file, not just source files.
139 file_filter = lambda x : x
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000140 keyword = 'DO NOT ''SUBMIT'
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000141 errors = _FindNewViolationsOfRule(lambda _, line : keyword not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000142 input_api, file_filter)
143 text = '\n'.join('Found %s in %s' % (keyword, loc) for loc in errors)
144 if text:
145 return [output_api.PresubmitError(text)]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000146 return []
147
148
tfarina@chromium.orgb6795642014-12-12 00:03:49 +0000149def CheckChangeLintsClean(input_api, output_api, source_file_filter=None,
tfarina@chromium.orga62208f2015-02-25 03:23:11 +0000150 lint_filters=None, verbose_level=None):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000151 """Checks that all '.cc' and '.h' files pass cpplint.py."""
erg@google.com26970fa2009-11-17 18:07:32 +0000152 _RE_IS_TEST = input_api.re.compile(r'.*tests?.(cc|h)$')
153 result = []
154
enne@chromium.orge72c5f52013-04-16 00:36:40 +0000155 cpplint = input_api.cpplint
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000156 # Access to a protected member _XX of a client class
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800157 # pylint: disable=protected-access
erg@google.com26970fa2009-11-17 18:07:32 +0000158 cpplint._cpplint_state.ResetErrorCounts()
159
tfarina@chromium.orgb6795642014-12-12 00:03:49 +0000160 lint_filters = lint_filters or DEFAULT_LINT_FILTERS
danakj@chromium.org0ae71222016-01-11 19:37:11 +0000161 lint_filters.extend(BLACKLIST_LINT_FILTERS)
tfarina@chromium.orgb6795642014-12-12 00:03:49 +0000162 cpplint._SetFilters(','.join(lint_filters))
erg@google.com26970fa2009-11-17 18:07:32 +0000163
164 # We currently are more strict with normal code than unit tests; 4 and 5 are
165 # the verbosity level that would normally be passed to cpplint.py through
166 # --verbose=#. Hopefully, in the future, we can be more verbose.
167 files = [f.AbsoluteLocalPath() for f in
168 input_api.AffectedSourceFiles(source_file_filter)]
169 for file_name in files:
170 if _RE_IS_TEST.match(file_name):
171 level = 5
172 else:
173 level = 4
174
tfarina@chromium.orga62208f2015-02-25 03:23:11 +0000175 verbose_level = verbose_level or level
176 cpplint.ProcessFile(file_name, verbose_level)
erg@google.com26970fa2009-11-17 18:07:32 +0000177
178 if cpplint._cpplint_state.error_count > 0:
179 if input_api.is_committing:
180 res_type = output_api.PresubmitError
181 else:
182 res_type = output_api.PresubmitPromptWarning
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000183 result = [res_type('Changelist failed cpplint.py check.')]
erg@google.com26970fa2009-11-17 18:07:32 +0000184
185 return result
186
187
maruel@chromium.org3410d912009-06-09 20:56:16 +0000188def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000189 """Checks no '\r' (CR) character is in any source files."""
190 cr_files = []
maruel@chromium.org3410d912009-06-09 20:56:16 +0000191 for f in input_api.AffectedSourceFiles(source_file_filter):
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000192 if '\r' in input_api.ReadFile(f, 'rb'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000193 cr_files.append(f.LocalPath())
194 if cr_files:
195 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000196 'Found a CR character in these files:', items=cr_files)]
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000197 return []
198
199
200def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
201 """Checks the files ends with one and only one \n (LF)."""
202 eof_files = []
203 for f in input_api.AffectedSourceFiles(source_file_filter):
204 contents = input_api.ReadFile(f, 'rb')
205 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000206 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000207 eof_files.append(f.LocalPath())
208
209 if eof_files:
210 return [output_api.PresubmitPromptWarning(
211 'These files should end in one (and only one) newline character:',
212 items=eof_files)]
213 return []
214
215
216def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api,
217 source_file_filter=None):
218 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass.
219
220 It is faster because it is reading the file only once.
221 """
222 cr_files = []
223 eof_files = []
224 for f in input_api.AffectedSourceFiles(source_file_filter):
225 contents = input_api.ReadFile(f, 'rb')
226 if '\r' in contents:
227 cr_files.append(f.LocalPath())
228 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000229 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000230 eof_files.append(f.LocalPath())
231 outputs = []
232 if cr_files:
233 outputs.append(output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000234 'Found a CR character in these files:', items=cr_files))
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000235 if eof_files:
236 outputs.append(output_api.PresubmitPromptWarning(
237 'These files should end in one (and only one) newline character:',
238 items=eof_files))
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000239 return outputs
240
seanmccullough0b670442016-06-07 10:45:58 -0700241def CheckGenderNeutral(input_api, output_api, source_file_filter=None):
242 """Checks that there are no gendered pronouns in any of the text files to be
243 submitted.
244 """
245 gendered_re = input_api.re.compile(
246 '(^|\s|\(|\[)([Hh]e|[Hh]is|[Hh]ers?|[Hh]im|[Ss]he|[Gg]uys?)\\b')
247
248 errors = []
249 for f in input_api.AffectedFiles(include_deletes=False,
250 file_filter=source_file_filter):
251 for line_num, line in f.ChangedContents():
252 if gendered_re.search(line):
253 errors.append('%s (%d): %s' % (f.LocalPath(), line_num, line))
254
255 if len(errors):
256 return [output_api.PresubmitPromptWarning('Found a gendered pronoun in:',
257 long_text='\n'.join(errors))]
258 return []
259
260
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000261
chrisha@google.com267d6592012-06-19 19:23:31 +0000262def _ReportErrorFileAndLine(filename, line_num, dummy_line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000263 """Default error formatter for _FindNewViolationsOfRule."""
danakj@chromium.orgc5965ba2013-08-14 00:27:24 +0000264 return '%s:%s' % (filename, line_num)
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000265
266
267def _FindNewViolationsOfRule(callable_rule, input_api, source_file_filter=None,
268 error_formatter=_ReportErrorFileAndLine):
269 """Find all newly introduced violations of a per-line rule (a callable).
270
271 Arguments:
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000272 callable_rule: a callable taking a file extension and line of input and
273 returning True if the rule is satisfied and False if there was a problem.
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000274 input_api: object to enumerate the affected files.
275 source_file_filter: a filter to be passed to the input api.
276 error_formatter: a callable taking (filename, line_number, line) and
277 returning a formatted error string.
278
279 Returns:
280 A list of the newly-introduced violations reported by the rule.
281 """
282 errors = []
sail@chromium.org5538e022011-05-12 17:53:16 +0000283 for f in input_api.AffectedFiles(include_deletes=False,
284 file_filter=source_file_filter):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000285 # For speed, we do two passes, checking first the full file. Shelling out
286 # to the SCM to determine the changed region can be quite expensive on
287 # Win32. Assuming that most files will be kept problem-free, we can
288 # skip the SCM operations most of the time.
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000289 extension = str(f.LocalPath()).rsplit('.', 1)[-1]
290 if all(callable_rule(extension, line) for line in f.NewContents()):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000291 continue # No violation found in full text: can skip considering diff.
292
293 for line_num, line in f.ChangedContents():
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000294 if not callable_rule(extension, line):
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000295 errors.append(error_formatter(f.LocalPath(), line_num, line))
296
297 return errors
298
299
maruel@chromium.org3410d912009-06-09 20:56:16 +0000300def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000301 """Checks that there are no tab characters in any of the text files to be
302 submitted.
303 """
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000304 # In addition to the filter, make sure that makefiles are blacklisted.
305 if not source_file_filter:
306 # It's the default filter.
307 source_file_filter = input_api.FilterSourceFile
308 def filter_more(affected_file):
cmp@chromium.orgcb5e57c2012-04-06 19:50:15 +0000309 basename = input_api.os_path.basename(affected_file.LocalPath())
310 return (not (basename in ('Makefile', 'makefile') or
311 basename.endswith('.mk')) and
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000312 source_file_filter(affected_file))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000313
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000314 tabs = _FindNewViolationsOfRule(lambda _, line : '\t' not in line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000315 input_api, filter_more)
316
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000317 if tabs:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000318 return [output_api.PresubmitPromptWarning('Found a tab character in:',
319 long_text='\n'.join(tabs))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000320 return []
321
322
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000323def CheckChangeTodoHasOwner(input_api, output_api, source_file_filter=None):
324 """Checks that the user didn't add TODO(name) without an owner."""
325
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000326 unowned_todo = input_api.re.compile('TO''DO[^(]')
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000327 errors = _FindNewViolationsOfRule(lambda _, x : not unowned_todo.search(x),
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000328 input_api, source_file_filter)
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +0000329 errors = ['Found TO''DO with no owner in ' + x for x in errors]
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000330 if errors:
331 return [output_api.PresubmitPromptWarning('\n'.join(errors))]
estade@chromium.orgfdcc9f72011-02-07 22:25:07 +0000332 return []
333
334
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000335def CheckChangeHasNoStrayWhitespace(input_api, output_api,
336 source_file_filter=None):
337 """Checks that there is no stray whitespace at source lines end."""
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000338 errors = _FindNewViolationsOfRule(lambda _, line : line.rstrip() == line,
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000339 input_api, source_file_filter)
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000340 if errors:
341 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000342 'Found line ending with white spaces in:',
343 long_text='\n'.join(errors))]
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000344 return []
345
346
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +0000347def CheckLongLines(input_api, output_api, maxlen, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000348 """Checks that there aren't any lines longer than maxlen characters in any of
349 the text files to be submitted.
350 """
dpranke@chromium.orge3b1c3d2012-10-20 22:28:14 +0000351 maxlens = {
352 'java': 100,
torne@chromium.org0ecad9c2013-02-15 16:35:16 +0000353 # This is specifically for Android's handwritten makefiles (Android.mk).
354 'mk': 200,
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000355 '': maxlen,
356 }
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000357
erikchen@google.com12816082013-12-03 02:04:20 +0000358 # Language specific exceptions to max line length.
359 # '.h' is considered an obj-c file extension, since OBJC_EXCEPTIONS are a
360 # superset of CPP_EXCEPTIONS.
361 CPP_FILE_EXTS = ('c', 'cc')
362 CPP_EXCEPTIONS = ('#define', '#endif', '#if', '#include', '#pragma')
Dave Schuyler6eea7c22017-03-03 18:46:25 -0800363 HTML_FILE_EXTS = ('html',)
364 HTML_EXCEPTIONS = ('<link',)
erikchen@google.com12816082013-12-03 02:04:20 +0000365 JAVA_FILE_EXTS = ('java',)
366 JAVA_EXCEPTIONS = ('import ', 'package ')
dbeam@chromium.org5a2af6d2015-10-08 17:52:29 +0000367 JS_FILE_EXTS = ('js',)
368 JS_EXCEPTIONS = ("GEN('#include",)
erikchen@google.com12816082013-12-03 02:04:20 +0000369 OBJC_FILE_EXTS = ('h', 'm', 'mm')
370 OBJC_EXCEPTIONS = ('#define', '#endif', '#if', '#import', '#include',
371 '#pragma')
dbeam@chromium.org5a2af6d2015-10-08 17:52:29 +0000372 PY_FILE_EXTS = ('py',)
aiolos@chromium.org84033cc2015-07-16 01:27:15 +0000373 PY_EXCEPTIONS = ('import', 'from')
erikchen@google.com12816082013-12-03 02:04:20 +0000374
375 LANGUAGE_EXCEPTIONS = [
376 (CPP_FILE_EXTS, CPP_EXCEPTIONS),
Dave Schuyler6eea7c22017-03-03 18:46:25 -0800377 (HTML_FILE_EXTS, HTML_EXCEPTIONS),
erikchen@google.com12816082013-12-03 02:04:20 +0000378 (JAVA_FILE_EXTS, JAVA_EXCEPTIONS),
dbeam@chromium.org5a2af6d2015-10-08 17:52:29 +0000379 (JS_FILE_EXTS, JS_EXCEPTIONS),
erikchen@google.com12816082013-12-03 02:04:20 +0000380 (OBJC_FILE_EXTS, OBJC_EXCEPTIONS),
aiolos@chromium.org84033cc2015-07-16 01:27:15 +0000381 (PY_FILE_EXTS, PY_EXCEPTIONS),
erikchen@google.com12816082013-12-03 02:04:20 +0000382 ]
sivachandra@chromium.org270db5c2012-10-09 22:38:44 +0000383
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000384 def no_long_lines(file_extension, line):
erikchen@google.com12816082013-12-03 02:04:20 +0000385 # Check for language specific exceptions.
386 if any(file_extension in exts and line.startswith(exceptions)
387 for exts, exceptions in LANGUAGE_EXCEPTIONS):
sivachandra@chromium.org270db5c2012-10-09 22:38:44 +0000388 return True
389
bulach@chromium.orgbfffd452012-02-22 01:13:29 +0000390 file_maxlen = maxlens.get(file_extension, maxlens[''])
391 # Stupidly long symbols that needs to be worked around if takes 66% of line.
392 long_symbol = file_maxlen * 2 / 3
393 # Hard line length limit at 50% more.
394 extra_maxlen = file_maxlen * 3 / 2
395
396 line_len = len(line)
397 if line_len <= file_maxlen:
maruel@chromium.orgeba64622011-06-20 18:26:48 +0000398 return True
399
dtu@chromium.org03f0c532015-03-27 22:22:07 +0000400 # Allow long URLs of any length.
treib@chromium.orgde3ee902014-07-28 14:23:11 +0000401 if any((url in line) for url in ('file://', 'http://', 'https://')):
402 return True
403
qyearsley12fa6ff2016-08-24 09:18:40 -0700404 # If 'line-too-long' is explicitly suppressed for the line, any length is
nednguyen@google.com6df90f32015-12-01 20:14:33 +0000405 # acceptable.
406 if 'pylint: disable=line-too-long' in line and file_extension == 'py':
407 return True
408
dtu@chromium.org03f0c532015-03-27 22:22:07 +0000409 if line_len > extra_maxlen:
410 return False
411
treib@chromium.orgde3ee902014-07-28 14:23:11 +0000412 if 'url(' in line and file_extension == 'css':
413 return True
414
dbeam@chromium.orgb5ccc9b2014-09-23 00:42:22 +0000415 if '<include' in line and file_extension in ('css', 'html', 'js'):
416 return True
417
treib@chromium.orgde3ee902014-07-28 14:23:11 +0000418 return input_api.re.match(
419 r'.*[A-Za-z][A-Za-z_0-9]{%d,}.*' % long_symbol, line)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000420
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000421 def format_error(filename, line_num, line):
422 return '%s, line %s, %s chars' % (filename, line_num, len(line))
423
424 errors = _FindNewViolationsOfRule(no_long_lines, input_api,
425 source_file_filter,
426 error_formatter=format_error)
427 if errors:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000428 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen
nick@chromium.orge06cb4e2011-04-23 01:20:38 +0000429 return [output_api.PresubmitPromptWarning(msg, items=errors[:5])]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000430 else:
431 return []
432
433
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000434def CheckLicense(input_api, output_api, license_re, source_file_filter=None,
maruel@chromium.org71626852010-11-03 13:14:25 +0000435 accept_empty_files=True):
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000436 """Verifies the license header.
437 """
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000438 license_re = input_api.re.compile(license_re, input_api.re.MULTILINE)
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000439 bad_files = []
440 for f in input_api.AffectedSourceFiles(source_file_filter):
441 contents = input_api.ReadFile(f, 'rb')
maruel@chromium.org71626852010-11-03 13:14:25 +0000442 if accept_empty_files and not contents:
443 continue
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000444 if not license_re.search(contents):
445 bad_files.append(f.LocalPath())
446 if bad_files:
phajdan.jr@chromium.orge27eb7e2015-11-16 12:47:53 +0000447 return [output_api.PresubmitPromptWarning(
bradnelson@google.com393460b2011-03-29 01:20:26 +0000448 'License must match:\n%s\n' % license_re.pattern +
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000449 'Found a bad license header in these files:', items=bad_files)]
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000450 return []
451
452
maruel@chromium.org3410d912009-06-09 20:56:16 +0000453### Other checks
454
455def CheckDoNotSubmit(input_api, output_api):
456 return (
457 CheckDoNotSubmitInDescription(input_api, output_api) +
458 CheckDoNotSubmitInFiles(input_api, output_api)
459 )
460
461
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000462def CheckTreeIsOpen(input_api, output_api,
463 url=None, closed=None, json_url=None):
464 """Check whether to allow commit without prompt.
465
466 Supports two styles:
467 1. Checks that an url's content doesn't match a regexp that would mean that
468 the tree is closed. (old)
469 2. Check the json_url to decide whether to allow commit without prompt.
470 Args:
471 input_api: input related apis.
472 output_api: output related apis.
473 url: url to use for regex based tree status.
474 closed: regex to match for closed status.
475 json_url: url to download json style status.
476 """
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000477 if not input_api.is_committing:
478 return []
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000479 try:
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000480 if json_url:
481 connection = input_api.urllib2.urlopen(json_url)
482 status = input_api.json.loads(connection.read())
483 connection.close()
484 if not status['can_commit_freely']:
485 short_text = 'Tree state is: ' + status['general_state']
486 long_text = status['message'] + '\n' + json_url
487 return [output_api.PresubmitError(short_text, long_text=long_text)]
488 else:
489 # TODO(bradnelson): drop this once all users are gone.
490 connection = input_api.urllib2.urlopen(url)
491 status = connection.read()
492 connection.close()
493 if input_api.re.match(closed, status):
494 long_text = status + '\n' + url
495 return [output_api.PresubmitError('The tree is closed.',
496 long_text=long_text)]
rohitrao@chromium.orgd490ee82012-02-06 19:31:33 +0000497 except IOError as e:
498 return [output_api.PresubmitError('Error fetching tree status.',
499 long_text=str(e))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000500 return []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000501
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000502def GetUnitTestsInDirectory(
smut@google.comac296202014-04-24 21:47:17 +0000503 input_api, output_api, directory, whitelist=None, blacklist=None, env=None):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000504 """Lists all files in a directory and runs them. Doesn't recurse.
505
nick@chromium.orgff526192013-06-10 19:30:26 +0000506 It's mainly a wrapper for RunUnitTests. Use whitelist and blacklist to filter
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000507 tests accordingly.
508 """
509 unit_tests = []
510 test_path = input_api.os_path.abspath(
511 input_api.os_path.join(input_api.PresubmitLocalPath(), directory))
512
513 def check(filename, filters):
514 return any(True for i in filters if input_api.re.match(i, filename))
515
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000516 to_run = found = 0
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000517 for filename in input_api.os_listdir(test_path):
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000518 found += 1
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000519 fullpath = input_api.os_path.join(test_path, filename)
520 if not input_api.os_path.isfile(fullpath):
521 continue
522 if whitelist and not check(filename, whitelist):
523 continue
524 if blacklist and check(filename, blacklist):
525 continue
526 unit_tests.append(input_api.os_path.join(directory, filename))
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000527 to_run += 1
anatoly techtonikbdcdc592017-03-29 17:00:13 +0300528 input_api.logging.debug('Found %d files, running %d unit tests'
529 % (found, to_run))
maruel@chromium.orgfae707b2011-09-15 18:57:58 +0000530 if not to_run:
531 return [
532 output_api.PresubmitPromptWarning(
533 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
534 % (found, whitelist, blacklist, directory))
535 ]
smut@google.comac296202014-04-24 21:47:17 +0000536 return GetUnitTests(input_api, output_api, unit_tests, env)
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000537
538
smut@google.comac296202014-04-24 21:47:17 +0000539def GetUnitTests(input_api, output_api, unit_tests, env=None):
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000540 """Runs all unit tests in a directory.
541
542 On Windows, sys.executable is used for unit tests ending with ".py".
543 """
544 # We don't want to hinder users from uploading incomplete patches.
545 if input_api.is_committing:
546 message_type = output_api.PresubmitError
547 else:
548 message_type = output_api.PresubmitPromptWarning
549
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000550 results = []
551 for unit_test in unit_tests:
552 cmd = []
553 if input_api.platform == 'win32' and unit_test.endswith('.py'):
554 # Windows needs some help.
555 cmd = [input_api.python_executable]
556 cmd.append(unit_test)
maruel@chromium.org899e1c12011-04-07 17:03:18 +0000557 if input_api.verbose:
maruel@chromium.org6c7723e2011-04-12 19:04:55 +0000558 cmd.append('--verbose')
smut@google.comac296202014-04-24 21:47:17 +0000559 kwargs = {'cwd': input_api.PresubmitLocalPath()}
560 if env:
561 kwargs['env'] = env
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000562 results.append(input_api.Command(
563 name=unit_test,
564 cmd=cmd,
smut@google.comac296202014-04-24 21:47:17 +0000565 kwargs=kwargs,
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000566 message=message_type))
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000567 return results
568
nick@chromium.orgff526192013-06-10 19:30:26 +0000569
agable@chromium.org40a3d0b2014-05-15 01:59:16 +0000570def GetUnitTestsRecursively(input_api, output_api, directory,
571 whitelist, blacklist):
572 """Gets all files in the directory tree (git repo) that match the whitelist.
573
574 Restricts itself to only find files within the Change's source repo, not
575 dependencies.
576 """
577 def check(filename):
578 return (any(input_api.re.match(f, filename) for f in whitelist) and
579 not any(input_api.re.match(f, filename) for f in blacklist))
580
581 tests = []
582
583 to_run = found = 0
584 for filepath in input_api.change.AllFiles(directory):
585 found += 1
586 if check(filepath):
587 to_run += 1
588 tests.append(filepath)
589 input_api.logging.debug('Found %d files, running %d' % (found, to_run))
590 if not to_run:
591 return [
592 output_api.PresubmitPromptWarning(
593 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
594 % (found, whitelist, blacklist, directory))
595 ]
596
597 return GetUnitTests(input_api, output_api, tests)
598
599
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000600def GetPythonUnitTests(input_api, output_api, unit_tests):
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000601 """Run the unit tests out of process, capture the output and use the result
602 code to determine success.
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000603
604 DEPRECATED.
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000605 """
maruel@chromium.orgd7dccf52009-06-06 18:51:58 +0000606 # We don't want to hinder users from uploading incomplete patches.
607 if input_api.is_committing:
608 message_type = output_api.PresubmitError
609 else:
610 message_type = output_api.PresubmitNotifyResult
maruel@chromium.org0e766052011-04-06 13:32:51 +0000611 results = []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000612 for unit_test in unit_tests:
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000613 # Run the unit tests out of process. This is because some unit tests
614 # stub out base libraries and don't clean up their mess. It's too easy to
615 # get subtle bugs.
616 cwd = None
617 env = None
618 unit_test_name = unit_test
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000619 # 'python -m test.unit_test' doesn't work. We need to change to the right
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000620 # directory instead.
621 if '.' in unit_test:
622 # Tests imported in submodules (subdirectories) assume that the current
623 # directory is in the PYTHONPATH. Manually fix that.
624 unit_test = unit_test.replace('.', '/')
625 cwd = input_api.os_path.dirname(unit_test)
626 unit_test = input_api.os_path.basename(unit_test)
627 env = input_api.environ.copy()
kbr@google.comab318592009-09-04 00:54:55 +0000628 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
629 backpath = [
630 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
631 ]
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000632 if env.get('PYTHONPATH'):
633 backpath.append(env.get('PYTHONPATH'))
ukai@chromium.orga301f1f2009-08-05 10:37:33 +0000634 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000635 cmd = [input_api.python_executable, '-m', '%s' % unit_test]
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000636 results.append(input_api.Command(
637 name=unit_test_name,
638 cmd=cmd,
639 kwargs={'env': env, 'cwd': cwd},
640 message=message_type))
maruel@chromium.org0e766052011-04-06 13:32:51 +0000641 return results
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000642
643
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000644def RunUnitTestsInDirectory(input_api, *args, **kwargs):
645 """Run tests in a directory serially.
646
647 For better performance, use GetUnitTestsInDirectory and then
648 pass to input_api.RunTests.
649 """
650 return input_api.RunTests(
651 GetUnitTestsInDirectory(input_api, *args, **kwargs), False)
652
653
654def RunUnitTests(input_api, *args, **kwargs):
655 """Run tests serially.
656
657 For better performance, use GetUnitTests and then pass to
658 input_api.RunTests.
659 """
660 return input_api.RunTests(GetUnitTests(input_api, *args, **kwargs), False)
661
662
663def RunPythonUnitTests(input_api, *args, **kwargs):
664 """Run python tests in a directory serially.
665
666 DEPRECATED
667 """
668 return input_api.RunTests(
669 GetPythonUnitTests(input_api, *args, **kwargs), False)
670
671
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000672def _FetchAllFiles(input_api, white_list, black_list):
673 """Hack to fetch all files."""
674 # We cannot use AffectedFiles here because we want to test every python
675 # file on each single python change. It's because a change in a python file
676 # can break another unmodified file.
677 # Use code similar to InputApi.FilterSourceFile()
678 def Find(filepath, filters):
anatoly techtonikbdcdc592017-03-29 17:00:13 +0300679 if input_api.platform == 'win32':
680 filepath = filepath.replace('\\', '/')
681
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000682 for item in filters:
683 if input_api.re.match(item, filepath):
684 return True
685 return False
686
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000687 files = []
688 path_len = len(input_api.PresubmitLocalPath())
maruel@chromium.org2b5ce562011-03-31 16:15:44 +0000689 for dirpath, dirnames, filenames in input_api.os_walk(
690 input_api.PresubmitLocalPath()):
maruel@chromium.org5d0dc432011-01-03 02:40:37 +0000691 # Passes dirnames in black list to speed up search.
692 for item in dirnames[:]:
693 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
694 if Find(filepath, black_list):
695 dirnames.remove(item)
696 for item in filenames:
697 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
698 if Find(filepath, white_list) and not Find(filepath, black_list):
699 files.append(filepath)
700 return files
701
702
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000703def GetPylint(input_api, output_api, white_list=None, black_list=None,
dtu@chromium.orgf23524d2016-01-27 20:08:49 +0000704 disabled_warnings=None, extra_paths_list=None, pylintrc=None):
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000705 """Run pylint on python files.
706
chrisha@google.com267d6592012-06-19 19:23:31 +0000707 The default white_list enforces looking only at *.py files.
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000708 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +0000709 white_list = tuple(white_list or ('.*\.py$',))
710 black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000711 extra_paths_list = extra_paths_list or []
712
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000713 if input_api.is_committing:
714 error_type = output_api.PresubmitError
715 else:
716 error_type = output_api.PresubmitPromptWarning
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000717
718 # Only trigger if there is at least one python file affected.
ilevy@chromium.org36576332013-01-08 03:16:15 +0000719 def rel_path(regex):
720 """Modifies a regex for a subject to accept paths relative to root."""
chrisha@chromium.org40e5d3d2013-01-18 17:46:57 +0000721 def samefile(a, b):
722 # Default implementation for platforms lacking os.path.samefile
723 # (like Windows).
724 return input_api.os_path.abspath(a) == input_api.os_path.abspath(b)
725 samefile = getattr(input_api.os_path, 'samefile', samefile)
726 if samefile(input_api.PresubmitLocalPath(),
727 input_api.change.RepositoryRoot()):
ilevy@chromium.orgdd4e9312013-01-15 03:22:04 +0000728 return regex
chrisha@chromium.org40e5d3d2013-01-18 17:46:57 +0000729
ilevy@chromium.org36576332013-01-08 03:16:15 +0000730 prefix = input_api.os_path.join(input_api.os_path.relpath(
731 input_api.PresubmitLocalPath(), input_api.change.RepositoryRoot()), '')
732 return input_api.re.escape(prefix) + regex
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000733 src_filter = lambda x: input_api.FilterSourceFile(
734 x, map(rel_path, white_list), map(rel_path, black_list))
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000735 if not input_api.AffectedSourceFiles(src_filter):
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000736 input_api.logging.info('Skipping pylint: no matching changes.')
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +0000737 return []
738
agable@chromium.org327d72b2015-04-21 20:22:50 +0000739 if pylintrc is not None:
740 pylintrc = input_api.os_path.join(input_api.PresubmitLocalPath(), pylintrc)
741 else:
742 pylintrc = input_api.os_path.join(_HERE, 'pylintrc')
dtu@chromium.orgf23524d2016-01-27 20:08:49 +0000743 extra_args = ['--rcfile=%s' % pylintrc]
maruel@chromium.orgff9a2172012-04-24 16:55:32 +0000744 if disabled_warnings:
745 extra_args.extend(['-d', ','.join(disabled_warnings)])
746
chrisha@google.com267d6592012-06-19 19:23:31 +0000747 files = _FetchAllFiles(input_api, white_list, black_list)
748 if not files:
maruel@chromium.orgfca53392010-12-21 18:42:57 +0000749 return []
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000750 files.sort()
chrisha@google.com267d6592012-06-19 19:23:31 +0000751
csharp@chromium.org40395342013-02-21 14:57:23 +0000752 input_api.logging.info('Running pylint on %d files', len(files))
753 input_api.logging.debug('Running pylint on: %s', files)
chrisha@google.com267d6592012-06-19 19:23:31 +0000754 # Copy the system path to the environment so pylint can find the right
755 # imports.
756 env = input_api.environ.copy()
757 import sys
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000758 env['PYTHONPATH'] = input_api.os_path.pathsep.join(
robertshield@chromium.orga2873932013-02-20 18:08:46 +0000759 extra_paths_list + sys.path).encode('utf8')
chrisha@google.com267d6592012-06-19 19:23:31 +0000760
iannucci@chromium.orgd61a4952015-07-01 23:21:26 +0000761 def GetPylintCmd(flist, extra, parallel):
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000762 # Windows needs help running python files so we explicitly specify
763 # the interpreter to use. It also has limitations on the size of
764 # the command-line, so we pass arguments via a pipe.
iannucci@chromium.org0af3bb32015-06-12 20:44:35 +0000765 cmd = [input_api.python_executable,
766 input_api.os_path.join(_HERE, 'third_party', 'pylint.py'),
767 '--args-on-stdin']
iannucci@chromium.orgd61a4952015-07-01 23:21:26 +0000768 if len(flist) == 1:
769 description = flist[0]
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000770 else:
iannucci@chromium.orgd61a4952015-07-01 23:21:26 +0000771 description = '%s files' % len(flist)
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000772
iannucci@chromium.orgd61a4952015-07-01 23:21:26 +0000773 args = extra_args[:]
iannucci@chromium.org0af3bb32015-06-12 20:44:35 +0000774 if extra:
iannucci@chromium.orgd61a4952015-07-01 23:21:26 +0000775 args.extend(extra)
iannucci@chromium.org0af3bb32015-06-12 20:44:35 +0000776 description += ' using %s' % (extra,)
777 if parallel:
iannucci@chromium.orgd61a4952015-07-01 23:21:26 +0000778 args.append('--jobs=%s' % input_api.cpu_count)
iannucci@chromium.org0af3bb32015-06-12 20:44:35 +0000779 description += ' on %d cores' % input_api.cpu_count
780
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000781 return input_api.Command(
782 name='Pylint (%s)' % description,
iannucci@chromium.org0af3bb32015-06-12 20:44:35 +0000783 cmd=cmd,
iannucci@chromium.orgd61a4952015-07-01 23:21:26 +0000784 kwargs={'env': env, 'stdin': '\n'.join(args + flist)},
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000785 message=error_type)
chrisha@chromium.org1b129e52012-06-22 17:08:11 +0000786
sbc@chromium.org7fc75ca2012-09-21 20:14:21 +0000787 # Always run pylint and pass it all the py files at once.
788 # Passing py files one at time is slower and can produce
789 # different results. input_api.verbose used to be used
790 # to enable this behaviour but differing behaviour in
791 # verbose mode is not desirable.
ilevy@chromium.org7b677f72013-01-07 18:49:26 +0000792 # Leave this unreachable code in here so users can make
793 # a quick local edit to diagnose pylint issues more
794 # easily.
sbc@chromium.org7fc75ca2012-09-21 20:14:21 +0000795 if True:
iannucci@chromium.org0af3bb32015-06-12 20:44:35 +0000796 # pylint's cycle detection doesn't work in parallel, so spawn a second,
797 # single-threaded job for just that check.
iannucci@chromium.orgd61a4952015-07-01 23:21:26 +0000798
799 # Some PRESUBMITs explicitly mention cycle detection.
800 if not any('R0401' in a or 'cyclic-import' in a for a in extra_args):
801 return [
802 GetPylintCmd(files, ["--disable=cyclic-import"], True),
803 GetPylintCmd(files, ["--disable=all", "--enable=cyclic-import"], False)
804 ]
805 else:
806 return [ GetPylintCmd(files, [], True) ]
807
chrisha@google.com267d6592012-06-19 19:23:31 +0000808 else:
iannucci@chromium.orgd61a4952015-07-01 23:21:26 +0000809 return map(lambda x: GetPylintCmd([x], [], 1), files)
ilevy@chromium.orgbc117312013-04-20 03:57:56 +0000810
811
812def RunPylint(input_api, *args, **kwargs):
813 """Legacy presubmit function.
814
815 For better performance, get all tests and then pass to
816 input_api.RunTests.
817 """
818 return input_api.RunTests(GetPylint(input_api, *args, **kwargs), False)
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000819
maruel@chromium.orgade9c592011-04-07 15:59:11 +0000820
tandrii@chromium.org5f30f2f2016-04-27 13:19:57 +0000821def CheckRietveldTryJobExecution(dummy_input_api, output_api,
chrisha@google.com267d6592012-06-19 19:23:31 +0000822 dummy_host_url, dummy_platforms,
823 dummy_owner):
tandrii@chromium.org5f30f2f2016-04-27 13:19:57 +0000824 return [
825 output_api.PresubmitNotifyResult(
826 'CheckRietveldTryJobExecution is deprecated, please remove it.')
827 ]
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000828
829
830def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
831 ignored):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000832 try:
833 connection = input_api.urllib2.urlopen(url)
834 raw_data = connection.read()
835 connection.close()
836 except IOError:
837 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)]
838
839 try:
840 data = input_api.json.loads(raw_data)
841 except ValueError:
842 return [output_api.PresubmitNotifyResult('Received malformed json while '
843 'looking up buildbot status')]
844
845 out = []
846 for (builder_name, builder) in data.iteritems():
847 if builder_name in ignored:
848 continue
maruel@chromium.orgcf1982c2010-10-04 15:08:28 +0000849 if builder.get('state', '') == 'offline':
850 continue
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000851 pending_builds_len = len(builder.get('pending_builds', []))
852 if pending_builds_len > max_pendings:
853 out.append('%s has %d build(s) pending' %
854 (builder_name, pending_builds_len))
855 if out:
856 return [output_api.PresubmitPromptWarning(
857 'Build(s) pending. It is suggested to wait that no more than %d '
858 'builds are pending.' % max_pendings,
859 long_text='\n'.join(out))]
860 return []
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000861
862
dpranke@chromium.orgbce925d2015-01-16 22:38:38 +0000863def CheckOwners(input_api, output_api, source_file_filter=None):
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000864 if input_api.is_committing:
865 if input_api.tbr:
866 return [output_api.PresubmitNotifyResult(
867 '--tbr was specified, skipping OWNERS check')]
Clemens Hammacherd0c226a2017-01-16 14:09:52 +0100868 needed = 'LGTM from an OWNER'
869 output_fn = output_api.PresubmitError
rmistry@google.com5fc6c8c2015-04-16 16:38:43 +0000870 if input_api.change.issue:
tandrii@chromium.org9dea2ac2016-04-28 06:26:20 +0000871 if input_api.dry_run:
Clemens Hammacherd0c226a2017-01-16 14:09:52 +0100872 output_fn = lambda text: output_api.PresubmitNotifyResult(
873 'This is a dry run, but these failures would be reported on ' +
874 'commit:\n' + text)
rmistry@google.com5fc6c8c2015-04-16 16:38:43 +0000875 else:
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000876 return [output_api.PresubmitError("OWNERS check failed: this change has "
877 "no Rietveld issue number, so we can't check it for approvals.")]
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000878 else:
879 needed = 'OWNER reviewers'
Clemens Hammacherd0c226a2017-01-16 14:09:52 +0100880 output_fn = output_api.PresubmitNotifyResult
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000881
dpranke@chromium.orgadd5df42011-03-08 23:04:01 +0000882 affected_files = set([f.LocalPath() for f in
Dirk Pranke3c86cee2017-01-23 22:02:06 -0800883 input_api.change.AffectedFiles(file_filter=source_file_filter)])
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000884
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000885 owners_db = input_api.owners_db
tandrii@chromium.org830dc0b2016-05-09 06:26:34 +0000886 owner_email, reviewers = GetCodereviewOwnerAndReviewers(
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000887 input_api,
888 owners_db.email_regexp,
889 approval_needed=input_api.is_committing)
890
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000891 owner_email = owner_email or input_api.change.author_email
892
dpranke@chromium.orgbce925d2015-01-16 22:38:38 +0000893 if owner_email:
dpranke@chromium.org4c7ce992013-03-06 17:15:40 +0000894 reviewers_plus_owner = set([owner_email]).union(reviewers)
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000895 missing_files = owners_db.files_not_covered_by(affected_files,
dpranke@chromium.orgf6ddfa42013-03-05 21:06:03 +0000896 reviewers_plus_owner)
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000897 else:
dpranke@chromium.orgdbf8b4e2013-02-28 19:24:16 +0000898 missing_files = owners_db.files_not_covered_by(affected_files, reviewers)
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000899
dpranke@chromium.org6b1e3ee2013-02-23 00:06:38 +0000900 if missing_files:
zork@chromium.org046e1752012-05-07 05:56:12 +0000901 output_list = [
Clemens Hammacherd0c226a2017-01-16 14:09:52 +0100902 output_fn('Missing %s for these files:\n %s' %
903 (needed, '\n '.join(sorted(missing_files))))]
zork@chromium.org046e1752012-05-07 05:56:12 +0000904 if not input_api.is_committing:
dpranke@chromium.org31b42c02013-09-19 19:24:15 +0000905 suggested_owners = owners_db.reviewers_for(missing_files, owner_email)
Clemens Hammacherd0c226a2017-01-16 14:09:52 +0100906 output_list.append(output_fn('Suggested OWNERS: ' +
scheib@chromium.org93276ab2013-10-14 23:55:32 +0000907 '(Use "git-cl owners" to interactively select owners.)\n %s' %
908 ('\n '.join(suggested_owners or []))))
zork@chromium.org046e1752012-05-07 05:56:12 +0000909 return output_list
dpranke@chromium.org2a009622011-03-01 02:43:31 +0000910
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000911 if input_api.is_committing and not reviewers:
Clemens Hammacherd0c226a2017-01-16 14:09:52 +0100912 return [output_fn('Missing LGTM from someone other than %s' % owner_email)]
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000913 return []
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000914
tandrii@chromium.org830dc0b2016-05-09 06:26:34 +0000915def GetCodereviewOwnerAndReviewers(input_api, email_regexp, approval_needed):
tandrii@chromium.org37b07a72016-04-29 16:42:28 +0000916 """Return the owner and reviewers of a change, if any.
917
918 If approval_needed is True, only reviewers who have approved the change
919 will be returned.
920 """
tandrii@chromium.org830dc0b2016-05-09 06:26:34 +0000921 # Rietveld is default.
922 func = _RietveldOwnerAndReviewers
923 if input_api.gerrit:
924 func = _GerritOwnerAndReviewers
925 return func(input_api, email_regexp, approval_needed)
tandrii@chromium.org37b07a72016-04-29 16:42:28 +0000926
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000927
ilevy@chromium.org1fa5cb92012-12-05 04:04:42 +0000928def _GetRietveldIssueProps(input_api, messages):
929 """Gets the issue properties from rietveld."""
930 issue = input_api.change.issue
931 if issue and input_api.rietveld:
932 return input_api.rietveld.get_issue_properties(
933 issue=int(issue), messages=messages)
934
935
isherman@chromium.orgb5cded62014-03-25 17:47:57 +0000936def _ReviewersFromChange(change):
937 """Return the reviewers specified in the |change|, if any."""
938 reviewers = set()
939 if change.R:
940 reviewers.update(set([r.strip() for r in change.R.split(',')]))
941 if change.TBR:
942 reviewers.update(set([r.strip() for r in change.TBR.split(',')]))
isherman@chromium.org103ae032014-04-09 09:06:19 +0000943
944 # Drop reviewers that aren't specified in email address format.
945 return set(reviewer for reviewer in reviewers if '@' in reviewer)
isherman@chromium.orgb5cded62014-03-25 17:47:57 +0000946
947
tandrii@chromium.org37b07a72016-04-29 16:42:28 +0000948def _match_reviewer_email(r, owner_email, email_regexp):
949 return email_regexp.match(r) and r != owner_email
950
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000951def _RietveldOwnerAndReviewers(input_api, email_regexp, approval_needed=False):
952 """Return the owner and reviewers of a change, if any.
953
954 If approval_needed is True, only reviewers who have approved the change
955 will be returned.
956 """
ilevy@chromium.org1fa5cb92012-12-05 04:04:42 +0000957 issue_props = _GetRietveldIssueProps(input_api, True)
958 if not issue_props:
tandrii@chromium.org830dc0b2016-05-09 06:26:34 +0000959 return None, (set() if approval_needed else
960 _ReviewersFromChange(input_api.change))
maruel@chromium.orge4067ab2011-06-03 01:07:35 +0000961
pam@chromium.orgf46aed92012-03-08 09:18:17 +0000962 if not approval_needed:
963 return issue_props['owner_email'], set(issue_props['reviewers'])
964
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000965 owner_email = issue_props['owner_email']
dpranke@chromium.org3e331bd2011-03-24 23:13:04 +0000966
maruel@chromium.org80941c22011-05-30 20:14:18 +0000967 messages = issue_props.get('messages', [])
968 approvers = set(
969 m['sender'] for m in messages
tandrii@chromium.org37b07a72016-04-29 16:42:28 +0000970 if m.get('approval') and _match_reviewer_email(m['sender'], owner_email,
971 email_regexp))
maruel@chromium.org80941c22011-05-30 20:14:18 +0000972 return owner_email, approvers
dpranke@chromium.org627ea672011-03-11 23:29:03 +0000973
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000974
tandrii@chromium.org37b07a72016-04-29 16:42:28 +0000975def _GerritOwnerAndReviewers(input_api, email_regexp, approval_needed=False):
976 """Return the owner and reviewers of a change, if any.
977
978 If approval_needed is True, only reviewers who have approved the change
979 will be returned.
tandrii@chromium.org37b07a72016-04-29 16:42:28 +0000980 """
981 issue = input_api.change.issue
982 if not issue:
tandrii@chromium.org830dc0b2016-05-09 06:26:34 +0000983 return None, (set() if approval_needed else
984 _ReviewersFromChange(input_api.change))
tandrii@chromium.org37b07a72016-04-29 16:42:28 +0000985
986 owner_email = input_api.gerrit.GetChangeOwner(issue)
987 reviewers = set(
988 r for r in input_api.gerrit.GetChangeReviewers(issue, approval_needed)
989 if _match_reviewer_email(r, owner_email, email_regexp))
tandrii81665dc2016-08-29 09:16:19 -0700990 input_api.logging.debug('owner: %s; approvals given by: %s',
991 owner_email, ', '.join(sorted(reviewers)))
tandrii@chromium.org37b07a72016-04-29 16:42:28 +0000992 return owner_email, reviewers
993
994
bradnelson@google.com56e48bc2011-03-24 20:51:21 +0000995def _CheckConstNSObject(input_api, output_api, source_file_filter):
996 """Checks to make sure no objective-c files have |const NSSomeClass*|."""
mark@chromium.orgedf744d2011-03-28 16:45:34 +0000997 pattern = input_api.re.compile(
dominik.rottsches@intel.com34a92d92015-03-25 07:36:05 +0000998 r'(?<!reinterpret_cast<)'
999 r'const\s+NS(?!(Point|Range|Rect|Size)\s*\*)\w*\s*\*')
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001000
1001 def objective_c_filter(f):
1002 return (source_file_filter(f) and
mark@chromium.orgedf744d2011-03-28 16:45:34 +00001003 input_api.os_path.splitext(f.LocalPath())[1] in ('.h', '.m', '.mm'))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001004
1005 files = []
1006 for f in input_api.AffectedSourceFiles(objective_c_filter):
1007 contents = input_api.ReadFile(f)
1008 if pattern.search(contents):
1009 files.append(f)
1010
1011 if files:
1012 if input_api.is_committing:
1013 res_type = output_api.PresubmitPromptWarning
1014 else:
1015 res_type = output_api.PresubmitNotifyResult
1016 return [ res_type('|const NSClass*| is wrong, see ' +
1017 'http://dev.chromium.org/developers/clang-mac',
1018 files) ]
1019 return []
1020
1021
bauerb@chromium.org4cea01e2012-03-20 19:49:05 +00001022def CheckSingletonInHeaders(input_api, output_api, source_file_filter=None):
glider@chromium.orgc3617f32015-02-19 16:33:33 +00001023 """Deprecated, must be removed."""
1024 return [
1025 output_api.PresubmitNotifyResult(
1026 'CheckSingletonInHeaders is deprecated, please remove it.')
1027 ]
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001028
1029
1030def PanProjectChecks(input_api, output_api,
1031 excluded_paths=None, text_files=None,
bradnelson@google.comd57771d2011-03-31 19:18:32 +00001032 license_header=None, project_name=None,
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +00001033 owners_check=True, maxlen=80):
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001034 """Checks that ALL chromium orbit projects should use.
1035
1036 These are checks to be run on all Chromium orbit project, including:
1037 Chromium
1038 Native Client
1039 V8
1040 When you update this function, please take this broad scope into account.
1041 Args:
1042 input_api: Bag of input related interfaces.
1043 output_api: Bag of output related interfaces.
1044 excluded_paths: Don't include these paths in common checks.
1045 text_files: Which file are to be treated as documentation text files.
1046 license_header: What license header should be on files.
1047 project_name: What is the name of the project as it appears in the license.
1048 Returns:
1049 A list of warning or error objects.
1050 """
maruel@chromium.org69eaecb2011-06-14 13:09:13 +00001051 excluded_paths = tuple(excluded_paths or [])
1052 text_files = tuple(text_files or (
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +00001053 r'.+\.txt$',
1054 r'.+\.json$',
maruel@chromium.org69eaecb2011-06-14 13:09:13 +00001055 ))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001056 project_name = project_name or 'Chromium'
mark@chromium.org2cc66422012-08-07 21:22:32 +00001057
1058 # Accept any year number from 2006 to the current year, or the special
rvargas@chromium.org02652602014-03-14 19:43:20 +00001059 # 2006-20xx string used on the oldest files. 2006-20xx is deprecated, but
1060 # tolerated on old files.
mark@chromium.org2cc66422012-08-07 21:22:32 +00001061 current_year = int(input_api.time.strftime('%Y'))
1062 allowed_years = (str(s) for s in reversed(xrange(2006, current_year + 1)))
rvargas@chromium.org02652602014-03-14 19:43:20 +00001063 years_re = '(' + '|'.join(allowed_years) + '|2006-2008|2006-2009|2006-2010)'
mark@chromium.org2cc66422012-08-07 21:22:32 +00001064
1065 # The (c) is deprecated, but tolerate it until it's removed from all files.
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001066 license_header = license_header or (
mark@chromium.org2cc66422012-08-07 21:22:32 +00001067 r'.*? Copyright (\(c\) )?%(year)s The %(project)s Authors\. '
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001068 r'All rights reserved\.\n'
1069 r'.*? Use of this source code is governed by a BSD-style license that '
1070 r'can be\n'
dbeam@chromium.orgb2312102012-02-15 02:01:55 +00001071 r'.*? found in the LICENSE file\.(?: \*/)?\n'
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001072 ) % {
mark@chromium.org2cc66422012-08-07 21:22:32 +00001073 'year': years_re,
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001074 'project': project_name,
1075 }
1076
1077 results = []
1078 # This code loads the default black list (e.g. third_party, experimental, etc)
1079 # and add our black list (breakpad, skia and v8 are still not following
1080 # google style and are not really living this repository).
1081 # See presubmit_support.py InputApi.FilterSourceFile for the (simple) usage.
1082 black_list = input_api.DEFAULT_BLACK_LIST + excluded_paths
1083 white_list = input_api.DEFAULT_WHITE_LIST + text_files
1084 sources = lambda x: input_api.FilterSourceFile(x, black_list=black_list)
maruel@chromium.orgfe1211a2011-05-28 18:54:17 +00001085 text_files = lambda x: input_api.FilterSourceFile(
1086 x, black_list=black_list, white_list=white_list)
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001087
1088 snapshot_memory = []
1089 def snapshot(msg):
1090 """Measures & prints performance warning if a rule is running slow."""
1091 dt2 = input_api.time.clock()
1092 if snapshot_memory:
1093 delta_ms = int(1000*(dt2 - snapshot_memory[0]))
1094 if delta_ms > 500:
1095 print " %s took a long time: %dms" % (snapshot_memory[1], delta_ms)
1096 snapshot_memory[:] = (dt2, msg)
1097
bradnelson@google.comd57771d2011-03-31 19:18:32 +00001098 if owners_check:
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001099 snapshot("checking owners")
bradnelson@google.comd57771d2011-03-31 19:18:32 +00001100 results.extend(input_api.canned_checks.CheckOwners(
Dirk Pranke3c86cee2017-01-23 22:02:06 -08001101 input_api, output_api, source_file_filter=None))
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001102
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001103 snapshot("checking long lines")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001104 results.extend(input_api.canned_checks.CheckLongLines(
jamesr@chromium.orgaf27f462013-04-04 21:44:22 +00001105 input_api, output_api, maxlen, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001106 snapshot( "checking tabs")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001107 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
1108 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001109 snapshot( "checking stray whitespace")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001110 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
1111 input_api, output_api, source_file_filter=sources))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001112 snapshot("checking nsobjects")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001113 results.extend(_CheckConstNSObject(
1114 input_api, output_api, source_file_filter=sources))
phajdan.jr@chromium.orgd965db32015-11-16 09:46:56 +00001115 snapshot("checking license")
1116 results.extend(input_api.canned_checks.CheckLicense(
1117 input_api, output_api, license_header, source_file_filter=sources))
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +00001118
maruel@chromium.orgb1ce7752011-05-08 13:50:16 +00001119 if input_api.is_committing:
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +00001120 snapshot("checking was uploaded")
1121 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
1122 input_api, output_api))
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +00001123 snapshot("checking description")
1124 results.extend(input_api.canned_checks.CheckChangeHasDescription(
1125 input_api, output_api))
1126 results.extend(input_api.canned_checks.CheckDoNotSubmitInDescription(
1127 input_api, output_api))
ilevy@chromium.orgc50d7612012-12-05 04:25:14 +00001128 snapshot("checking do not submit in files")
1129 results.extend(input_api.canned_checks.CheckDoNotSubmitInFiles(
1130 input_api, output_api))
nick@chromium.orge06cb4e2011-04-23 01:20:38 +00001131 snapshot("done")
bradnelson@google.com56e48bc2011-03-24 20:51:21 +00001132 return results
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001133
1134
Christopher Lamc5ba6922017-01-24 11:19:14 +11001135def CheckPatchFormatted(input_api, output_api, check_js=False):
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001136 import git_cl
Christopher Lamc5ba6922017-01-24 11:19:14 +11001137 cmd = ['cl', 'format', '--dry-run']
1138 if check_js:
1139 cmd.append('--js')
1140 cmd.append(input_api.PresubmitLocalPath())
1141
enne@chromium.org555cfe42014-01-29 18:21:39 +00001142 code, _ = git_cl.RunGitWithCode(cmd, suppress_stderr=True)
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001143 if code == 2:
enne@chromium.orge969cc72015-03-13 18:56:54 +00001144 short_path = input_api.basename(input_api.PresubmitLocalPath())
1145 full_path = input_api.os_path.relpath(input_api.PresubmitLocalPath(),
1146 input_api.change.RepositoryRoot())
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001147 return [output_api.PresubmitPromptWarning(
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00001148 'The %s directory requires source formatting. '
Christopher Lamc5ba6922017-01-24 11:19:14 +11001149 'Please run git cl format %s%s' %
1150 (short_path, '--js ' if check_js else '', full_path))]
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00001151 # As this is just a warning, ignore all other errors if the user
1152 # happens to have a broken clang-format, doesn't use git, etc etc.
1153 return []
scottmg@chromium.orgd05ab352014-12-05 17:24:26 +00001154
1155
1156def CheckGNFormatted(input_api, output_api):
1157 import gn
1158 affected_files = input_api.AffectedFiles(
1159 include_deletes=False,
1160 file_filter=lambda x: x.LocalPath().endswith('.gn') or
kylechar58edce22016-06-17 06:07:51 -07001161 x.LocalPath().endswith('.gni') or
1162 x.LocalPath().endswith('.typemap'))
scottmg@chromium.orgd05ab352014-12-05 17:24:26 +00001163 warnings = []
1164 for f in affected_files:
1165 cmd = ['gn', 'format', '--dry-run', f.AbsoluteLocalPath()]
1166 rc = gn.main(cmd)
1167 if rc == 2:
1168 warnings.append(output_api.PresubmitPromptWarning(
brettw4b8ed592016-08-05 16:19:12 -07001169 '%s requires formatting. Please run:\n gn format %s' % (
scottmg@chromium.orgd05ab352014-12-05 17:24:26 +00001170 f.AbsoluteLocalPath(), f.LocalPath())))
1171 # It's just a warning, so ignore other types of failures assuming they'll be
1172 # caught elsewhere.
1173 return warnings