xref: /aosp_15_r20/external/pdfium/testing/tools/generate_cas_paths.py (revision 3ac0a46f773bac49fa9476ec2b1cf3f8da5ec3a4)
1#!/usr/bin/env python3
2# Copyright 2023 The PDFium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Tool for converting GN runtime_deps to CAS archive paths."""
6
7import argparse
8from collections import deque
9import filecmp
10import json
11import logging
12from pathlib import Path
13import os
14
15EXCLUDE_DIRS = {
16    '.git',
17    '__pycache__',
18}
19
20
21def parse_runtime_deps(runtime_deps):
22  """Parses GN's `runtime_deps` format."""
23  with runtime_deps:
24    return [line.rstrip() for line in runtime_deps]
25
26
27def resolve_paths(root, initial_paths):
28  """Converts paths to CAS archive paths format."""
29  absolute_root = os.path.abspath(root)
30
31  resolved_paths = []
32  unvisited_paths = deque(map(Path, initial_paths))
33  while unvisited_paths:
34    path = unvisited_paths.popleft()
35
36    if not path.exists():
37      logging.warning('"%(path)s" does not exist', {'path': path})
38      continue
39
40    if path.is_dir():
41      # Expand specific children if any are excluded.
42      child_paths = expand_dir(path)
43      if child_paths:
44        unvisited_paths.extendleft(child_paths)
45        continue
46
47    resolved_paths.append(os.path.relpath(path, start=absolute_root))
48
49  resolved_paths.sort()
50  return [[absolute_root, path] for path in resolved_paths]
51
52
53def expand_dir(path):
54  """Explicitly expands directory if any children are excluded."""
55  expand = False
56  expanded_paths = []
57
58  for child_path in path.iterdir():
59    if child_path.name in EXCLUDE_DIRS and path.is_dir():
60      expand = True
61      continue
62    expanded_paths.append(child_path)
63
64  return expanded_paths if expand else []
65
66
67def replace_output(resolved, output_path):
68  """Atomically replaces the output with the resolved JSON if changed."""
69  new_output_path = output_path + '.new'
70  try:
71    with open(new_output_path, 'w', encoding='ascii') as new_output:
72      json.dump(resolved, new_output)
73
74    if (os.path.exists(output_path) and
75        filecmp.cmp(new_output_path, output_path, shallow=False)):
76      return
77
78    os.replace(new_output_path, output_path)
79    new_output_path = None
80  finally:
81    if new_output_path:
82      os.remove(new_output_path)
83
84
85def main():
86  parser = argparse.ArgumentParser(description=__doc__)
87  parser.add_argument('--root')
88  parser.add_argument(
89      'runtime_deps',
90      help='runtime_deps written by GN',
91      type=argparse.FileType('r', encoding='utf_8'),
92      metavar='input.runtime_deps')
93  parser.add_argument(
94      'output_json',
95      help='CAS archive paths in JSON format',
96      metavar='output.json')
97  args = parser.parse_args()
98
99  runtime_deps = parse_runtime_deps(args.runtime_deps)
100  resolved_paths = resolve_paths(args.root, runtime_deps)
101  replace_output(resolved_paths, args.output_json)
102
103
104if __name__ == '__main__':
105  main()
106