blob: d80f95838bf2162018899ad429c2ab1b3a52d9d8 [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 Nguyencb8eea22020-07-01 23:17:43 -040047 def _SplitIntoMultipleCommits(description_text):
48 paragraph_split_pattern = r"((?m)^\s*$\n)"
49 multiple_paragraphs = re.split(paragraph_split_pattern, description_text)
50 multiple_commits = [""]
51 change_id_pattern = re.compile(r"(?m)^Change-Id: [a-zA-Z0-9]*$")
52 for paragraph in multiple_paragraphs:
53 multiple_commits[-1] += paragraph
54 if change_id_pattern.search(paragraph):
55 multiple_commits.append("")
56 if multiple_commits[-1] == "":
57 multiple_commits.pop()
58 return multiple_commits
59
60 def _CheckTabInCommit(lines):
61 return all([line.find("\t") == -1 for line in lines])
62
Trevor David Blacke815afb2020-09-07 22:09:22 +000063 allowlist_strings = ['Revert "', 'Roll ', 'Reland ', 'Re-land ']
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040064 summary_linelength_warning_lower_limit = 65
65 summary_linelength_warning_upper_limit = 70
Shahbaz Youssefi462e40f2020-06-04 14:15:59 -040066 description_linelength_limit = 72
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040067
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040068 git_output = input_api.change.DescriptionText()
69
Manh Nguyencb8eea22020-07-01 23:17:43 -040070 multiple_commits = _SplitIntoMultipleCommits(git_output)
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040071 errors = []
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040072
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040073 for k in range(len(multiple_commits)):
Manh Nguyencb8eea22020-07-01 23:17:43 -040074 commit_msg_lines = multiple_commits[k].splitlines()
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040075 commit_number = len(multiple_commits) - k
76 commit_tag = "Commit " + str(commit_number) + ":"
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040077 commit_msg_line_numbers = {}
78 for i in range(len(commit_msg_lines)):
79 commit_msg_line_numbers[commit_msg_lines[i]] = i + 1
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040080 _PopBlankLines(commit_msg_lines, True)
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040081 _PopBlankLines(commit_msg_lines, False)
Trevor David Blacke815afb2020-09-07 22:09:22 +000082 allowlisted = False
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040083 if len(commit_msg_lines) > 0:
Trevor David Blacke815afb2020-09-07 22:09:22 +000084 for allowlist_string in allowlist_strings:
85 if commit_msg_lines[0].startswith(allowlist_string):
86 allowlisted = True
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040087 break
Trevor David Blacke815afb2020-09-07 22:09:22 +000088 if allowlisted:
Manh Nguyen505b6eb2020-06-04 09:11:21 -040089 continue
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040090
Manh Nguyencb8eea22020-07-01 23:17:43 -040091 if not _CheckTabInCommit(commit_msg_lines):
Manh Nguyen31bbe1b2020-06-11 10:46:55 -040092 errors.append(
93 output_api.PresubmitError(commit_tag + "Tabs are not allowed in commit message."))
94
95 # the tags paragraph is at the end of the message
96 # the break between the tags paragraph is the first line without ":"
97 # this is sufficient because if a line is blank, it will not have ":"
98 last_paragraph_line_count = 0
99 while len(commit_msg_lines) > 0 and _IsTagLine(commit_msg_lines[-1]):
100 last_paragraph_line_count += 1
101 commit_msg_lines.pop()
102 if last_paragraph_line_count == 0:
Manh Nguyenebc6d0a2020-05-27 18:21:30 -0400103 errors.append(
104 output_api.PresubmitError(
Manh Nguyen31bbe1b2020-06-11 10:46:55 -0400105 commit_tag +
106 "Please ensure that there are tags (e.g., Bug:, Test:) in your description."))
107 if len(commit_msg_lines) > 0:
108 if not _IsLineBlank(commit_msg_lines[-1]):
109 output_api.PresubmitError(commit_tag +
110 "Please ensure that there exists 1 blank line " +
111 "between tags and description body.")
112 else:
113 # pop the blank line between tag paragraph and description body
114 commit_msg_lines.pop()
115 if len(commit_msg_lines) > 0 and _IsLineBlank(commit_msg_lines[-1]):
116 errors.append(
117 output_api.PresubmitError(
118 commit_tag + 'Please ensure that there exists only 1 blank line '
119 'between tags and description body.'))
120 # pop all the remaining blank lines between tag and description body
121 _PopBlankLines(commit_msg_lines, True)
122 if len(commit_msg_lines) == 0:
123 errors.append(
124 output_api.PresubmitError(commit_tag +
125 'Please ensure that your description summary'
126 ' and description body are not blank.'))
127 continue
128
129 if summary_linelength_warning_lower_limit <= len(commit_msg_lines[0]) \
130 <= summary_linelength_warning_upper_limit:
131 errors.append(
132 output_api.PresubmitPromptWarning(
133 commit_tag + "Your description summary should be on one line of " +
134 str(summary_linelength_warning_lower_limit - 1) + " or less characters."))
135 elif len(commit_msg_lines[0]) > summary_linelength_warning_upper_limit:
136 errors.append(
137 output_api.PresubmitError(
138 commit_tag + "Please ensure that your description summary is on one line of " +
139 str(summary_linelength_warning_lower_limit - 1) + " or less characters."))
140 commit_msg_lines.pop(0) # get rid of description summary
141 if len(commit_msg_lines) == 0:
142 continue
143 if not _IsLineBlank(commit_msg_lines[0]):
144 errors.append(
145 output_api.PresubmitError(commit_tag +
146 'Please ensure the summary is only 1 line and '
147 'there is 1 blank line between the summary '
148 'and description body.'))
149 else:
150 commit_msg_lines.pop(0) # pop first blank line
151 if len(commit_msg_lines) == 0:
152 continue
153 if _IsLineBlank(commit_msg_lines[0]):
154 errors.append(
155 output_api.PresubmitError(commit_tag +
156 'Please ensure that there exists only 1 blank line '
157 'between description summary and description body.'))
158 # pop all the remaining blank lines between
159 # description summary and description body
160 _PopBlankLines(commit_msg_lines)
161
162 # loop through description body
163 while len(commit_msg_lines) > 0:
164 line = commit_msg_lines.pop(0)
165 # lines starting with 4 spaces or lines without space(urls)
166 # are exempt from length check
167 if line.startswith(" ") or " " not in line:
168 continue
169 if len(line) > description_linelength_limit:
170 errors.append(
171 output_api.PresubmitError(
172 commit_tag + 'Line ' + str(commit_msg_line_numbers[line]) +
173 ' is too long.\n' + '"' + line + '"\n' + 'Please wrap it to ' +
174 str(description_linelength_limit) + ' characters. ' +
175 "Lines without spaces or lines starting with 4 spaces are exempt."))
176 break
Manh Nguyenebc6d0a2020-05-27 18:21:30 -0400177 return errors
178
179
Jamie Madill9e438ee2019-07-05 08:44:23 -0400180def _CheckChangeHasBugField(input_api, output_api):
Shahbaz Youssefi634ee432019-12-16 11:39:10 -0500181 """Requires that the changelist have a Bug: field from a known project."""
Jamie Madill9e438ee2019-07-05 08:44:23 -0400182 bugs = input_api.change.BugsFromDescription()
183 if not bugs:
184 return [
Alexis Hetu4210e492019-10-10 14:22:03 -0400185 output_api.PresubmitError('Please ensure that your description contains:\n'
186 '"Bug: angleproject:[bug number]"\n'
187 'directly above the Change-Id tag.')
Jamie Madill9e438ee2019-07-05 08:44:23 -0400188 ]
Shahbaz Youssefi634ee432019-12-16 11:39:10 -0500189
190 # The bug must be in the form of "project:number". None is also accepted, which is used by
191 # rollers as well as in very minor changes.
192 if len(bugs) == 1 and bugs[0] == 'None':
Jamie Madill9e438ee2019-07-05 08:44:23 -0400193 return []
194
Geoff Lang13e4cdb2020-04-16 14:35:54 -0400195 projects = ['angleproject:', 'chromium:', 'dawn:', 'fuchsia:', 'skia:', 'swiftshader:', 'b/']
196 bug_regex = re.compile(r"([a-z]+[:/])(\d+)")
Shahbaz Youssefi634ee432019-12-16 11:39:10 -0500197 errors = []
198 extra_help = None
199
200 for bug in bugs:
201 if bug == 'None':
202 errors.append(
203 output_api.PresubmitError('Invalid bug tag "None" in presence of other bug tags.'))
204 continue
205
206 match = re.match(bug_regex, bug)
207 if match == None or bug != match.group(0) or match.group(1) not in projects:
208 errors.append(output_api.PresubmitError('Incorrect bug tag "' + bug + '".'))
209 if not extra_help:
210 extra_help = output_api.PresubmitError('Acceptable format is:\n\n'
211 ' Bug: project:bugnumber\n\n'
212 'Acceptable projects are:\n\n ' +
213 '\n '.join(projects))
214
215 if extra_help:
216 errors.append(extra_help)
217
218 return errors
219
Jamie Madill9e438ee2019-07-05 08:44:23 -0400220
Jamie Madill73397e82019-01-09 10:33:16 -0500221def _CheckCodeGeneration(input_api, output_api):
Jamie Madill04e9e552019-04-01 14:40:21 -0400222
223 class Msg(output_api.PresubmitError):
Geoff Langd7d42392019-05-06 13:15:35 -0400224 """Specialized error message"""
225
226 def __init__(self, message):
227 super(output_api.PresubmitError, self).__init__(
228 message,
Jamie Madill8dfc2b02019-11-25 15:12:58 -0500229 long_text='Please ensure your ANGLE repositiory is synced to tip-of-tree\n'
Jamie Madill9e438ee2019-07-05 08:44:23 -0400230 'and all ANGLE DEPS are fully up-to-date by running gclient sync.\n'
231 '\n'
Jamie Madill8dfc2b02019-11-25 15:12:58 -0500232 'If that fails, run scripts/run_code_generation.py to refresh generated hashes.\n'
233 '\n'
Jamie Madill9e438ee2019-07-05 08:44:23 -0400234 'If you are building ANGLE inside Chromium you must bootstrap ANGLE\n'
235 'before gclient sync. See the DevSetup documentation for more details.\n')
Jamie Madill04e9e552019-04-01 14:40:21 -0400236
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500237 code_gen_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
238 'scripts/run_code_generation.py')
239 cmd_name = 'run_code_generation'
240 cmd = [input_api.python_executable, code_gen_path, '--verify-no-dirty']
Geoff Langd7d42392019-05-06 13:15:35 -0400241 test_cmd = input_api.Command(name=cmd_name, cmd=cmd, kwargs={}, message=Msg)
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500242 if input_api.verbose:
243 print('Running ' + cmd_name)
244 return input_api.RunTests([test_cmd])
245
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500246
Jamie Madill73397e82019-01-09 10:33:16 -0500247# Taken directly from Chromium's PRESUBMIT.py
248def _CheckNewHeaderWithoutGnChange(input_api, output_api):
Geoff Langd7d42392019-05-06 13:15:35 -0400249 """Checks that newly added header files have corresponding GN changes.
Jamie Madill73397e82019-01-09 10:33:16 -0500250 Note that this is only a heuristic. To be precise, run script:
251 build/check_gn_headers.py.
252 """
253
Geoff Langd7d42392019-05-06 13:15:35 -0400254 def headers(f):
Trevor David Blackf69aaf62020-10-05 21:50:10 +0000255 return input_api.FilterSourceFile(f, files_to_check=(r'.+%s' % _HEADER_EXTENSIONS,))
Jamie Madill73397e82019-01-09 10:33:16 -0500256
Geoff Langd7d42392019-05-06 13:15:35 -0400257 new_headers = []
258 for f in input_api.AffectedSourceFiles(headers):
259 if f.Action() != 'A':
260 continue
261 new_headers.append(f.LocalPath())
Jamie Madill73397e82019-01-09 10:33:16 -0500262
Geoff Langd7d42392019-05-06 13:15:35 -0400263 def gn_files(f):
Trevor David Blackf69aaf62020-10-05 21:50:10 +0000264 return input_api.FilterSourceFile(f, files_to_check=(r'.+\.gn',))
Jamie Madill73397e82019-01-09 10:33:16 -0500265
Geoff Langd7d42392019-05-06 13:15:35 -0400266 all_gn_changed_contents = ''
267 for f in input_api.AffectedSourceFiles(gn_files):
268 for _, line in f.ChangedContents():
269 all_gn_changed_contents += line
Jamie Madill73397e82019-01-09 10:33:16 -0500270
Geoff Langd7d42392019-05-06 13:15:35 -0400271 problems = []
272 for header in new_headers:
273 basename = input_api.os_path.basename(header)
274 if basename not in all_gn_changed_contents:
275 problems.append(header)
Jamie Madill73397e82019-01-09 10:33:16 -0500276
Geoff Langd7d42392019-05-06 13:15:35 -0400277 if problems:
278 return [
279 output_api.PresubmitPromptWarning(
280 'Missing GN changes for new header files',
281 items=sorted(problems),
282 long_text='Please double check whether newly added header files need '
283 'corresponding changes in gn or gni files.\nThis checking is only a '
284 'heuristic. Run build/check_gn_headers.py to be precise.\n'
285 'Read https://crbug.com/661774 for more info.')
286 ]
287 return []
Jamie Madill73397e82019-01-09 10:33:16 -0500288
289
Brian Sheedycc4d8332019-09-11 17:26:00 -0700290def _CheckExportValidity(input_api, output_api):
291 outdir = tempfile.mkdtemp()
292 # shell=True is necessary on Windows, as otherwise subprocess fails to find
293 # either 'gn' or 'vpython3' even if they are findable via PATH.
294 use_shell = input_api.is_windows
295 try:
296 try:
297 subprocess.check_output(['gn', 'gen', outdir], shell=use_shell)
298 except subprocess.CalledProcessError as e:
299 return [
300 output_api.PresubmitError(
301 'Unable to run gn gen for export_targets.py: %s' % e.output)
302 ]
303 export_target_script = os.path.join(input_api.PresubmitLocalPath(), 'scripts',
304 'export_targets.py')
305 try:
306 subprocess.check_output(
307 ['vpython3', export_target_script, outdir] + _PRIMARY_EXPORT_TARGETS,
308 stderr=subprocess.STDOUT,
309 shell=use_shell)
310 except subprocess.CalledProcessError as e:
311 if input_api.is_committing:
312 return [output_api.PresubmitError('export_targets.py failed: %s' % e.output)]
313 return [
314 output_api.PresubmitPromptWarning(
315 'export_targets.py failed, this may just be due to your local checkout: %s' %
316 e.output)
317 ]
318 return []
319 finally:
320 shutil.rmtree(outdir)
321
322
James Darpinian4dff9952019-12-10 17:05:37 -0800323def _CheckTabsInSourceFiles(input_api, output_api):
324 """Forbids tab characters in source files due to a WebKit repo requirement. """
325
326 def implementation_and_headers(f):
327 return input_api.FilterSourceFile(
Trevor David Blackf69aaf62020-10-05 21:50:10 +0000328 f, files_to_check=(r'.+%s' % _IMPLEMENTATION_AND_HEADER_EXTENSIONS,))
James Darpinian4dff9952019-12-10 17:05:37 -0800329
330 files_with_tabs = []
331 for f in input_api.AffectedSourceFiles(implementation_and_headers):
332 for (num, line) in f.ChangedContents():
333 if '\t' in line:
334 files_with_tabs.append(f)
335 break
336
337 if files_with_tabs:
338 return [
339 output_api.PresubmitError(
340 'Tab characters in source files.',
341 items=sorted(files_with_tabs),
342 long_text=
343 'Tab characters are forbidden in ANGLE source files because WebKit\'s Subversion\n'
344 'repository does not allow tab characters in source files.\n'
345 'Please remove tab characters from these files.')
346 ]
347 return []
348
349
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600350# https://stackoverflow.com/a/196392
351def is_ascii(s):
352 return all(ord(c) < 128 for c in s)
353
354
355def _CheckNonAsciiInSourceFiles(input_api, output_api):
356 """Forbids non-ascii characters in source files. """
357
358 def implementation_and_headers(f):
359 return input_api.FilterSourceFile(
Trevor David Blackf69aaf62020-10-05 21:50:10 +0000360 f, files_to_check=(r'.+%s' % _IMPLEMENTATION_AND_HEADER_EXTENSIONS,))
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600361
362 files_with_non_ascii = []
363 for f in input_api.AffectedSourceFiles(implementation_and_headers):
364 for (num, line) in f.ChangedContents():
365 if not is_ascii(line):
Jamie Madilla89750e2020-04-30 18:00:00 -0400366 files_with_non_ascii.append("%s: %s" % (f, line))
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600367 break
368
369 if files_with_non_ascii:
370 return [
371 output_api.PresubmitError(
372 'Non-ASCII characters in source files.',
373 items=sorted(files_with_non_ascii),
374 long_text='Non-ASCII characters are forbidden in ANGLE source files.\n'
375 'Please remove non-ASCII characters from these files.')
376 ]
377 return []
378
379
Jamie Madill73397e82019-01-09 10:33:16 -0500380def CheckChangeOnUpload(input_api, output_api):
381 results = []
James Darpinian4dff9952019-12-10 17:05:37 -0800382 results.extend(_CheckTabsInSourceFiles(input_api, output_api))
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600383 results.extend(_CheckNonAsciiInSourceFiles(input_api, output_api))
Jamie Madill73397e82019-01-09 10:33:16 -0500384 results.extend(_CheckCodeGeneration(input_api, output_api))
Jamie Madill9e438ee2019-07-05 08:44:23 -0400385 results.extend(_CheckChangeHasBugField(input_api, output_api))
Geoff Langd7d42392019-05-06 13:15:35 -0400386 results.extend(input_api.canned_checks.CheckChangeHasDescription(input_api, output_api))
Jamie Madill73397e82019-01-09 10:33:16 -0500387 results.extend(_CheckNewHeaderWithoutGnChange(input_api, output_api))
Brian Sheedycc4d8332019-09-11 17:26:00 -0700388 results.extend(_CheckExportValidity(input_api, output_api))
Jamie Madill9e438ee2019-07-05 08:44:23 -0400389 results.extend(
390 input_api.canned_checks.CheckPatchFormatted(
391 input_api, output_api, result_factory=output_api.PresubmitError))
Manh Nguyen2df3bf52020-06-17 17:07:47 -0400392 results.extend(_CheckCommitMessageFormatting(input_api, output_api))
Jamie Madill73397e82019-01-09 10:33:16 -0500393 return results
394
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500395
396def CheckChangeOnCommit(input_api, output_api):
Jamie Madilla89750e2020-04-30 18:00:00 -0400397 return CheckChangeOnUpload(input_api, output_api)