|  | # Copyright (c) 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. | 
|  |  | 
|  | """Checks protobuf files for illegal imports.""" | 
|  |  | 
|  | import codecs | 
|  | import os | 
|  | import re | 
|  |  | 
|  | import results | 
|  | from rules import Rule, MessageRule | 
|  |  | 
|  |  | 
|  | class ProtoChecker(object): | 
|  |  | 
|  | EXTENSIONS = [ | 
|  | '.proto', | 
|  | ] | 
|  |  | 
|  | # The maximum number of non-import lines we can see before giving up. | 
|  | _MAX_UNINTERESTING_LINES = 50 | 
|  |  | 
|  | # The maximum line length, this is to be efficient in the case of very long | 
|  | # lines (which can't be import). | 
|  | _MAX_LINE_LENGTH = 128 | 
|  |  | 
|  | # This regular expression will be used to extract filenames from import | 
|  | # statements. | 
|  | _EXTRACT_IMPORT_PATH = re.compile( | 
|  | '[ \t]*[ \t]*import[ \t]+"(.*)"') | 
|  |  | 
|  | def __init__(self, verbose, resolve_dotdot=False, root_dir=''): | 
|  | self._verbose = verbose | 
|  | self._resolve_dotdot = resolve_dotdot | 
|  | self._root_dir = root_dir | 
|  |  | 
|  | def IsFullPath(self, import_path): | 
|  | """Checks if the given path is a valid path starting from |_root_dir|.""" | 
|  | match = re.match('(.*)/([^/]*\.proto)', import_path) | 
|  | if not match: | 
|  | return False | 
|  | return os.path.isdir(self._root_dir + "/" + match.group(1)) | 
|  |  | 
|  | def CheckLine(self, rules, line, dependee_path, fail_on_temp_allow=False): | 
|  | """Checks the given line with the given rule set. | 
|  |  | 
|  | Returns a tuple (is_import, dependency_violation) where | 
|  | is_import is True only if the line is an import | 
|  | statement, and dependency_violation is an instance of | 
|  | results.DependencyViolation if the line violates a rule, or None | 
|  | if it does not. | 
|  | """ | 
|  | found_item = self._EXTRACT_IMPORT_PATH.match(line) | 
|  | if not found_item: | 
|  | return False, None  # Not a match | 
|  |  | 
|  | import_path = found_item.group(1) | 
|  |  | 
|  | if '\\' in import_path: | 
|  | return True, results.DependencyViolation( | 
|  | import_path, | 
|  | MessageRule('Import paths may not include backslashes.'), | 
|  | rules) | 
|  |  | 
|  | if '/' not in import_path: | 
|  | # Don't fail when no directory is specified. We may want to be more | 
|  | # strict about this in the future. | 
|  | if self._verbose: | 
|  | print ' WARNING: import specified with no directory: ' + import_path | 
|  | return True, None | 
|  |  | 
|  | if self._resolve_dotdot and '../' in import_path: | 
|  | dependee_dir = os.path.dirname(dependee_path) | 
|  | import_path = os.path.join(dependee_dir, import_path) | 
|  | import_path = os.path.relpath(import_path, self._root_dir) | 
|  |  | 
|  | if not self.IsFullPath(import_path): | 
|  | return True, None | 
|  |  | 
|  | rule = rules.RuleApplyingTo(import_path, dependee_path) | 
|  |  | 
|  | if (rule.allow == Rule.DISALLOW or | 
|  | (fail_on_temp_allow and rule.allow == Rule.TEMP_ALLOW)): | 
|  | return True, results.DependencyViolation(import_path, rule, rules) | 
|  | return True, None | 
|  |  | 
|  | def CheckFile(self, rules, filepath): | 
|  | if self._verbose: | 
|  | print 'Checking: ' + filepath | 
|  |  | 
|  | dependee_status = results.DependeeStatus(filepath) | 
|  | last_import = 0 | 
|  | with codecs.open(filepath, encoding='utf-8') as f: | 
|  | for line_num, line in enumerate(f): | 
|  | if line_num - last_import > self._MAX_UNINTERESTING_LINES: | 
|  | break | 
|  |  | 
|  | line = line.strip() | 
|  |  | 
|  | is_import, violation = self.CheckLine(rules, line, filepath) | 
|  | if is_import: | 
|  | last_import = line_num | 
|  | if violation: | 
|  | dependee_status.AddViolation(violation) | 
|  |  | 
|  | return dependee_status | 
|  |  | 
|  | @staticmethod | 
|  | def IsProtoFile(file_path): | 
|  | """Returns True iff the given path ends in one of the extensions | 
|  | handled by this checker. | 
|  | """ | 
|  | return os.path.splitext(file_path)[1] in ProtoChecker.EXTENSIONS | 
|  |  | 
|  | def ShouldCheck(self, file_path): | 
|  | """Check if the new #include file path should be presubmit checked. | 
|  |  | 
|  | Args: | 
|  | file_path: file path to be checked | 
|  |  | 
|  | Return: | 
|  | bool: True if the file should be checked; False otherwise. | 
|  | """ | 
|  | return self.IsProtoFile(file_path) |