blob: 9833f447d61e30e594b10507248be937f983e6a9 [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.
#
# IMPORTANT: This script should not depend on any Bazel workspace setup
# or specific environment. Only depend on standard Python3 modules
# and assume a minimum of Python3.8 is being used.
"""A tool to perform useful Bazel operations easily."""
import argparse
import os
import re
import subprocess
import sys
import typing as T
def make_bazel_quiet_command(bazel: str, command: str) -> T.List[str]:
"""Create command argument list for a Bazel command that does not print too much.
Args:
bazel: Path to Bazel program.
command: Bazel command (e.g. 'query', 'build', etc..)
Returns:
A sequence of strings that can be used as a command line prefix.
"""
result = [
bazel,
command,
"--noshow_loading_progress",
"--noshow_progress",
"--ui_event_filters=-info",
]
if command != "query":
result += ["--show_result=0"]
return result
def cmd_target_dump(args: argparse.Namespace) -> int:
"""Implement the target_dump command."""
bazel_cmd = (
make_bazel_quiet_command(args.bazel, "query")
+ ["--output=build"]
+ args.target_set
)
buildifier_cmd = [
args.buildifier,
"--type=build",
"--mode=fix",
"--lint=off",
]
proc1 = subprocess.Popen(
bazel_cmd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=None
)
proc2 = subprocess.Popen(
buildifier_cmd, stdin=proc1.stdout, stdout=None, stderr=None
)
proc1.wait()
proc2.wait()
if proc1.returncode != 0:
return proc1.returncode
return proc2.returncode
def cmd_actions(args: argparse.Namespace) -> int:
"""Implement the 'actions' command."""
bazel_cmd = (
make_bazel_quiet_command(args.bazel, "aquery")
+ args.extra_args[1:]
+ args.target_set
)
print("CMD %s" % bazel_cmd)
ret = subprocess.run(
bazel_cmd, stdin=subprocess.DEVNULL, capture_output=True, text=True
)
if ret.returncode != 0:
print("%s\n" % ret.stdout, file=sys.stdout)
print("%s\n" % ret.stderr, file=sys.stderr)
return ret.returncode
def ignored_path(path: str) -> bool:
if path.startswith(
(
"external/prebuilt_clang/",
"external/fuchsia_clang/",
"prebuilt/third_party/sysroot/",
)
):
return True
if path.find("external/fuchsia_prebuilt_rust/") >= 0:
return True
return False
inputs_re = re.compile(r"^ Inputs: \[(.*)\]$")
for line in ret.stdout.splitlines():
m = inputs_re.match(line)
if m:
input_paths = []
for path in m.group(1).split(","):
path = path.strip()
if not ignored_path(path):
input_paths.append(path)
line = " Inputs: [%s]" % (", ".join(input_paths))
print(line)
return 0
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--bazel", default="bazel", help="Specify bazel executable"
)
parser.add_argument(
"--buildifier",
default="buildifier",
help="Specify buildifier executable",
)
parser.add_argument("--workspace", help="Specify workspace directory")
subparsers = parser.add_subparsers(help="available commands")
# The target_dump command.
parser_target_dump = subparsers.add_parser(
"target_dump",
help="Dump definitions of Bazel targets.",
formatter_class=argparse.RawDescriptionHelpFormatter,
description=r"""
Print the real definition of a target, or set of targets, in a
Bazel graph, after all macros have been expanded. Note that:
- Each TARGET_SET argument is a standard Bazel target set expression
(e.g. `//src/lib:foo` or `//:*`).
- The output is pretty printed for readability.
- For each target that is the result of a macro expansion, comments
are added to describe the call chain that led to it.
- For each target generated by native.filegroup() in macros, new
`generator_{function,location,name}` fields are added to indicate
how it was generated.
""",
)
parser_target_dump.add_argument(
"target_set",
metavar="TARGET_SET",
nargs="+",
help="Set of targets to dump.",
)
parser_target_dump.set_defaults(func=cmd_target_dump)
# The target_commands command.
parser_actions = subparsers.add_parser(
"actions",
help="Dump action commands of Bazel targets.",
formatter_class=argparse.RawDescriptionHelpFormatter,
description=r"""
Print the action commands of a target, or set of targets, omitting
from the list of inputs sysroot and toolchain related headers, which
can be _very_ long when using the Fuchsia C++ toolchain.
""",
)
parser_actions.add_argument(
"target_set",
metavar="TARGET_SET",
nargs="+",
help="Set to targets to print actions for.",
)
parser_actions.add_argument(
"--",
dest="extra_args",
default=[],
nargs=argparse.REMAINDER,
help="extra Bazel aquery-compatible arguments.",
)
parser_actions.set_defaults(func=cmd_actions)
args = parser.parse_args()
if args.workspace:
os.chdir(args.workspace)
return args.func(args)
if __name__ == "__main__":
sys.exit(main())