1cb93a386Sopenharmony_ci#!/usr/bin/env python
2cb93a386Sopenharmony_ci# Copyright 2013 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"""Traverses the source tree, parses all found DEPS files, and constructs
7cb93a386Sopenharmony_cia dependency rule table to be used by subclasses.
8cb93a386Sopenharmony_ci
9cb93a386Sopenharmony_ciSee README.md for the format of the deps file.
10cb93a386Sopenharmony_ci"""
11cb93a386Sopenharmony_ci
12cb93a386Sopenharmony_ci
13cb93a386Sopenharmony_ci
14cb93a386Sopenharmony_ciimport copy
15cb93a386Sopenharmony_ciimport os.path
16cb93a386Sopenharmony_ciimport posixpath
17cb93a386Sopenharmony_ciimport subprocess
18cb93a386Sopenharmony_ci
19cb93a386Sopenharmony_cifrom rules import Rule, Rules
20cb93a386Sopenharmony_ci
21cb93a386Sopenharmony_ci
22cb93a386Sopenharmony_ci# Variable name used in the DEPS file to add or subtract include files from
23cb93a386Sopenharmony_ci# the module-level deps.
24cb93a386Sopenharmony_ciINCLUDE_RULES_VAR_NAME = 'include_rules'
25cb93a386Sopenharmony_ci
26cb93a386Sopenharmony_ci# Variable name used in the DEPS file to add or subtract include files
27cb93a386Sopenharmony_ci# from module-level deps specific to files whose basename (last
28cb93a386Sopenharmony_ci# component of path) matches a given regular expression.
29cb93a386Sopenharmony_ciSPECIFIC_INCLUDE_RULES_VAR_NAME = 'specific_include_rules'
30cb93a386Sopenharmony_ci
31cb93a386Sopenharmony_ci# Optionally present in the DEPS file to list subdirectories which should not
32cb93a386Sopenharmony_ci# be checked. This allows us to skip third party code, for example.
33cb93a386Sopenharmony_ciSKIP_SUBDIRS_VAR_NAME = 'skip_child_includes'
34cb93a386Sopenharmony_ci
35cb93a386Sopenharmony_ci# Optionally discard rules from parent directories, similar to "noparent" in
36cb93a386Sopenharmony_ci# OWNERS files. For example, if //ash/components has "noparent = True" then
37cb93a386Sopenharmony_ci# it will not inherit rules from //ash/DEPS, forcing each //ash/component/foo
38cb93a386Sopenharmony_ci# to declare all its dependencies.
39cb93a386Sopenharmony_ciNOPARENT_VAR_NAME = 'noparent'
40cb93a386Sopenharmony_ci
41cb93a386Sopenharmony_ci
42cb93a386Sopenharmony_ciclass DepsBuilderError(Exception):
43cb93a386Sopenharmony_ci    """Base class for exceptions in this module."""
44cb93a386Sopenharmony_ci    pass
45cb93a386Sopenharmony_ci
46cb93a386Sopenharmony_ci
47cb93a386Sopenharmony_cidef NormalizePath(path):
48cb93a386Sopenharmony_ci  """Returns a path normalized to how we write DEPS rules and compare paths."""
49cb93a386Sopenharmony_ci  return os.path.normcase(path).replace(os.path.sep, posixpath.sep)
50cb93a386Sopenharmony_ci
51cb93a386Sopenharmony_ci
52cb93a386Sopenharmony_cidef _GitSourceDirectories(base_directory):
53cb93a386Sopenharmony_ci  """Returns set of normalized paths to subdirectories containing sources
54cb93a386Sopenharmony_ci  managed by git."""
55cb93a386Sopenharmony_ci  base_dir_norm = NormalizePath(base_directory)
56cb93a386Sopenharmony_ci  git_source_directories = set([base_dir_norm])
57cb93a386Sopenharmony_ci
58cb93a386Sopenharmony_ci  git_cmd = 'git.bat' if os.name == 'nt' else 'git'
59cb93a386Sopenharmony_ci  git_ls_files_cmd = [git_cmd, 'ls-files']
60cb93a386Sopenharmony_ci  # FIXME: Use a context manager in Python 3.2+
61cb93a386Sopenharmony_ci  popen = subprocess.Popen(git_ls_files_cmd,
62cb93a386Sopenharmony_ci                           stdout=subprocess.PIPE,
63cb93a386Sopenharmony_ci                           cwd=base_directory)
64cb93a386Sopenharmony_ci  try:
65cb93a386Sopenharmony_ci    try:
66cb93a386Sopenharmony_ci      for line in popen.stdout.read().decode('utf-8').splitlines():
67cb93a386Sopenharmony_ci        dir_path = os.path.join(base_directory, os.path.dirname(line))
68cb93a386Sopenharmony_ci        dir_path_norm = NormalizePath(dir_path)
69cb93a386Sopenharmony_ci        # Add the directory as well as all the parent directories,
70cb93a386Sopenharmony_ci        # stopping once we reach an already-listed directory.
71cb93a386Sopenharmony_ci        while dir_path_norm not in git_source_directories:
72cb93a386Sopenharmony_ci          git_source_directories.add(dir_path_norm)
73cb93a386Sopenharmony_ci          dir_path_norm = posixpath.dirname(dir_path_norm)
74cb93a386Sopenharmony_ci    finally:
75cb93a386Sopenharmony_ci      popen.stdout.close()
76cb93a386Sopenharmony_ci  finally:
77cb93a386Sopenharmony_ci    popen.wait()
78cb93a386Sopenharmony_ci
79cb93a386Sopenharmony_ci  return git_source_directories
80cb93a386Sopenharmony_ci
81cb93a386Sopenharmony_ci
82cb93a386Sopenharmony_ciclass DepsBuilder(object):
83cb93a386Sopenharmony_ci  """Parses include_rules from DEPS files."""
84cb93a386Sopenharmony_ci
85cb93a386Sopenharmony_ci  def __init__(self,
86cb93a386Sopenharmony_ci               base_directory=None,
87cb93a386Sopenharmony_ci               extra_repos=[],
88cb93a386Sopenharmony_ci               verbose=False,
89cb93a386Sopenharmony_ci               being_tested=False,
90cb93a386Sopenharmony_ci               ignore_temp_rules=False,
91cb93a386Sopenharmony_ci               ignore_specific_rules=False):
92cb93a386Sopenharmony_ci    """Creates a new DepsBuilder.
93cb93a386Sopenharmony_ci
94cb93a386Sopenharmony_ci    Args:
95cb93a386Sopenharmony_ci      base_directory: local path to root of checkout, e.g. C:\chr\src.
96cb93a386Sopenharmony_ci      verbose: Set to True for debug output.
97cb93a386Sopenharmony_ci      being_tested: Set to True to ignore the DEPS file at
98cb93a386Sopenharmony_ci                    buildtools/checkdeps/DEPS.
99cb93a386Sopenharmony_ci      ignore_temp_rules: Ignore rules that start with Rule.TEMP_ALLOW ("!").
100cb93a386Sopenharmony_ci    """
101cb93a386Sopenharmony_ci    base_directory = (base_directory or
102cb93a386Sopenharmony_ci                      os.path.join(os.path.dirname(__file__),
103cb93a386Sopenharmony_ci                      os.path.pardir, os.path.pardir))
104cb93a386Sopenharmony_ci    self.base_directory = os.path.abspath(base_directory)  # Local absolute path
105cb93a386Sopenharmony_ci    self.extra_repos = extra_repos
106cb93a386Sopenharmony_ci    self.verbose = verbose
107cb93a386Sopenharmony_ci    self._under_test = being_tested
108cb93a386Sopenharmony_ci    self._ignore_temp_rules = ignore_temp_rules
109cb93a386Sopenharmony_ci    self._ignore_specific_rules = ignore_specific_rules
110cb93a386Sopenharmony_ci    self._git_source_directories = None
111cb93a386Sopenharmony_ci
112cb93a386Sopenharmony_ci    if os.path.exists(os.path.join(base_directory, '.git')):
113cb93a386Sopenharmony_ci      self.is_git = True
114cb93a386Sopenharmony_ci    elif os.path.exists(os.path.join(base_directory, '.svn')):
115cb93a386Sopenharmony_ci      self.is_git = False
116cb93a386Sopenharmony_ci    else:
117cb93a386Sopenharmony_ci      raise DepsBuilderError("%s is not a repository root" % base_directory)
118cb93a386Sopenharmony_ci
119cb93a386Sopenharmony_ci    # Map of normalized directory paths to rules to use for those
120cb93a386Sopenharmony_ci    # directories, or None for directories that should be skipped.
121cb93a386Sopenharmony_ci    # Normalized is: absolute, lowercase, / for separator.
122cb93a386Sopenharmony_ci    self.directory_rules = {}
123cb93a386Sopenharmony_ci    self._ApplyDirectoryRulesAndSkipSubdirs(Rules(), self.base_directory)
124cb93a386Sopenharmony_ci
125cb93a386Sopenharmony_ci  def _ApplyRules(self, existing_rules, includes, specific_includes,
126cb93a386Sopenharmony_ci                  cur_dir_norm):
127cb93a386Sopenharmony_ci    """Applies the given include rules, returning the new rules.
128cb93a386Sopenharmony_ci
129cb93a386Sopenharmony_ci    Args:
130cb93a386Sopenharmony_ci      existing_rules: A set of existing rules that will be combined.
131cb93a386Sopenharmony_ci      include: The list of rules from the "include_rules" section of DEPS.
132cb93a386Sopenharmony_ci      specific_includes: E.g. {'.*_unittest\.cc': ['+foo', '-blat']} rules
133cb93a386Sopenharmony_ci                         from the "specific_include_rules" section of DEPS.
134cb93a386Sopenharmony_ci      cur_dir_norm: The current directory, normalized path. We will create an
135cb93a386Sopenharmony_ci                    implicit rule that allows inclusion from this directory.
136cb93a386Sopenharmony_ci
137cb93a386Sopenharmony_ci    Returns: A new set of rules combining the existing_rules with the other
138cb93a386Sopenharmony_ci             arguments.
139cb93a386Sopenharmony_ci    """
140cb93a386Sopenharmony_ci    rules = copy.deepcopy(existing_rules)
141cb93a386Sopenharmony_ci
142cb93a386Sopenharmony_ci    # First apply the implicit "allow" rule for the current directory.
143cb93a386Sopenharmony_ci    base_dir_norm = NormalizePath(self.base_directory)
144cb93a386Sopenharmony_ci    if not cur_dir_norm.startswith(base_dir_norm):
145cb93a386Sopenharmony_ci      raise Exception(
146cb93a386Sopenharmony_ci          'Internal error: base directory is not at the beginning for\n'
147cb93a386Sopenharmony_ci          '  %s and base dir\n'
148cb93a386Sopenharmony_ci          '  %s' % (cur_dir_norm, base_dir_norm))
149cb93a386Sopenharmony_ci    relative_dir = posixpath.relpath(cur_dir_norm, base_dir_norm)
150cb93a386Sopenharmony_ci
151cb93a386Sopenharmony_ci    # Make the help string a little more meaningful.
152cb93a386Sopenharmony_ci    source = relative_dir or 'top level'
153cb93a386Sopenharmony_ci    rules.AddRule('+' + relative_dir,
154cb93a386Sopenharmony_ci                  relative_dir,
155cb93a386Sopenharmony_ci                  'Default rule for ' + source)
156cb93a386Sopenharmony_ci
157cb93a386Sopenharmony_ci    def ApplyOneRule(rule_str, dependee_regexp=None):
158cb93a386Sopenharmony_ci      """Deduces a sensible description for the rule being added, and
159cb93a386Sopenharmony_ci      adds the rule with its description to |rules|.
160cb93a386Sopenharmony_ci
161cb93a386Sopenharmony_ci      If we are ignoring temporary rules, this function does nothing
162cb93a386Sopenharmony_ci      for rules beginning with the Rule.TEMP_ALLOW character.
163cb93a386Sopenharmony_ci      """
164cb93a386Sopenharmony_ci      if self._ignore_temp_rules and rule_str.startswith(Rule.TEMP_ALLOW):
165cb93a386Sopenharmony_ci        return
166cb93a386Sopenharmony_ci
167cb93a386Sopenharmony_ci      rule_block_name = 'include_rules'
168cb93a386Sopenharmony_ci      if dependee_regexp:
169cb93a386Sopenharmony_ci        rule_block_name = 'specific_include_rules'
170cb93a386Sopenharmony_ci      if relative_dir:
171cb93a386Sopenharmony_ci        rule_description = relative_dir + "'s %s" % rule_block_name
172cb93a386Sopenharmony_ci      else:
173cb93a386Sopenharmony_ci        rule_description = 'the top level %s' % rule_block_name
174cb93a386Sopenharmony_ci      rules.AddRule(rule_str, relative_dir, rule_description, dependee_regexp)
175cb93a386Sopenharmony_ci
176cb93a386Sopenharmony_ci    # Apply the additional explicit rules.
177cb93a386Sopenharmony_ci    for rule_str in includes:
178cb93a386Sopenharmony_ci      ApplyOneRule(rule_str)
179cb93a386Sopenharmony_ci
180cb93a386Sopenharmony_ci    # Finally, apply the specific rules.
181cb93a386Sopenharmony_ci    if self._ignore_specific_rules:
182cb93a386Sopenharmony_ci      return rules
183cb93a386Sopenharmony_ci
184cb93a386Sopenharmony_ci    for regexp, specific_rules in specific_includes.items():
185cb93a386Sopenharmony_ci      for rule_str in specific_rules:
186cb93a386Sopenharmony_ci        ApplyOneRule(rule_str, regexp)
187cb93a386Sopenharmony_ci
188cb93a386Sopenharmony_ci    return rules
189cb93a386Sopenharmony_ci
190cb93a386Sopenharmony_ci  def _ApplyDirectoryRules(self, existing_rules, dir_path_local_abs):
191cb93a386Sopenharmony_ci    """Combines rules from the existing rules and the new directory.
192cb93a386Sopenharmony_ci
193cb93a386Sopenharmony_ci    Any directory can contain a DEPS file. Top-level DEPS files can contain
194cb93a386Sopenharmony_ci    module dependencies which are used by gclient. We use these, along with
195cb93a386Sopenharmony_ci    additional include rules and implicit rules for the given directory, to
196cb93a386Sopenharmony_ci    come up with a combined set of rules to apply for the directory.
197cb93a386Sopenharmony_ci
198cb93a386Sopenharmony_ci    Args:
199cb93a386Sopenharmony_ci      existing_rules: The rules for the parent directory. We'll add-on to these.
200cb93a386Sopenharmony_ci      dir_path_local_abs: The directory path that the DEPS file may live in (if
201cb93a386Sopenharmony_ci                          it exists). This will also be used to generate the
202cb93a386Sopenharmony_ci                          implicit rules. This is a local path.
203cb93a386Sopenharmony_ci
204cb93a386Sopenharmony_ci    Returns: A 2-tuple of:
205cb93a386Sopenharmony_ci      (1) the combined set of rules to apply to the sub-tree,
206cb93a386Sopenharmony_ci      (2) a list of all subdirectories that should NOT be checked, as specified
207cb93a386Sopenharmony_ci          in the DEPS file (if any).
208cb93a386Sopenharmony_ci          Subdirectories are single words, hence no OS dependence.
209cb93a386Sopenharmony_ci    """
210cb93a386Sopenharmony_ci    dir_path_norm = NormalizePath(dir_path_local_abs)
211cb93a386Sopenharmony_ci
212cb93a386Sopenharmony_ci    # Check the DEPS file in this directory.
213cb93a386Sopenharmony_ci    if self.verbose:
214cb93a386Sopenharmony_ci      print('Applying rules from', dir_path_local_abs)
215cb93a386Sopenharmony_ci    def FromImpl(*_):
216cb93a386Sopenharmony_ci      pass  # NOP function so "From" doesn't fail.
217cb93a386Sopenharmony_ci
218cb93a386Sopenharmony_ci    def FileImpl(_):
219cb93a386Sopenharmony_ci      pass  # NOP function so "File" doesn't fail.
220cb93a386Sopenharmony_ci
221cb93a386Sopenharmony_ci    class _VarImpl:
222cb93a386Sopenharmony_ci      def __init__(self, local_scope):
223cb93a386Sopenharmony_ci        self._local_scope = local_scope
224cb93a386Sopenharmony_ci
225cb93a386Sopenharmony_ci      def Lookup(self, var_name):
226cb93a386Sopenharmony_ci        """Implements the Var syntax."""
227cb93a386Sopenharmony_ci        try:
228cb93a386Sopenharmony_ci          return self._local_scope['vars'][var_name]
229cb93a386Sopenharmony_ci        except KeyError:
230cb93a386Sopenharmony_ci          raise Exception('Var is not defined: %s' % var_name)
231cb93a386Sopenharmony_ci
232cb93a386Sopenharmony_ci    local_scope = {}
233cb93a386Sopenharmony_ci    global_scope = {
234cb93a386Sopenharmony_ci      'File': FileImpl,
235cb93a386Sopenharmony_ci      'From': FromImpl,
236cb93a386Sopenharmony_ci      'Var': _VarImpl(local_scope).Lookup,
237cb93a386Sopenharmony_ci      'Str': str,
238cb93a386Sopenharmony_ci    }
239cb93a386Sopenharmony_ci    deps_file_path = os.path.join(dir_path_local_abs, 'DEPS')
240cb93a386Sopenharmony_ci
241cb93a386Sopenharmony_ci    # The second conditional here is to disregard the
242cb93a386Sopenharmony_ci    # buildtools/checkdeps/DEPS file while running tests.  This DEPS file
243cb93a386Sopenharmony_ci    # has a skip_child_includes for 'testdata' which is necessary for
244cb93a386Sopenharmony_ci    # running production tests, since there are intentional DEPS
245cb93a386Sopenharmony_ci    # violations under the testdata directory.  On the other hand when
246cb93a386Sopenharmony_ci    # running tests, we absolutely need to verify the contents of that
247cb93a386Sopenharmony_ci    # directory to trigger those intended violations and see that they
248cb93a386Sopenharmony_ci    # are handled correctly.
249cb93a386Sopenharmony_ci    if os.path.isfile(deps_file_path) and not (
250cb93a386Sopenharmony_ci        self._under_test and
251cb93a386Sopenharmony_ci        os.path.basename(dir_path_local_abs) == 'checkdeps'):
252cb93a386Sopenharmony_ci      try:
253cb93a386Sopenharmony_ci        with open(deps_file_path) as file:
254cb93a386Sopenharmony_ci          exec(file.read(), global_scope, local_scope)
255cb93a386Sopenharmony_ci      except Exception as e:
256cb93a386Sopenharmony_ci        print(' Error reading %s: %s' % (deps_file_path, str(e)))
257cb93a386Sopenharmony_ci        raise
258cb93a386Sopenharmony_ci    elif self.verbose:
259cb93a386Sopenharmony_ci      print('  No deps file found in', dir_path_local_abs)
260cb93a386Sopenharmony_ci
261cb93a386Sopenharmony_ci    # Even if a DEPS file does not exist we still invoke ApplyRules
262cb93a386Sopenharmony_ci    # to apply the implicit "allow" rule for the current directory
263cb93a386Sopenharmony_ci    include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, [])
264cb93a386Sopenharmony_ci    specific_include_rules = local_scope.get(SPECIFIC_INCLUDE_RULES_VAR_NAME,
265cb93a386Sopenharmony_ci                                             {})
266cb93a386Sopenharmony_ci    skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, [])
267cb93a386Sopenharmony_ci    noparent = local_scope.get(NOPARENT_VAR_NAME, False)
268cb93a386Sopenharmony_ci    if noparent:
269cb93a386Sopenharmony_ci      parent_rules = Rules()
270cb93a386Sopenharmony_ci    else:
271cb93a386Sopenharmony_ci      parent_rules = existing_rules
272cb93a386Sopenharmony_ci
273cb93a386Sopenharmony_ci    return (self._ApplyRules(parent_rules, include_rules,
274cb93a386Sopenharmony_ci                             specific_include_rules, dir_path_norm),
275cb93a386Sopenharmony_ci            skip_subdirs)
276cb93a386Sopenharmony_ci
277cb93a386Sopenharmony_ci  def _ApplyDirectoryRulesAndSkipSubdirs(self, parent_rules,
278cb93a386Sopenharmony_ci                                         dir_path_local_abs):
279cb93a386Sopenharmony_ci    """Given |parent_rules| and a subdirectory |dir_path_local_abs| of the
280cb93a386Sopenharmony_ci    directory that owns the |parent_rules|, add |dir_path_local_abs|'s rules to
281cb93a386Sopenharmony_ci    |self.directory_rules|, and add None entries for any of its
282cb93a386Sopenharmony_ci    subdirectories that should be skipped.
283cb93a386Sopenharmony_ci    """
284cb93a386Sopenharmony_ci    directory_rules, excluded_subdirs = self._ApplyDirectoryRules(
285cb93a386Sopenharmony_ci        parent_rules, dir_path_local_abs)
286cb93a386Sopenharmony_ci    dir_path_norm = NormalizePath(dir_path_local_abs)
287cb93a386Sopenharmony_ci    self.directory_rules[dir_path_norm] = directory_rules
288cb93a386Sopenharmony_ci    for subdir in excluded_subdirs:
289cb93a386Sopenharmony_ci      subdir_path_norm = posixpath.join(dir_path_norm, subdir)
290cb93a386Sopenharmony_ci      self.directory_rules[subdir_path_norm] = None
291cb93a386Sopenharmony_ci
292cb93a386Sopenharmony_ci  def GetAllRulesAndFiles(self, dir_name=None):
293cb93a386Sopenharmony_ci    """Yields (rules, filenames) for each repository directory with DEPS rules.
294cb93a386Sopenharmony_ci
295cb93a386Sopenharmony_ci    This walks the directory tree while staying in the repository. Specify
296cb93a386Sopenharmony_ci    |dir_name| to walk just one directory and its children; omit |dir_name| to
297cb93a386Sopenharmony_ci    walk the entire repository.
298cb93a386Sopenharmony_ci
299cb93a386Sopenharmony_ci    Yields:
300cb93a386Sopenharmony_ci      Two-element (rules, filenames) tuples. |rules| is a rules.Rules object
301cb93a386Sopenharmony_ci      for a directory, and |filenames| is a list of the absolute local paths
302cb93a386Sopenharmony_ci      of all files in that directory.
303cb93a386Sopenharmony_ci    """
304cb93a386Sopenharmony_ci    if self.is_git and self._git_source_directories is None:
305cb93a386Sopenharmony_ci      self._git_source_directories = _GitSourceDirectories(self.base_directory)
306cb93a386Sopenharmony_ci      for repo in self.extra_repos:
307cb93a386Sopenharmony_ci        repo_path = os.path.join(self.base_directory, repo)
308cb93a386Sopenharmony_ci        self._git_source_directories.update(_GitSourceDirectories(repo_path))
309cb93a386Sopenharmony_ci
310cb93a386Sopenharmony_ci    # Collect a list of all files and directories to check.
311cb93a386Sopenharmony_ci    if dir_name and not os.path.isabs(dir_name):
312cb93a386Sopenharmony_ci      dir_name = os.path.join(self.base_directory, dir_name)
313cb93a386Sopenharmony_ci    dirs_to_check = [dir_name or self.base_directory]
314cb93a386Sopenharmony_ci    while dirs_to_check:
315cb93a386Sopenharmony_ci      current_dir = dirs_to_check.pop()
316cb93a386Sopenharmony_ci
317cb93a386Sopenharmony_ci      # Check that this directory is part of the source repository. This
318cb93a386Sopenharmony_ci      # prevents us from descending into third-party code or directories
319cb93a386Sopenharmony_ci      # generated by the build system.
320cb93a386Sopenharmony_ci      if self.is_git:
321cb93a386Sopenharmony_ci        if NormalizePath(current_dir) not in self._git_source_directories:
322cb93a386Sopenharmony_ci          continue
323cb93a386Sopenharmony_ci      elif not os.path.exists(os.path.join(current_dir, '.svn')):
324cb93a386Sopenharmony_ci        continue
325cb93a386Sopenharmony_ci
326cb93a386Sopenharmony_ci      current_dir_rules = self.GetDirectoryRules(current_dir)
327cb93a386Sopenharmony_ci
328cb93a386Sopenharmony_ci      if not current_dir_rules:
329cb93a386Sopenharmony_ci        continue  # Handle the 'skip_child_includes' case.
330cb93a386Sopenharmony_ci
331cb93a386Sopenharmony_ci      current_dir_contents = sorted(os.listdir(current_dir))
332cb93a386Sopenharmony_ci      file_names = []
333cb93a386Sopenharmony_ci      sub_dirs = []
334cb93a386Sopenharmony_ci      for file_name in current_dir_contents:
335cb93a386Sopenharmony_ci        full_name = os.path.join(current_dir, file_name)
336cb93a386Sopenharmony_ci        if os.path.isdir(full_name):
337cb93a386Sopenharmony_ci          sub_dirs.append(full_name)
338cb93a386Sopenharmony_ci        else:
339cb93a386Sopenharmony_ci          file_names.append(full_name)
340cb93a386Sopenharmony_ci      dirs_to_check.extend(reversed(sub_dirs))
341cb93a386Sopenharmony_ci
342cb93a386Sopenharmony_ci      yield (current_dir_rules, file_names)
343cb93a386Sopenharmony_ci
344cb93a386Sopenharmony_ci  def GetDirectoryRules(self, dir_path_local):
345cb93a386Sopenharmony_ci    """Returns a Rules object to use for the given directory, or None
346cb93a386Sopenharmony_ci    if the given directory should be skipped.
347cb93a386Sopenharmony_ci
348cb93a386Sopenharmony_ci    Also modifies |self.directory_rules| to store the Rules.
349cb93a386Sopenharmony_ci    This takes care of first building rules for parent directories (up to
350cb93a386Sopenharmony_ci    |self.base_directory|) if needed, which may add rules for skipped
351cb93a386Sopenharmony_ci    subdirectories.
352cb93a386Sopenharmony_ci
353cb93a386Sopenharmony_ci    Args:
354cb93a386Sopenharmony_ci      dir_path_local: A local path to the directory you want rules for.
355cb93a386Sopenharmony_ci        Can be relative and unnormalized. It is the caller's responsibility
356cb93a386Sopenharmony_ci        to ensure that this is part of the repository rooted at
357cb93a386Sopenharmony_ci        |self.base_directory|.
358cb93a386Sopenharmony_ci    """
359cb93a386Sopenharmony_ci    if os.path.isabs(dir_path_local):
360cb93a386Sopenharmony_ci      dir_path_local_abs = dir_path_local
361cb93a386Sopenharmony_ci    else:
362cb93a386Sopenharmony_ci      dir_path_local_abs = os.path.join(self.base_directory, dir_path_local)
363cb93a386Sopenharmony_ci    dir_path_norm = NormalizePath(dir_path_local_abs)
364cb93a386Sopenharmony_ci
365cb93a386Sopenharmony_ci    if dir_path_norm in self.directory_rules:
366cb93a386Sopenharmony_ci      return self.directory_rules[dir_path_norm]
367cb93a386Sopenharmony_ci
368cb93a386Sopenharmony_ci    parent_dir_local_abs = os.path.dirname(dir_path_local_abs)
369cb93a386Sopenharmony_ci    parent_rules = self.GetDirectoryRules(parent_dir_local_abs)
370cb93a386Sopenharmony_ci    # We need to check for an entry for our dir_path again, since
371cb93a386Sopenharmony_ci    # GetDirectoryRules can modify entries for subdirectories, namely setting
372cb93a386Sopenharmony_ci    # to None if they should be skipped, via _ApplyDirectoryRulesAndSkipSubdirs.
373cb93a386Sopenharmony_ci    # For example, if dir_path == 'A/B/C' and A/B/DEPS specifies that the C
374cb93a386Sopenharmony_ci    # subdirectory be skipped, GetDirectoryRules('A/B') will fill in the entry
375cb93a386Sopenharmony_ci    # for 'A/B/C' as None.
376cb93a386Sopenharmony_ci    if dir_path_norm in self.directory_rules:
377cb93a386Sopenharmony_ci      return self.directory_rules[dir_path_norm]
378cb93a386Sopenharmony_ci
379cb93a386Sopenharmony_ci    if parent_rules:
380cb93a386Sopenharmony_ci      self._ApplyDirectoryRulesAndSkipSubdirs(parent_rules, dir_path_local_abs)
381cb93a386Sopenharmony_ci    else:
382cb93a386Sopenharmony_ci      # If the parent directory should be skipped, then the current
383cb93a386Sopenharmony_ci      # directory should also be skipped.
384cb93a386Sopenharmony_ci      self.directory_rules[dir_path_norm] = None
385cb93a386Sopenharmony_ci    return self.directory_rules[dir_path_norm]
386