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