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