Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 2 | |
| 3 # Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. | |
| 4 # | |
| 5 # Use of this source code is governed by a BSD-style license | |
| 6 # that can be found in the LICENSE file in the root of the source | |
| 7 # tree. An additional intellectual property rights grant can be found | |
| 8 # in the file PATENTS. All contributing project authors may | |
| 9 # be found in the AUTHORS file in the root of the source tree. | |
| 10 | |
| 11 import argparse | |
| 12 import logging | |
| 13 import os | |
| 14 import re | |
| 15 import sys | |
| 16 | |
| 17 | |
| 18 DISPLAY_LEVEL = 1 | |
| 19 IGNORE_LEVEL = 0 | |
| 20 | |
| 21 # TARGET_RE matches a GN target, and extracts the target name and the contents. | |
| 22 TARGET_RE = re.compile(r'\d+\$(?P<indent>\s*)\w+\("(?P<target_name>\w+)"\) {' | |
| 23 r'(?P<target_contents>.*?)' | |
| 24 r'\d+\$(?P=indent)}', | |
| 25 re.MULTILINE | re.DOTALL) | |
| 26 | |
| 27 # SOURCES_RE matches a block of sources inside a GN target. | |
| 28 SOURCES_RE = re.compile(r'sources \+?= \[(?P<sources>.*?)\]', | |
| 29 re.MULTILINE | re.DOTALL) | |
| 30 | |
| 31 LOG_FORMAT = '%(message)s' | |
| 32 ERROR_MESSAGE = ("{}:{} in target '{}':\n" | |
| 33 " Source file '{}'\n" | |
| 34 " crosses boundary of package '{}'.\n") | |
| 35 | |
| 36 | |
| 37 class Logger(object): | |
| 38 def __init__(self, messages_left=None): | |
| 39 self.log_level = DISPLAY_LEVEL | |
| 40 self.messages_left = messages_left | |
| 41 | |
| 42 def log(self, message): | |
| 43 if self.messages_left is not None: | |
| 44 if not self.messages_left: | |
| 45 self.log_level = IGNORE_LEVEL | |
| 46 else: | |
| 47 self.messages_left -= 1 | |
| 48 logging.log(self.log_level, message) | |
| 49 | |
| 50 | |
| 51 def _BuildSubpackagesPattern(packages, query): | |
| 52 """Returns a regular expression that matches source files inside subpackages | |
| 53 of the given query.""" | |
| 54 query += '/' | |
| 55 length = len(query) | |
| 56 pattern = r'(?P<line_number>\d+)\$\s*"(?P<source_file>(?P<subpackage>' | |
| 57 pattern += '|'.join(package[length:] for package in packages | |
| 58 if package.startswith(query)) | |
| 59 pattern += r')[\w\./]*)"' | |
| 60 return re.compile(pattern) | |
| 61 | |
| 62 def _ReadFileAndPrependLines(file_path): | |
| 63 """Reads the contents of a file and prepends the line number to every line.""" | |
| 64 with open(file_path) as f: | |
| 65 return "".join("{}${}".format(line_number, line) | |
| 66 for line_number, line in enumerate(f, 1)) | |
| 67 | |
| 68 def _CheckBuildFile(build_file_path, packages, logger): | |
| 69 """Iterates oven all the targets of the given BUILD.gn file, and verifies that | |
| 70 the source files referenced by it don't belong to any of it's subpackages. | |
| 71 Returns True if a package boundary violation was found. | |
| 72 """ | |
| 73 found_violations = False | |
| 74 package = os.path.dirname(build_file_path) | |
| 75 subpackages_re = _BuildSubpackagesPattern(packages, package) | |
| 76 | |
| 77 build_file_contents = _ReadFileAndPrependLines(build_file_path) | |
| 78 for target_match in TARGET_RE.finditer(build_file_contents): | |
| 79 target_name = target_match.group('target_name') | |
| 80 target_contents = target_match.group('target_contents') | |
| 81 for sources_match in SOURCES_RE.finditer(target_contents): | |
| 82 sources = sources_match.group('sources') | |
| 83 for subpackages_match in subpackages_re.finditer(sources): | |
| 84 subpackage = subpackages_match.group('subpackage') | |
| 85 source_file = subpackages_match.group('source_file') | |
| 86 line_number = subpackages_match.group('line_number') | |
| 87 if subpackage: | |
| 88 found_violations = True | |
|
kjellander_webrtc
2017/01/19 10:51:26
Seems like a bug was found with the tests? :)
ehmaldonado_webrtc
2017/01/19 13:33:56
Yup :)
| |
| 89 logger.log(ERROR_MESSAGE.format(build_file_path, line_number, | |
| 90 target_name, source_file, subpackage)) | |
| 91 | |
| 92 return found_violations | |
| 93 | |
| 94 def main(): | |
| 95 parser = argparse.ArgumentParser( | |
| 96 description='Script that checks package boundary violations in GN ' | |
| 97 'build files.') | |
| 98 | |
| 99 parser.add_argument('root_dir', metavar='ROOT_DIR', | |
| 100 help='The root directory that contains all BUILD.gn ' | |
| 101 'files to be processed.') | |
| 102 parser.add_argument('build_files', metavar='BUILD_FILE', nargs='*', | |
| 103 help='A list of BUILD.gn files to be processed. If no ' | |
| 104 'files are given, all BUILD.gn files under ROOT_DIR ' | |
| 105 'will be processed.') | |
| 106 parser.add_argument('--max_messages', type=int, default=None, | |
| 107 help='If set, the maximum number of violations to be ' | |
| 108 'displayed.') | |
| 109 | |
| 110 args = parser.parse_args() | |
| 111 | |
| 112 logging.basicConfig(format=LOG_FORMAT) | |
| 113 logging.getLogger().setLevel(DISPLAY_LEVEL) | |
| 114 logger = Logger(args.max_messages) | |
| 115 | |
| 116 packages = [root for root, _, files in os.walk(args.root_dir) | |
| 117 if 'BUILD.gn' in files] | |
| 118 default_build_files = [os.path.join(package, 'BUILD.gn') | |
| 119 for package in packages] | |
| 120 | |
| 121 build_files = args.build_files or default_build_files | |
| 122 return any(_CheckBuildFile(build_file_path, packages, logger) | |
| 123 for build_file_path in build_files) | |
| 124 | |
| 125 if __name__ == '__main__': | |
| 126 sys.exit(main()) | |
| OLD | NEW |