blob: c3e989f18b6e1473aca9397bb77ff159ade45dd6 [file] [log] [blame]
# Copyright 2019 The ANGLE Project Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Top-level presubmit script for code generation.
See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details on the presubmit API built into depot_tools.
"""
import os
import re
import shutil
import subprocess
import sys
import tempfile
# Fragment of a regular expression that matches C++ and Objective-C++ implementation files and headers.
_IMPLEMENTATION_AND_HEADER_EXTENSIONS = r'\.(cc|cpp|cxx|mm|h|hpp|hxx)$'
# Fragment of a regular expression that matches C++ and Objective-C++ header files.
_HEADER_EXTENSIONS = r'\.(h|hpp|hxx)$'
_PRIMARY_EXPORT_TARGETS = [
'//:libEGL',
'//:libGLESv1_CM',
'//:libGLESv2',
'//:translator',
]
def _CheckChangeHasBugField(input_api, output_api):
"""Requires that the changelist have a Bug: field from a known project."""
bugs = input_api.change.BugsFromDescription()
if not bugs:
return [
output_api.PresubmitError('Please ensure that your description contains:\n'
'"Bug: angleproject:[bug number]"\n'
'directly above the Change-Id tag.')
]
# The bug must be in the form of "project:number". None is also accepted, which is used by
# rollers as well as in very minor changes.
if len(bugs) == 1 and bugs[0] == 'None':
return []
projects = ['angleproject:', 'chromium:', 'dawn:', 'fuchsia:', 'skia:', 'swiftshader:', 'b/']
bug_regex = re.compile(r"([a-z]+[:/])(\d+)")
errors = []
extra_help = None
for bug in bugs:
if bug == 'None':
errors.append(
output_api.PresubmitError('Invalid bug tag "None" in presence of other bug tags.'))
continue
match = re.match(bug_regex, bug)
if match == None or bug != match.group(0) or match.group(1) not in projects:
errors.append(output_api.PresubmitError('Incorrect bug tag "' + bug + '".'))
if not extra_help:
extra_help = output_api.PresubmitError('Acceptable format is:\n\n'
' Bug: project:bugnumber\n\n'
'Acceptable projects are:\n\n ' +
'\n '.join(projects))
if extra_help:
errors.append(extra_help)
return errors
def _CheckCodeGeneration(input_api, output_api):
class Msg(output_api.PresubmitError):
"""Specialized error message"""
def __init__(self, message):
super(output_api.PresubmitError, self).__init__(
message,
long_text='Please ensure your ANGLE repositiory is synced to tip-of-tree\n'
'and all ANGLE DEPS are fully up-to-date by running gclient sync.\n'
'\n'
'If that fails, run scripts/run_code_generation.py to refresh generated hashes.\n'
'\n'
'If you are building ANGLE inside Chromium you must bootstrap ANGLE\n'
'before gclient sync. See the DevSetup documentation for more details.\n')
code_gen_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
'scripts/run_code_generation.py')
cmd_name = 'run_code_generation'
cmd = [input_api.python_executable, code_gen_path, '--verify-no-dirty']
test_cmd = input_api.Command(name=cmd_name, cmd=cmd, kwargs={}, message=Msg)
if input_api.verbose:
print('Running ' + cmd_name)
return input_api.RunTests([test_cmd])
# Taken directly from Chromium's PRESUBMIT.py
def _CheckNewHeaderWithoutGnChange(input_api, output_api):
"""Checks that newly added header files have corresponding GN changes.
Note that this is only a heuristic. To be precise, run script:
build/check_gn_headers.py.
"""
def headers(f):
return input_api.FilterSourceFile(f, white_list=(r'.+%s' % _HEADER_EXTENSIONS,))
new_headers = []
for f in input_api.AffectedSourceFiles(headers):
if f.Action() != 'A':
continue
new_headers.append(f.LocalPath())
def gn_files(f):
return input_api.FilterSourceFile(f, white_list=(r'.+\.gn',))
all_gn_changed_contents = ''
for f in input_api.AffectedSourceFiles(gn_files):
for _, line in f.ChangedContents():
all_gn_changed_contents += line
problems = []
for header in new_headers:
basename = input_api.os_path.basename(header)
if basename not in all_gn_changed_contents:
problems.append(header)
if problems:
return [
output_api.PresubmitPromptWarning(
'Missing GN changes for new header files',
items=sorted(problems),
long_text='Please double check whether newly added header files need '
'corresponding changes in gn or gni files.\nThis checking is only a '
'heuristic. Run build/check_gn_headers.py to be precise.\n'
'Read https://crbug.com/661774 for more info.')
]
return []
def _CheckExportValidity(input_api, output_api):
outdir = tempfile.mkdtemp()
# shell=True is necessary on Windows, as otherwise subprocess fails to find
# either 'gn' or 'vpython3' even if they are findable via PATH.
use_shell = input_api.is_windows
try:
try:
subprocess.check_output(['gn', 'gen', outdir], shell=use_shell)
except subprocess.CalledProcessError as e:
return [
output_api.PresubmitError(
'Unable to run gn gen for export_targets.py: %s' % e.output)
]
export_target_script = os.path.join(input_api.PresubmitLocalPath(), 'scripts',
'export_targets.py')
try:
subprocess.check_output(
['vpython3', export_target_script, outdir] + _PRIMARY_EXPORT_TARGETS,
stderr=subprocess.STDOUT,
shell=use_shell)
except subprocess.CalledProcessError as e:
if input_api.is_committing:
return [output_api.PresubmitError('export_targets.py failed: %s' % e.output)]
return [
output_api.PresubmitPromptWarning(
'export_targets.py failed, this may just be due to your local checkout: %s' %
e.output)
]
return []
finally:
shutil.rmtree(outdir)
def _CheckTabsInSourceFiles(input_api, output_api):
"""Forbids tab characters in source files due to a WebKit repo requirement. """
def implementation_and_headers(f):
return input_api.FilterSourceFile(
f, white_list=(r'.+%s' % _IMPLEMENTATION_AND_HEADER_EXTENSIONS,))
files_with_tabs = []
for f in input_api.AffectedSourceFiles(implementation_and_headers):
for (num, line) in f.ChangedContents():
if '\t' in line:
files_with_tabs.append(f)
break
if files_with_tabs:
return [
output_api.PresubmitError(
'Tab characters in source files.',
items=sorted(files_with_tabs),
long_text=
'Tab characters are forbidden in ANGLE source files because WebKit\'s Subversion\n'
'repository does not allow tab characters in source files.\n'
'Please remove tab characters from these files.')
]
return []
# https://stackoverflow.com/a/196392
def is_ascii(s):
return all(ord(c) < 128 for c in s)
def _CheckNonAsciiInSourceFiles(input_api, output_api):
"""Forbids non-ascii characters in source files. """
def implementation_and_headers(f):
return input_api.FilterSourceFile(
f, white_list=(r'.+%s' % _IMPLEMENTATION_AND_HEADER_EXTENSIONS,))
files_with_non_ascii = []
for f in input_api.AffectedSourceFiles(implementation_and_headers):
for (num, line) in f.ChangedContents():
if not is_ascii(line):
files_with_non_ascii.append("%s: %s" % (f, line))
break
if files_with_non_ascii:
return [
output_api.PresubmitError(
'Non-ASCII characters in source files.',
items=sorted(files_with_non_ascii),
long_text='Non-ASCII characters are forbidden in ANGLE source files.\n'
'Please remove non-ASCII characters from these files.')
]
return []
def CheckChangeOnUpload(input_api, output_api):
results = []
results.extend(_CheckTabsInSourceFiles(input_api, output_api))
results.extend(_CheckNonAsciiInSourceFiles(input_api, output_api))
results.extend(_CheckCodeGeneration(input_api, output_api))
results.extend(_CheckChangeHasBugField(input_api, output_api))
results.extend(input_api.canned_checks.CheckChangeHasDescription(input_api, output_api))
results.extend(_CheckNewHeaderWithoutGnChange(input_api, output_api))
results.extend(_CheckExportValidity(input_api, output_api))
results.extend(
input_api.canned_checks.CheckPatchFormatted(
input_api, output_api, result_factory=output_api.PresubmitError))
return results
def CheckChangeOnCommit(input_api, output_api):
return CheckChangeOnUpload(input_api, output_api)