blob: 6477a17c39a4da45bfeed5b768510e15226377d2 [file] [log] [blame]
Christoffer Jansson4e8a7732022-02-08 08:01:121#!/usr/bin/env vpython3
ehmaldonado4fb97462017-01-30 13:27:222
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
11import argparse
Oleh Prypin2f33a562017-10-04 18:17:5412import collections
ehmaldonado4fb97462017-01-30 13:27:2213import os
14import re
15import sys
16
ehmaldonado4fb97462017-01-30 13:27:2217# TARGET_RE matches a GN target, and extracts the target name and the contents.
Mirko Bonadei8cc66952020-10-30 09:13:4518TARGET_RE = re.compile(
19 r'(?P<indent>\s*)\w+\("(?P<target_name>\w+)"\) {'
20 r'(?P<target_contents>.*?)'
21 r'(?P=indent)}', re.MULTILINE | re.DOTALL)
ehmaldonado4fb97462017-01-30 13:27:2222
23# SOURCES_RE matches a block of sources inside a GN target.
24SOURCES_RE = re.compile(r'sources \+?= \[(?P<sources>.*?)\]',
25 re.MULTILINE | re.DOTALL)
26
Mirko Bonadeib7dc45f2020-01-21 07:42:4127ERROR_MESSAGE = ("{build_file_path} in target '{target_name}':\n"
Oleh Prypin2f33a562017-10-04 18:17:5428 " Source file '{source_file}'\n"
29 " crosses boundary of package '{subpackage}'.")
ehmaldonado4fb97462017-01-30 13:27:2230
31
Oleh Prypin2f33a562017-10-04 18:17:5432class PackageBoundaryViolation(
Mirko Bonadei8cc66952020-10-30 09:13:4533 collections.namedtuple(
34 'PackageBoundaryViolation',
35 'build_file_path target_name source_file subpackage')):
Christoffer Jansson4e8a7732022-02-08 08:01:1236 def __str__(self):
37 return ERROR_MESSAGE.format(**self._asdict())
ehmaldonado4fb97462017-01-30 13:27:2238
39
40def _BuildSubpackagesPattern(packages, query):
Christoffer Jansson4e8a7732022-02-08 08:01:1241 """Returns a regular expression that matches source files inside subpackages
ehmaldonado4fb97462017-01-30 13:27:2242 of the given query."""
Christoffer Jansson4e8a7732022-02-08 08:01:1243 query += os.path.sep
44 length = len(query)
45 pattern = r'\s*"(?P<source_file>(?P<subpackage>'
46 pattern += '|'.join(
47 re.escape(package[length:].replace(os.path.sep, '/'))
48 for package in packages if package.startswith(query))
49 pattern += r')/[\w\./]*)"'
50 return re.compile(pattern)
ehmaldonado4fb97462017-01-30 13:27:2251
52
53def _ReadFileAndPrependLines(file_path):
Christoffer Jansson4e8a7732022-02-08 08:01:1254 """Reads the contents of a file."""
55 with open(file_path) as f:
56 return "".join(f.readlines())
ehmaldonado4fb97462017-01-30 13:27:2257
58
Oleh Prypin2f33a562017-10-04 18:17:5459def _CheckBuildFile(build_file_path, packages):
Christoffer Jansson4e8a7732022-02-08 08:01:1260 """Iterates over all the targets of the given BUILD.gn file, and verifies that
ehmaldonado4fb97462017-01-30 13:27:2261 the source files referenced by it don't belong to any of it's subpackages.
Oleh Prypin2f33a562017-10-04 18:17:5462 Returns an iterator over PackageBoundaryViolations for this package.
ehmaldonado4fb97462017-01-30 13:27:2263 """
Christoffer Jansson4e8a7732022-02-08 08:01:1264 package = os.path.dirname(build_file_path)
65 subpackages_re = _BuildSubpackagesPattern(packages, package)
ehmaldonado4fb97462017-01-30 13:27:2266
Christoffer Jansson4e8a7732022-02-08 08:01:1267 build_file_contents = _ReadFileAndPrependLines(build_file_path)
68 for target_match in TARGET_RE.finditer(build_file_contents):
69 target_name = target_match.group('target_name')
70 target_contents = target_match.group('target_contents')
71 for sources_match in SOURCES_RE.finditer(target_contents):
72 sources = sources_match.group('sources')
73 for subpackages_match in subpackages_re.finditer(sources):
74 subpackage = subpackages_match.group('subpackage')
75 source_file = subpackages_match.group('source_file')
76 if subpackage:
77 yield PackageBoundaryViolation(build_file_path, target_name,
78 source_file, subpackage)
ehmaldonado4fb97462017-01-30 13:27:2279
80
Oleh Prypin2f33a562017-10-04 18:17:5481def CheckPackageBoundaries(root_dir, build_files=None):
Christoffer Jansson4e8a7732022-02-08 08:01:1282 packages = [
83 root for root, _, files in os.walk(root_dir) if 'BUILD.gn' in files
84 ]
ehmaldonado4fb97462017-01-30 13:27:2285
Christoffer Jansson4e8a7732022-02-08 08:01:1286 if build_files is not None:
Oleh Prypinafe01652017-10-04 13:56:0887 for build_file_path in build_files:
Christoffer Jansson4e8a7732022-02-08 08:01:1288 assert build_file_path.startswith(root_dir)
89 else:
90 build_files = [os.path.join(package, 'BUILD.gn') for package in packages]
91
92 messages = []
93 for build_file_path in build_files:
94 messages.extend(_CheckBuildFile(build_file_path, packages))
95 return messages
ehmaldonado4fb97462017-01-30 13:27:2296
97
Oleh Prypin2f33a562017-10-04 18:17:5498def main(argv):
Christoffer Jansson4e8a7732022-02-08 08:01:1299 parser = argparse.ArgumentParser(
100 description='Script that checks package boundary violations in GN '
101 'build files.')
ehmaldonado4fb97462017-01-30 13:27:22102
Christoffer Jansson4e8a7732022-02-08 08:01:12103 parser.add_argument('root_dir',
104 metavar='ROOT_DIR',
105 help='The root directory that contains all BUILD.gn '
106 'files to be processed.')
107 parser.add_argument('build_files',
108 metavar='BUILD_FILE',
109 nargs='*',
110 help='A list of BUILD.gn files to be processed. If no '
111 'files are given, all BUILD.gn files under ROOT_DIR '
112 'will be processed.')
113 parser.add_argument('--max_messages',
114 type=int,
115 default=None,
116 help='If set, the maximum number of violations to be '
117 'displayed.')
ehmaldonado4fb97462017-01-30 13:27:22118
Christoffer Jansson4e8a7732022-02-08 08:01:12119 args = parser.parse_args(argv)
ehmaldonado4fb97462017-01-30 13:27:22120
Christoffer Jansson4e8a7732022-02-08 08:01:12121 messages = CheckPackageBoundaries(args.root_dir, args.build_files)
122 messages = messages[:args.max_messages]
ehmaldonado4fb97462017-01-30 13:27:22123
Christoffer Jansson4e8a7732022-02-08 08:01:12124 for i, message in enumerate(messages):
125 if i > 0:
126 print()
127 print(message)
Oleh Prypin2f33a562017-10-04 18:17:54128
Christoffer Jansson4e8a7732022-02-08 08:01:12129 return bool(messages)
ehmaldonado4fb97462017-01-30 13:27:22130
131
132if __name__ == '__main__':
Christoffer Jansson4e8a7732022-02-08 08:01:12133 sys.exit(main(sys.argv[1:]))