1cb93a386Sopenharmony_ci# Copyright 2012 The Chromium Authors. All rights reserved.
2cb93a386Sopenharmony_ci# Use of this source code is governed by a BSD-style license that can be
3cb93a386Sopenharmony_ci# found in the LICENSE file.
4cb93a386Sopenharmony_ci
5cb93a386Sopenharmony_ci"""Base classes to represent dependency rules, used by checkdeps.py"""
6cb93a386Sopenharmony_ci
7cb93a386Sopenharmony_ci
8cb93a386Sopenharmony_ciimport os
9cb93a386Sopenharmony_ciimport re
10cb93a386Sopenharmony_ci
11cb93a386Sopenharmony_ci
12cb93a386Sopenharmony_ciclass Rule(object):
13cb93a386Sopenharmony_ci  """Specifies a single rule for an include, which can be one of
14cb93a386Sopenharmony_ci  ALLOW, DISALLOW and TEMP_ALLOW.
15cb93a386Sopenharmony_ci  """
16cb93a386Sopenharmony_ci
17cb93a386Sopenharmony_ci  # These are the prefixes used to indicate each type of rule. These
18cb93a386Sopenharmony_ci  # are also used as values for self.allow to indicate which type of
19cb93a386Sopenharmony_ci  # rule this is.
20cb93a386Sopenharmony_ci  ALLOW = '+'
21cb93a386Sopenharmony_ci  DISALLOW = '-'
22cb93a386Sopenharmony_ci  TEMP_ALLOW = '!'
23cb93a386Sopenharmony_ci
24cb93a386Sopenharmony_ci  def __init__(self, allow, directory, dependent_directory, source):
25cb93a386Sopenharmony_ci    self.allow = allow
26cb93a386Sopenharmony_ci    self._dir = directory
27cb93a386Sopenharmony_ci    self._dependent_dir = dependent_directory
28cb93a386Sopenharmony_ci    self._source = source
29cb93a386Sopenharmony_ci
30cb93a386Sopenharmony_ci  def __str__(self):
31cb93a386Sopenharmony_ci    return '"%s%s" from %s.' % (self.allow, self._dir, self._source)
32cb93a386Sopenharmony_ci
33cb93a386Sopenharmony_ci  def AsDependencyTuple(self):
34cb93a386Sopenharmony_ci    """Returns a tuple (allow, dependent dir, dependee dir) for this rule,
35cb93a386Sopenharmony_ci    which is fully self-sufficient to answer the question whether the dependent
36cb93a386Sopenharmony_ci    is allowed to depend on the dependee, without knowing the external
37cb93a386Sopenharmony_ci    context."""
38cb93a386Sopenharmony_ci    return self.allow, self._dependent_dir or '.', self._dir or '.'
39cb93a386Sopenharmony_ci
40cb93a386Sopenharmony_ci  def ParentOrMatch(self, other):
41cb93a386Sopenharmony_ci    """Returns true if the input string is an exact match or is a parent
42cb93a386Sopenharmony_ci    of the current rule. For example, the input "foo" would match "foo/bar"."""
43cb93a386Sopenharmony_ci    return self._dir == other or self._dir.startswith(other + '/')
44cb93a386Sopenharmony_ci
45cb93a386Sopenharmony_ci  def ChildOrMatch(self, other):
46cb93a386Sopenharmony_ci    """Returns true if the input string would be covered by this rule. For
47cb93a386Sopenharmony_ci    example, the input "foo/bar" would match the rule "foo"."""
48cb93a386Sopenharmony_ci    return self._dir == other or other.startswith(self._dir + '/')
49cb93a386Sopenharmony_ci
50cb93a386Sopenharmony_ci
51cb93a386Sopenharmony_ciclass MessageRule(Rule):
52cb93a386Sopenharmony_ci  """A rule that has a simple message as the reason for failing,
53cb93a386Sopenharmony_ci  unrelated to directory or source.
54cb93a386Sopenharmony_ci  """
55cb93a386Sopenharmony_ci
56cb93a386Sopenharmony_ci  def __init__(self, reason):
57cb93a386Sopenharmony_ci    super(MessageRule, self).__init__(Rule.DISALLOW, '', '', '')
58cb93a386Sopenharmony_ci    self._reason = reason
59cb93a386Sopenharmony_ci
60cb93a386Sopenharmony_ci  def __str__(self):
61cb93a386Sopenharmony_ci    return self._reason
62cb93a386Sopenharmony_ci
63cb93a386Sopenharmony_ci
64cb93a386Sopenharmony_cidef ParseRuleString(rule_string, source):
65cb93a386Sopenharmony_ci  """Returns a tuple of a character indicating what type of rule this
66cb93a386Sopenharmony_ci  is, and a string holding the path the rule applies to.
67cb93a386Sopenharmony_ci  """
68cb93a386Sopenharmony_ci  if not rule_string:
69cb93a386Sopenharmony_ci    raise Exception('The rule string "%s" is empty\nin %s' %
70cb93a386Sopenharmony_ci                    (rule_string, source))
71cb93a386Sopenharmony_ci
72cb93a386Sopenharmony_ci  if not rule_string[0] in [Rule.ALLOW, Rule.DISALLOW, Rule.TEMP_ALLOW]:
73cb93a386Sopenharmony_ci    raise Exception(
74cb93a386Sopenharmony_ci      'The rule string "%s" does not begin with a "+", "-" or "!".' %
75cb93a386Sopenharmony_ci      rule_string)
76cb93a386Sopenharmony_ci
77cb93a386Sopenharmony_ci  # If a directory is specified in a DEPS file with a trailing slash, then it
78cb93a386Sopenharmony_ci  # will not match as a parent directory in Rule's [Parent|Child]OrMatch above.
79cb93a386Sopenharmony_ci  # Ban them.
80cb93a386Sopenharmony_ci  if rule_string[-1] == '/':
81cb93a386Sopenharmony_ci    raise Exception(
82cb93a386Sopenharmony_ci      'The rule string "%s" ends with a "/" which is not allowed.'
83cb93a386Sopenharmony_ci      ' Please remove the trailing "/".' % rule_string)
84cb93a386Sopenharmony_ci
85cb93a386Sopenharmony_ci  return rule_string[0], rule_string[1:]
86cb93a386Sopenharmony_ci
87cb93a386Sopenharmony_ci
88cb93a386Sopenharmony_ciclass Rules(object):
89cb93a386Sopenharmony_ci  """Sets of rules for files in a directory.
90cb93a386Sopenharmony_ci
91cb93a386Sopenharmony_ci  By default, rules are added to the set of rules applicable to all
92cb93a386Sopenharmony_ci  dependee files in the directory.  Rules may also be added that apply
93cb93a386Sopenharmony_ci  only to dependee files whose filename (last component of their path)
94cb93a386Sopenharmony_ci  matches a given regular expression; hence there is one additional
95cb93a386Sopenharmony_ci  set of rules per unique regular expression.
96cb93a386Sopenharmony_ci  """
97cb93a386Sopenharmony_ci
98cb93a386Sopenharmony_ci  def __init__(self):
99cb93a386Sopenharmony_ci    """Initializes the current rules with an empty rule list for all
100cb93a386Sopenharmony_ci    files.
101cb93a386Sopenharmony_ci    """
102cb93a386Sopenharmony_ci    # We keep the general rules out of the specific rules dictionary,
103cb93a386Sopenharmony_ci    # as we need to always process them last.
104cb93a386Sopenharmony_ci    self._general_rules = []
105cb93a386Sopenharmony_ci
106cb93a386Sopenharmony_ci    # Keys are regular expression strings, values are arrays of rules
107cb93a386Sopenharmony_ci    # that apply to dependee files whose basename matches the regular
108cb93a386Sopenharmony_ci    # expression.  These are applied before the general rules, but
109cb93a386Sopenharmony_ci    # their internal order is arbitrary.
110cb93a386Sopenharmony_ci    self._specific_rules = {}
111cb93a386Sopenharmony_ci
112cb93a386Sopenharmony_ci  def __str__(self):
113cb93a386Sopenharmony_ci    result = ['Rules = {\n    (apply to all files): [\n%s\n    ],' % '\n'.join(
114cb93a386Sopenharmony_ci        '      %s' % x for x in self._general_rules)]
115cb93a386Sopenharmony_ci    for regexp, rules in list(self._specific_rules.items()):
116cb93a386Sopenharmony_ci      result.append('    (limited to files matching %s): [\n%s\n    ]' % (
117cb93a386Sopenharmony_ci          regexp, '\n'.join('      %s' % x for x in rules)))
118cb93a386Sopenharmony_ci    result.append('  }')
119cb93a386Sopenharmony_ci    return '\n'.join(result)
120cb93a386Sopenharmony_ci
121cb93a386Sopenharmony_ci  def AsDependencyTuples(self, include_general_rules, include_specific_rules):
122cb93a386Sopenharmony_ci    """Returns a list of tuples (allow, dependent dir, dependee dir) for the
123cb93a386Sopenharmony_ci    specified rules (general/specific). Currently only general rules are
124cb93a386Sopenharmony_ci    supported."""
125cb93a386Sopenharmony_ci    def AddDependencyTuplesImpl(deps, rules, extra_dependent_suffix=""):
126cb93a386Sopenharmony_ci      for rule in rules:
127cb93a386Sopenharmony_ci        (allow, dependent, dependee) = rule.AsDependencyTuple()
128cb93a386Sopenharmony_ci        tup = (allow, dependent + extra_dependent_suffix, dependee)
129cb93a386Sopenharmony_ci        deps.add(tup)
130cb93a386Sopenharmony_ci
131cb93a386Sopenharmony_ci    deps = set()
132cb93a386Sopenharmony_ci    if include_general_rules:
133cb93a386Sopenharmony_ci      AddDependencyTuplesImpl(deps, self._general_rules)
134cb93a386Sopenharmony_ci    if include_specific_rules:
135cb93a386Sopenharmony_ci      for regexp, rules in list(self._specific_rules.items()):
136cb93a386Sopenharmony_ci        AddDependencyTuplesImpl(deps, rules, "/" + regexp)
137cb93a386Sopenharmony_ci    return deps
138cb93a386Sopenharmony_ci
139cb93a386Sopenharmony_ci  def AddRule(self, rule_string, dependent_dir, source, dependee_regexp=None):
140cb93a386Sopenharmony_ci    """Adds a rule for the given rule string.
141cb93a386Sopenharmony_ci
142cb93a386Sopenharmony_ci    Args:
143cb93a386Sopenharmony_ci      rule_string: The include_rule string read from the DEPS file to apply.
144cb93a386Sopenharmony_ci      source: A string representing the location of that string (filename, etc.)
145cb93a386Sopenharmony_ci              so that we can give meaningful errors.
146cb93a386Sopenharmony_ci      dependent_dir: The directory to which this rule applies.
147cb93a386Sopenharmony_ci      dependee_regexp: The rule will only be applied to dependee files
148cb93a386Sopenharmony_ci                       whose filename (last component of their path)
149cb93a386Sopenharmony_ci                       matches the expression. None to match all
150cb93a386Sopenharmony_ci                       dependee files.
151cb93a386Sopenharmony_ci    """
152cb93a386Sopenharmony_ci    rule_type, rule_dir = ParseRuleString(rule_string, source)
153cb93a386Sopenharmony_ci
154cb93a386Sopenharmony_ci    if not dependee_regexp:
155cb93a386Sopenharmony_ci      rules_to_update = self._general_rules
156cb93a386Sopenharmony_ci    else:
157cb93a386Sopenharmony_ci      if dependee_regexp in self._specific_rules:
158cb93a386Sopenharmony_ci        rules_to_update = self._specific_rules[dependee_regexp]
159cb93a386Sopenharmony_ci      else:
160cb93a386Sopenharmony_ci        rules_to_update = []
161cb93a386Sopenharmony_ci
162cb93a386Sopenharmony_ci    # Remove any existing rules or sub-rules that apply. For example, if we're
163cb93a386Sopenharmony_ci    # passed "foo", we should remove "foo", "foo/bar", but not "foobar".
164cb93a386Sopenharmony_ci    rules_to_update = [x for x in rules_to_update
165cb93a386Sopenharmony_ci                       if not x.ParentOrMatch(rule_dir)]
166cb93a386Sopenharmony_ci    rules_to_update.insert(0, Rule(rule_type, rule_dir, dependent_dir, source))
167cb93a386Sopenharmony_ci
168cb93a386Sopenharmony_ci    if not dependee_regexp:
169cb93a386Sopenharmony_ci      self._general_rules = rules_to_update
170cb93a386Sopenharmony_ci    else:
171cb93a386Sopenharmony_ci      self._specific_rules[dependee_regexp] = rules_to_update
172cb93a386Sopenharmony_ci
173cb93a386Sopenharmony_ci  def RuleApplyingTo(self, include_path, dependee_path):
174cb93a386Sopenharmony_ci    """Returns the rule that applies to |include_path| for a dependee
175cb93a386Sopenharmony_ci    file located at |dependee_path|.
176cb93a386Sopenharmony_ci    """
177cb93a386Sopenharmony_ci    dependee_filename = os.path.basename(dependee_path)
178cb93a386Sopenharmony_ci    for regexp, specific_rules in list(self._specific_rules.items()):
179cb93a386Sopenharmony_ci      if re.match(regexp, dependee_filename):
180cb93a386Sopenharmony_ci        for rule in specific_rules:
181cb93a386Sopenharmony_ci          if rule.ChildOrMatch(include_path):
182cb93a386Sopenharmony_ci            return rule
183cb93a386Sopenharmony_ci    for rule in self._general_rules:
184cb93a386Sopenharmony_ci      if rule.ChildOrMatch(include_path):
185cb93a386Sopenharmony_ci        return rule
186cb93a386Sopenharmony_ci    return MessageRule('no rule applying.')
187