|  | # Copyright 2012 The Chromium Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | """Base classes to represent dependency rules, used by checkdeps.py""" | 
|  |  | 
|  |  | 
|  | import os | 
|  | import re | 
|  |  | 
|  |  | 
|  | class Rule(object): | 
|  | """Specifies a single rule for an include, which can be one of | 
|  | ALLOW, DISALLOW and TEMP_ALLOW. | 
|  | """ | 
|  |  | 
|  | # These are the prefixes used to indicate each type of rule. These | 
|  | # are also used as values for self.allow to indicate which type of | 
|  | # rule this is. | 
|  | ALLOW = '+' | 
|  | DISALLOW = '-' | 
|  | TEMP_ALLOW = '!' | 
|  |  | 
|  | def __init__(self, allow, directory, dependent_directory, source): | 
|  | self.allow = allow | 
|  | self._dir = directory | 
|  | self._dependent_dir = dependent_directory | 
|  | self._source = source | 
|  |  | 
|  | def __str__(self): | 
|  | return '"%s%s" from %s.' % (self.allow, self._dir, self._source) | 
|  |  | 
|  | def AsDependencyTuple(self): | 
|  | """Returns a tuple (allow, dependent dir, dependee dir) for this rule, | 
|  | which is fully self-sufficient to answer the question whether the dependent | 
|  | is allowed to depend on the dependee, without knowing the external | 
|  | context.""" | 
|  | return self.allow, self._dependent_dir or '.', self._dir or '.' | 
|  |  | 
|  | def ParentOrMatch(self, other): | 
|  | """Returns true if the input string is an exact match or is a parent | 
|  | of the current rule. For example, the input "foo" would match "foo/bar".""" | 
|  | return self._dir == other or self._dir.startswith(other + '/') | 
|  |  | 
|  | def ChildOrMatch(self, other): | 
|  | """Returns true if the input string would be covered by this rule. For | 
|  | example, the input "foo/bar" would match the rule "foo".""" | 
|  | return self._dir == other or other.startswith(self._dir + '/') | 
|  |  | 
|  |  | 
|  | class MessageRule(Rule): | 
|  | """A rule that has a simple message as the reason for failing, | 
|  | unrelated to directory or source. | 
|  | """ | 
|  |  | 
|  | def __init__(self, reason): | 
|  | super(MessageRule, self).__init__(Rule.DISALLOW, '', '', '') | 
|  | self._reason = reason | 
|  |  | 
|  | def __str__(self): | 
|  | return self._reason | 
|  |  | 
|  |  | 
|  | def ParseRuleString(rule_string, source): | 
|  | """Returns a tuple of a character indicating what type of rule this | 
|  | is, and a string holding the path the rule applies to. | 
|  | """ | 
|  | if not rule_string: | 
|  | raise Exception('The rule string "%s" is empty\nin %s' % | 
|  | (rule_string, source)) | 
|  |  | 
|  | if not rule_string[0] in [Rule.ALLOW, Rule.DISALLOW, Rule.TEMP_ALLOW]: | 
|  | raise Exception( | 
|  | 'The rule string "%s" does not begin with a "+", "-" or "!".' % | 
|  | rule_string) | 
|  |  | 
|  | return rule_string[0], rule_string[1:] | 
|  |  | 
|  |  | 
|  | class Rules(object): | 
|  | """Sets of rules for files in a directory. | 
|  |  | 
|  | By default, rules are added to the set of rules applicable to all | 
|  | dependee files in the directory.  Rules may also be added that apply | 
|  | only to dependee files whose filename (last component of their path) | 
|  | matches a given regular expression; hence there is one additional | 
|  | set of rules per unique regular expression. | 
|  | """ | 
|  |  | 
|  | def __init__(self): | 
|  | """Initializes the current rules with an empty rule list for all | 
|  | files. | 
|  | """ | 
|  | # We keep the general rules out of the specific rules dictionary, | 
|  | # as we need to always process them last. | 
|  | self._general_rules = [] | 
|  |  | 
|  | # Keys are regular expression strings, values are arrays of rules | 
|  | # that apply to dependee files whose basename matches the regular | 
|  | # expression.  These are applied before the general rules, but | 
|  | # their internal order is arbitrary. | 
|  | self._specific_rules = {} | 
|  |  | 
|  | def __str__(self): | 
|  | result = ['Rules = {\n    (apply to all files): [\n%s\n    ],' % '\n'.join( | 
|  | '      %s' % x for x in self._general_rules)] | 
|  | for regexp, rules in self._specific_rules.iteritems(): | 
|  | result.append('    (limited to files matching %s): [\n%s\n    ]' % ( | 
|  | regexp, '\n'.join('      %s' % x for x in rules))) | 
|  | result.append('  }') | 
|  | return '\n'.join(result) | 
|  |  | 
|  | def AsDependencyTuples(self, include_general_rules, include_specific_rules): | 
|  | """Returns a list of tuples (allow, dependent dir, dependee dir) for the | 
|  | specified rules (general/specific). Currently only general rules are | 
|  | supported.""" | 
|  | def AddDependencyTuplesImpl(deps, rules, extra_dependent_suffix=""): | 
|  | for rule in rules: | 
|  | (allow, dependent, dependee) = rule.AsDependencyTuple() | 
|  | tup = (allow, dependent + extra_dependent_suffix, dependee) | 
|  | deps.add(tup) | 
|  |  | 
|  | deps = set() | 
|  | if include_general_rules: | 
|  | AddDependencyTuplesImpl(deps, self._general_rules) | 
|  | if include_specific_rules: | 
|  | for regexp, rules in self._specific_rules.iteritems(): | 
|  | AddDependencyTuplesImpl(deps, rules, "/" + regexp) | 
|  | return deps | 
|  |  | 
|  | def AddRule(self, rule_string, dependent_dir, source, dependee_regexp=None): | 
|  | """Adds a rule for the given rule string. | 
|  |  | 
|  | Args: | 
|  | rule_string: The include_rule string read from the DEPS file to apply. | 
|  | source: A string representing the location of that string (filename, etc.) | 
|  | so that we can give meaningful errors. | 
|  | dependent_dir: The directory to which this rule applies. | 
|  | dependee_regexp: The rule will only be applied to dependee files | 
|  | whose filename (last component of their path) | 
|  | matches the expression. None to match all | 
|  | dependee files. | 
|  | """ | 
|  | rule_type, rule_dir = ParseRuleString(rule_string, source) | 
|  |  | 
|  | if not dependee_regexp: | 
|  | rules_to_update = self._general_rules | 
|  | else: | 
|  | if dependee_regexp in self._specific_rules: | 
|  | rules_to_update = self._specific_rules[dependee_regexp] | 
|  | else: | 
|  | rules_to_update = [] | 
|  |  | 
|  | # Remove any existing rules or sub-rules that apply. For example, if we're | 
|  | # passed "foo", we should remove "foo", "foo/bar", but not "foobar". | 
|  | rules_to_update = [x for x in rules_to_update | 
|  | if not x.ParentOrMatch(rule_dir)] | 
|  | rules_to_update.insert(0, Rule(rule_type, rule_dir, dependent_dir, source)) | 
|  |  | 
|  | if not dependee_regexp: | 
|  | self._general_rules = rules_to_update | 
|  | else: | 
|  | self._specific_rules[dependee_regexp] = rules_to_update | 
|  |  | 
|  | def RuleApplyingTo(self, include_path, dependee_path): | 
|  | """Returns the rule that applies to |include_path| for a dependee | 
|  | file located at |dependee_path|. | 
|  | """ | 
|  | dependee_filename = os.path.basename(dependee_path) | 
|  | for regexp, specific_rules in self._specific_rules.iteritems(): | 
|  | if re.match(regexp, dependee_filename): | 
|  | for rule in specific_rules: | 
|  | if rule.ChildOrMatch(include_path): | 
|  | return rule | 
|  | for rule in self._general_rules: | 
|  | if rule.ChildOrMatch(include_path): | 
|  | return rule | 
|  | return MessageRule('no rule applying.') |