blob: dfced2dee078a48261e68b02cba68e2f5f50da1a [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 Nguyen75037212020-06-09 17:36:54 -040044 whitelist_strings = ['Revert "', 'Roll ', 'Reland ']
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040045 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()
Manh Nguyen75037212020-06-09 17:36:54 -040054 commit_msg_line_numbers = {}
55 for i in range(len(commit_msg_lines)):
56 commit_msg_line_numbers[commit_msg_lines[i]] = i + 1
Manh Nguyenebc6d0a2020-05-27 18:21:30 -040057 _PopBlankLines(commit_msg_lines, True)
58 _PopBlankLines(commit_msg_lines, False)
59 if len(commit_msg_lines) > 0:
60 for whitelist_string in whitelist_strings:
61 if commit_msg_lines[0].startswith(whitelist_string):
62 return []
63 errors = []
64 if git_output.find("\t") != -1:
65 errors.append(output_api.PresubmitError("Tabs are not allowed in commit message."))
66
67 # get rid of the last paragraph, which we assume to always be the tags
68 last_paragraph_line_count = 0
69 while len(commit_msg_lines) > 0 and not _IsLineBlank(commit_msg_lines[-1]):
70 last_paragraph_line_count += 1
71 commit_msg_lines.pop()
72 if last_paragraph_line_count == 0:
73 errors.append(
74 output_api.PresubmitError(
75 "Please ensure that there are tags (e.g., Bug:, Test:) in your description."))
76 if len(commit_msg_lines) > 0:
77 # pop the blank line between tag paragraph and description body
78 commit_msg_lines.pop()
79 if len(commit_msg_lines) > 0 and _IsLineBlank(commit_msg_lines[-1]):
80 errors.append(
81 output_api.PresubmitError('Please ensure that there exists only 1 blank line '
82 'between tags and description body.'))
83 # pop all the remaining blank lines between tag and description body
84 _PopBlankLines(commit_msg_lines, True)
85 if len(commit_msg_lines) == 0:
86 errors.append(
87 output_api.PresubmitError('Please ensure that your description summary'
88 ' and description body are not blank.'))
89 return errors
90
91 if summary_linelength_warning_lower_limit <= len(commit_msg_lines[0]) \
92 <= summary_linelength_warning_upper_limit:
93 errors.append(
94 output_api.PresubmitPromptWarning(
95 "Your description summary should be on one line of " +
96 str(summary_linelength_warning_lower_limit - 1) + " or less characters."))
97 elif len(commit_msg_lines[0]) > summary_linelength_warning_upper_limit:
98 errors.append(
99 output_api.PresubmitError(
100 "Please ensure that your description summary is on one line of " +
101 str(summary_linelength_warning_lower_limit - 1) + " or less characters."))
102 commit_msg_lines.pop(0) # get rid of description summary
103 if len(commit_msg_lines) == 0:
104 return errors
105 if not _IsLineBlank(commit_msg_lines[0]):
106 errors.append(
107 output_api.PresubmitError('Please ensure the summary is only 1 line and '
108 ' there is 1 blank line between the summary '
109 'and description body.'))
110 else:
111 commit_msg_lines.pop(0) # pop first blank line
112 if len(commit_msg_lines) == 0:
113 return errors
114 if _IsLineBlank(commit_msg_lines[0]):
115 errors.append(
116 output_api.PresubmitError('Please ensure that there exists only 1 blank line '
117 'between description summary and description body.'))
118 # pop all the remaining blank lines between description summary and description body
119 _PopBlankLines(commit_msg_lines)
120
121 # loop through description body
122 while len(commit_msg_lines) > 0:
Manh Nguyen505b6eb2020-06-04 09:11:21 -0400123 line = commit_msg_lines.pop(0)
124 # lines starting with 4 spaces or lines without space(urls) are exempt from length check
125 if line.startswith(" ") or " " not in line:
126 continue
127 if len(line) > description_linelength_limit:
Manh Nguyenebc6d0a2020-05-27 18:21:30 -0400128 errors.append(
129 output_api.PresubmitError(
Manh Nguyen75037212020-06-09 17:36:54 -0400130 'Line ' + str(commit_msg_line_numbers[line]) + ' is too long.\n' + '"' + line +
131 '"\n' + 'Please wrap it to ' + str(description_linelength_limit) +
132 ' characters. ' +
133 "Lines without spaces or lines starting with 4 spaces are exempt."))
Manh Nguyenebc6d0a2020-05-27 18:21:30 -0400134 return errors
Manh Nguyenebc6d0a2020-05-27 18:21:30 -0400135 return errors
136
137
Jamie Madill9e438ee2019-07-05 08:44:23 -0400138def _CheckChangeHasBugField(input_api, output_api):
Shahbaz Youssefi634ee432019-12-16 11:39:10 -0500139 """Requires that the changelist have a Bug: field from a known project."""
Jamie Madill9e438ee2019-07-05 08:44:23 -0400140 bugs = input_api.change.BugsFromDescription()
141 if not bugs:
142 return [
Alexis Hetu4210e492019-10-10 14:22:03 -0400143 output_api.PresubmitError('Please ensure that your description contains:\n'
144 '"Bug: angleproject:[bug number]"\n'
145 'directly above the Change-Id tag.')
Jamie Madill9e438ee2019-07-05 08:44:23 -0400146 ]
Shahbaz Youssefi634ee432019-12-16 11:39:10 -0500147
148 # The bug must be in the form of "project:number". None is also accepted, which is used by
149 # rollers as well as in very minor changes.
150 if len(bugs) == 1 and bugs[0] == 'None':
Jamie Madill9e438ee2019-07-05 08:44:23 -0400151 return []
152
Geoff Lang13e4cdb2020-04-16 14:35:54 -0400153 projects = ['angleproject:', 'chromium:', 'dawn:', 'fuchsia:', 'skia:', 'swiftshader:', 'b/']
154 bug_regex = re.compile(r"([a-z]+[:/])(\d+)")
Shahbaz Youssefi634ee432019-12-16 11:39:10 -0500155 errors = []
156 extra_help = None
157
158 for bug in bugs:
159 if bug == 'None':
160 errors.append(
161 output_api.PresubmitError('Invalid bug tag "None" in presence of other bug tags.'))
162 continue
163
164 match = re.match(bug_regex, bug)
165 if match == None or bug != match.group(0) or match.group(1) not in projects:
166 errors.append(output_api.PresubmitError('Incorrect bug tag "' + bug + '".'))
167 if not extra_help:
168 extra_help = output_api.PresubmitError('Acceptable format is:\n\n'
169 ' Bug: project:bugnumber\n\n'
170 'Acceptable projects are:\n\n ' +
171 '\n '.join(projects))
172
173 if extra_help:
174 errors.append(extra_help)
175
176 return errors
177
Jamie Madill9e438ee2019-07-05 08:44:23 -0400178
Jamie Madill73397e82019-01-09 10:33:16 -0500179def _CheckCodeGeneration(input_api, output_api):
Jamie Madill04e9e552019-04-01 14:40:21 -0400180
181 class Msg(output_api.PresubmitError):
Geoff Langd7d42392019-05-06 13:15:35 -0400182 """Specialized error message"""
183
184 def __init__(self, message):
185 super(output_api.PresubmitError, self).__init__(
186 message,
Jamie Madill8dfc2b02019-11-25 15:12:58 -0500187 long_text='Please ensure your ANGLE repositiory is synced to tip-of-tree\n'
Jamie Madill9e438ee2019-07-05 08:44:23 -0400188 'and all ANGLE DEPS are fully up-to-date by running gclient sync.\n'
189 '\n'
Jamie Madill8dfc2b02019-11-25 15:12:58 -0500190 'If that fails, run scripts/run_code_generation.py to refresh generated hashes.\n'
191 '\n'
Jamie Madill9e438ee2019-07-05 08:44:23 -0400192 'If you are building ANGLE inside Chromium you must bootstrap ANGLE\n'
193 'before gclient sync. See the DevSetup documentation for more details.\n')
Jamie Madill04e9e552019-04-01 14:40:21 -0400194
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500195 code_gen_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
196 'scripts/run_code_generation.py')
197 cmd_name = 'run_code_generation'
198 cmd = [input_api.python_executable, code_gen_path, '--verify-no-dirty']
Geoff Langd7d42392019-05-06 13:15:35 -0400199 test_cmd = input_api.Command(name=cmd_name, cmd=cmd, kwargs={}, message=Msg)
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500200 if input_api.verbose:
201 print('Running ' + cmd_name)
202 return input_api.RunTests([test_cmd])
203
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500204
Jamie Madill73397e82019-01-09 10:33:16 -0500205# Taken directly from Chromium's PRESUBMIT.py
206def _CheckNewHeaderWithoutGnChange(input_api, output_api):
Geoff Langd7d42392019-05-06 13:15:35 -0400207 """Checks that newly added header files have corresponding GN changes.
Jamie Madill73397e82019-01-09 10:33:16 -0500208 Note that this is only a heuristic. To be precise, run script:
209 build/check_gn_headers.py.
210 """
211
Geoff Langd7d42392019-05-06 13:15:35 -0400212 def headers(f):
213 return input_api.FilterSourceFile(f, white_list=(r'.+%s' % _HEADER_EXTENSIONS,))
Jamie Madill73397e82019-01-09 10:33:16 -0500214
Geoff Langd7d42392019-05-06 13:15:35 -0400215 new_headers = []
216 for f in input_api.AffectedSourceFiles(headers):
217 if f.Action() != 'A':
218 continue
219 new_headers.append(f.LocalPath())
Jamie Madill73397e82019-01-09 10:33:16 -0500220
Geoff Langd7d42392019-05-06 13:15:35 -0400221 def gn_files(f):
222 return input_api.FilterSourceFile(f, white_list=(r'.+\.gn',))
Jamie Madill73397e82019-01-09 10:33:16 -0500223
Geoff Langd7d42392019-05-06 13:15:35 -0400224 all_gn_changed_contents = ''
225 for f in input_api.AffectedSourceFiles(gn_files):
226 for _, line in f.ChangedContents():
227 all_gn_changed_contents += line
Jamie Madill73397e82019-01-09 10:33:16 -0500228
Geoff Langd7d42392019-05-06 13:15:35 -0400229 problems = []
230 for header in new_headers:
231 basename = input_api.os_path.basename(header)
232 if basename not in all_gn_changed_contents:
233 problems.append(header)
Jamie Madill73397e82019-01-09 10:33:16 -0500234
Geoff Langd7d42392019-05-06 13:15:35 -0400235 if problems:
236 return [
237 output_api.PresubmitPromptWarning(
238 'Missing GN changes for new header files',
239 items=sorted(problems),
240 long_text='Please double check whether newly added header files need '
241 'corresponding changes in gn or gni files.\nThis checking is only a '
242 'heuristic. Run build/check_gn_headers.py to be precise.\n'
243 'Read https://crbug.com/661774 for more info.')
244 ]
245 return []
Jamie Madill73397e82019-01-09 10:33:16 -0500246
247
Brian Sheedycc4d8332019-09-11 17:26:00 -0700248def _CheckExportValidity(input_api, output_api):
249 outdir = tempfile.mkdtemp()
250 # shell=True is necessary on Windows, as otherwise subprocess fails to find
251 # either 'gn' or 'vpython3' even if they are findable via PATH.
252 use_shell = input_api.is_windows
253 try:
254 try:
255 subprocess.check_output(['gn', 'gen', outdir], shell=use_shell)
256 except subprocess.CalledProcessError as e:
257 return [
258 output_api.PresubmitError(
259 'Unable to run gn gen for export_targets.py: %s' % e.output)
260 ]
261 export_target_script = os.path.join(input_api.PresubmitLocalPath(), 'scripts',
262 'export_targets.py')
263 try:
264 subprocess.check_output(
265 ['vpython3', export_target_script, outdir] + _PRIMARY_EXPORT_TARGETS,
266 stderr=subprocess.STDOUT,
267 shell=use_shell)
268 except subprocess.CalledProcessError as e:
269 if input_api.is_committing:
270 return [output_api.PresubmitError('export_targets.py failed: %s' % e.output)]
271 return [
272 output_api.PresubmitPromptWarning(
273 'export_targets.py failed, this may just be due to your local checkout: %s' %
274 e.output)
275 ]
276 return []
277 finally:
278 shutil.rmtree(outdir)
279
280
James Darpinian4dff9952019-12-10 17:05:37 -0800281def _CheckTabsInSourceFiles(input_api, output_api):
282 """Forbids tab characters in source files due to a WebKit repo requirement. """
283
284 def implementation_and_headers(f):
285 return input_api.FilterSourceFile(
286 f, white_list=(r'.+%s' % _IMPLEMENTATION_AND_HEADER_EXTENSIONS,))
287
288 files_with_tabs = []
289 for f in input_api.AffectedSourceFiles(implementation_and_headers):
290 for (num, line) in f.ChangedContents():
291 if '\t' in line:
292 files_with_tabs.append(f)
293 break
294
295 if files_with_tabs:
296 return [
297 output_api.PresubmitError(
298 'Tab characters in source files.',
299 items=sorted(files_with_tabs),
300 long_text=
301 'Tab characters are forbidden in ANGLE source files because WebKit\'s Subversion\n'
302 'repository does not allow tab characters in source files.\n'
303 'Please remove tab characters from these files.')
304 ]
305 return []
306
307
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600308# https://stackoverflow.com/a/196392
309def is_ascii(s):
310 return all(ord(c) < 128 for c in s)
311
312
313def _CheckNonAsciiInSourceFiles(input_api, output_api):
314 """Forbids non-ascii characters in source files. """
315
316 def implementation_and_headers(f):
317 return input_api.FilterSourceFile(
318 f, white_list=(r'.+%s' % _IMPLEMENTATION_AND_HEADER_EXTENSIONS,))
319
320 files_with_non_ascii = []
321 for f in input_api.AffectedSourceFiles(implementation_and_headers):
322 for (num, line) in f.ChangedContents():
323 if not is_ascii(line):
Jamie Madilla89750e2020-04-30 18:00:00 -0400324 files_with_non_ascii.append("%s: %s" % (f, line))
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600325 break
326
327 if files_with_non_ascii:
328 return [
329 output_api.PresubmitError(
330 'Non-ASCII characters in source files.',
331 items=sorted(files_with_non_ascii),
332 long_text='Non-ASCII characters are forbidden in ANGLE source files.\n'
333 'Please remove non-ASCII characters from these files.')
334 ]
335 return []
336
337
Jamie Madill73397e82019-01-09 10:33:16 -0500338def CheckChangeOnUpload(input_api, output_api):
339 results = []
James Darpinian4dff9952019-12-10 17:05:37 -0800340 results.extend(_CheckTabsInSourceFiles(input_api, output_api))
Tim Van Pattenf7d44732020-04-29 16:55:01 -0600341 results.extend(_CheckNonAsciiInSourceFiles(input_api, output_api))
Jamie Madill73397e82019-01-09 10:33:16 -0500342 results.extend(_CheckCodeGeneration(input_api, output_api))
Jamie Madill9e438ee2019-07-05 08:44:23 -0400343 results.extend(_CheckChangeHasBugField(input_api, output_api))
Geoff Langd7d42392019-05-06 13:15:35 -0400344 results.extend(input_api.canned_checks.CheckChangeHasDescription(input_api, output_api))
Jamie Madill73397e82019-01-09 10:33:16 -0500345 results.extend(_CheckNewHeaderWithoutGnChange(input_api, output_api))
Brian Sheedycc4d8332019-09-11 17:26:00 -0700346 results.extend(_CheckExportValidity(input_api, output_api))
Jamie Madill9e438ee2019-07-05 08:44:23 -0400347 results.extend(
348 input_api.canned_checks.CheckPatchFormatted(
349 input_api, output_api, result_factory=output_api.PresubmitError))
Manh Nguyenebc6d0a2020-05-27 18:21:30 -0400350 results.extend(_CheckCommitMessageFormatting(input_api, output_api))
Jamie Madill73397e82019-01-09 10:33:16 -0500351 return results
352
Shahbaz Youssefi98a35502019-01-08 22:09:39 -0500353
354def CheckChangeOnCommit(input_api, output_api):
Jamie Madilla89750e2020-04-30 18:00:00 -0400355 return CheckChangeOnUpload(input_api, output_api)