blob: 9f2f590058e9fc18e788e199a60fe0bc42bd2f03 [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.
"""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())