blob: 0f6e60773ccf091f252e723f840f387981727dcb [file] [log] [blame]
Shahbaz Youssefi98a35502019-01-08 22:09:39 -05001# Copyright 2019 The ANGLE Project Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Shahbaz Youssefi98a35502019-01-08 22:09:39 -05004"""Top-level presubmit script for code generation.
5
6See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
7for more details on the presubmit API built into depot_tools.
8"""
9
Brian Sheedycc4d8332019-09-11 17:26:00 -070010import os
Shahbaz Youssefi634ee432019-12-16 11:39:10 -050011import re
Brian Sheedycc4d8332019-09-11 17:26:00 -070012import shutil
13import subprocess
14import sys
15import tempfile
Shahbaz Youssefi98a35502019-01-08 22:09:39 -050016
James Darpinian4dff9952019-12-10 17:05:37 -080017# Fragment of a regular expression that matches C++ and Objective-C++ implementation files and headers.
18_IMPLEMENTATION_AND_HEADER_EXTENSIONS = r'\.(cc|cpp|cxx|mm|h|hpp|hxx)$'
Jamie Madill73397e82019-01-09 10:33:16 -050019
Jamie Madill73397e82019-01-09 10:33:16 -050020# Fragment of a regular expression that matches C++ and Objective-C++ header files.
21_HEADER_EXTENSIONS = r'\.(h|hpp|hxx)$'
22
Brian Sheedycc4d8332019-09-11 17:26:00 -070023_PRIMARY_EXPORT_TARGETS = [
24 '//:libEGL',
25 '//:libGLESv1_CM',
26 '//:libGLESv2',
27 '//:translator',
28]
29
Jamie Madill73397e82019-01-09 10:33:16 -050030
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040031def _CheckCommitMessageFormatting(input_api, output_api):
32
33 def _IsLineBlank(line):
34 return line.isspace() or line == ""
35
36 def _PopBlankLines(lines, reverse=False):
37 if reverse:
38 while len(lines) > 0 and _IsLineBlank(lines[-1]):
39 lines.pop()
40 else:
41 while len(lines) > 0 and _IsLineBlank(lines[0]):
42 lines.pop(0)
43
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040044 def _IsTagLine(line):
45 return ":" in line
46
Manh Nguyen75037212020-06-09 17:36:54 -040047 whitelist_strings = ['Revert "', 'Roll ', 'Reland ']
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040048 summary_linelength_warning_lower_limit = 65
49 summary_linelength_warning_upper_limit = 70
Shahbaz Youssefi462e40f2020-06-04 14:15:59 -040050 description_linelength_limit = 72
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040051
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040052 git_output = input_api.change.DescriptionText()
53
54 multiple_commits = re.split(r"Change-Id: [a-zA-Z0-9]*\n", git_output)
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040055 errors = []
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040056
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040057 for k in range(len(multiple_commits)):
58 commit = multiple_commits[k]
59 commit_number = len(multiple_commits) - k
60 commit_tag = "Commit " + str(commit_number) + ":"
61 commit_msg_lines = commit.splitlines()
62 commit_msg_line_numbers = {}
63 for i in range(len(commit_msg_lines)):
64 commit_msg_line_numbers[commit_msg_lines[i]] = i + 1
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040065 _PopBlankLines(commit_msg_lines, True)
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040066 _PopBlankLines(commit_msg_lines, False)
67 whitelisted = False
68 if len(commit_msg_lines) > 0:
69 for whitelist_string in whitelist_strings:
70 if commit_msg_lines[0].startswith(whitelist_string):
71 whitelisted = True
72 break
73 if whitelisted:
Manh Nguyen505b6eb2020-06-04 09:11:21 -040074 continue
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040075
76 if commit.find("\t") != -1:
77 errors.append(
78 output_api.PresubmitError(commit_tag + "Tabs are not allowed in commit message."))
79
80 # the tags paragraph is at the end of the message
81 # the break between the tags paragraph is the first line without ":"
82 # this is sufficient because if a line is blank, it will not have ":"
83 last_paragraph_line_count = 0
84 while len(commit_msg_lines) > 0 and _IsTagLine(commit_msg_lines[-1]):
85 last_paragraph_line_count += 1
86 commit_msg_lines.pop()
87 if last_paragraph_line_count == 0:
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040088 errors.append(
89 output_api.PresubmitError(
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040090 commit_tag +
91 "Please ensure that there are tags (e.g., Bug:, Test:) in your description."))
92 if len(commit_msg_lines) > 0:
93 if not _IsLineBlank(commit_msg_lines[-1]):
94 output_api.PresubmitError(commit_tag +
95 "Please ensure that there exists 1 blank line " +
96 "between tags and description body.")
97 else:
98 # pop the blank line between tag paragraph and description body
99 commit_msg_lines.pop()
100 if len(commit_msg_lines) > 0 and _IsLineBlank(commit_msg_lines[-1]):
101 errors.append(
102 output_api.PresubmitError(
103 commit_tag + 'Please ensure that there exists only 1 blank line '
104 'between tags and description body.'))
105 # pop all the remaining blank lines between tag and description body
106 _PopBlankLines(commit_msg_lines, True)
107 if len(commit_msg_lines) == 0:
108 errors.append(
109 output_api.PresubmitError(commit_tag +
110 'Please ensure that your description summary'
111 ' and description body are not blank.'))
112 continue
113
114 if summary_linelength_warning_lower_limit <= len(commit_msg_lines[0]) \
115 <= summary_linelength_warning_upper_limit:
116 errors.append(
117 output_api.PresubmitPromptWarning(
118 commit_tag + "Your description summary should be on one line of " +
119 str(summary_linelength_warning_lower_limit - 1) + " or less characters."))
120 elif len(commit_msg_lines[0]) > summary_linelength_warning_upper_limit:
121 errors.append(
122 output_api.PresubmitError(
123 commit_tag + "Please ensure that your description summary is on one line of " +
124 str(summary_linelength_warning_lower_limit - 1) + " or less characters."))
125 commit_msg_lines.pop(0) # get rid of description summary
126 if len(commit_msg_lines) == 0:
127 continue
128 if not _IsLineBlank(commit_msg_lines[0]):
129 errors.append(
130 output_api.PresubmitError(commit_tag +
131 'Please ensure the summary is only 1 line and '
132 'there is 1 blank line between the summary '
133 'and description body.'))
134 else:
135 commit_msg_lines.pop(0) # pop first blank line
136 if len(commit_msg_lines) == 0:
137 continue
138 if _IsLineBlank(commit_msg_lines[0]):
139 errors.append(
140 output_api.PresubmitError(commit_tag +
141 'Please ensure that there exists only 1 blank line '
142 'between description summary and description body.'))
143 # pop all the remaining blank lines between
144 # description summary and description body
145 _PopBlankLines(commit_msg_lines)
146
147 # loop through description body
148 while len(commit_msg_lines) > 0:
149 line = commit_msg_lines.pop(0)
150 # lines starting with 4 spaces or lines without space(urls)
151 # are exempt from length check
152 if line.startswith(" ") or " " not in line:
153 continue
154 if len(line) > description_linelength_limit:
155 errors.append(
156 output_api.PresubmitError(
157 commit_tag + 'Line ' + str(commit_msg_line_numbers[line]) +
158 ' is too long.\n' + '"' + line + '"\n' + 'Please wrap it to ' +
159 str(description_linelength_limit) + ' characters. ' +
160 "Lines without spaces or lines starting with 4 spaces are exempt."))
161 break
Manh Nguyenebc6d0a2020-05-27 18:21:30 -0400162 return errors
163
164
Jamie Madill9e438ee2019-07-05 08:44:23 -0400165def _CheckChangeHasBugField(input_api, output_api):
Shahbaz Youssefi634ee432019-12-16 11:39:10 -0500166 """Requires that the changelist have a Bug: field from a known project."""
Jamie Madill9e438ee2019-07-05 08:44:23 -0400167 bugs = input_api.change.BugsFromDescription()
168 if not bugs:
169 return [
Alexis Hetu4210e492019-10-10 14:22:03 -0400170 output_api.PresubmitError('Please ensure that your description contains:\n'
171 '"Bug: angleproject:[bug number]"\n'
172 'directly above the Change-Id tag.')
Jamie Madill9e438ee2019-07-05 08:44:23 -0400173 ]
Shahbaz Youssefi634ee432019-12-16 11:39:10 -0500174
175 # The bug must be in the form of "project:number". None is also accepted, which is used by
176 # rollers as well as in very minor changes.
177 if len(bugs) == 1 and bugs[0] == 'None':
Jamie Madill9e438ee2019-07-05 08:44:23 -0400178 return []
179
Geoff Lang13e4cdb2020-04-16 14:35:54 -0400180 projects = ['angleproject:', 'chromium:', 'dawn:', 'fuchsia:', 'skia:', 'swiftshader:', 'b/']
181 bug_regex = re.compile(r"([a-z]+[:/])(\d+)")
Shahbaz Youssefi634ee432019-12-16 11:39:10 -0500182 errors = []
183 extra_help = None
184
185 for bug in bugs:
186 if bug == 'None':
187 errors.append(
188 output_api.PresubmitError('Invalid bug tag "None" in presence of other bug tags.'))
189 continue
190
191 match = re.match(bug_regex, bug)
192 if match == None or bug != match.group(0) or match.group(1) not in projects:
193 errors.append(output_api.PresubmitError('Incorrect bug tag "' + bug + '".'))
194 if not extra_help:
195 extra_help = output_api.PresubmitError('Acceptable format is:\n\n'
196 ' Bug: project:bugnumber\n\n'
197 'Acceptable projects are:\n\n ' +
198 '\n '.join(projects))
199
200 if extra_help:
201 errors.append(extra_help)
202
203 return errors
204
Jamie Madill9e438ee2019-07-05 08:44:23 -0400205
Jamie Madill73397e82019-01-09 10:33:16 -0500206def _CheckCodeGeneration(input_api, output_api):
Jamie Madill04e9e552019-04-01 14:40:21 -0400207
208 class Msg(output_api.PresubmitError):
Geoff Langd7d42392019-05-06 13:15:35 -0400209 """Specialized error message"""
210
211 def __init__(self, message):
212 super(output_api.PresubmitError, self).__init__(
213 message,
Jamie Madill8dfc2b02019-11-25 15:12:58 -0500214 long_text='Please ensure your ANGLE repositiory is synced to tip-of-tree\n'
Jamie Madill9e438ee2019-07-05 08:44:23 -0400215 'and all ANGLE DEPS are fully up-to-date by running gclient sync.\n'
216 '\n'
Jamie Madill8dfc2b02019-11-25 15:12:58 -0500217 'If that fails, run scripts/run_code_generation.py to refresh generated hashes.\n'
218 '\n'
Jamie Madill9e438ee2019-07-05 08:44:23 -0400219 'If you are building ANGLE inside Chromium you must bootstrap ANGLE\n'
220 'before gclient sync. See the DevSetup documentation for more details.\n')
Jamie Madill04e9e552019-04-01 14:40:21 -0400221
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500222 code_gen_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
223 'scripts/run_code_generation.py')
224 cmd_name = 'run_code_generation'
225 cmd = [input_api.python_executable, code_gen_path, '--verify-no-dirty']
Geoff Langd7d42392019-05-06 13:15:35 -0400226 test_cmd = input_api.Command(name=cmd_name, cmd=cmd, kwargs={}, message=Msg)
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500227 if input_api.verbose:
228 print('Running ' + cmd_name)
229 return input_api.RunTests([test_cmd])
230
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500231
Jamie Madill73397e82019-01-09 10:33:16 -0500232# Taken directly from Chromium's PRESUBMIT.py
233def _CheckNewHeaderWithoutGnChange(input_api, output_api):
Geoff Langd7d42392019-05-06 13:15:35 -0400234 """Checks that newly added header files have corresponding GN changes.
Jamie Madill73397e82019-01-09 10:33:16 -0500235 Note that this is only a heuristic. To be precise, run script:
236 build/check_gn_headers.py.
237 """
238
Geoff Langd7d42392019-05-06 13:15:35 -0400239 def headers(f):
240 return input_api.FilterSourceFile(f, white_list=(r'.+%s' % _HEADER_EXTENSIONS,))
Jamie Madill73397e82019-01-09 10:33:16 -0500241
Geoff Langd7d42392019-05-06 13:15:35 -0400242 new_headers = []
243 for f in input_api.AffectedSourceFiles(headers):
244 if f.Action() != 'A':
245 continue
246 new_headers.append(f.LocalPath())
Jamie Madill73397e82019-01-09 10:33:16 -0500247
Geoff Langd7d42392019-05-06 13:15:35 -0400248 def gn_files(f):
249 return input_api.FilterSourceFile(f, white_list=(r'.+\.gn',))
Jamie Madill73397e82019-01-09 10:33:16 -0500250
Geoff Langd7d42392019-05-06 13:15:35 -0400251 all_gn_changed_contents = ''
252 for f in input_api.AffectedSourceFiles(gn_files):
253 for _, line in f.ChangedContents():
254 all_gn_changed_contents += line
Jamie Madill73397e82019-01-09 10:33:16 -0500255
Geoff Langd7d42392019-05-06 13:15:35 -0400256 problems = []
257 for header in new_headers:
258 basename = input_api.os_path.basename(header)
259 if basename not in all_gn_changed_contents:
260 problems.append(header)
Jamie Madill73397e82019-01-09 10:33:16 -0500261
Geoff Langd7d42392019-05-06 13:15:35 -0400262 if problems:
263 return [
264 output_api.PresubmitPromptWarning(
265 'Missing GN changes for new header files',
266 items=sorted(problems),
267 long_text='Please double check whether newly added header files need '
268 'corresponding changes in gn or gni files.\nThis checking is only a '
269 'heuristic. Run build/check_gn_headers.py to be precise.\n'
270 'Read https://crbug.com/661774 for more info.')
271 ]
272 return []
Jamie Madill73397e82019-01-09 10:33:16 -0500273
274
Brian Sheedycc4d8332019-09-11 17:26:00 -0700275def _CheckExportValidity(input_api, output_api):
276 outdir = tempfile.mkdtemp()
277 # shell=True is necessary on Windows, as otherwise subprocess fails to find
278 # either 'gn' or 'vpython3' even if they are findable via PATH.
279 use_shell = input_api.is_windows
280 try:
281 try:
282 subprocess.check_output(['gn', 'gen', outdir], shell=use_shell)
283 except subprocess.CalledProcessError as e:
284 return [
285 output_api.PresubmitError(
286 'Unable to run gn gen for export_targets.py: %s' % e.output)
287 ]
288 export_target_script = os.path.join(input_api.PresubmitLocalPath(), 'scripts',
289 'export_targets.py')
290 try:
291 subprocess.check_output(
292 ['vpython3', export_target_script, outdir] + _PRIMARY_EXPORT_TARGETS,
293 stderr=subprocess.STDOUT,
294 shell=use_shell)
295 except subprocess.CalledProcessError as e:
296 if input_api.is_committing:
297 return [output_api.PresubmitError('export_targets.py failed: %s' % e.output)]
298 return [
299 output_api.PresubmitPromptWarning(
300 'export_targets.py failed, this may just be due to your local checkout: %s' %
301 e.output)
302 ]
303 return []
304 finally:
305 shutil.rmtree(outdir)
306
307
James Darpinian4dff9952019-12-10 17:05:37 -0800308def _CheckTabsInSourceFiles(input_api, output_api):
309 """Forbids tab characters in source files due to a WebKit repo requirement. """
310
311 def implementation_and_headers(f):
312 return input_api.FilterSourceFile(
313 f, white_list=(r'.+%s' % _IMPLEMENTATION_AND_HEADER_EXTENSIONS,))
314
315 files_with_tabs = []
316 for f in input_api.AffectedSourceFiles(implementation_and_headers):
317 for (num, line) in f.ChangedContents():
318 if '\t' in line:
319 files_with_tabs.append(f)
320 break
321
322 if files_with_tabs:
323 return [
324 output_api.PresubmitError(
325 'Tab characters in source files.',
326 items=sorted(files_with_tabs),
327 long_text=
328 'Tab characters are forbidden in ANGLE source files because WebKit\'s Subversion\n'
329 'repository does not allow tab characters in source files.\n'
330 'Please remove tab characters from these files.')
331 ]
332 return []
333
334
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600335# https://stackoverflow.com/a/196392
336def is_ascii(s):
337 return all(ord(c) < 128 for c in s)
338
339
340def _CheckNonAsciiInSourceFiles(input_api, output_api):
341 """Forbids non-ascii characters in source files. """
342
343 def implementation_and_headers(f):
344 return input_api.FilterSourceFile(
345 f, white_list=(r'.+%s' % _IMPLEMENTATION_AND_HEADER_EXTENSIONS,))
346
347 files_with_non_ascii = []
348 for f in input_api.AffectedSourceFiles(implementation_and_headers):
349 for (num, line) in f.ChangedContents():
350 if not is_ascii(line):
Jamie Madilla89750e2020-04-30 18:00:00 -0400351 files_with_non_ascii.append("%s: %s" % (f, line))
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600352 break
353
354 if files_with_non_ascii:
355 return [
356 output_api.PresubmitError(
357 'Non-ASCII characters in source files.',
358 items=sorted(files_with_non_ascii),
359 long_text='Non-ASCII characters are forbidden in ANGLE source files.\n'
360 'Please remove non-ASCII characters from these files.')
361 ]
362 return []
363
364
Jamie Madill73397e82019-01-09 10:33:16 -0500365def CheckChangeOnUpload(input_api, output_api):
366 results = []
James Darpinian4dff9952019-12-10 17:05:37 -0800367 results.extend(_CheckTabsInSourceFiles(input_api, output_api))
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600368 results.extend(_CheckNonAsciiInSourceFiles(input_api, output_api))
Jamie Madill73397e82019-01-09 10:33:16 -0500369 results.extend(_CheckCodeGeneration(input_api, output_api))
Jamie Madill9e438ee2019-07-05 08:44:23 -0400370 results.extend(_CheckChangeHasBugField(input_api, output_api))
Geoff Langd7d42392019-05-06 13:15:35 -0400371 results.extend(input_api.canned_checks.CheckChangeHasDescription(input_api, output_api))
Jamie Madill73397e82019-01-09 10:33:16 -0500372 results.extend(_CheckNewHeaderWithoutGnChange(input_api, output_api))
Brian Sheedycc4d8332019-09-11 17:26:00 -0700373 results.extend(_CheckExportValidity(input_api, output_api))
Jamie Madill9e438ee2019-07-05 08:44:23 -0400374 results.extend(
375 input_api.canned_checks.CheckPatchFormatted(
376 input_api, output_api, result_factory=output_api.PresubmitError))
Manh Nguyen2df3bf52020-06-17 17:07:47 -0400377 results.extend(_CheckCommitMessageFormatting(input_api, output_api))
Jamie Madill73397e82019-01-09 10:33:16 -0500378 return results
379
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500380
381def CheckChangeOnCommit(input_api, output_api):
Jamie Madilla89750e2020-04-30 18:00:00 -0400382 return CheckChangeOnUpload(input_api, output_api)