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 |