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
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
, source
):
30 return '"%s%s" from %s.' % (self
.allow
, self
._dir
, self
._source
)
32 def ParentOrMatch(self
, other
):
33 """Returns true if the input string is an exact match or is a parent
34 of the current rule. For example, the input "foo" would match "foo/bar"."""
35 return self
._dir
== other
or self
._dir
.startswith(other
+ '/')
37 def ChildOrMatch(self
, other
):
38 """Returns true if the input string would be covered by this rule. For
39 example, the input "foo/bar" would match the rule "foo"."""
40 return self
._dir
== other
or other
.startswith(self
._dir
+ '/')
43 class MessageRule(Rule
):
44 """A rule that has a simple message as the reason for failing,
45 unrelated to directory or source.
48 def __init__(self
, reason
):
49 super(MessageRule
, self
).__init
__(Rule
.DISALLOW
, '', '')
56 def ParseRuleString(rule_string
, source
):
57 """Returns a tuple of a character indicating what type of rule this
58 is, and a string holding the path the rule applies to.
61 raise Exception('The rule string "%s" is empty\nin %s' %
62 (rule_string
, source
))
64 if not rule_string
[0] in [Rule
.ALLOW
, Rule
.DISALLOW
, Rule
.TEMP_ALLOW
]:
66 'The rule string "%s" does not begin with a "+", "-" or "!".' %
69 return (rule_string
[0], rule_string
[1:])
73 """Sets of rules for files in a directory.
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.
83 """Initializes the current rules with an empty rule list for all
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
= []
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
= {}
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
)))
103 return '\n'.join(result
)
105 def AddRule(self
, rule_string
, source
, dependee_regexp
=None):
106 """Adds a rule for the given rule string.
109 rule_string: The include_rule string read from the DEPS file to apply.
110 source: A string representing the location of that string (filename, etc.)
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
117 (rule_type
, rule_dir
) = ParseRuleString(rule_string
, source
)
119 if not dependee_regexp
:
120 rules_to_update
= self
._general
_rules
122 if dependee_regexp
in self
._specific
_rules
:
123 rules_to_update
= self
._specific
_rules
[dependee_regexp
]
127 # Remove any existing rules or sub-rules that apply. For example, if we're
128 # passed "foo", we should remove "foo", "foo/bar", but not "foobar".
129 rules_to_update
= [x
for x
in rules_to_update
130 if not x
.ParentOrMatch(rule_dir
)]
131 rules_to_update
.insert(0, Rule(rule_type
, rule_dir
, source
))
133 if not dependee_regexp
:
134 self
._general
_rules
= rules_to_update
136 self
._specific
_rules
[dependee_regexp
] = rules_to_update
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|.
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
):
148 for rule
in self
._general
_rules
:
149 if rule
.ChildOrMatch(include_path
):
151 return MessageRule('no rule applying.')