Revert 264226 "Reduce dependency of TiclInvalidationService on P..."
[chromium-blink-merge.git] / tools / checkdeps / rules.py
blobb8a07df0050d424d1e6858884584549cae9321c1
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.
5 """Base classes to represent dependency rules, used by checkdeps.py"""
8 import os
9 import re
12 class Rule(object):
13 """Specifies a single rule for an include, which can be one of
14 ALLOW, DISALLOW and TEMP_ALLOW.
15 """
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 = '!'
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
30 def __str__(self):
31 return '"%s%s" from %s.' % (self.allow, self._dir, self._source)
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 '.')
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 + '/')
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 + '/')
51 class MessageRule(Rule):
52 """A rule that has a simple message as the reason for failing,
53 unrelated to directory or source.
54 """
56 def __init__(self, reason):
57 super(MessageRule, self).__init__(Rule.DISALLOW, '', '', '')
58 self._reason = reason
60 def __str__(self):
61 return self._reason
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))
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)
77 return (rule_string[0], rule_string[1:])
80 class Rules(object):
81 """Sets of rules for files in a directory.
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 """
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 = []
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 = {}
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)
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)
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
131 def AddRule(self, rule_string, dependent_dir, source, dependee_regexp=None):
132 """Adds a rule for the given rule string.
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.
144 (rule_type, rule_dir) = ParseRuleString(rule_string, source)
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 = []
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))
160 if not dependee_regexp:
161 self._general_rules = rules_to_update
162 else:
163 self._specific_rules[dependee_regexp] = rules_to_update
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|.
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.')