| #!/usr/bin/env python3 |
| # Copyright 2023 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Tests that no linker inputs are from private paths.""" |
| |
| import argparse |
| import fnmatch |
| import os |
| import pathlib |
| import sys |
| |
| _DIR_SRC_ROOT = pathlib.Path(__file__).resolve().parents[2] |
| |
| |
| def _print_paths(paths, limit): |
| for path in paths[:limit]: |
| print(path) |
| if len(paths) > limit: |
| print(f'... and {len(paths) - limit} more.') |
| print() |
| |
| |
| def _apply_allowlist(found, globs): |
| ignored_paths = [] |
| new_found = [] |
| for path in found: |
| for pattern in globs: |
| if fnmatch.fnmatch(path, pattern): |
| ignored_paths.append(path) |
| break |
| else: |
| new_found.append(path) |
| return new_found, ignored_paths |
| |
| |
| def _find_private_paths(linker_inputs, private_paths, root_out_dir): |
| seen = set() |
| found = [] |
| for linker_input in linker_inputs: |
| dirname = os.path.dirname(linker_input) |
| if dirname in seen: |
| continue |
| |
| to_check = dirname |
| # Strip ../ prefix. |
| if to_check.startswith('..'): |
| to_check = os.path.relpath(to_check, _DIR_SRC_ROOT) |
| else: |
| if root_out_dir: |
| # Strip secondary toolchain subdir |
| to_check = to_check[len(root_out_dir) + 1:] |
| # Strip top-level dir (e.g. "obj", "gen"). |
| parts = to_check.split(os.path.sep, 1) |
| if len(parts) == 1: |
| continue |
| to_check = parts[1] |
| |
| if any(to_check.startswith(p) for p in private_paths): |
| found.append(linker_input) |
| else: |
| seen.add(dirname) |
| return found |
| |
| |
| def _read_private_paths(path): |
| text = pathlib.Path(path).read_text() |
| |
| # Check if .gclient_entries was not valid. https://crbug.com/1427829 |
| if text.startswith('# ERROR: '): |
| sys.stderr.write(text) |
| sys.exit(1) |
| |
| # Remove src/ prefix from paths. |
| # We care only about paths within src/ since GN cannot reference files |
| # outside of // (and what would the obj/ path for them look like?). |
| ret = [p[4:] for p in text.splitlines() if p.startswith('src/')] |
| if not ret: |
| sys.stderr.write(f'No src/ paths found in {args.private_paths_file}\n') |
| sys.stderr.write(f'This test should not be run on public bots.\n') |
| sys.stderr.write(f'File contents:\n') |
| sys.stderr.write(text) |
| sys.exit(1) |
| |
| return ret |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--linker-inputs', |
| required=True, |
| help='Path to file containing one linker input per line, ' |
| 'relative to --root-out-dir') |
| parser.add_argument('--private-paths-file', |
| required=True, |
| help='Path to file containing list of paths that are ' |
| 'considered private, relative gclient root.') |
| parser.add_argument('--root-out-dir', |
| required=True, |
| help='See --linker-inputs.') |
| parser.add_argument('--allow-violation', |
| action='append', |
| help='globs of private paths to allow.') |
| parser.add_argument('--expect-failure', |
| action='store_true', |
| help='Invert exit code.') |
| args = parser.parse_args() |
| |
| private_paths = _read_private_paths(args.private_paths_file) |
| linker_inputs = pathlib.Path(args.linker_inputs).read_text().splitlines() |
| |
| root_out_dir = args.root_out_dir |
| if root_out_dir == '.': |
| root_out_dir = '' |
| |
| found = _find_private_paths(linker_inputs, private_paths, root_out_dir) |
| |
| if args.allow_violation: |
| found, ignored_paths = _apply_allowlist(found, args.allow_violation) |
| if ignored_paths: |
| print('Ignoring {len(ignored_paths)} allowlisted private paths:') |
| _print_paths(sorted(ignored_paths), 10) |
| |
| if found: |
| limit = 10 if args.expect_failure else 1000 |
| print(f'Found {len(found)} private paths being linked into public code:') |
| _print_paths(found, limit) |
| elif args.expect_failure: |
| print('Expected to find a private path, but none were found.') |
| |
| sys.exit(0 if bool(found) == args.expect_failure else 1) |
| |
| |
| if __name__ == '__main__': |
| main() |