xref: /aosp_15_r20/external/angle/build/util/action_remote.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1#!/usr/bin/env python3
2# Copyright 2022 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"""Wrapper script to run action remotely through rewrapper with gn.
6
7Also includes Chromium-specific input processors which don't make sense to
8be reclient inbuilt input processors."""
9
10import argparse
11import json
12import os
13import subprocess
14import sys
15from enum import Enum
16
17_THIS_DIR = os.path.realpath(os.path.dirname(__file__))
18_SRC_DIR = os.path.dirname(os.path.dirname(_THIS_DIR))
19_MOJOM_DIR = os.path.join(_SRC_DIR, 'mojo', 'public', 'tools', 'mojom')
20
21
22class CustomProcessor(Enum):
23  mojom_parser = 'mojom_parser'
24
25  def __str__(self):
26    return self.value
27
28
29def _normalize_path(path):
30  # Always use posix-style directory separators as GN does it.
31  return os.path.normpath(path).replace("\\", "/")
32
33
34def _process_build_metadata_json(bm_file, input_roots, output_root,
35                                 output_files, processed_inputs):
36  """Recursively find mojom_parser inputs from a build_metadata file."""
37  # Import Mojo-specific dep here so non-Mojo remote actions don't need it.
38  if _MOJOM_DIR not in sys.path:
39    sys.path.insert(0, _MOJOM_DIR)
40  from mojom_parser import RebaseAbsolutePath
41
42  if bm_file in processed_inputs:
43    return
44
45  processed_inputs.add(bm_file)
46
47  bm_dir = os.path.dirname(bm_file)
48
49  with open(bm_file) as f:
50    bm = json.load(f)
51
52  # All sources and corresponding module files are inputs.
53  for s in bm["sources"]:
54    src = _normalize_path(os.path.join(bm_dir, s))
55    if src not in processed_inputs and os.path.exists(src):
56      processed_inputs.add(src)
57    src_module = _normalize_path(
58        os.path.join(
59            output_root,
60            RebaseAbsolutePath(os.path.abspath(src), input_roots) + "-module"))
61    if src_module in output_files:
62      continue
63    if src_module not in processed_inputs and os.path.exists(src_module):
64      processed_inputs.add(src_module)
65
66  # Recurse into build_metadata deps.
67  for d in bm["deps"]:
68    dep = _normalize_path(os.path.join(bm_dir, d))
69    _process_build_metadata_json(dep, input_roots, output_root, output_files,
70                                 processed_inputs)
71
72
73def _get_mojom_parser_inputs(exec_root, output_files, extra_args):
74  """Get mojom inputs by walking generated build_metadata files.
75
76  This is less complexity and disk I/O compared to parsing mojom files for
77  imports and finding all imports.
78
79  Start from the root build_metadata file passed to mojom_parser's
80  --check-imports flag.
81  """
82  argparser = argparse.ArgumentParser()
83  argparser.add_argument('--check-imports', dest='check_imports', required=True)
84  argparser.add_argument('--output-root', dest='output_root', required=True)
85  argparser.add_argument('--input-root',
86                         default=[],
87                         action='append',
88                         dest='input_root_paths')
89  mojom_parser_args, _ = argparser.parse_known_args(args=extra_args)
90
91  input_roots = list(map(os.path.abspath, mojom_parser_args.input_root_paths))
92  output_root = os.path.abspath(mojom_parser_args.output_root)
93  processed_inputs = set()
94  _process_build_metadata_json(mojom_parser_args.check_imports, input_roots,
95                               output_root, output_files, processed_inputs)
96
97  # Rebase paths onto rewrapper exec root.
98  return map(lambda dep: _normalize_path(os.path.relpath(dep, exec_root)),
99             processed_inputs)
100
101
102def main():
103  # Set up argparser with some rewrapper flags.
104  argparser = argparse.ArgumentParser(description='rewrapper executor for gn',
105                                      allow_abbrev=False)
106  argparser.add_argument('--custom_processor',
107                         type=CustomProcessor,
108                         choices=list(CustomProcessor))
109  argparser.add_argument('rewrapper_path')
110  argparser.add_argument('--input_list_paths')
111  argparser.add_argument('--output_list_paths')
112  argparser.add_argument('--exec_root')
113  parsed_args, extra_args = argparser.parse_known_args()
114
115  # This script expects to be calling rewrapper.
116  args = [parsed_args.rewrapper_path]
117
118  # Get the output files list.
119  output_files = set()
120  with open(parsed_args.output_list_paths, 'r') as file:
121    for line in file:
122      # Output files are relative to exec_root.
123      output_file = _normalize_path(
124          os.path.join(parsed_args.exec_root, line.rstrip('\n')))
125      output_files.add(output_file)
126
127  # Scan for and add explicit inputs for rewrapper if necessary.
128  # These should be in a new input list paths file, as using --inputs can fail
129  # if the list is extremely large.
130  if parsed_args.custom_processor == CustomProcessor.mojom_parser:
131    root, ext = os.path.splitext(parsed_args.input_list_paths)
132    extra_inputs = _get_mojom_parser_inputs(parsed_args.exec_root, output_files,
133                                            extra_args)
134    extra_input_list_path = '%s__extra%s' % (root, ext)
135    with open(extra_input_list_path, 'w') as file:
136      with open(parsed_args.input_list_paths, 'r') as inputs:
137        file.write(inputs.read())
138      file.write("\n".join(extra_inputs))
139    args += ["--input_list_paths=%s" % extra_input_list_path]
140  else:
141    args += ["--input_list_paths=%s" % parsed_args.input_list_paths]
142
143  # Filter out --custom_processor= which is a flag for this script,
144  # and filter out --input_list_paths= because we replace it above.
145  # Pass on the rest of the args to rewrapper.
146  args_rest = filter(lambda arg: '--custom_processor=' not in arg, sys.argv[2:])
147  args += filter(lambda arg: '--input_list_paths=' not in arg, args_rest)
148
149  # Run rewrapper.
150  proc = subprocess.run(args)
151  return proc.returncode
152
153
154if __name__ == '__main__':
155  sys.exit(main())
156