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