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