136 lines
4.1 KiB
Python
Executable File
136 lines
4.1 KiB
Python
Executable File
#!/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()
|