123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- #!/usr/bin/env python3
- # Copyright 2012 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.
- """Makes sure that files include headers from allowed directories.
- Checks DEPS files in the source tree for rules, and applies those rules to
- "#include" and "import" directives in the .cpp, .java, and .proto source files.
- Any source file including something not permitted by the DEPS files will fail.
- See README.md for a detailed description of the DEPS format.
- """
- import os
- import optparse
- import re
- import sys
- import proto_checker
- import cpp_checker
- import java_checker
- import results
- from builddeps import DepsBuilder
- from rules import Rule, Rules
- def _IsTestFile(filename):
- """Does a rudimentary check to try to skip test files; this could be
- improved but is good enough for now.
- """
- return re.match(r'(test|mock|dummy)_.*|.*_[a-z]*test\.(cc|mm|java)', filename)
- class DepsChecker(DepsBuilder):
- """Parses include_rules from DEPS files and verifies files in the
- source tree against them.
- """
- def __init__(self,
- base_directory=None,
- extra_repos=[],
- verbose=False,
- being_tested=False,
- ignore_temp_rules=False,
- skip_tests=False,
- resolve_dotdot=True):
- """Creates a new DepsChecker.
- Args:
- base_directory: OS-compatible path to root of checkout, e.g. C:\chr\src.
- verbose: Set to true for debug output.
- being_tested: Set to true to ignore the DEPS file at
- buildtools/checkdeps/DEPS.
- ignore_temp_rules: Ignore rules that start with Rule.TEMP_ALLOW ("!").
- """
- DepsBuilder.__init__(
- self, base_directory, extra_repos, verbose, being_tested,
- ignore_temp_rules)
- self._skip_tests = skip_tests
- self._resolve_dotdot = resolve_dotdot
- self.results_formatter = results.NormalResultsFormatter(verbose)
- def Report(self):
- """Prints a report of results, and returns an exit code for the process."""
- if self.results_formatter.GetResults():
- self.results_formatter.PrintResults()
- return 1
- print('\nSUCCESS\n')
- return 0
- def CheckDirectory(self, start_dir):
- """Checks all relevant source files in the specified directory and
- its subdirectories for compliance with DEPS rules throughout the
- tree (starting at |self.base_directory|). |start_dir| must be a
- subdirectory of |self.base_directory|.
- On completion, self.results_formatter has the results of
- processing, and calling Report() will print a report of results.
- """
- java = java_checker.JavaChecker(self.base_directory, self.verbose)
- cpp = cpp_checker.CppChecker(
- self.verbose, self._resolve_dotdot, self.base_directory)
- proto = proto_checker.ProtoChecker(
- self.verbose, self._resolve_dotdot, self.base_directory)
- checkers = dict(
- (extension, checker)
- for checker in [java, cpp, proto] for extension in checker.EXTENSIONS)
- for rules, file_paths in self.GetAllRulesAndFiles(start_dir):
- for full_name in file_paths:
- if self._skip_tests and _IsTestFile(os.path.basename(full_name)):
- continue
- file_extension = os.path.splitext(full_name)[1]
- if not file_extension in checkers:
- continue
- checker = checkers[file_extension]
- file_status = checker.CheckFile(rules, full_name)
- if file_status.HasViolations():
- self.results_formatter.AddError(file_status)
- def CheckIncludesAndImports(self, added_lines, checker):
- """Check new import/#include statements added in the change
- being presubmit checked.
- Args:
- added_lines: ((file_path, (changed_line, changed_line, ...), ...)
- checker: CppChecker/JavaChecker/ProtoChecker checker instance
- Return:
- A list of tuples, (bad_file_path, rule_type, rule_description)
- where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and
- rule_description is human-readable. Empty if no problems.
- """
- problems = []
- for file_path, changed_lines in added_lines:
- if not checker.ShouldCheck(file_path):
- continue
- rules_for_file = self.GetDirectoryRules(os.path.dirname(file_path))
- if not rules_for_file:
- continue
- for line in changed_lines:
- is_include, violation = checker.CheckLine(
- rules_for_file, line, file_path, True)
- if not violation:
- continue
- rule_type = violation.violated_rule.allow
- if rule_type == Rule.ALLOW:
- continue
- violation_text = results.NormalResultsFormatter.FormatViolation(
- violation, self.verbose)
- problems.append((file_path, rule_type, violation_text))
- return problems
- def CheckAddedCppIncludes(self, added_includes):
- """This is used from PRESUBMIT.py to check new #include statements added in
- the change being presubmit checked.
- Args:
- added_includes: ((file_path, (include_line, include_line, ...), ...)
- Return:
- A list of tuples, (bad_file_path, rule_type, rule_description)
- where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and
- rule_description is human-readable. Empty if no problems.
- """
- return self.CheckIncludesAndImports(
- added_includes, cpp_checker.CppChecker(self.verbose))
- def CheckAddedJavaImports(self, added_imports,
- allow_multiple_definitions=None):
- """This is used from PRESUBMIT.py to check new import statements added in
- the change being presubmit checked.
- Args:
- added_imports: ((file_path, (import_line, import_line, ...), ...)
- allow_multiple_definitions: [file_name, file_name, ...]. List of java
- file names allowing multiple definitions in
- presubmit check.
- Return:
- A list of tuples, (bad_file_path, rule_type, rule_description)
- where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and
- rule_description is human-readable. Empty if no problems.
- """
- return self.CheckIncludesAndImports(
- added_imports,
- java_checker.JavaChecker(self.base_directory, self.verbose,
- added_imports, allow_multiple_definitions))
- def CheckAddedProtoImports(self, added_imports):
- """This is used from PRESUBMIT.py to check new #import statements added in
- the change being presubmit checked.
- Args:
- added_imports : ((file_path, (import_line, import_line, ...), ...)
- Return:
- A list of tuples, (bad_file_path, rule_type, rule_description)
- where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and
- rule_description is human-readable. Empty if no problems.
- """
- return self.CheckIncludesAndImports(
- added_imports, proto_checker.ProtoChecker(
- verbose=self.verbose, root_dir=self.base_directory))
- def PrintUsage():
- print("""Usage: python checkdeps.py [--root <root>] [tocheck]
- --root ROOT Specifies the repository root. This defaults to "../../.."
- relative to the script file. This will be correct given the
- normal location of the script in "<root>/buildtools/checkdeps".
- --(others) There are a few lesser-used options; run with --help to show them.
- tocheck Specifies the directory, relative to root, to check. This defaults
- to "." so it checks everything.
- Examples:
- python checkdeps.py
- python checkdeps.py --root c:\\source chrome""")
- def main():
- option_parser = optparse.OptionParser()
- option_parser.add_option(
- '', '--root',
- default='', dest='base_directory',
- help='Specifies the repository root. This defaults '
- 'to "../../.." relative to the script file, which '
- 'will normally be the repository root.')
- option_parser.add_option(
- '', '--extra-repos',
- action='append', dest='extra_repos', default=[],
- help='Specifies extra repositories relative to root repository.')
- option_parser.add_option(
- '', '--ignore-temp-rules',
- action='store_true', dest='ignore_temp_rules', default=False,
- help='Ignore !-prefixed (temporary) rules.')
- option_parser.add_option(
- '', '--generate-temp-rules',
- action='store_true', dest='generate_temp_rules', default=False,
- help='Print rules to temporarily allow files that fail '
- 'dependency checking.')
- option_parser.add_option(
- '', '--count-violations',
- action='store_true', dest='count_violations', default=False,
- help='Count #includes in violation of intended rules.')
- option_parser.add_option(
- '', '--skip-tests',
- action='store_true', dest='skip_tests', default=False,
- help='Skip checking test files (best effort).')
- option_parser.add_option(
- '-v', '--verbose',
- action='store_true', default=False,
- help='Print debug logging')
- option_parser.add_option(
- '', '--json',
- help='Path to JSON output file')
- option_parser.add_option(
- '', '--no-resolve-dotdot',
- action='store_false', dest='resolve_dotdot', default=True,
- help='resolve leading ../ in include directive paths relative '
- 'to the file perfoming the inclusion.')
- options, args = option_parser.parse_args()
- deps_checker = DepsChecker(options.base_directory,
- extra_repos=options.extra_repos,
- verbose=options.verbose,
- ignore_temp_rules=options.ignore_temp_rules,
- skip_tests=options.skip_tests,
- resolve_dotdot=options.resolve_dotdot)
- base_directory = deps_checker.base_directory # Default if needed, normalized
- # Figure out which directory we have to check.
- start_dir = base_directory
- if len(args) == 1:
- # Directory specified. Start here. It's supposed to be relative to the
- # base directory.
- start_dir = os.path.abspath(os.path.join(base_directory, args[0]))
- elif len(args) >= 2 or (options.generate_temp_rules and
- options.count_violations):
- # More than one argument, or incompatible flags, we don't handle this.
- PrintUsage()
- return 1
- if not start_dir.startswith(deps_checker.base_directory):
- print('Directory to check must be a subdirectory of the base directory,')
- print('but %s is not a subdirectory of %s' % (start_dir, base_directory))
- return 1
- print('Using base directory:', base_directory)
- print('Checking:', start_dir)
- if options.generate_temp_rules:
- deps_checker.results_formatter = results.TemporaryRulesFormatter()
- elif options.count_violations:
- deps_checker.results_formatter = results.CountViolationsFormatter()
- if options.json:
- deps_checker.results_formatter = results.JSONResultsFormatter(
- options.json, deps_checker.results_formatter)
- deps_checker.CheckDirectory(start_dir)
- return deps_checker.Report()
- if '__main__' == __name__:
- sys.exit(main())
|