You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
466 lines
17 KiB
466 lines
17 KiB
# Copyright 2012 the V8 project authors. All rights reserved. |
|
# Redistribution and use in source and binary forms, with or without |
|
# modification, are permitted provided that the following conditions are |
|
# met: |
|
# |
|
# * Redistributions of source code must retain the above copyright |
|
# notice, this list of conditions and the following disclaimer. |
|
# * Redistributions in binary form must reproduce the above |
|
# copyright notice, this list of conditions and the following |
|
# disclaimer in the documentation and/or other materials provided |
|
# with the distribution. |
|
# * Neither the name of Google Inc. nor the names of its |
|
# contributors may be used to endorse or promote products derived |
|
# from this software without specific prior written permission. |
|
# |
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
|
"""Top-level presubmit script for V8. |
|
|
|
See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts |
|
for more details about the presubmit API built into gcl. |
|
""" |
|
|
|
import json |
|
import re |
|
import sys |
|
|
|
|
|
_EXCLUDED_PATHS = ( |
|
r"^test[\\\/].*", |
|
r"^testing[\\\/].*", |
|
r"^third_party[\\\/].*", |
|
r"^tools[\\\/].*", |
|
) |
|
|
|
|
|
# Regular expression that matches code which should not be run through cpplint. |
|
_NO_LINT_PATHS = ( |
|
r'src[\\\/]base[\\\/]export-template\.h', |
|
) |
|
|
|
|
|
# Regular expression that matches code only used for test binaries |
|
# (best effort). |
|
_TEST_CODE_EXCLUDED_PATHS = ( |
|
r'.+-unittest\.cc', |
|
# Has a method VisitForTest(). |
|
r'src[\\\/]compiler[\\\/]ast-graph-builder\.cc', |
|
# Test extension. |
|
r'src[\\\/]extensions[\\\/]gc-extension\.cc', |
|
) |
|
|
|
|
|
_TEST_ONLY_WARNING = ( |
|
'You might be calling functions intended only for testing from\n' |
|
'production code. It is OK to ignore this warning if you know what\n' |
|
'you are doing, as the heuristics used to detect the situation are\n' |
|
'not perfect. The commit queue will not block on this warning.') |
|
|
|
|
|
def _V8PresubmitChecks(input_api, output_api): |
|
"""Runs the V8 presubmit checks.""" |
|
import sys |
|
sys.path.append(input_api.os_path.join( |
|
input_api.PresubmitLocalPath(), 'tools')) |
|
from v8_presubmit import CppLintProcessor |
|
from v8_presubmit import SourceProcessor |
|
from v8_presubmit import StatusFilesProcessor |
|
|
|
def FilterFile(affected_file): |
|
return input_api.FilterSourceFile( |
|
affected_file, |
|
white_list=None, |
|
black_list=_NO_LINT_PATHS) |
|
|
|
results = [] |
|
if not CppLintProcessor().RunOnFiles( |
|
input_api.AffectedFiles(file_filter=FilterFile, include_deletes=False)): |
|
results.append(output_api.PresubmitError("C++ lint check failed")) |
|
if not SourceProcessor().RunOnFiles( |
|
input_api.AffectedFiles(include_deletes=False)): |
|
results.append(output_api.PresubmitError( |
|
"Copyright header, trailing whitespaces and two empty lines " \ |
|
"between declarations check failed")) |
|
if not StatusFilesProcessor().RunOnFiles( |
|
input_api.AffectedFiles(include_deletes=True)): |
|
results.append(output_api.PresubmitError("Status file check failed")) |
|
results.extend(input_api.canned_checks.CheckAuthorizedAuthor( |
|
input_api, output_api, bot_whitelist=[ |
|
'v8-ci-autoroll-builder@chops-service-accounts.iam.gserviceaccount.com' |
|
])) |
|
return results |
|
|
|
|
|
def _CheckUnwantedDependencies(input_api, output_api): |
|
"""Runs checkdeps on #include statements added in this |
|
change. Breaking - rules is an error, breaking ! rules is a |
|
warning. |
|
""" |
|
# We need to wait until we have an input_api object and use this |
|
# roundabout construct to import checkdeps because this file is |
|
# eval-ed and thus doesn't have __file__. |
|
original_sys_path = sys.path |
|
try: |
|
sys.path = sys.path + [input_api.os_path.join( |
|
input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')] |
|
import checkdeps |
|
from cpp_checker import CppChecker |
|
from rules import Rule |
|
finally: |
|
# Restore sys.path to what it was before. |
|
sys.path = original_sys_path |
|
|
|
added_includes = [] |
|
for f in input_api.AffectedFiles(): |
|
if not CppChecker.IsCppFile(f.LocalPath()): |
|
continue |
|
|
|
changed_lines = [line for line_num, line in f.ChangedContents()] |
|
added_includes.append([f.LocalPath(), changed_lines]) |
|
|
|
deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath()) |
|
|
|
error_descriptions = [] |
|
warning_descriptions = [] |
|
for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes( |
|
added_includes): |
|
description_with_path = '%s\n %s' % (path, rule_description) |
|
if rule_type == Rule.DISALLOW: |
|
error_descriptions.append(description_with_path) |
|
else: |
|
warning_descriptions.append(description_with_path) |
|
|
|
results = [] |
|
if error_descriptions: |
|
results.append(output_api.PresubmitError( |
|
'You added one or more #includes that violate checkdeps rules.', |
|
error_descriptions)) |
|
if warning_descriptions: |
|
results.append(output_api.PresubmitPromptOrNotify( |
|
'You added one or more #includes of files that are temporarily\n' |
|
'allowed but being removed. Can you avoid introducing the\n' |
|
'#include? See relevant DEPS file(s) for details and contacts.', |
|
warning_descriptions)) |
|
return results |
|
|
|
|
|
def _CheckHeadersHaveIncludeGuards(input_api, output_api): |
|
"""Ensures that all header files have include guards.""" |
|
file_inclusion_pattern = r'src/.+\.h' |
|
|
|
def FilterFile(affected_file): |
|
black_list = (_EXCLUDED_PATHS + |
|
input_api.DEFAULT_BLACK_LIST) |
|
return input_api.FilterSourceFile( |
|
affected_file, |
|
white_list=(file_inclusion_pattern, ), |
|
black_list=black_list) |
|
|
|
leading_src_pattern = input_api.re.compile(r'^src/') |
|
dash_dot_slash_pattern = input_api.re.compile(r'[-./]') |
|
def PathToGuardMacro(path): |
|
"""Guards should be of the form V8_PATH_TO_FILE_WITHOUT_SRC_H_.""" |
|
x = input_api.re.sub(leading_src_pattern, 'v8_', path) |
|
x = input_api.re.sub(dash_dot_slash_pattern, '_', x) |
|
x = x.upper() + "_" |
|
return x |
|
|
|
problems = [] |
|
for f in input_api.AffectedSourceFiles(FilterFile): |
|
local_path = f.LocalPath() |
|
guard_macro = PathToGuardMacro(local_path) |
|
guard_patterns = [ |
|
input_api.re.compile(r'^#ifndef ' + guard_macro + '$'), |
|
input_api.re.compile(r'^#define ' + guard_macro + '$'), |
|
input_api.re.compile(r'^#endif // ' + guard_macro + '$')] |
|
skip_check_pattern = input_api.re.compile( |
|
r'^// PRESUBMIT_INTENTIONALLY_MISSING_INCLUDE_GUARD') |
|
found_patterns = [ False, False, False ] |
|
file_omitted = False |
|
|
|
for line in f.NewContents(): |
|
for i in range(len(guard_patterns)): |
|
if guard_patterns[i].match(line): |
|
found_patterns[i] = True |
|
if skip_check_pattern.match(line): |
|
file_omitted = True |
|
break |
|
|
|
if not file_omitted and not all(found_patterns): |
|
problems.append( |
|
'%s: Missing include guard \'%s\'' % (local_path, guard_macro)) |
|
|
|
if problems: |
|
return [output_api.PresubmitError( |
|
'You added one or more header files without an appropriate\n' |
|
'include guard. Add the include guard {#ifndef,#define,#endif}\n' |
|
'triplet or omit the check entirely through the magic comment:\n' |
|
'"// PRESUBMIT_INTENTIONALLY_MISSING_INCLUDE_GUARD".', problems)] |
|
else: |
|
return [] |
|
|
|
|
|
# TODO(mstarzinger): Similar checking should be made available as part of |
|
# tools/presubmit.py (note that tools/check-inline-includes.sh exists). |
|
def _CheckNoInlineHeaderIncludesInNormalHeaders(input_api, output_api): |
|
"""Attempts to prevent inclusion of inline headers into normal header |
|
files. This tries to establish a layering where inline headers can be |
|
included by other inline headers or compilation units only.""" |
|
file_inclusion_pattern = r'(?!.+-inl\.h).+\.h' |
|
include_directive_pattern = input_api.re.compile(r'#include ".+-inl.h"') |
|
include_error = ( |
|
'You are including an inline header (e.g. foo-inl.h) within a normal\n' |
|
'header (e.g. bar.h) file. This violates layering of dependencies.') |
|
|
|
def FilterFile(affected_file): |
|
black_list = (_EXCLUDED_PATHS + |
|
input_api.DEFAULT_BLACK_LIST) |
|
return input_api.FilterSourceFile( |
|
affected_file, |
|
white_list=(file_inclusion_pattern, ), |
|
black_list=black_list) |
|
|
|
problems = [] |
|
for f in input_api.AffectedSourceFiles(FilterFile): |
|
local_path = f.LocalPath() |
|
for line_number, line in f.ChangedContents(): |
|
if (include_directive_pattern.search(line)): |
|
problems.append( |
|
'%s:%d\n %s' % (local_path, line_number, line.strip())) |
|
|
|
if problems: |
|
return [output_api.PresubmitError(include_error, problems)] |
|
else: |
|
return [] |
|
|
|
|
|
def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api): |
|
"""Attempts to prevent use of functions intended only for testing in |
|
non-testing code. For now this is just a best-effort implementation |
|
that ignores header files and may have some false positives. A |
|
better implementation would probably need a proper C++ parser. |
|
""" |
|
# We only scan .cc files, as the declaration of for-testing functions in |
|
# header files are hard to distinguish from calls to such functions without a |
|
# proper C++ parser. |
|
file_inclusion_pattern = r'.+\.cc' |
|
|
|
base_function_pattern = r'[ :]test::[^\s]+|ForTest(ing)?|for_test(ing)?' |
|
inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' % base_function_pattern) |
|
comment_pattern = input_api.re.compile(r'//.*(%s)' % base_function_pattern) |
|
exclusion_pattern = input_api.re.compile( |
|
r'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % ( |
|
base_function_pattern, base_function_pattern)) |
|
|
|
def FilterFile(affected_file): |
|
black_list = (_EXCLUDED_PATHS + |
|
_TEST_CODE_EXCLUDED_PATHS + |
|
input_api.DEFAULT_BLACK_LIST) |
|
return input_api.FilterSourceFile( |
|
affected_file, |
|
white_list=(file_inclusion_pattern, ), |
|
black_list=black_list) |
|
|
|
problems = [] |
|
for f in input_api.AffectedSourceFiles(FilterFile): |
|
local_path = f.LocalPath() |
|
for line_number, line in f.ChangedContents(): |
|
if (inclusion_pattern.search(line) and |
|
not comment_pattern.search(line) and |
|
not exclusion_pattern.search(line)): |
|
problems.append( |
|
'%s:%d\n %s' % (local_path, line_number, line.strip())) |
|
|
|
if problems: |
|
return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)] |
|
else: |
|
return [] |
|
|
|
|
|
def _CommonChecks(input_api, output_api): |
|
"""Checks common to both upload and commit.""" |
|
results = [] |
|
# TODO(machenbach): Replace some of those checks, e.g. owners and copyright, |
|
# with the canned PanProjectChecks. Need to make sure that the checks all |
|
# pass on all existing files. |
|
results.extend(input_api.canned_checks.CheckOwnersFormat( |
|
input_api, output_api)) |
|
results.extend(input_api.canned_checks.CheckOwners( |
|
input_api, output_api)) |
|
results.extend(_CheckCommitMessageBugEntry(input_api, output_api)) |
|
results.extend(input_api.canned_checks.CheckPatchFormatted( |
|
input_api, output_api)) |
|
results.extend(input_api.canned_checks.CheckGenderNeutral( |
|
input_api, output_api)) |
|
results.extend(_V8PresubmitChecks(input_api, output_api)) |
|
results.extend(_CheckUnwantedDependencies(input_api, output_api)) |
|
results.extend( |
|
_CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api)) |
|
results.extend(_CheckHeadersHaveIncludeGuards(input_api, output_api)) |
|
results.extend( |
|
_CheckNoInlineHeaderIncludesInNormalHeaders(input_api, output_api)) |
|
results.extend(_CheckJSONFiles(input_api, output_api)) |
|
results.extend(_CheckMacroUndefs(input_api, output_api)) |
|
results.extend(input_api.RunTests( |
|
input_api.canned_checks.CheckVPythonSpec(input_api, output_api))) |
|
return results |
|
|
|
|
|
def _SkipTreeCheck(input_api, output_api): |
|
"""Check the env var whether we want to skip tree check. |
|
Only skip if include/v8-version.h has been updated.""" |
|
src_version = 'include/v8-version.h' |
|
if not input_api.AffectedSourceFiles( |
|
lambda file: file.LocalPath() == src_version): |
|
return False |
|
return input_api.environ.get('PRESUBMIT_TREE_CHECK') == 'skip' |
|
|
|
|
|
def _CheckCommitMessageBugEntry(input_api, output_api): |
|
"""Check that bug entries are well-formed in commit message.""" |
|
bogus_bug_msg = ( |
|
'Bogus BUG entry: %s. Please specify the issue tracker prefix and the ' |
|
'issue number, separated by a colon, e.g. v8:123 or chromium:12345.') |
|
results = [] |
|
for bug in (input_api.change.BUG or '').split(','): |
|
bug = bug.strip() |
|
if 'none'.startswith(bug.lower()): |
|
continue |
|
if ':' not in bug: |
|
try: |
|
if int(bug) > 100000: |
|
# Rough indicator for current chromium bugs. |
|
prefix_guess = 'chromium' |
|
else: |
|
prefix_guess = 'v8' |
|
results.append('BUG entry requires issue tracker prefix, e.g. %s:%s' % |
|
(prefix_guess, bug)) |
|
except ValueError: |
|
results.append(bogus_bug_msg % bug) |
|
elif not re.match(r'\w+:\d+', bug): |
|
results.append(bogus_bug_msg % bug) |
|
return [output_api.PresubmitError(r) for r in results] |
|
|
|
|
|
def _CheckJSONFiles(input_api, output_api): |
|
def FilterFile(affected_file): |
|
return input_api.FilterSourceFile( |
|
affected_file, |
|
white_list=(r'.+\.json',)) |
|
|
|
results = [] |
|
for f in input_api.AffectedFiles( |
|
file_filter=FilterFile, include_deletes=False): |
|
with open(f.LocalPath()) as j: |
|
try: |
|
json.load(j) |
|
except Exception as e: |
|
results.append( |
|
'JSON validation failed for %s. Error:\n%s' % (f.LocalPath(), e)) |
|
|
|
return [output_api.PresubmitError(r) for r in results] |
|
|
|
|
|
def _CheckMacroUndefs(input_api, output_api): |
|
""" |
|
Checks that each #define in a .cc file is eventually followed by an #undef. |
|
|
|
TODO(clemensh): This check should eventually be enabled for all cc files via |
|
tools/presubmit.py (https://crbug.com/v8/6811). |
|
""" |
|
def FilterFile(affected_file): |
|
# Skip header files, as they often define type lists which are used in |
|
# other files. |
|
white_list = (r'.+\.cc',r'.+\.cpp',r'.+\.c') |
|
return input_api.FilterSourceFile(affected_file, white_list=white_list) |
|
|
|
def TouchesMacros(f): |
|
for line in f.GenerateScmDiff().splitlines(): |
|
if not line.startswith('+') and not line.startswith('-'): |
|
continue |
|
if define_pattern.match(line[1:]) or undef_pattern.match(line[1:]): |
|
return True |
|
return False |
|
|
|
define_pattern = input_api.re.compile(r'#define (\w+)') |
|
undef_pattern = input_api.re.compile(r'#undef (\w+)') |
|
errors = [] |
|
for f in input_api.AffectedFiles( |
|
file_filter=FilterFile, include_deletes=False): |
|
if not TouchesMacros(f): |
|
continue |
|
|
|
defined_macros = dict() |
|
with open(f.LocalPath()) as fh: |
|
line_nr = 0 |
|
for line in fh: |
|
line_nr += 1 |
|
|
|
define_match = define_pattern.match(line) |
|
if define_match: |
|
name = define_match.group(1) |
|
defined_macros[name] = line_nr |
|
|
|
undef_match = undef_pattern.match(line) |
|
if undef_match: |
|
name = undef_match.group(1) |
|
if not name in defined_macros: |
|
errors.append('{}:{}: Macro named \'{}\' was not defined before.' |
|
.format(f.LocalPath(), line_nr, name)) |
|
else: |
|
del defined_macros[name] |
|
for name, line_nr in sorted(defined_macros.items(), key=lambda e: e[1]): |
|
errors.append('{}:{}: Macro missing #undef: {}' |
|
.format(f.LocalPath(), line_nr, name)) |
|
|
|
if errors: |
|
return [output_api.PresubmitPromptOrNotify( |
|
'Detected mismatches in #define / #undef in the file(s) where you ' |
|
'modified preprocessor macros.', |
|
errors)] |
|
return [] |
|
|
|
|
|
def CheckChangeOnUpload(input_api, output_api): |
|
results = [] |
|
results.extend(_CommonChecks(input_api, output_api)) |
|
return results |
|
|
|
|
|
def CheckChangeOnCommit(input_api, output_api): |
|
results = [] |
|
results.extend(_CommonChecks(input_api, output_api)) |
|
results.extend(input_api.canned_checks.CheckChangeHasDescription( |
|
input_api, output_api)) |
|
if not _SkipTreeCheck(input_api, output_api): |
|
results.extend(input_api.canned_checks.CheckTreeIsOpen( |
|
input_api, output_api, |
|
json_url='http://v8-status.appspot.com/current?format=json')) |
|
return results |
|
|
|
def PostUploadHook(cl, change, output_api): |
|
"""git cl upload will call this hook after the issue is created/modified. |
|
|
|
This hook adds a noi18n bot if the patch affects Intl. |
|
""" |
|
def affects_intl(f): |
|
return 'intl' in f.LocalPath() or 'test262' in f.LocalPath() |
|
if not change.AffectedFiles(file_filter=affects_intl): |
|
return [] |
|
return output_api.EnsureCQIncludeTrybotsAreAdded( |
|
cl, |
|
[ |
|
'luci.v8.try:v8_linux_noi18n_rel_ng' |
|
], |
|
'Automatically added noi18n trybots to run tests on CQ.')
|
|
|