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"""
13 """Specifies a single rule for an include, which can be one of
14 ALLOW, DISALLOW and TEMP_ALLOW.
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
24 def __init__(self
, allow
, directory
, dependent_directory
, source
):
27 self
._dependent
_dir
= dependent_directory
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
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.
56 def __init__(self
, reason
):
57 super(MessageRule
, self
).__init
__(Rule
.DISALLOW
, '', '', '')
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.
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
]:
74 'The rule string "%s" does not begin with a "+", "-" or "!".' %
77 return (rule_string
[0], rule_string
[1:])
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.
91 """Initializes the current rules with an empty rule list for all
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
= {}
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
)))
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
117 def AddDependencyTuplesImpl(deps
, rules
, extra_dependent_suffix
=""):
119 (allow
, dependent
, dependee
) = rule
.AsDependencyTuple()
120 tup
= (allow
, dependent
+ extra_dependent_suffix
, dependee
)
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
)
131 def AddRule(self
, rule_string
, dependent_dir
, source
, dependee_regexp
=None):
132 """Adds a rule for the given rule string.
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
144 (rule_type
, rule_dir
) = ParseRuleString(rule_string
, source
)
146 if not dependee_regexp
:
147 rules_to_update
= self
._general
_rules
149 if dependee_regexp
in self
._specific
_rules
:
150 rules_to_update
= self
._specific
_rules
[dependee_regexp
]
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
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
):
175 for rule
in self
._general
_rules
:
176 if rule
.ChildOrMatch(include_path
):
178 return MessageRule('no rule applying.')