| #!/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()) |