| #!/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 |
| from pathlib import Path |
| from typing import Sequence |
| |
| def make_bazel_quiet_command(bazel: str, command: str) -> Sequence[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): |
| 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('^ 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(): |
| 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 descirbe 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()) |