blob: 7a304d3d82deaaf2d5979cdd53abca01fdb60f69 [file] [log] [blame]
#!/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.
"""Prepare a Fuchsia checkout for 'fx bazel'. This is a temporary measure
until all requirements are properly managed with 'jiri'."""
import argparse
import os
import shutil
import subprocess
import sys
import tempfile
import urllib.request
import xml.etree.ElementTree as ET
_SCRIPT_DIR = os.path.dirname(__file__)
def get_host_platform() -> str:
"""Return host platform name, following Fuchsia conventions."""
if sys.platform == "linux":
return "linux"
elif sys.platform == "darwin":
return "mac"
else:
return os.uname().sysname
def get_host_arch() -> str:
"""Return host CPU architecture, following Fuchsia conventions."""
host_arch = os.uname().machine
if host_arch == "x86_64":
return "x64"
elif host_arch.startswith(("armv8", "aarch64")):
return "arm64"
else:
return host_arch
def get_host_tag():
"""Return host tag, following Fuchsia conventions."""
return "%s-%s" % (get_host_platform(), get_host_arch())
def write_file(path, content):
with open(path, "w") as f:
f.write(content)
def clone_git_branch(git_url, git_branch, dst_dir):
subprocess.check_call(
["git", "clone", "--branch", git_branch, "--depth=1", git_url, dst_dir],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
def clone_git_commit(git_url, git_commit, dst_dir):
def git_cmd(args):
subprocess.check_call(
["git", "-C", dst_dir] + args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
git_cmd(["init"])
git_cmd(["remote", "add", "origin", git_url])
git_cmd(["fetch", "origin", git_commit])
git_cmd(["reset", "--hard", "FETCH_HEAD"])
def get_bazel_download_url(version: str) -> str:
"""Return Bazel download URL for a specific version and the current host platform."""
if sys.platform == "linux":
host_os = "linux"
elif sys.platform == "darwin":
host_os = "darwin"
elif sys.platform in ("win32", "cygwin"):
host_os = "windows"
else:
host_os = os.uname().sysname
host_cpu = os.uname().machine
if host_cpu.startswith(("armv8", "aarch64")):
host_cpu = "arm64"
ext = ".exe" if host_os == "windows" else ""
return f"https://github.com/bazelbuild/bazel/releases/download/{version}/bazel-{version}-{host_os}-{host_cpu}{ext}"
def get_bazel_version(bazel_launcher):
"""Return version of a given Bazel binary."""
output = subprocess.check_output(
[bazel_launcher, "version"], stderr=subprocess.DEVNULL, text=True
)
version_prefix = "Build label: "
for line in output.splitlines():
if line.startswith(version_prefix):
return line[len(version_prefix) :].strip()
return None
def ignore_log(message):
pass
_FALLBACK_BAZEL_VERSION = "5.3.0"
def get_jiri_bazel_version(fuchsia_dir, log=ignore_log):
# The location of the file that contains the Jiri Bazel package definition
prebuilts_file = os.path.join(
fuchsia_dir, "integration", "fuchsia", "prebuilts"
)
if not os.path.exists(prebuilts_file):
log(
"Could not find Jiri file defining Bazel version at: %s (using fallback version %s)"
% (prebuilts_file, _FALLBACK_BAZEL_VERSION)
)
return _FALLBACK_BAZEL_VERSION
version = None
prebuilts = ET.parse(prebuilts_file)
for package in prebuilts.findall("packages/package"):
package_name = package.get("name")
if package_name == "fuchsia/third_party/bazel/${platform}":
version = package.get("version")
break
if not version:
log(
"Could not find Bazel package in: %s (using fallback version %s)"
% (prebuilts_file, _FALLBACK_BAZEL_VERSION)
)
return _FALLBACK_BAZEL_VERSION
# The package version has a .<patch> sufix which corresponds to
# the LUCI recipe patch number, so remove it.
pos = version.rfind(".")
if pos > 0:
version = version[:pos]
version_prefix = "version:2@"
if not version.startswith(version_prefix):
log(
"Unsupported Bazel version tag (%s), using fallback %s"
% (version, _FALLBACK_BAZEL_VERSION)
)
return _FALLBACK_BAZEL_VERSION
return version[len(version_prefix) :]
class InstallDirectory(object):
def __init__(self, path):
self._tmp_dir = path + ".tmp"
self._dst_dir = path
self._old_dir = path + ".old"
@property
def path(self):
return self._tmp_dir
@property
def final_path(self):
return self._dst_dir
def __enter__(self):
if os.path.exists(self._tmp_dir):
shutil.rmtree(self._tmp_dir)
os.makedirs(self._tmp_dir)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
# An exception occurred, cleanup temp dir.
shutil.rmtree(self._tmp_dir)
else:
if os.path.exists(self._old_dir):
shutil.rmtree(self._old_dir)
os.rename(self._dst_dir, self._old_dir)
os.rename(self._tmp_dir, self._dst_dir)
return False
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--fuchsia-dir", help="Path to Fuchsia checkout directory."
)
parser.add_argument(
"--bazel-version", help="Set Bazel binary version to use."
)
parser.add_argument(
"--quiet", action="store_true", help="Disable verbose output."
)
args = parser.parse_args()
def log(message):
if not args.quiet:
print(message, flush=True)
if not args.fuchsia_dir:
# Assume this script is under build/bazel/scripts/
args.fuchsia_dir = os.path.abspath(
os.path.join(_SCRIPT_DIR, "..", "..", "..")
)
log("Found Fuchsia dir: %s" % args.fuchsia_dir)
if not args.bazel_version:
args.bazel_version = get_jiri_bazel_version(args.fuchsia_dir, log=log)
if not args.bazel_version:
return 1
log("Using default Bazel version: " + args.bazel_version)
# Compare the available Bazel version with the one we need.
bazel_install_path = os.path.join(
args.fuchsia_dir, "prebuilt", "third_party", "bazel", get_host_tag()
)
bazel_launcher = os.path.join(bazel_install_path, "bazel")
bazel_update = not os.path.exists(bazel_launcher)
if not bazel_update:
current_version = get_bazel_version(bazel_launcher)
if current_version != args.bazel_version:
log(
"Found installed Bazel version %s, updating to %s"
% (current_version, args.bazel_version)
)
bazel_update = True
if bazel_update:
with tempfile.TemporaryDirectory() as tmpdirname:
bazel_bin = os.path.join(tmpdirname, "bazel-" + args.bazel_version)
url = get_bazel_download_url(args.bazel_version)
log("Downloading %s" % url)
urllib.request.urlretrieve(url, bazel_bin)
os.chmod(bazel_bin, 0o750)
log("Generating Bazel install at: %s" % bazel_install_path)
with InstallDirectory(bazel_install_path) as install:
subprocess.check_call(
[
sys.executable,
"-S",
os.path.join(_SCRIPT_DIR, "generate-bazel-install.py"),
"--bazel",
bazel_bin,
"--install-dir",
install.path,
"--force",
],
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
)
return 0
if __name__ == "__main__":
sys.exit(main())