fixup! Upload upstream chromium 76.0.3809.146
[platform/framework/web/chromium-efl.git] / buildtools / checkdeps / rules.py
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.')