OLD | NEW |
(Empty) | |
| 1 # Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 """Base classes to represent dependency rules, used by checkdeps.py""" |
| 6 |
| 7 |
| 8 import os |
| 9 import re |
| 10 |
| 11 |
| 12 class Rule(object): |
| 13 """Specifies a single rule for an include, which can be one of |
| 14 ALLOW, DISALLOW and TEMP_ALLOW. |
| 15 """ |
| 16 |
| 17 # These are the prefixes used to indicate each type of rule. These |
| 18 # are also used as values for self.allow to indicate which type of |
| 19 # rule this is. |
| 20 ALLOW = '+' |
| 21 DISALLOW = '-' |
| 22 TEMP_ALLOW = '!' |
| 23 |
| 24 def __init__(self, allow, directory, dependent_directory, source): |
| 25 self.allow = allow |
| 26 self._dir = directory |
| 27 self._dependent_dir = dependent_directory |
| 28 self._source = source |
| 29 |
| 30 def __str__(self): |
| 31 return '"%s%s" from %s.' % (self.allow, self._dir, self._source) |
| 32 |
| 33 def AsDependencyTuple(self): |
| 34 """Returns a tuple (allow, dependent dir, dependee dir) for this rule, |
| 35 which is fully self-sufficient to answer the question whether the dependent |
| 36 is allowed to depend on the dependee, without knowing the external |
| 37 context.""" |
| 38 return self.allow, self._dependent_dir or '.', self._dir or '.' |
| 39 |
| 40 def ParentOrMatch(self, other): |
| 41 """Returns true if the input string is an exact match or is a parent |
| 42 of the current rule. For example, the input "foo" would match "foo/bar".""" |
| 43 return self._dir == other or self._dir.startswith(other + '/') |
| 44 |
| 45 def ChildOrMatch(self, other): |
| 46 """Returns true if the input string would be covered by this rule. For |
| 47 example, the input "foo/bar" would match the rule "foo".""" |
| 48 return self._dir == other or other.startswith(self._dir + '/') |
| 49 |
| 50 |
| 51 class MessageRule(Rule): |
| 52 """A rule that has a simple message as the reason for failing, |
| 53 unrelated to directory or source. |
| 54 """ |
| 55 |
| 56 def __init__(self, reason): |
| 57 super(MessageRule, self).__init__(Rule.DISALLOW, '', '', '') |
| 58 self._reason = reason |
| 59 |
| 60 def __str__(self): |
| 61 return self._reason |
| 62 |
| 63 |
| 64 def ParseRuleString(rule_string, source): |
| 65 """Returns a tuple of a character indicating what type of rule this |
| 66 is, and a string holding the path the rule applies to. |
| 67 """ |
| 68 if not rule_string: |
| 69 raise Exception('The rule string "%s" is empty\nin %s' % |
| 70 (rule_string, source)) |
| 71 |
| 72 if not rule_string[0] in [Rule.ALLOW, Rule.DISALLOW, Rule.TEMP_ALLOW]: |
| 73 raise Exception( |
| 74 'The rule string "%s" does not begin with a "+", "-" or "!".' % |
| 75 rule_string) |
| 76 |
| 77 return rule_string[0], rule_string[1:] |
| 78 |
| 79 |
| 80 class Rules(object): |
| 81 """Sets of rules for files in a directory. |
| 82 |
| 83 By default, rules are added to the set of rules applicable to all |
| 84 dependee files in the directory. Rules may also be added that apply |
| 85 only to dependee files whose filename (last component of their path) |
| 86 matches a given regular expression; hence there is one additional |
| 87 set of rules per unique regular expression. |
| 88 """ |
| 89 |
| 90 def __init__(self): |
| 91 """Initializes the current rules with an empty rule list for all |
| 92 files. |
| 93 """ |
| 94 # We keep the general rules out of the specific rules dictionary, |
| 95 # as we need to always process them last. |
| 96 self._general_rules = [] |
| 97 |
| 98 # Keys are regular expression strings, values are arrays of rules |
| 99 # that apply to dependee files whose basename matches the regular |
| 100 # expression. These are applied before the general rules, but |
| 101 # their internal order is arbitrary. |
| 102 self._specific_rules = {} |
| 103 |
| 104 def __str__(self): |
| 105 result = ['Rules = {\n (apply to all files): [\n%s\n ],' % '\n'.join( |
| 106 ' %s' % x for x in self._general_rules)] |
| 107 for regexp, rules in self._specific_rules.iteritems(): |
| 108 result.append(' (limited to files matching %s): [\n%s\n ]' % ( |
| 109 regexp, '\n'.join(' %s' % x for x in rules))) |
| 110 result.append(' }') |
| 111 return '\n'.join(result) |
| 112 |
| 113 def AsDependencyTuples(self, include_general_rules, include_specific_rules): |
| 114 """Returns a list of tuples (allow, dependent dir, dependee dir) for the |
| 115 specified rules (general/specific). Currently only general rules are |
| 116 supported.""" |
| 117 def AddDependencyTuplesImpl(deps, rules, extra_dependent_suffix=""): |
| 118 for rule in rules: |
| 119 (allow, dependent, dependee) = rule.AsDependencyTuple() |
| 120 tup = (allow, dependent + extra_dependent_suffix, dependee) |
| 121 deps.add(tup) |
| 122 |
| 123 deps = set() |
| 124 if include_general_rules: |
| 125 AddDependencyTuplesImpl(deps, self._general_rules) |
| 126 if include_specific_rules: |
| 127 for regexp, rules in self._specific_rules.iteritems(): |
| 128 AddDependencyTuplesImpl(deps, rules, "/" + regexp) |
| 129 return deps |
| 130 |
| 131 def AddRule(self, rule_string, dependent_dir, source, dependee_regexp=None): |
| 132 """Adds a rule for the given rule string. |
| 133 |
| 134 Args: |
| 135 rule_string: The include_rule string read from the DEPS file to apply. |
| 136 source: A string representing the location of that string (filename, etc.) |
| 137 so that we can give meaningful errors. |
| 138 dependent_dir: The directory to which this rule applies. |
| 139 dependee_regexp: The rule will only be applied to dependee files |
| 140 whose filename (last component of their path) |
| 141 matches the expression. None to match all |
| 142 dependee files. |
| 143 """ |
| 144 rule_type, rule_dir = ParseRuleString(rule_string, source) |
| 145 |
| 146 if not dependee_regexp: |
| 147 rules_to_update = self._general_rules |
| 148 else: |
| 149 if dependee_regexp in self._specific_rules: |
| 150 rules_to_update = self._specific_rules[dependee_regexp] |
| 151 else: |
| 152 rules_to_update = [] |
| 153 |
| 154 # Remove any existing rules or sub-rules that apply. For example, if we're |
| 155 # passed "foo", we should remove "foo", "foo/bar", but not "foobar". |
| 156 rules_to_update = [x for x in rules_to_update |
| 157 if not x.ParentOrMatch(rule_dir)] |
| 158 rules_to_update.insert(0, Rule(rule_type, rule_dir, dependent_dir, source)) |
| 159 |
| 160 if not dependee_regexp: |
| 161 self._general_rules = rules_to_update |
| 162 else: |
| 163 self._specific_rules[dependee_regexp] = rules_to_update |
| 164 |
| 165 def RuleApplyingTo(self, include_path, dependee_path): |
| 166 """Returns the rule that applies to |include_path| for a dependee |
| 167 file located at |dependee_path|. |
| 168 """ |
| 169 dependee_filename = os.path.basename(dependee_path) |
| 170 for regexp, specific_rules in self._specific_rules.iteritems(): |
| 171 if re.match(regexp, dependee_filename): |
| 172 for rule in specific_rules: |
| 173 if rule.ChildOrMatch(include_path): |
| 174 return rule |
| 175 for rule in self._general_rules: |
| 176 if rule.ChildOrMatch(include_path): |
| 177 return rule |
| 178 return MessageRule('no rule applying.') |
OLD | NEW |