Index: tools/checkdeps/builddeps.py |
diff --git a/tools/checkdeps/builddeps.py b/tools/checkdeps/builddeps.py |
index 01e107b0b80a9708fcdd472336e790fc1565a149..4f14030fda6a099073473c6588f39d83e64b1e14 100755 |
--- a/tools/checkdeps/builddeps.py |
+++ b/tools/checkdeps/builddeps.py |
@@ -75,9 +75,10 @@ backslashes. All directories should be relative to the source root and use |
only lowercase. |
""" |
+import copy |
import os |
+import posixpath |
import subprocess |
-import copy |
from rules import Rule, Rules |
@@ -97,14 +98,12 @@ SKIP_SUBDIRS_VAR_NAME = 'skip_child_includes' |
def NormalizePath(path): |
- """Returns a path normalized to how we write DEPS rules and compare paths. |
- """ |
- return path.lower().replace('\\', '/') |
+ """Returns a path normalized to how we write DEPS rules and compare paths.""" |
+ return os.path.normcase(path).replace(os.path.sep, posixpath.sep) |
class DepsBuilder(object): |
- """Parses include_rules from DEPS files. |
- """ |
+ """Parses include_rules from DEPS files.""" |
def __init__(self, |
base_directory=None, |
@@ -115,28 +114,31 @@ class DepsBuilder(object): |
"""Creates a new DepsBuilder. |
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 tools/checkdeps/DEPS. |
+ base_directory: local 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 tools/checkdeps/DEPS. |
ignore_temp_rules: Ignore rules that start with Rule.TEMP_ALLOW ("!"). |
""" |
base_directory = (base_directory or |
- os.path.join(os.path.dirname(__file__), '..', '..')) |
- self.base_directory = os.path.abspath(base_directory) |
+ os.path.join(os.path.dirname(__file__), |
+ os.pardir, os.pardir)) |
+ self.base_directory = os.path.abspath(base_directory) # Local absolute path |
self.verbose = verbose |
self._under_test = being_tested |
self._ignore_temp_rules = ignore_temp_rules |
self._ignore_specific_rules = ignore_specific_rules |
- self.git_source_directories = set() |
+ self.git_source_directories = set() # Normalized paths |
self._AddGitSourceDirectories() |
# Map of normalized directory paths to rules to use for those |
# directories, or None for directories that should be skipped. |
+ # Normalized is: absolute, lowercase, / for separator. |
self.directory_rules = {} |
self._ApplyDirectoryRulesAndSkipSubdirs(Rules(), self.base_directory) |
- def _ApplyRules(self, existing_rules, includes, specific_includes, cur_dir): |
+ def _ApplyRules(self, existing_rules, includes, specific_includes, |
+ cur_dir_norm): |
"""Applies the given include rules, returning the new rules. |
Args: |
@@ -144,8 +146,8 @@ class DepsBuilder(object): |
include: The list of rules from the "include_rules" section of DEPS. |
specific_includes: E.g. {'.*_unittest\.cc': ['+foo', '-blat']} rules |
from the "specific_include_rules" section of DEPS. |
- cur_dir: The current directory, normalized path. We will create an |
- implicit rule that allows inclusion from this directory. |
+ cur_dir_norm: The current directory, normalized path. We will create an |
+ implicit rule that allows inclusion from this directory. |
Returns: A new set of rules combining the existing_rules with the other |
arguments. |
@@ -153,22 +155,21 @@ class DepsBuilder(object): |
rules = copy.deepcopy(existing_rules) |
# First apply the implicit "allow" rule for the current directory. |
- if cur_dir.startswith( |
- NormalizePath(os.path.normpath(self.base_directory))): |
- relative_dir = cur_dir[len(self.base_directory) + 1:] |
- |
- source = relative_dir |
- if len(source) == 0: |
- source = 'top level' # Make the help string a little more meaningful. |
- rules.AddRule('+' + relative_dir, |
- relative_dir, |
- 'Default rule for ' + source) |
- else: |
- raise Exception('Internal error: base directory is not at the beginning' + |
- ' for\n %s and base dir\n %s' % |
- (cur_dir, self.base_directory)) |
- |
- def ApplyOneRule(rule_str, cur_dir, dependee_regexp=None): |
+ base_dir_norm = NormalizePath(self.base_directory) |
+ if not cur_dir_norm.startswith(base_dir_norm): |
+ raise Exception( |
+ 'Internal error: base directory is not at the beginning for\n' |
+ ' %s and base dir\n' |
+ ' %s' % (cur_dir_norm, base_dir_norm)) |
+ relative_dir = posixpath.relpath(cur_dir_norm, base_dir_norm) |
+ |
+ # Make the help string a little more meaningful. |
+ source = relative_dir or 'top level' |
+ rules.AddRule('+' + relative_dir, |
+ relative_dir, |
+ 'Default rule for ' + source) |
+ |
+ def ApplyOneRule(rule_str, dependee_regexp=None): |
"""Deduces a sensible description for the rule being added, and |
adds the rule with its description to |rules|. |
@@ -181,25 +182,27 @@ class DepsBuilder(object): |
rule_block_name = 'include_rules' |
if dependee_regexp: |
rule_block_name = 'specific_include_rules' |
- if not relative_dir: |
- rule_description = 'the top level %s' % rule_block_name |
- else: |
+ if relative_dir: |
rule_description = relative_dir + "'s %s" % rule_block_name |
+ else: |
+ rule_description = 'the top level %s' % rule_block_name |
rules.AddRule(rule_str, relative_dir, rule_description, dependee_regexp) |
# Apply the additional explicit rules. |
- for (_, rule_str) in enumerate(includes): |
- ApplyOneRule(rule_str, cur_dir) |
+ for rule_str in includes: |
+ ApplyOneRule(rule_str) |
# Finally, apply the specific rules. |
- if not self._ignore_specific_rules: |
- for regexp, specific_rules in specific_includes.iteritems(): |
- for rule_str in specific_rules: |
- ApplyOneRule(rule_str, cur_dir, regexp) |
+ if self._ignore_specific_rules: |
+ return rules |
+ |
+ for regexp, specific_rules in specific_includes.iteritems(): |
+ for rule_str in specific_rules: |
+ ApplyOneRule(rule_str, regexp) |
return rules |
- def _ApplyDirectoryRules(self, existing_rules, dir_name): |
+ def _ApplyDirectoryRules(self, existing_rules, dir_path_local_abs): |
"""Combines rules from the existing rules and the new directory. |
Any directory can contain a DEPS file. Toplevel DEPS files can contain |
@@ -209,30 +212,31 @@ class DepsBuilder(object): |
Args: |
existing_rules: The rules for the parent directory. We'll add-on to these. |
- dir_name: The directory name that the deps file may live in (if |
- it exists). This will also be used to generate the |
- implicit rules. This is a non-normalized path. |
+ dir_path_local_abs: The directory path that the DEPS file may live in (if |
+ it exists). This will also be used to generate the |
+ implicit rules. This is a local, non-normalized path. |
Returns: A tuple containing: (1) the combined set of rules to apply to the |
sub-tree, and (2) a list of all subdirectories that should NOT be |
- checked, as specified in the DEPS file (if any). |
+ checked, as specified in the DEPS file (if any). Subdirectories |
+ are single words, hence no OS-dependence. |
""" |
- norm_dir_name = NormalizePath(dir_name) |
+ dir_path_norm = NormalizePath(dir_path_local_abs) |
# Check for a .svn directory in this directory or check this directory is |
# contained in git source direcotries. This will tell us if it's a source |
# directory and should be checked. |
- if not (os.path.exists(os.path.join(dir_name, ".svn")) or |
- (norm_dir_name in self.git_source_directories)): |
- return (None, []) |
+ if not (os.path.exists(os.path.join(dir_path_local_abs, '.svn')) or |
+ dir_path_norm in self.git_source_directories): |
+ return None, [] |
# Check the DEPS file in this directory. |
if self.verbose: |
- print 'Applying rules from', dir_name |
- def FromImpl(_unused, _unused2): |
+ print 'Applying rules from', dir_path_local_abs |
+ def FromImpl(*_): |
pass # NOP function so "From" doesn't fail. |
- def FileImpl(_unused): |
+ def FileImpl(_): |
pass # NOP function so "File" doesn't fail. |
class _VarImpl: |
@@ -241,17 +245,18 @@ class DepsBuilder(object): |
def Lookup(self, var_name): |
"""Implements the Var syntax.""" |
- if var_name in self._local_scope.get('vars', {}): |
+ try: |
return self._local_scope['vars'][var_name] |
- raise Exception('Var is not defined: %s' % var_name) |
+ except KeyError: |
+ raise Exception('Var is not defined: %s' % var_name) |
local_scope = {} |
global_scope = { |
- 'File': FileImpl, |
- 'From': FromImpl, |
- 'Var': _VarImpl(local_scope).Lookup, |
- } |
- deps_file = os.path.join(dir_name, 'DEPS') |
+ 'File': FileImpl, |
+ 'From': FromImpl, |
+ 'Var': _VarImpl(local_scope).Lookup, |
+ } |
+ deps_file_path = os.path.join(dir_path_local_abs, 'DEPS') |
# The second conditional here is to disregard the |
# tools/checkdeps/DEPS file while running tests. This DEPS file |
@@ -261,11 +266,12 @@ class DepsBuilder(object): |
# running tests, we absolutely need to verify the contents of that |
# directory to trigger those intended violations and see that they |
# are handled correctly. |
- if os.path.isfile(deps_file) and ( |
- not self._under_test or not os.path.split(dir_name)[1] == 'checkdeps'): |
- execfile(deps_file, global_scope, local_scope) |
+ if os.path.isfile(deps_file_path) and not ( |
+ self._under_test and |
+ os.path.basename(dir_path_local_abs) == 'checkdeps'): |
+ execfile(deps_file_path, global_scope, local_scope) |
elif self.verbose: |
- print ' No deps file found in', dir_name |
+ print ' No deps file found in', dir_path_local_abs |
# Even if a DEPS file does not exist we still invoke ApplyRules |
# to apply the implicit "allow" rule for the current directory |
@@ -275,57 +281,64 @@ class DepsBuilder(object): |
skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, []) |
return (self._ApplyRules(existing_rules, include_rules, |
- specific_include_rules, norm_dir_name), |
+ specific_include_rules, dir_path_norm), |
skip_subdirs) |
- def _ApplyDirectoryRulesAndSkipSubdirs(self, parent_rules, dir_path): |
- """Given |parent_rules| and a subdirectory |dir_path| from the |
- directory that owns the |parent_rules|, add |dir_path|'s rules to |
+ def _ApplyDirectoryRulesAndSkipSubdirs(self, parent_rules, |
+ dir_path_local_abs): |
+ """Given |parent_rules| and a subdirectory |dir_path_local_abs| of the |
+ directory that owns the |parent_rules|, add |dir_path_local_abs|'s rules to |
|self.directory_rules|, and add None entries for any of its |
subdirectories that should be skipped. |
""" |
- directory_rules, excluded_subdirs = self._ApplyDirectoryRules(parent_rules, |
- dir_path) |
- self.directory_rules[NormalizePath(dir_path)] = directory_rules |
+ directory_rules, excluded_subdirs = self._ApplyDirectoryRules( |
+ parent_rules, dir_path_local_abs) |
+ dir_path_norm = NormalizePath(dir_path_local_abs) |
+ self.directory_rules[dir_path_norm] = directory_rules |
for subdir in excluded_subdirs: |
- self.directory_rules[NormalizePath( |
- os.path.normpath(os.path.join(dir_path, subdir)))] = None |
+ subdir_path_norm = posixpath.join(dir_path_norm, subdir) |
+ self.directory_rules[subdir_path_norm] = None |
- def GetDirectoryRules(self, dir_path): |
+ def GetDirectoryRules(self, dir_path_local): |
"""Returns a Rules object to use for the given directory, or None |
- if the given directory should be skipped. This takes care of |
- first building rules for parent directories (up to |
- self.base_directory) if needed. |
+ if the given directory should be skipped. |
+ |
+ Also modifies |self.directory_rules| to store the Rules. |
+ This takes care of first building rules for parent directories (up to |
+ |self.base_directory|) if needed, which may add rules for skipped |
+ subdirectories. |
Args: |
- dir_path: A real (non-normalized) path to the directory you want |
- rules for. |
+ dir_path_local: A local path to the directory you want rules for. |
+ Can be relative and unnormalized. |
""" |
- norm_dir_path = NormalizePath(dir_path) |
- |
- if not norm_dir_path.startswith( |
- NormalizePath(os.path.normpath(self.base_directory))): |
- dir_path = os.path.join(self.base_directory, dir_path) |
- norm_dir_path = NormalizePath(dir_path) |
- |
- parent_dir = os.path.dirname(dir_path) |
- parent_rules = None |
- if not norm_dir_path in self.directory_rules: |
- parent_rules = self.GetDirectoryRules(parent_dir) |
- |
- # We need to check for an entry for our dir_path again, in case we |
- # are at a path e.g. A/B/C where A/B/DEPS specifies the C |
- # subdirectory to be skipped; in this case, the invocation to |
- # GetDirectoryRules(parent_dir) has already filled in an entry for |
- # A/B/C. |
- if not norm_dir_path in self.directory_rules: |
- if not parent_rules: |
- # If the parent directory should be skipped, then the current |
- # directory should also be skipped. |
- self.directory_rules[norm_dir_path] = None |
- else: |
- self._ApplyDirectoryRulesAndSkipSubdirs(parent_rules, dir_path) |
- return self.directory_rules[norm_dir_path] |
+ if os.path.isabs(dir_path_local): |
+ dir_path_local_abs = dir_path_local |
+ else: |
+ dir_path_local_abs = os.path.join(self.base_directory, dir_path_local) |
+ dir_path_norm = NormalizePath(dir_path_local_abs) |
+ |
+ if dir_path_norm in self.directory_rules: |
+ return self.directory_rules[dir_path_norm] |
+ |
+ parent_dir_local_abs = os.path.dirname(dir_path_local_abs) |
+ parent_rules = self.GetDirectoryRules(parent_dir_local_abs) |
+ # We need to check for an entry for our dir_path again, since |
+ # GetDirectoryRules can modify entries for subdirectories, namely setting |
+ # to None if they should be skipped, via _ApplyDirectoryRulesAndSkipSubdirs. |
+ # For example, if dir_path == 'A/B/C' and A/B/DEPS specifies that the C |
+ # subdirectory be skipped, GetDirectoryRules('A/B') will fill in the entry |
+ # for 'A/B/C' as None. |
+ if dir_path_norm in self.directory_rules: |
+ return self.directory_rules[dir_path_norm] |
+ |
+ if parent_rules: |
+ self._ApplyDirectoryRulesAndSkipSubdirs(parent_rules, dir_path_local_abs) |
+ else: |
+ # If the parent directory should be skipped, then the current |
+ # directory should also be skipped. |
+ self.directory_rules[dir_path_norm] = None |
+ return self.directory_rules[dir_path_norm] |
def _AddGitSourceDirectories(self): |
"""Adds any directories containing sources managed by git to |
@@ -337,10 +350,10 @@ class DepsBuilder(object): |
popen_out = os.popen('cd %s && git ls-files --full-name .' % |
subprocess.list2cmdline([self.base_directory])) |
for line in popen_out.readlines(): |
- dir_name = os.path.join(self.base_directory, os.path.dirname(line)) |
+ dir_path = os.path.join(self.base_directory, os.path.dirname(line)) |
# Add the directory as well as all the parent directories. Use |
# forward slashes and lower case to normalize paths. |
- while dir_name != self.base_directory: |
- self.git_source_directories.add(NormalizePath(dir_name)) |
- dir_name = os.path.dirname(dir_name) |
+ while dir_path != self.base_directory: |
+ self.git_source_directories.add(NormalizePath(dir_path)) |
+ dir_path = os.path.dirname(dir_path) |
self.git_source_directories.add(NormalizePath(self.base_directory)) |