blob: d7be3d09107f85a5a33cd4298cad2e1ee373715f [file] [log] [blame] [edit]
#!/usr/bin/env python3
# Copyright 2022 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.
"Run a Bazel command from Ninja. See bazel_action.gni for details."
import argparse
import errno
import filecmp
import json
import os
import shlex
import shutil
import subprocess
import sys
def copy_file_if_changed(src_path, dst_path):
# NOTE: For some reason, filecmp.cmp() will return True if
# dst_path does not exist, even if src_path is not empty!?
if os.path.exists(dst_path) and filecmp.cmp(src_path, dst_path,
shallow=False):
return
if os.path.exists(dst_path):
os.remove(dst_path)
try:
os.link(src_path, dst_path)
except OSError as e:
if e.errno == errno.EXDEV:
# Cross-device link, simple copy.
shutil.copy2(src_path, dst_path)
else:
raise
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
'--bazel-launcher',
required=True,
help='Path to Bazel launcher script.')
parser.add_argument(
'--workspace-dir', required=True, help='Bazel workspace path')
parser.add_argument(
'--command',
required=True,
help='Bazel command, e.g. `build`, `run`, `test`')
parser.add_argument(
'--inputs-manifest',
help=
'Path to the manifest file describing Ninja outputs as bazel inputs.')
parser.add_argument(
'--bazel-inputs',
default=[],
nargs='*',
help='Labels of GN bazel_input_xxx() targets for this action.')
parser.add_argument(
'--bazel-targets',
action='append',
default=[],
help='List of bazel target patterns.')
parser.add_argument(
'--bazel-outputs',
default=[],
nargs='*',
help='Bazel output paths, relative to bazel-bin/.')
parser.add_argument(
'--bazel-platform',
help='Set the Bazel target/default platform for this action.')
parser.add_argument(
'--ninja-outputs',
default=[],
nargs='*',
help='Ninja output paths relative to current directory.')
parser.add_argument('extra_bazel_args', nargs=argparse.REMAINDER)
args = parser.parse_args()
if not args.bazel_targets:
return parser.error('A least one --bazel-targets value is needed!')
if not args.bazel_outputs:
return parser.error('At least one --bazel-outputs value is needed!')
if not args.ninja_outputs:
return parser.error('At least one --ninja-outputs value is needed!')
if len(args.bazel_outputs) != len(args.ninja_outputs):
return parser.error(
'The --bazel-outputs and --ninja-outputs lists must have the same size!'
)
if args.bazel_inputs:
if not args.inputs_manifest:
return parser.error(
'--inputs-manifest is required with --bazel-inputs')
# Verify that all bazel input labels are pare of the inputs manifest.
# If not, print a user-friendly message that explains the situation and
# how to fix it.
with open(args.inputs_manifest) as f:
inputs_manifest = json.load(f)
all_input_labels = set(e['gn_label'] for e in inputs_manifest)
unknown_labels = set(args.bazel_inputs) - all_input_labels
if unknown_labels:
print(
'''ERROR: The following bazel_inputs labels are not listed in the Bazel inputs manifest:
%s
These labels must be in one of `gn_labels_for_bazel_inputs` or `extra_gn_labels_for_bazel_inputs`.
For more details, see the comments in //build/bazel/legacy_ninja_build_outputs.gni.
''' % '\n '.join(list(unknown_labels)),
file=sys.stderr)
return 1
if args.extra_bazel_args and args.extra_bazel_args[0] != '--':
return parser.error(
'Extra bazel args should be seperate with script args using --')
args.extra_bazel_args = args.extra_bazel_args[1:]
if not os.path.exists(args.workspace_dir):
return parser.error(
'Workspace directory does not exist: %s' % args.workspace_dir)
if not os.path.exists(args.bazel_launcher):
return parser.error(
'Bazel launcher does not exist: %s' % args.bazel_launcher)
cmd = [args.bazel_launcher, args.command]
if args.bazel_platform:
cmd.append('--platforms=' + args.bazel_platform)
cmd += [
shlex.quote(arg) for arg in args.extra_bazel_args
] + args.bazel_targets
ret = subprocess.run(cmd)
if ret.returncode != 0:
print(
'ERROR when calling Bazel. To reproduce, run this in the Ninja output directory:\n\n %s\n'
% ' '.join(shlex.quote(c) for c in cmd),
file=sys.stderr)
return 1
for bazel_out, ninja_out in zip(args.bazel_outputs, args.ninja_outputs):
src_path = os.path.join(args.workspace_dir, bazel_out)
dst_path = ninja_out
copy_file_if_changed(src_path, dst_path)
# Done!
return 0
if __name__ == "__main__":
sys.exit(main())