blob: ff8623c46c0e586c13204d71f6fc822f478b3d0f [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2023 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""rpl_tool
General collection of tools that involve both reproxy_logs.py
and remotetool.py.
"""
import argparse
import contextlib
import os
import sys
import cl_utils
import fuchsia
import remotetool
import reproxy_logs
# This requires python pb2 in build/rbe/proto (generated).
from api.log import log_pb2
from pathlib import Path
from typing import Any, Callable, Dict, Iterable, Optional, Sequence
_SCRIPT_BASENAME = Path(__file__).name
_SCRIPT_DIR = Path(__file__).parent
PROJECT_ROOT = fuchsia.project_root_dir()
PROJECT_ROOT_REL = cl_utils.relpath(PROJECT_ROOT, start=os.curdir)
_REPROXY_CFG = _SCRIPT_DIR / "fuchsia-reproxy.cfg"
def msg(text: str):
print(f"[{_SCRIPT_BASENAME}] {text}")
# Abstract representation of files in the working dir or remote working dir,
# which can be a subdir of exec_root.
_WORKING_DIR_SYMBOL = "${working_directory}"
def normalize_input_path_prefix(path: Path, working_dirs: Iterable[str]) -> str:
"""Substitutes the beginning of path with a symbolic value.
Args:
path: file or directory path (relative to exec_root).
working_dirs: set of path prefixes to normalize.
"""
path_str = str(path)
for working_dir in working_dirs:
if working_dir and path_str.startswith(working_dir):
return path_str.replace(working_dir, _WORKING_DIR_SYMBOL)
return path_str # unmodified
def infer_record_command_and_inputs(
record: log_pb2.LogRecord, rtool: remotetool.RemoteTool
):
"""Adds command and inputs to a reduced reproxy log record.
Args:
record: api.log.LogRecord proto from reproxy (modify-by-reference)
rtool: remotetool instance for retrieving action details.
"""
action_digest = record.remote_metadata.action_digest
# Reconstruct the missing action details (command, inputs)
# from `remotetool --operation show_action`.
show_action_details = rtool.show_action(action_digest)
working_dirs = [
record.command.working_directory,
record.command.remote_working_directory,
]
record.command.args.extend(show_action_details.command)
record.command.input.inputs.extend(
sorted(
normalize_input_path_prefix(path, working_dirs)
for path in show_action_details.inputs.keys()
)
)
def expand_to_rpl(
logdump: log_pb2.LogDump,
rtool: remotetool.RemoteTool,
action: Callable[[log_pb2.LogRecord], None] = None,
) -> log_pb2.LogDump:
"""Expands .rrpl to .rpl, adding data for command and inputs.
Args:
logdump: api.log.LogDump proto from reproxy (modify-by-reference).
rtool: remotetool instance for retrieving action details.
action: action to run on each processed record.
Returns:
logdump, modified.
"""
for record in logdump.records:
if record.HasField("remote_metadata"):
infer_record_command_and_inputs(record, rtool)
if action:
action(record)
return logdump
def expand_to_rpl_command(args: argparse.Namespace) -> int:
"""Expands .rrpl to .rpl, adding data for command and inputs.
This conforms to the interface required for an
argparse subcommand function.
Args:
args: argparse parameters.
Returns:
exit code, 0 for success.
"""
rrpl = reproxy_logs.parse_log(
log_path=args.rrpl,
reclient_bindir=fuchsia.RECLIENT_BINDIR,
verbose=False,
)
rtool = remotetool.configure_remotetool(args.cfg)
# Stream records out to avoid losing data in the event of an error.
if args.output:
outf = open(args.output, "w")
def printer(record: log_pb2.LogRecord):
outf.write(str(record) + "\n")
else:
outf = contextlib.nullcontext()
def printer(record: log_pb2.LogRecord):
print(str(record) + "\n") # print to stdout
with outf:
expand_to_rpl(rrpl.proto, rtool, action=printer)
return 0
def _main_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Fetch build artifacts from RBE CAS.",
argument_default=None,
)
subparsers = parser.add_subparsers(required=True)
expand_parser = subparsers.add_parser(
"expand_to_rpl", help="Convert .rrpl to .rpl"
)
expand_parser.set_defaults(func=expand_to_rpl_command)
expand_parser.add_argument(
"rrpl",
type=Path,
help="Reduced reproxy log (.rrpl)",
metavar="LOG",
default=None,
)
expand_parser.add_argument(
"--cfg",
type=Path,
help="reproxy config",
default=_REPROXY_CFG,
)
expand_parser.add_argument(
"-o",
type=Path,
dest="output",
help="output to write .rpl",
default=None,
)
return parser
_MAIN_ARG_PARSER = _main_arg_parser()
def main(argv: Sequence[str]) -> int:
args = _MAIN_ARG_PARSER.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))