# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Presubmit script for pdfium.
See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details about the presubmit API built into depot_tools.
"""
LINT_FILTERS = [
# Rvalue ref checks are unreliable.
'-build/c++11',
# Need to fix header names not matching cpp names.
'-build/include_order',
# Too many to fix at the moment.
'-readability/casting',
# Need to refactor large methods to fix.
'-readability/fn_size',
# Lots of usage to fix first.
'-runtime/int',
# Lots of non-const references need to be fixed
'-runtime/references',
# We are not thread safe, so this will never pass.
'-runtime/threadsafe_fn',
# Figure out how to deal with #defines that git cl format creates.
'-whitespace/indent',
]
_INCLUDE_ORDER_WARNING = (
'Your #include order seems to be broken. Remember to use the right '
'collation (LC_COLLATE=C) and check\nhttps://google.github.io/styleguide/'
'cppguide.html#Names_and_Order_of_Includes')
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.
"""
import sys
# 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
except ImportError:
return [output_api.PresubmitError(
'Unable to run checkdeps, does pdfium/buildtools/checkdeps exist?')]
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 _CheckIncludeOrderForScope(scope, input_api, file_path, changed_linenums):
"""Checks that the lines in scope occur in the right order.
1. C system files in alphabetical order
2. C++ system files in alphabetical order
3. Project's .h files
"""
c_system_include_pattern = input_api.re.compile(r'\s*#include <.*\.h>')
cpp_system_include_pattern = input_api.re.compile(r'\s*#include <.*>')
custom_include_pattern = input_api.re.compile(r'\s*#include ".*')
C_SYSTEM_INCLUDES, CPP_SYSTEM_INCLUDES, CUSTOM_INCLUDES = range(3)
state = C_SYSTEM_INCLUDES
previous_line = ''
previous_line_num = 0
problem_linenums = []
out_of_order = " - line belongs before previous line"
for line_num, line in scope:
if c_system_include_pattern.match(line):
if state != C_SYSTEM_INCLUDES:
problem_linenums.append((line_num, previous_line_num,
" - C system include file in wrong block"))
elif previous_line and previous_line > line:
problem_linenums.append((line_num, previous_line_num,
out_of_order))
elif cpp_system_include_pattern.match(line):
if state == C_SYSTEM_INCLUDES:
state = CPP_SYSTEM_INCLUDES
elif state == CUSTOM_INCLUDES:
problem_linenums.append((line_num, previous_line_num,
" - c++ system include file in wrong block"))
elif previous_line and previous_line > line:
problem_linenums.append((line_num, previous_line_num, out_of_order))
elif custom_include_pattern.match(line):
if state != CUSTOM_INCLUDES:
state = CUSTOM_INCLUDES
elif previous_line and previous_line > line:
problem_linenums.append((line_num, previous_line_num, out_of_order))
else:
problem_linenums.append((line_num, previous_line_num,
"Unknown include type"))
previous_line = line
previous_line_num = line_num
warnings = []
for (line_num, previous_line_num, failure_type) in problem_linenums:
if line_num in changed_linenums or previous_line_num in changed_linenums:
warnings.append(' %s:%d:%s' % (file_path, line_num, failure_type))
return warnings
def _CheckIncludeOrderInFile(input_api, f, changed_linenums):
"""Checks the #include order for the given file f."""
system_include_pattern = input_api.re.compile(r'\s*#include \<.*')
# Exclude the following includes from the check:
# 1) #include <.../...>, e.g., <sys/...> includes often need to appear in a
# specific order.
# 2) <atlbase.h>, "build/build_config.h"
excluded_include_pattern = input_api.re.compile(
r'\s*#include (\<.*/.*|\<atlbase\.h\>|"build/build_config.h")')
custom_include_pattern = input_api.re.compile(r'\s*#include "(?P<FILE>.*)"')
# Match the final or penultimate token if it is xxxtest so we can ignore it
# when considering the special first include.
test_file_tag_pattern = input_api.re.compile(
r'_[a-z]+test(?=(_[a-zA-Z0-9]+)?\.)')
if_pattern = input_api.re.compile(
r'\s*#\s*(if|elif|else|endif|define|undef).*')
# Some files need specialized order of includes; exclude such files from this
# check.
uncheckable_includes_pattern = input_api.re.compile(
r'\s*#include '
'("ipc/.*macros\.h"|<windows\.h>|".*gl.*autogen.h")\s*')
contents = f.NewContents()
warnings = []
line_num = 0
# Handle the special first include. If the first include file is
# some/path/file.h, the corresponding including file can be some/path/file.cc,
# some/other/path/file.cc, some/path/file_platform.cc, some/path/file-suffix.h
# etc. It's also possible that no special first include exists.
# If the included file is some/path/file_platform.h the including file could
# also be some/path/file_xxxtest_platform.h.
including_file_base_name = test_file_tag_pattern.sub(
'', input_api.os_path.basename(f.LocalPath()))
for line in contents:
line_num += 1
if system_include_pattern.match(line):
# No special first include -> process the line again along with normal
# includes.
line_num -= 1
break
match = custom_include_pattern.match(line)
if match:
match_dict = match.groupdict()
header_basename = test_file_tag_pattern.sub(
'', input_api.os_path.basename(match_dict['FILE'])).replace('.h', '')
if header_basename not in including_file_base_name:
# No special first include -> process the line again along with normal
# includes.
line_num -= 1
break
# Split into scopes: Each region between #if and #endif is its own scope.
scopes = []
current_scope = []
for line in contents[line_num:]:
line_num += 1
if uncheckable_includes_pattern.match(line):
continue
if if_pattern.match(line):
scopes.append(current_scope)
current_scope = []
elif ((system_include_pattern.match(line) or
custom_include_pattern.match(line)) and
not excluded_include_pattern.match(line)):
current_scope.append((line_num, line))
scopes.append(current_scope)
for scope in scopes:
warnings.extend(_CheckIncludeOrderForScope(scope, input_api, f.LocalPath(),
changed_linenums))
return warnings
def _CheckIncludeOrder(input_api, output_api):
"""Checks that the #include order is correct.
1. The corresponding header for source files.
2. C system files in alphabetical order
3. C++ system files in alphabetical order
4. Project's .h files in alphabetical order
Each region separated by #if, #elif, #else, #endif, #define and #undef follows
these rules separately.
"""
def FileFilterIncludeOrder(affected_file):
black_list = (input_api.DEFAULT_BLACK_LIST)
return input_api.FilterSourceFile(affected_file, black_list=black_list)
warnings = []
for f in input_api.AffectedFiles(file_filter=FileFilterIncludeOrder):
if f.LocalPath().endswith(('.cc', '.cpp', '.h', '.mm')):
changed_linenums = set(line_num for line_num, _ in f.ChangedContents())
warnings.extend(_CheckIncludeOrderInFile(input_api, f, changed_linenums))
results = []
if warnings:
results.append(output_api.PresubmitPromptOrNotify(_INCLUDE_ORDER_WARNING,
warnings))
return results
def CheckChangeOnUpload(input_api, output_api):
results = []
results += _CheckUnwantedDependencies(input_api, output_api)
results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
results += input_api.canned_checks.CheckChangeLintsClean(
input_api, output_api, None, LINT_FILTERS)
results += _CheckIncludeOrder(input_api, output_api)
return results