blob: c0523d980501af8958ae10f7177d50134ee76584 [file] [log] [blame] [edit]
#!/usr/bin/env python3
#
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# pylint: disable=not-callable
"""Update the prebuilt clang from the build server."""
import argparse
import inspect
import logging
import os
import shutil
import subprocess
import sys
import utils
def logger():
"""Returns the module level logger."""
return logging.getLogger(__name__)
class ArgParser(argparse.ArgumentParser):
def __init__(self):
super(ArgParser, self).__init__(
description=inspect.getdoc(sys.modules[__name__]))
self.add_argument(
'build', metavar='BUILD',
help='Build number to pull from the build server.')
self.add_argument(
'-b', '--bug', help='Bug to reference in commit message.')
self.add_argument(
'-br', '--branch', help='Branch to fetch from (or automatic).')
self.add_argument(
'--use-current-branch', action='store_true',
help='Do not repo start a new branch for the update.')
self.add_argument(
'--skip-fetch',
'-sf',
action='store_true',
default=False,
help='Skip the fetch, and only do the extraction step')
self.add_argument(
'--skip-cleanup',
'-sc',
action='store_true',
default=False,
help='Skip the cleanup, and leave intermediate files')
self.add_argument(
'--overwrite', action='store_true',
help='Remove/overwrite any existing prebuilt directories.')
self.add_argument(
'--no-sanity-check', action='store_true',
help='Skip sanity checks on the prebuilt binaries.')
def fetch_artifact(branch, target, build, pattern):
fetch_artifact_path = '/google/data/ro/projects/android/fetch_artifact'
cmd = [fetch_artifact_path, f'--branch={branch}',
f'--target={target}', f'--bid={build}', pattern]
utils.check_call(cmd)
def extract_package(package, install_dir):
cmd = ['tar', 'xf', package, '-C', install_dir]
utils.check_call(cmd)
def extract_clang_info(clang_dir):
version_file_path = os.path.join(clang_dir, 'AndroidVersion.txt')
with open(version_file_path) as version_file:
# e.g. for contents: ['7.0.1', 'based on r326829']
contents = [l.strip() for l in version_file.readlines()]
version = contents[0]
revision = contents[1].split()[-1]
return version, revision
def symlink_to_linux_resource_dir(install_dir):
# Assume we're in a Darwin (non-linux) prebuilt dir. Find the Clang version
# string. Pick the longest string, if there's more than one.
version_dirs = os.listdir(os.path.join(install_dir, 'lib64', 'clang'))
if version_dirs:
version_dirs.sort(key=len)
version_dir = version_dirs[-1]
symlink_dir = os.path.join(install_dir, 'lib64', 'clang', version_dir,
'lib')
link_src = os.path.join('/'.join(['..'] * 6), 'linux-x86', symlink_dir,
'linux')
link_dst = 'linux'
# 'cd' to symlink_dir and create a symlink from link_dst to link_src
prebuilt_dir = os.getcwd()
os.chdir(symlink_dir)
os.symlink(link_src, link_dst)
os.chdir(prebuilt_dir)
def sanity_check(host, install_dir, clang_version_major):
# Make sure the binary has correct PGO profile.
if host == 'linux-x86':
realClangPath = os.path.join(install_dir, 'bin', 'clang-' + clang_version_major)
strings = utils.check_output(['strings', realClangPath])
if strings.find('NO PGO PROFILE') != -1:
logger().error('The Clang binary is not built with profiles.')
return False
# Check that all the files listed in remote_toolchain_inputs are valid
if host == 'linux-x86':
with open(os.path.join(install_dir, 'bin', 'remote_toolchain_inputs')) as inputs_file:
files = [line.strip() for line in inputs_file.readlines()]
fail = False
for f in files:
if not os.path.exists(os.path.join(install_dir, 'bin', f)):
logger().error(f'remote_toolchain_inputs malformed, {f} does not exist')
fail = True
if fail:
return False
return True
def format_bug(bug):
"""Formats a bug for use in a commit message.
Bugs might be a number, in which case they're a buganizer reference to be
formatted. If not, assume the user knows what they're doing and just return
the string as-is.
"""
try:
return f'http://b/{bug}'
except ValueError:
return bug
def update_clang(host, build_number, use_current_branch, download_dir, bug,
manifest, overwrite, do_sanity_check):
prebuilt_dir = utils.android_path('prebuilts/clang/host', host)
os.chdir(prebuilt_dir)
if not use_current_branch:
branch_name = f'update-clang-{build_number}'
utils.unchecked_call(
['repo', 'abandon', branch_name, '.'])
utils.check_call(
['repo', 'start', branch_name, '.'])
package = f'{download_dir}/clang-{build_number}-{host}.tar.bz2'
# Handle legacy versions of packages (like those from aosp/llvm-r365631).
if not os.path.exists(package) and host == 'windows-x86':
package = f'{download_dir}/clang-{build_number}-windows-x86-64.tar.bz2'
manifest_file = f'{download_dir}/{manifest}'
extract_package(package, prebuilt_dir)
extract_subdir = 'clang-' + build_number
clang_version, svn_revision = extract_clang_info(extract_subdir)
# Install into clang-<svn_revision>. Suffixes ('a', 'b', 'c' etc.), if any,
# are included in the svn_revision.
install_subdir = 'clang-' + svn_revision
if os.path.exists(install_subdir):
if overwrite:
logger().info('Removing/overwriting existing path: %s',
install_subdir)
shutil.rmtree(install_subdir)
else:
logger().info('Cannot remove/overwrite existing path: %s',
install_subdir)
sys.exit(1)
os.rename(extract_subdir, install_subdir)
# Some platform tests (e.g. system/bt/profile/sdp) build directly with
# coverage instrumentation and rely on the driver to pick the correct
# profile runtime. Symlink the Linux resource dir from the Linux toolchain
# into the Darwin toolchain so the runtime is found by the Darwin Clang
# driver.
if host == 'darwin-x86':
symlink_to_linux_resource_dir(install_subdir)
if do_sanity_check:
if not sanity_check(host, install_subdir, clang_version.split('.')[0]):
sys.exit(1)
shutil.copy(manifest_file, prebuilt_dir + '/' + install_subdir)
utils.check_call(['git', 'add', install_subdir])
# If there is no difference with the new files, we are already done.
diff = utils.unchecked_call(['git', 'diff', '--cached', '--quiet'])
if diff == 0:
logger().info('Bypassed commit with no diff')
return
message_lines = [
f'Update prebuilt Clang to {svn_revision}.',
'',
f'clang {clang_version} (based on {svn_revision}) from build {build_number}.'
]
if bug is not None:
message_lines.append('')
message_lines.append(f'Bug: {format_bug(bug)}')
message_lines.append('Test: N/A')
message = '\n'.join(message_lines)
utils.check_call(['git', 'commit', '-m', message])
def main():
args = ArgParser().parse_args()
logging.basicConfig(level=logging.INFO)
do_fetch = not args.skip_fetch
do_cleanup = not args.skip_cleanup
download_dir = os.path.realpath('.download')
if do_fetch:
if os.path.isdir(download_dir):
shutil.rmtree(download_dir)
os.makedirs(download_dir)
os.chdir(download_dir)
targets = ['darwin_mac', 'linux', 'windows_x86_64']
hosts = ['darwin-x86', 'linux-x86', 'windows-x86']
clang_pattern = 'clang-*.tar.bz2'
manifest = f'manifest_{args.build}.xml'
branch = args.branch
if branch is None:
git_dir = utils.android_path('toolchain', 'llvm_android', '.git')
o = utils.check_output(['git', '--git-dir=' + git_dir, 'branch', '-av'])
branch = o.split(' ')[-1].strip().replace('/', '-')
# aosp/llvm-toolchain uses the branch 'aosp-master', but we really only
# pull prebuilts from 'aosp-llvm-toolchain' or other release branches.
if branch == 'aosp-master':
branch = 'aosp-llvm-toolchain'
logger().info('Using branch: %s', branch)
try:
if do_fetch:
fetch_artifact(branch, targets[0], args.build, manifest)
for target in targets:
fetch_artifact(branch, target, args.build, clang_pattern)
for host in hosts:
update_clang(host, args.build, args.use_current_branch,
download_dir, args.bug, manifest, args.overwrite,
not args.no_sanity_check)
finally:
if do_cleanup:
shutil.rmtree(download_dir)
return 0
if __name__ == '__main__':
main()