xref: /aosp_15_r20/external/angle/build/private_code_test/private_code_test.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1#!/usr/bin/env python3
2# Copyright 2023 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Tests that no linker inputs are from private paths."""
6
7import argparse
8import fnmatch
9import os
10import pathlib
11import sys
12
13_DIR_SRC_ROOT = pathlib.Path(__file__).resolve().parents[2]
14
15
16def _print_paths(paths, limit):
17  for path in paths[:limit]:
18    print(path)
19  if len(paths) > limit:
20    print(f'... and {len(paths) - limit} more.')
21  print()
22
23
24def _apply_allowlist(found, globs):
25  ignored_paths = []
26  new_found = []
27  for path in found:
28    for pattern in globs:
29      if fnmatch.fnmatch(path, pattern):
30        ignored_paths.append(path)
31        break
32    else:
33      new_found.append(path)
34  return new_found, ignored_paths
35
36
37def _find_private_paths(linker_inputs, private_paths, root_out_dir):
38  seen = set()
39  found = []
40  for linker_input in linker_inputs:
41    dirname = os.path.dirname(linker_input)
42    if dirname in seen:
43      continue
44
45    to_check = dirname
46    # Strip ../ prefix.
47    if to_check.startswith('..'):
48      to_check = os.path.relpath(to_check, _DIR_SRC_ROOT)
49    else:
50      if root_out_dir:
51        # Strip secondary toolchain subdir
52        to_check = to_check[len(root_out_dir) + 1:]
53      # Strip top-level dir (e.g. "obj", "gen").
54      parts = to_check.split(os.path.sep, 1)
55      if len(parts) == 1:
56        continue
57      to_check = parts[1]
58
59    if any(to_check.startswith(p) for p in private_paths):
60      found.append(linker_input)
61    else:
62      seen.add(dirname)
63  return found
64
65
66def _read_private_paths(path):
67  text = pathlib.Path(path).read_text()
68
69  # Check if .gclient_entries was not valid.  https://crbug.com/1427829
70  if text.startswith('# ERROR: '):
71    sys.stderr.write(text)
72    sys.exit(1)
73
74  # Remove src/ prefix from paths.
75  # We care only about paths within src/ since GN cannot reference files
76  # outside of // (and what would the obj/ path for them look like?).
77  ret = [p[4:] for p in text.splitlines() if p.startswith('src/')]
78  if not ret:
79    sys.stderr.write(f'No src/ paths found in {args.private_paths_file}\n')
80    sys.stderr.write(f'This test should not be run on public bots.\n')
81    sys.stderr.write(f'File contents:\n')
82    sys.stderr.write(text)
83    sys.exit(1)
84
85  return ret
86
87
88def main():
89  parser = argparse.ArgumentParser()
90  parser.add_argument('--linker-inputs',
91                      required=True,
92                      help='Path to file containing one linker input per line, '
93                      'relative to --root-out-dir')
94  parser.add_argument('--private-paths-file',
95                      required=True,
96                      help='Path to file containing list of paths that are '
97                      'considered private, relative gclient root.')
98  parser.add_argument('--root-out-dir',
99                      required=True,
100                      help='See --linker-inputs.')
101  parser.add_argument('--allow-violation',
102                      action='append',
103                      help='globs of private paths to allow.')
104  parser.add_argument('--expect-failure',
105                      action='store_true',
106                      help='Invert exit code.')
107  args = parser.parse_args()
108
109  private_paths = _read_private_paths(args.private_paths_file)
110  linker_inputs = pathlib.Path(args.linker_inputs).read_text().splitlines()
111
112  root_out_dir = args.root_out_dir
113  if root_out_dir == '.':
114    root_out_dir = ''
115
116  found = _find_private_paths(linker_inputs, private_paths, root_out_dir)
117
118  if args.allow_violation:
119    found, ignored_paths = _apply_allowlist(found, args.allow_violation)
120    if ignored_paths:
121      print('Ignoring {len(ignored_paths)} allowlisted private paths:')
122      _print_paths(sorted(ignored_paths), 10)
123
124  if found:
125    limit = 10 if args.expect_failure else 1000
126    print(f'Found {len(found)} private paths being linked into public code:')
127    _print_paths(found, limit)
128  elif args.expect_failure:
129    print('Expected to find a private path, but none were found.')
130
131  sys.exit(0 if bool(found) == args.expect_failure else 1)
132
133
134if __name__ == '__main__':
135  main()
136