blob: 09baa6bdfd4e3ff6f15c4afc04a78930ca284783 [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
44 whitelist_strings = ['Revert "', 'Roll ']
45 summary_linelength_warning_lower_limit = 65
46 summary_linelength_warning_upper_limit = 70
Shahbaz Youssefi462e40f2020-06-04 14:15:59 -040047 description_linelength_limit = 72
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040048
49 if input_api.change.issue:
50 git_output = input_api.gerrit.GetChangeDescription(input_api.change.issue)
51 else:
52 git_output = subprocess.check_output(["git", "log", "-n", "1", "--pretty=format:%B"])
53 commit_msg_lines = git_output.splitlines()
54 _PopBlankLines(commit_msg_lines, True)
55 _PopBlankLines(commit_msg_lines, False)
56 if len(commit_msg_lines) > 0:
57 for whitelist_string in whitelist_strings:
58 if commit_msg_lines[0].startswith(whitelist_string):
59 return []
60 errors = []
61 if git_output.find("\t") != -1:
62 errors.append(output_api.PresubmitError("Tabs are not allowed in commit message."))
63
64 # get rid of the last paragraph, which we assume to always be the tags
65 last_paragraph_line_count = 0
66 while len(commit_msg_lines) > 0 and not _IsLineBlank(commit_msg_lines[-1]):
67 last_paragraph_line_count += 1
68 commit_msg_lines.pop()
69 if last_paragraph_line_count == 0:
70 errors.append(
71 output_api.PresubmitError(
72 "Please ensure that there are tags (e.g., Bug:, Test:) in your description."))
73 if len(commit_msg_lines) > 0:
74 # pop the blank line between tag paragraph and description body
75 commit_msg_lines.pop()
76 if len(commit_msg_lines) > 0 and _IsLineBlank(commit_msg_lines[-1]):
77 errors.append(
78 output_api.PresubmitError('Please ensure that there exists only 1 blank line '
79 'between tags and description body.'))
80 # pop all the remaining blank lines between tag and description body
81 _PopBlankLines(commit_msg_lines, True)
82 if len(commit_msg_lines) == 0:
83 errors.append(
84 output_api.PresubmitError('Please ensure that your description summary'
85 ' and description body are not blank.'))
86 return errors
87
88 if summary_linelength_warning_lower_limit <= len(commit_msg_lines[0]) \
89 <= summary_linelength_warning_upper_limit:
90 errors.append(
91 output_api.PresubmitPromptWarning(
92 "Your description summary should be on one line of " +
93 str(summary_linelength_warning_lower_limit - 1) + " or less characters."))
94 elif len(commit_msg_lines[0]) > summary_linelength_warning_upper_limit:
95 errors.append(
96 output_api.PresubmitError(
97 "Please ensure that your description summary is on one line of " +
98 str(summary_linelength_warning_lower_limit - 1) + " or less characters."))
99 commit_msg_lines.pop(0) # get rid of description summary
100 if len(commit_msg_lines) == 0:
101 return errors
102 if not _IsLineBlank(commit_msg_lines[0]):
103 errors.append(
104 output_api.PresubmitError('Please ensure the summary is only 1 line and '
105 ' there is 1 blank line between the summary '
106 'and description body.'))
107 else:
108 commit_msg_lines.pop(0) # pop first blank line
109 if len(commit_msg_lines) == 0:
110 return errors
111 if _IsLineBlank(commit_msg_lines[0]):
112 errors.append(
113 output_api.PresubmitError('Please ensure that there exists only 1 blank line '
114 'between description summary and description body.'))
115 # pop all the remaining blank lines between description summary and description body
116 _PopBlankLines(commit_msg_lines)
117
118 # loop through description body
119 while len(commit_msg_lines) > 0:
Manh Nguyen505b6eb2020-06-04 09:11:21 -0400120 line = commit_msg_lines.pop(0)
121 # lines starting with 4 spaces or lines without space(urls) are exempt from length check
122 if line.startswith(" ") or " " not in line:
123 continue
124 if len(line) > description_linelength_limit:
Manh Nguyenebc6d0a2020-05-27 18:21:30 -0400125 errors.append(
126 output_api.PresubmitError(
127 "Please ensure that your description body is wrapped to " +
128 str(description_linelength_limit) + " characters or less."))
129 return errors
Manh Nguyenebc6d0a2020-05-27 18:21:30 -0400130 return errors
131
132
Jamie Madill9e438ee2019-07-05 08:44:23 -0400133def _CheckChangeHasBugField(input_api, output_api):
Shahbaz Youssefi634ee432019-12-16 11:39:10 -0500134 """Requires that the changelist have a Bug: field from a known project."""
Jamie Madill9e438ee2019-07-05 08:44:23 -0400135 bugs = input_api.change.BugsFromDescription()
136 if not bugs:
137 return [
Alexis Hetu4210e492019-10-10 14:22:03 -0400138 output_api.PresubmitError('Please ensure that your description contains:\n'
139 '"Bug: angleproject:[bug number]"\n'
140 'directly above the Change-Id tag.')
Jamie Madill9e438ee2019-07-05 08:44:23 -0400141 ]
Shahbaz Youssefi634ee432019-12-16 11:39:10 -0500142
143 # The bug must be in the form of "project:number". None is also accepted, which is used by
144 # rollers as well as in very minor changes.
145 if len(bugs) == 1 and bugs[0] == 'None':
Jamie Madill9e438ee2019-07-05 08:44:23 -0400146 return []
147
Geoff Lang13e4cdb2020-04-16 14:35:54 -0400148 projects = ['angleproject:', 'chromium:', 'dawn:', 'fuchsia:', 'skia:', 'swiftshader:', 'b/']
149 bug_regex = re.compile(r"([a-z]+[:/])(\d+)")
Shahbaz Youssefi634ee432019-12-16 11:39:10 -0500150 errors = []
151 extra_help = None
152
153 for bug in bugs:
154 if bug == 'None':
155 errors.append(
156 output_api.PresubmitError('Invalid bug tag "None" in presence of other bug tags.'))
157 continue
158
159 match = re.match(bug_regex, bug)
160 if match == None or bug != match.group(0) or match.group(1) not in projects:
161 errors.append(output_api.PresubmitError('Incorrect bug tag "' + bug + '".'))
162 if not extra_help:
163 extra_help = output_api.PresubmitError('Acceptable format is:\n\n'
164 ' Bug: project:bugnumber\n\n'
165 'Acceptable projects are:\n\n ' +
166 '\n '.join(projects))
167
168 if extra_help:
169 errors.append(extra_help)
170
171 return errors
172
Jamie Madill9e438ee2019-07-05 08:44:23 -0400173
Jamie Madill73397e82019-01-09 10:33:16 -0500174def _CheckCodeGeneration(input_api, output_api):
Jamie Madill04e9e552019-04-01 14:40:21 -0400175
176 class Msg(output_api.PresubmitError):
Geoff Langd7d42392019-05-06 13:15:35 -0400177 """Specialized error message"""
178
179 def __init__(self, message):
180 super(output_api.PresubmitError, self).__init__(
181 message,
Jamie Madill8dfc2b02019-11-25 15:12:58 -0500182 long_text='Please ensure your ANGLE repositiory is synced to tip-of-tree\n'
Jamie Madill9e438ee2019-07-05 08:44:23 -0400183 'and all ANGLE DEPS are fully up-to-date by running gclient sync.\n'
184 '\n'
Jamie Madill8dfc2b02019-11-25 15:12:58 -0500185 'If that fails, run scripts/run_code_generation.py to refresh generated hashes.\n'
186 '\n'
Jamie Madill9e438ee2019-07-05 08:44:23 -0400187 'If you are building ANGLE inside Chromium you must bootstrap ANGLE\n'
188 'before gclient sync. See the DevSetup documentation for more details.\n')
Jamie Madill04e9e552019-04-01 14:40:21 -0400189
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500190 code_gen_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
191 'scripts/run_code_generation.py')
192 cmd_name = 'run_code_generation'
193 cmd = [input_api.python_executable, code_gen_path, '--verify-no-dirty']
Geoff Langd7d42392019-05-06 13:15:35 -0400194 test_cmd = input_api.Command(name=cmd_name, cmd=cmd, kwargs={}, message=Msg)
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500195 if input_api.verbose:
196 print('Running ' + cmd_name)
197 return input_api.RunTests([test_cmd])
198
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500199
Jamie Madill73397e82019-01-09 10:33:16 -0500200# Taken directly from Chromium's PRESUBMIT.py
201def _CheckNewHeaderWithoutGnChange(input_api, output_api):
Geoff Langd7d42392019-05-06 13:15:35 -0400202 """Checks that newly added header files have corresponding GN changes.
Jamie Madill73397e82019-01-09 10:33:16 -0500203 Note that this is only a heuristic. To be precise, run script:
204 build/check_gn_headers.py.
205 """
206
Geoff Langd7d42392019-05-06 13:15:35 -0400207 def headers(f):
208 return input_api.FilterSourceFile(f, white_list=(r'.+%s' % _HEADER_EXTENSIONS,))
Jamie Madill73397e82019-01-09 10:33:16 -0500209
Geoff Langd7d42392019-05-06 13:15:35 -0400210 new_headers = []
211 for f in input_api.AffectedSourceFiles(headers):
212 if f.Action() != 'A':
213 continue
214 new_headers.append(f.LocalPath())
Jamie Madill73397e82019-01-09 10:33:16 -0500215
Geoff Langd7d42392019-05-06 13:15:35 -0400216 def gn_files(f):
217 return input_api.FilterSourceFile(f, white_list=(r'.+\.gn',))
Jamie Madill73397e82019-01-09 10:33:16 -0500218
Geoff Langd7d42392019-05-06 13:15:35 -0400219 all_gn_changed_contents = ''
220 for f in input_api.AffectedSourceFiles(gn_files):
221 for _, line in f.ChangedContents():
222 all_gn_changed_contents += line
Jamie Madill73397e82019-01-09 10:33:16 -0500223
Geoff Langd7d42392019-05-06 13:15:35 -0400224 problems = []
225 for header in new_headers:
226 basename = input_api.os_path.basename(header)
227 if basename not in all_gn_changed_contents:
228 problems.append(header)
Jamie Madill73397e82019-01-09 10:33:16 -0500229
Geoff Langd7d42392019-05-06 13:15:35 -0400230 if problems:
231 return [
232 output_api.PresubmitPromptWarning(
233 'Missing GN changes for new header files',
234 items=sorted(problems),
235 long_text='Please double check whether newly added header files need '
236 'corresponding changes in gn or gni files.\nThis checking is only a '
237 'heuristic. Run build/check_gn_headers.py to be precise.\n'
238 'Read https://crbug.com/661774 for more info.')
239 ]
240 return []
Jamie Madill73397e82019-01-09 10:33:16 -0500241
242
Brian Sheedycc4d8332019-09-11 17:26:00 -0700243def _CheckExportValidity(input_api, output_api):
244 outdir = tempfile.mkdtemp()
245 # shell=True is necessary on Windows, as otherwise subprocess fails to find
246 # either 'gn' or 'vpython3' even if they are findable via PATH.
247 use_shell = input_api.is_windows
248 try:
249 try:
250 subprocess.check_output(['gn', 'gen', outdir], shell=use_shell)
251 except subprocess.CalledProcessError as e:
252 return [
253 output_api.PresubmitError(
254 'Unable to run gn gen for export_targets.py: %s' % e.output)
255 ]
256 export_target_script = os.path.join(input_api.PresubmitLocalPath(), 'scripts',
257 'export_targets.py')
258 try:
259 subprocess.check_output(
260 ['vpython3', export_target_script, outdir] + _PRIMARY_EXPORT_TARGETS,
261 stderr=subprocess.STDOUT,
262 shell=use_shell)
263 except subprocess.CalledProcessError as e:
264 if input_api.is_committing:
265 return [output_api.PresubmitError('export_targets.py failed: %s' % e.output)]
266 return [
267 output_api.PresubmitPromptWarning(
268 'export_targets.py failed, this may just be due to your local checkout: %s' %
269 e.output)
270 ]
271 return []
272 finally:
273 shutil.rmtree(outdir)
274
275
James Darpinian4dff9952019-12-10 17:05:37 -0800276def _CheckTabsInSourceFiles(input_api, output_api):
277 """Forbids tab characters in source files due to a WebKit repo requirement. """
278
279 def implementation_and_headers(f):
280 return input_api.FilterSourceFile(
281 f, white_list=(r'.+%s' % _IMPLEMENTATION_AND_HEADER_EXTENSIONS,))
282
283 files_with_tabs = []
284 for f in input_api.AffectedSourceFiles(implementation_and_headers):
285 for (num, line) in f.ChangedContents():
286 if '\t' in line:
287 files_with_tabs.append(f)
288 break
289
290 if files_with_tabs:
291 return [
292 output_api.PresubmitError(
293 'Tab characters in source files.',
294 items=sorted(files_with_tabs),
295 long_text=
296 'Tab characters are forbidden in ANGLE source files because WebKit\'s Subversion\n'
297 'repository does not allow tab characters in source files.\n'
298 'Please remove tab characters from these files.')
299 ]
300 return []
301
302
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600303# https://stackoverflow.com/a/196392
304def is_ascii(s):
305 return all(ord(c) < 128 for c in s)
306
307
308def _CheckNonAsciiInSourceFiles(input_api, output_api):
309 """Forbids non-ascii characters in source files. """
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_non_ascii = []
316 for f in input_api.AffectedSourceFiles(implementation_and_headers):
317 for (num, line) in f.ChangedContents():
318 if not is_ascii(line):
Jamie Madilla89750e2020-04-30 18:00:00 -0400319 files_with_non_ascii.append("%s: %s" % (f, line))
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600320 break
321
322 if files_with_non_ascii:
323 return [
324 output_api.PresubmitError(
325 'Non-ASCII characters in source files.',
326 items=sorted(files_with_non_ascii),
327 long_text='Non-ASCII characters are forbidden in ANGLE source files.\n'
328 'Please remove non-ASCII characters from these files.')
329 ]
330 return []
331
332
Jamie Madill73397e82019-01-09 10:33:16 -0500333def CheckChangeOnUpload(input_api, output_api):
334 results = []
James Darpinian4dff9952019-12-10 17:05:37 -0800335 results.extend(_CheckTabsInSourceFiles(input_api, output_api))
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600336 results.extend(_CheckNonAsciiInSourceFiles(input_api, output_api))
Jamie Madill73397e82019-01-09 10:33:16 -0500337 results.extend(_CheckCodeGeneration(input_api, output_api))
Jamie Madill9e438ee2019-07-05 08:44:23 -0400338 results.extend(_CheckChangeHasBugField(input_api, output_api))
Geoff Langd7d42392019-05-06 13:15:35 -0400339 results.extend(input_api.canned_checks.CheckChangeHasDescription(input_api, output_api))
Jamie Madill73397e82019-01-09 10:33:16 -0500340 results.extend(_CheckNewHeaderWithoutGnChange(input_api, output_api))
Brian Sheedycc4d8332019-09-11 17:26:00 -0700341 results.extend(_CheckExportValidity(input_api, output_api))
Jamie Madill9e438ee2019-07-05 08:44:23 -0400342 results.extend(
343 input_api.canned_checks.CheckPatchFormatted(
344 input_api, output_api, result_factory=output_api.PresubmitError))
Manh Nguyenebc6d0a2020-05-27 18:21:30 -0400345 results.extend(_CheckCommitMessageFormatting(input_api, output_api))
Jamie Madill73397e82019-01-09 10:33:16 -0500346 return results
347
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500348
349def CheckChangeOnCommit(input_api, output_api):
Jamie Madilla89750e2020-04-30 18:00:00 -0400350 return CheckChangeOnUpload(input_api, output_api)