1cb93a386Sopenharmony_ci#!/usr/bin/env python3 2cb93a386Sopenharmony_ci# Copyright 2012 The Chromium Authors. All rights reserved. 3cb93a386Sopenharmony_ci# Use of this source code is governed by a BSD-style license that can be 4cb93a386Sopenharmony_ci# found in the LICENSE file. 5cb93a386Sopenharmony_ci 6cb93a386Sopenharmony_ci"""Makes sure that files include headers from allowed directories. 7cb93a386Sopenharmony_ci 8cb93a386Sopenharmony_ciChecks DEPS files in the source tree for rules, and applies those rules to 9cb93a386Sopenharmony_ci"#include" and "import" directives in the .cpp, .java, and .proto source files. 10cb93a386Sopenharmony_ciAny source file including something not permitted by the DEPS files will fail. 11cb93a386Sopenharmony_ci 12cb93a386Sopenharmony_ciSee README.md for a detailed description of the DEPS format. 13cb93a386Sopenharmony_ci""" 14cb93a386Sopenharmony_ci 15cb93a386Sopenharmony_ci 16cb93a386Sopenharmony_ci 17cb93a386Sopenharmony_ciimport os 18cb93a386Sopenharmony_ciimport optparse 19cb93a386Sopenharmony_ciimport re 20cb93a386Sopenharmony_ciimport sys 21cb93a386Sopenharmony_ci 22cb93a386Sopenharmony_ciimport proto_checker 23cb93a386Sopenharmony_ciimport cpp_checker 24cb93a386Sopenharmony_ciimport java_checker 25cb93a386Sopenharmony_ciimport results 26cb93a386Sopenharmony_ci 27cb93a386Sopenharmony_cifrom builddeps import DepsBuilder 28cb93a386Sopenharmony_cifrom rules import Rule, Rules 29cb93a386Sopenharmony_ci 30cb93a386Sopenharmony_ci 31cb93a386Sopenharmony_cidef _IsTestFile(filename): 32cb93a386Sopenharmony_ci """Does a rudimentary check to try to skip test files; this could be 33cb93a386Sopenharmony_ci improved but is good enough for now. 34cb93a386Sopenharmony_ci """ 35cb93a386Sopenharmony_ci return re.match(r'(test|mock|dummy)_.*|.*_[a-z]*test\.(cc|mm|java)', filename) 36cb93a386Sopenharmony_ci 37cb93a386Sopenharmony_ci 38cb93a386Sopenharmony_ciclass DepsChecker(DepsBuilder): 39cb93a386Sopenharmony_ci """Parses include_rules from DEPS files and verifies files in the 40cb93a386Sopenharmony_ci source tree against them. 41cb93a386Sopenharmony_ci """ 42cb93a386Sopenharmony_ci 43cb93a386Sopenharmony_ci def __init__(self, 44cb93a386Sopenharmony_ci base_directory=None, 45cb93a386Sopenharmony_ci extra_repos=[], 46cb93a386Sopenharmony_ci verbose=False, 47cb93a386Sopenharmony_ci being_tested=False, 48cb93a386Sopenharmony_ci ignore_temp_rules=False, 49cb93a386Sopenharmony_ci skip_tests=False, 50cb93a386Sopenharmony_ci resolve_dotdot=True): 51cb93a386Sopenharmony_ci """Creates a new DepsChecker. 52cb93a386Sopenharmony_ci 53cb93a386Sopenharmony_ci Args: 54cb93a386Sopenharmony_ci base_directory: OS-compatible path to root of checkout, e.g. C:\chr\src. 55cb93a386Sopenharmony_ci verbose: Set to true for debug output. 56cb93a386Sopenharmony_ci being_tested: Set to true to ignore the DEPS file at 57cb93a386Sopenharmony_ci buildtools/checkdeps/DEPS. 58cb93a386Sopenharmony_ci ignore_temp_rules: Ignore rules that start with Rule.TEMP_ALLOW ("!"). 59cb93a386Sopenharmony_ci """ 60cb93a386Sopenharmony_ci DepsBuilder.__init__( 61cb93a386Sopenharmony_ci self, base_directory, extra_repos, verbose, being_tested, 62cb93a386Sopenharmony_ci ignore_temp_rules) 63cb93a386Sopenharmony_ci 64cb93a386Sopenharmony_ci self._skip_tests = skip_tests 65cb93a386Sopenharmony_ci self._resolve_dotdot = resolve_dotdot 66cb93a386Sopenharmony_ci self.results_formatter = results.NormalResultsFormatter(verbose) 67cb93a386Sopenharmony_ci 68cb93a386Sopenharmony_ci def Report(self): 69cb93a386Sopenharmony_ci """Prints a report of results, and returns an exit code for the process.""" 70cb93a386Sopenharmony_ci if self.results_formatter.GetResults(): 71cb93a386Sopenharmony_ci self.results_formatter.PrintResults() 72cb93a386Sopenharmony_ci return 1 73cb93a386Sopenharmony_ci print('\nSUCCESS\n') 74cb93a386Sopenharmony_ci return 0 75cb93a386Sopenharmony_ci 76cb93a386Sopenharmony_ci def CheckDirectory(self, start_dir): 77cb93a386Sopenharmony_ci """Checks all relevant source files in the specified directory and 78cb93a386Sopenharmony_ci its subdirectories for compliance with DEPS rules throughout the 79cb93a386Sopenharmony_ci tree (starting at |self.base_directory|). |start_dir| must be a 80cb93a386Sopenharmony_ci subdirectory of |self.base_directory|. 81cb93a386Sopenharmony_ci 82cb93a386Sopenharmony_ci On completion, self.results_formatter has the results of 83cb93a386Sopenharmony_ci processing, and calling Report() will print a report of results. 84cb93a386Sopenharmony_ci """ 85cb93a386Sopenharmony_ci java = java_checker.JavaChecker(self.base_directory, self.verbose) 86cb93a386Sopenharmony_ci cpp = cpp_checker.CppChecker( 87cb93a386Sopenharmony_ci self.verbose, self._resolve_dotdot, self.base_directory) 88cb93a386Sopenharmony_ci proto = proto_checker.ProtoChecker( 89cb93a386Sopenharmony_ci self.verbose, self._resolve_dotdot, self.base_directory) 90cb93a386Sopenharmony_ci checkers = dict( 91cb93a386Sopenharmony_ci (extension, checker) 92cb93a386Sopenharmony_ci for checker in [java, cpp, proto] for extension in checker.EXTENSIONS) 93cb93a386Sopenharmony_ci 94cb93a386Sopenharmony_ci for rules, file_paths in self.GetAllRulesAndFiles(start_dir): 95cb93a386Sopenharmony_ci for full_name in file_paths: 96cb93a386Sopenharmony_ci if self._skip_tests and _IsTestFile(os.path.basename(full_name)): 97cb93a386Sopenharmony_ci continue 98cb93a386Sopenharmony_ci file_extension = os.path.splitext(full_name)[1] 99cb93a386Sopenharmony_ci if not file_extension in checkers: 100cb93a386Sopenharmony_ci continue 101cb93a386Sopenharmony_ci checker = checkers[file_extension] 102cb93a386Sopenharmony_ci file_status = checker.CheckFile(rules, full_name) 103cb93a386Sopenharmony_ci if file_status.HasViolations(): 104cb93a386Sopenharmony_ci self.results_formatter.AddError(file_status) 105cb93a386Sopenharmony_ci 106cb93a386Sopenharmony_ci def CheckIncludesAndImports(self, added_lines, checker): 107cb93a386Sopenharmony_ci """Check new import/#include statements added in the change 108cb93a386Sopenharmony_ci being presubmit checked. 109cb93a386Sopenharmony_ci 110cb93a386Sopenharmony_ci Args: 111cb93a386Sopenharmony_ci added_lines: ((file_path, (changed_line, changed_line, ...), ...) 112cb93a386Sopenharmony_ci checker: CppChecker/JavaChecker/ProtoChecker checker instance 113cb93a386Sopenharmony_ci 114cb93a386Sopenharmony_ci Return: 115cb93a386Sopenharmony_ci A list of tuples, (bad_file_path, rule_type, rule_description) 116cb93a386Sopenharmony_ci where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and 117cb93a386Sopenharmony_ci rule_description is human-readable. Empty if no problems. 118cb93a386Sopenharmony_ci """ 119cb93a386Sopenharmony_ci problems = [] 120cb93a386Sopenharmony_ci for file_path, changed_lines in added_lines: 121cb93a386Sopenharmony_ci if not checker.ShouldCheck(file_path): 122cb93a386Sopenharmony_ci continue 123cb93a386Sopenharmony_ci rules_for_file = self.GetDirectoryRules(os.path.dirname(file_path)) 124cb93a386Sopenharmony_ci if not rules_for_file: 125cb93a386Sopenharmony_ci continue 126cb93a386Sopenharmony_ci for line in changed_lines: 127cb93a386Sopenharmony_ci is_include, violation = checker.CheckLine( 128cb93a386Sopenharmony_ci rules_for_file, line, file_path, True) 129cb93a386Sopenharmony_ci if not violation: 130cb93a386Sopenharmony_ci continue 131cb93a386Sopenharmony_ci rule_type = violation.violated_rule.allow 132cb93a386Sopenharmony_ci if rule_type == Rule.ALLOW: 133cb93a386Sopenharmony_ci continue 134cb93a386Sopenharmony_ci violation_text = results.NormalResultsFormatter.FormatViolation( 135cb93a386Sopenharmony_ci violation, self.verbose) 136cb93a386Sopenharmony_ci problems.append((file_path, rule_type, violation_text)) 137cb93a386Sopenharmony_ci return problems 138cb93a386Sopenharmony_ci 139cb93a386Sopenharmony_ci def CheckAddedCppIncludes(self, added_includes): 140cb93a386Sopenharmony_ci """This is used from PRESUBMIT.py to check new #include statements added in 141cb93a386Sopenharmony_ci the change being presubmit checked. 142cb93a386Sopenharmony_ci 143cb93a386Sopenharmony_ci Args: 144cb93a386Sopenharmony_ci added_includes: ((file_path, (include_line, include_line, ...), ...) 145cb93a386Sopenharmony_ci 146cb93a386Sopenharmony_ci Return: 147cb93a386Sopenharmony_ci A list of tuples, (bad_file_path, rule_type, rule_description) 148cb93a386Sopenharmony_ci where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and 149cb93a386Sopenharmony_ci rule_description is human-readable. Empty if no problems. 150cb93a386Sopenharmony_ci """ 151cb93a386Sopenharmony_ci return self.CheckIncludesAndImports( 152cb93a386Sopenharmony_ci added_includes, cpp_checker.CppChecker(self.verbose)) 153cb93a386Sopenharmony_ci 154cb93a386Sopenharmony_ci def CheckAddedJavaImports(self, added_imports, 155cb93a386Sopenharmony_ci allow_multiple_definitions=None): 156cb93a386Sopenharmony_ci """This is used from PRESUBMIT.py to check new import statements added in 157cb93a386Sopenharmony_ci the change being presubmit checked. 158cb93a386Sopenharmony_ci 159cb93a386Sopenharmony_ci Args: 160cb93a386Sopenharmony_ci added_imports: ((file_path, (import_line, import_line, ...), ...) 161cb93a386Sopenharmony_ci allow_multiple_definitions: [file_name, file_name, ...]. List of java 162cb93a386Sopenharmony_ci file names allowing multiple definitions in 163cb93a386Sopenharmony_ci presubmit check. 164cb93a386Sopenharmony_ci 165cb93a386Sopenharmony_ci Return: 166cb93a386Sopenharmony_ci A list of tuples, (bad_file_path, rule_type, rule_description) 167cb93a386Sopenharmony_ci where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and 168cb93a386Sopenharmony_ci rule_description is human-readable. Empty if no problems. 169cb93a386Sopenharmony_ci """ 170cb93a386Sopenharmony_ci return self.CheckIncludesAndImports( 171cb93a386Sopenharmony_ci added_imports, 172cb93a386Sopenharmony_ci java_checker.JavaChecker(self.base_directory, self.verbose, 173cb93a386Sopenharmony_ci added_imports, allow_multiple_definitions)) 174cb93a386Sopenharmony_ci 175cb93a386Sopenharmony_ci def CheckAddedProtoImports(self, added_imports): 176cb93a386Sopenharmony_ci """This is used from PRESUBMIT.py to check new #import statements added in 177cb93a386Sopenharmony_ci the change being presubmit checked. 178cb93a386Sopenharmony_ci 179cb93a386Sopenharmony_ci Args: 180cb93a386Sopenharmony_ci added_imports : ((file_path, (import_line, import_line, ...), ...) 181cb93a386Sopenharmony_ci 182cb93a386Sopenharmony_ci Return: 183cb93a386Sopenharmony_ci A list of tuples, (bad_file_path, rule_type, rule_description) 184cb93a386Sopenharmony_ci where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and 185cb93a386Sopenharmony_ci rule_description is human-readable. Empty if no problems. 186cb93a386Sopenharmony_ci """ 187cb93a386Sopenharmony_ci return self.CheckIncludesAndImports( 188cb93a386Sopenharmony_ci added_imports, proto_checker.ProtoChecker( 189cb93a386Sopenharmony_ci verbose=self.verbose, root_dir=self.base_directory)) 190cb93a386Sopenharmony_ci 191cb93a386Sopenharmony_cidef PrintUsage(): 192cb93a386Sopenharmony_ci print("""Usage: python checkdeps.py [--root <root>] [tocheck] 193cb93a386Sopenharmony_ci 194cb93a386Sopenharmony_ci --root ROOT Specifies the repository root. This defaults to "../../.." 195cb93a386Sopenharmony_ci relative to the script file. This will be correct given the 196cb93a386Sopenharmony_ci normal location of the script in "<root>/buildtools/checkdeps". 197cb93a386Sopenharmony_ci 198cb93a386Sopenharmony_ci --(others) There are a few lesser-used options; run with --help to show them. 199cb93a386Sopenharmony_ci 200cb93a386Sopenharmony_ci tocheck Specifies the directory, relative to root, to check. This defaults 201cb93a386Sopenharmony_ci to "." so it checks everything. 202cb93a386Sopenharmony_ci 203cb93a386Sopenharmony_ciExamples: 204cb93a386Sopenharmony_ci python checkdeps.py 205cb93a386Sopenharmony_ci python checkdeps.py --root c:\\source chrome""") 206cb93a386Sopenharmony_ci 207cb93a386Sopenharmony_ci 208cb93a386Sopenharmony_cidef main(): 209cb93a386Sopenharmony_ci option_parser = optparse.OptionParser() 210cb93a386Sopenharmony_ci option_parser.add_option( 211cb93a386Sopenharmony_ci '', '--root', 212cb93a386Sopenharmony_ci default='', dest='base_directory', 213cb93a386Sopenharmony_ci help='Specifies the repository root. This defaults ' 214cb93a386Sopenharmony_ci 'to "../../.." relative to the script file, which ' 215cb93a386Sopenharmony_ci 'will normally be the repository root.') 216cb93a386Sopenharmony_ci option_parser.add_option( 217cb93a386Sopenharmony_ci '', '--extra-repos', 218cb93a386Sopenharmony_ci action='append', dest='extra_repos', default=[], 219cb93a386Sopenharmony_ci help='Specifies extra repositories relative to root repository.') 220cb93a386Sopenharmony_ci option_parser.add_option( 221cb93a386Sopenharmony_ci '', '--ignore-temp-rules', 222cb93a386Sopenharmony_ci action='store_true', dest='ignore_temp_rules', default=False, 223cb93a386Sopenharmony_ci help='Ignore !-prefixed (temporary) rules.') 224cb93a386Sopenharmony_ci option_parser.add_option( 225cb93a386Sopenharmony_ci '', '--generate-temp-rules', 226cb93a386Sopenharmony_ci action='store_true', dest='generate_temp_rules', default=False, 227cb93a386Sopenharmony_ci help='Print rules to temporarily allow files that fail ' 228cb93a386Sopenharmony_ci 'dependency checking.') 229cb93a386Sopenharmony_ci option_parser.add_option( 230cb93a386Sopenharmony_ci '', '--count-violations', 231cb93a386Sopenharmony_ci action='store_true', dest='count_violations', default=False, 232cb93a386Sopenharmony_ci help='Count #includes in violation of intended rules.') 233cb93a386Sopenharmony_ci option_parser.add_option( 234cb93a386Sopenharmony_ci '', '--skip-tests', 235cb93a386Sopenharmony_ci action='store_true', dest='skip_tests', default=False, 236cb93a386Sopenharmony_ci help='Skip checking test files (best effort).') 237cb93a386Sopenharmony_ci option_parser.add_option( 238cb93a386Sopenharmony_ci '-v', '--verbose', 239cb93a386Sopenharmony_ci action='store_true', default=False, 240cb93a386Sopenharmony_ci help='Print debug logging') 241cb93a386Sopenharmony_ci option_parser.add_option( 242cb93a386Sopenharmony_ci '', '--json', 243cb93a386Sopenharmony_ci help='Path to JSON output file') 244cb93a386Sopenharmony_ci option_parser.add_option( 245cb93a386Sopenharmony_ci '', '--no-resolve-dotdot', 246cb93a386Sopenharmony_ci action='store_false', dest='resolve_dotdot', default=True, 247cb93a386Sopenharmony_ci help='resolve leading ../ in include directive paths relative ' 248cb93a386Sopenharmony_ci 'to the file perfoming the inclusion.') 249cb93a386Sopenharmony_ci 250cb93a386Sopenharmony_ci options, args = option_parser.parse_args() 251cb93a386Sopenharmony_ci 252cb93a386Sopenharmony_ci deps_checker = DepsChecker(options.base_directory, 253cb93a386Sopenharmony_ci extra_repos=options.extra_repos, 254cb93a386Sopenharmony_ci verbose=options.verbose, 255cb93a386Sopenharmony_ci ignore_temp_rules=options.ignore_temp_rules, 256cb93a386Sopenharmony_ci skip_tests=options.skip_tests, 257cb93a386Sopenharmony_ci resolve_dotdot=options.resolve_dotdot) 258cb93a386Sopenharmony_ci base_directory = deps_checker.base_directory # Default if needed, normalized 259cb93a386Sopenharmony_ci 260cb93a386Sopenharmony_ci # Figure out which directory we have to check. 261cb93a386Sopenharmony_ci start_dir = base_directory 262cb93a386Sopenharmony_ci if len(args) == 1: 263cb93a386Sopenharmony_ci # Directory specified. Start here. It's supposed to be relative to the 264cb93a386Sopenharmony_ci # base directory. 265cb93a386Sopenharmony_ci start_dir = os.path.abspath(os.path.join(base_directory, args[0])) 266cb93a386Sopenharmony_ci elif len(args) >= 2 or (options.generate_temp_rules and 267cb93a386Sopenharmony_ci options.count_violations): 268cb93a386Sopenharmony_ci # More than one argument, or incompatible flags, we don't handle this. 269cb93a386Sopenharmony_ci PrintUsage() 270cb93a386Sopenharmony_ci return 1 271cb93a386Sopenharmony_ci 272cb93a386Sopenharmony_ci if not start_dir.startswith(deps_checker.base_directory): 273cb93a386Sopenharmony_ci print('Directory to check must be a subdirectory of the base directory,') 274cb93a386Sopenharmony_ci print('but %s is not a subdirectory of %s' % (start_dir, base_directory)) 275cb93a386Sopenharmony_ci return 1 276cb93a386Sopenharmony_ci 277cb93a386Sopenharmony_ci print('Using base directory:', base_directory) 278cb93a386Sopenharmony_ci print('Checking:', start_dir) 279cb93a386Sopenharmony_ci 280cb93a386Sopenharmony_ci if options.generate_temp_rules: 281cb93a386Sopenharmony_ci deps_checker.results_formatter = results.TemporaryRulesFormatter() 282cb93a386Sopenharmony_ci elif options.count_violations: 283cb93a386Sopenharmony_ci deps_checker.results_formatter = results.CountViolationsFormatter() 284cb93a386Sopenharmony_ci 285cb93a386Sopenharmony_ci if options.json: 286cb93a386Sopenharmony_ci deps_checker.results_formatter = results.JSONResultsFormatter( 287cb93a386Sopenharmony_ci options.json, deps_checker.results_formatter) 288cb93a386Sopenharmony_ci 289cb93a386Sopenharmony_ci deps_checker.CheckDirectory(start_dir) 290cb93a386Sopenharmony_ci return deps_checker.Report() 291cb93a386Sopenharmony_ci 292cb93a386Sopenharmony_ci 293cb93a386Sopenharmony_ciif '__main__' == __name__: 294cb93a386Sopenharmony_ci sys.exit(main()) 295