1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155

build / util / action_remote.py [blame]

#!/usr/bin/env python3
# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Wrapper script to run action remotely through rewrapper with gn.

Also includes Chromium-specific input processors which don't make sense to
be reclient inbuilt input processors."""

import argparse
import json
import os
import subprocess
import sys
from enum import Enum

_THIS_DIR = os.path.realpath(os.path.dirname(__file__))
_SRC_DIR = os.path.dirname(os.path.dirname(_THIS_DIR))
_MOJOM_DIR = os.path.join(_SRC_DIR, 'mojo', 'public', 'tools', 'mojom')


class CustomProcessor(Enum):
  mojom_parser = 'mojom_parser'

  def __str__(self):
    return self.value


def _normalize_path(path):
  # Always use posix-style directory separators as GN does it.
  return os.path.normpath(path).replace("\\", "/")


def _process_build_metadata_json(bm_file, input_roots, output_root,
                                 output_files, processed_inputs):
  """Recursively find mojom_parser inputs from a build_metadata file."""
  # Import Mojo-specific dep here so non-Mojo remote actions don't need it.
  if _MOJOM_DIR not in sys.path:
    sys.path.insert(0, _MOJOM_DIR)
  from mojom_parser import RebaseAbsolutePath

  if bm_file in processed_inputs:
    return

  processed_inputs.add(bm_file)

  bm_dir = os.path.dirname(bm_file)

  with open(bm_file) as f:
    bm = json.load(f)

  # All sources and corresponding module files are inputs.
  for s in bm["sources"]:
    src = _normalize_path(os.path.join(bm_dir, s))
    if src not in processed_inputs and os.path.exists(src):
      processed_inputs.add(src)
    src_module = _normalize_path(
        os.path.join(
            output_root,
            RebaseAbsolutePath(os.path.abspath(src), input_roots) + "-module"))
    if src_module in output_files:
      continue
    if src_module not in processed_inputs and os.path.exists(src_module):
      processed_inputs.add(src_module)

  # Recurse into build_metadata deps.
  for d in bm["deps"]:
    dep = _normalize_path(os.path.join(bm_dir, d))
    _process_build_metadata_json(dep, input_roots, output_root, output_files,
                                 processed_inputs)


def _get_mojom_parser_inputs(exec_root, output_files, extra_args):
  """Get mojom inputs by walking generated build_metadata files.

  This is less complexity and disk I/O compared to parsing mojom files for
  imports and finding all imports.

  Start from the root build_metadata file passed to mojom_parser's
  --check-imports flag.
  """
  argparser = argparse.ArgumentParser()
  argparser.add_argument('--check-imports', dest='check_imports', required=True)
  argparser.add_argument('--output-root', dest='output_root', required=True)
  argparser.add_argument('--input-root',
                         default=[],
                         action='append',
                         dest='input_root_paths')
  mojom_parser_args, _ = argparser.parse_known_args(args=extra_args)

  input_roots = list(map(os.path.abspath, mojom_parser_args.input_root_paths))
  output_root = os.path.abspath(mojom_parser_args.output_root)
  processed_inputs = set()
  _process_build_metadata_json(mojom_parser_args.check_imports, input_roots,
                               output_root, output_files, processed_inputs)

  # Rebase paths onto rewrapper exec root.
  return map(lambda dep: _normalize_path(os.path.relpath(dep, exec_root)),
             processed_inputs)


def main():
  # Set up argparser with some rewrapper flags.
  argparser = argparse.ArgumentParser(description='rewrapper executor for gn',
                                      allow_abbrev=False)
  argparser.add_argument('--custom_processor',
                         type=CustomProcessor,
                         choices=list(CustomProcessor))
  argparser.add_argument('rewrapper_path')
  argparser.add_argument('--input_list_paths')
  argparser.add_argument('--output_list_paths')
  argparser.add_argument('--exec_root')
  parsed_args, extra_args = argparser.parse_known_args()

  # This script expects to be calling rewrapper.
  args = [parsed_args.rewrapper_path]

  # Get the output files list.
  output_files = set()
  with open(parsed_args.output_list_paths, 'r') as file:
    for line in file:
      # Output files are relative to exec_root.
      output_file = _normalize_path(
          os.path.join(parsed_args.exec_root, line.rstrip('\n')))
      output_files.add(output_file)

  # Scan for and add explicit inputs for rewrapper if necessary.
  # These should be in a new input list paths file, as using --inputs can fail
  # if the list is extremely large.
  if parsed_args.custom_processor == CustomProcessor.mojom_parser:
    root, ext = os.path.splitext(parsed_args.input_list_paths)
    extra_inputs = _get_mojom_parser_inputs(parsed_args.exec_root, output_files,
                                            extra_args)
    extra_input_list_path = '%s__extra%s' % (root, ext)
    with open(extra_input_list_path, 'w') as file:
      with open(parsed_args.input_list_paths, 'r') as inputs:
        file.write(inputs.read())
      file.write("\n".join(extra_inputs))
    args += ["--input_list_paths=%s" % extra_input_list_path]
  else:
    args += ["--input_list_paths=%s" % parsed_args.input_list_paths]

  # Filter out --custom_processor= which is a flag for this script,
  # and filter out --input_list_paths= because we replace it above.
  # Pass on the rest of the args to rewrapper.
  args_rest = filter(lambda arg: '--custom_processor=' not in arg, sys.argv[2:])
  args += filter(lambda arg: '--input_list_paths=' not in arg, args_rest)

  # Run rewrapper.
  proc = subprocess.run(args)
  return proc.returncode


if __name__ == '__main__':
  sys.exit(main())