blob: 871d38ac491bdb84b57ab44dc662431df6991748 [file] [log] [blame] [edit]
#!/usr/bin/env python3
# Copyright 2024 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Runs a nested bazel invocation and collects the outputs to out_dir."""
import json
import logging
import os
import pathlib
import subprocess
import sys
from typing import List
def _fail(msg: str):
print(msg, file=sys.stderr)
# When invoked by the nested bazel repo rules, this will be enabled, and
# thus it will tell you to run the binary directly. This adds colors to your
# error messages (eg. compilation errors), and improves performance by
# bypassing the outer bazel.
if os.environ.get("SHOW_REPRO", None):
cmd = " ".join(sys.argv)
print(f"\n\nTo reproduce, run:\n{cmd}\n\n", file=sys.stderr)
sys.exit(1)
def _run(args, **kwargs) -> subprocess.CompletedProcess:
logging.debug("Running %s", " ".join(args))
ps = subprocess.run(
args,
check=False,
**kwargs,
env={**os.environ, "IS_NESTED_BAZEL": "1"},
)
if ps.returncode != 0:
# The command-line is really long and not particularly useful.
_fail("Nested bazel invocation failed to execute.")
return ps
def main(
base_command: List[str],
common_opts: List[str],
build_opts: List[str],
target: str,
out_dir: str,
repo_rule_deps: List[str],
nested_output_base: str,
):
# Eg. ~/.cache/_bazel_$USER_nested/<checksum>/external/a~b~c.
out_dir = pathlib.Path(out_dir)
out_root = pathlib.Path(nested_output_base, "execroot/_main")
external_dir = out_dir.parent
if external_dir.parent.name.endswith("_nested"):
_fail(
"You appear to be attempting to build a nested target from "
"another nested target. This is not currently supported"
)
for repo_rule in repo_rule_deps:
repo_path = external_dir.joinpath(repo_rule)
common_opts.append(f"--override_repository={repo_rule}={repo_path}")
_run(
[
*base_command,
"build",
*common_opts,
*build_opts,
target,
]
)
exported_files = []
out_files = (
_run(
# This should get a cache hit because we already built everything.
[
*base_command,
"cquery",
"--output=files",
*common_opts,
*build_opts,
target,
],
stdout=subprocess.PIPE,
)
.stdout.decode("utf-8")
.strip()
)
for path in out_files.split("\n"):
path = out_root / path
dst = out_dir / path.name
exported_files.append(path.name)
logging.debug("Symlinking %s to %s", dst, path)
dst.symlink_to(path)
with out_dir.joinpath("BUILD.bazel").open("w", encoding="utf-8") as build:
build.write("exports_files([\n")
for name in sorted(exported_files):
build.write(f' "{name}",\n')
build.write("])\n")
if __name__ == "__main__":
level = os.environ.get("NESTED_LOGGING", "").upper() or logging.INFO
logging.basicConfig(level=level)
with open(sys.argv[1], encoding="utf-8") as f:
main(**(json.load(f)))