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