You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
8.5 KiB
251 lines
8.5 KiB
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# Copyright 2020 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
# pylint: disable=cros-logging-import
|
|
|
|
"""Adds a cherrypick to LLVM's PATCHES.json."""
|
|
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import os
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
|
|
import chroot
|
|
import get_llvm_hash
|
|
import git
|
|
import git_llvm_rev
|
|
import update_chromeos_llvm_hash
|
|
|
|
|
|
def add_cherrypick(patches_json_path: str, patches_dir: str,
|
|
relative_patches_dir: str, start_version: git_llvm_rev.Rev,
|
|
llvm_dir: str, rev: git_llvm_rev.Rev, sha: str,
|
|
package: str):
|
|
with open(patches_json_path, encoding='utf-8') as f:
|
|
patches_json = json.load(f)
|
|
|
|
file_name = sha + '.patch'
|
|
rel_patch_path = os.path.join(relative_patches_dir, file_name)
|
|
|
|
for p in patches_json:
|
|
rel_path = p['rel_patch_path']
|
|
if rel_path == rel_patch_path:
|
|
raise ValueError('Patch at %r already exists in PATCHES.json' % rel_path)
|
|
if sha in rel_path:
|
|
logging.warning(
|
|
'Similarly-named patch already exists in PATCHES.json: %r', rel_path)
|
|
|
|
with open(os.path.join(patches_dir, file_name), 'wb') as f:
|
|
cmd = ['git', 'show', sha]
|
|
# Only apply the part of the patch that belongs to this package, expect
|
|
# LLVM. This is because some packages are built with LLVM ebuild on X86 but
|
|
# not on the other architectures. e.g. compiler-rt. Therefore always apply
|
|
# the entire patch to LLVM ebuild as a workaround.
|
|
if package != 'llvm':
|
|
cmd.append(package_to_project(package))
|
|
subprocess.check_call(cmd, stdout=f, cwd=llvm_dir)
|
|
|
|
commit_subject = subprocess.check_output(
|
|
['git', 'log', '-n1', '--format=%s', sha], cwd=llvm_dir, encoding='utf-8')
|
|
|
|
patches_json.append({
|
|
'comment': commit_subject.strip(),
|
|
'rel_patch_path': rel_patch_path,
|
|
'start_version': start_version.number,
|
|
'end_version': rev.number,
|
|
})
|
|
|
|
temp_file = patches_json_path + '.tmp'
|
|
with open(temp_file, 'w', encoding='utf-8') as f:
|
|
json.dump(patches_json, f, indent=4, separators=(',', ': '))
|
|
os.rename(temp_file, patches_json_path)
|
|
|
|
|
|
def parse_ebuild_for_assignment(ebuild_path: str, var_name: str) -> str:
|
|
# '_pre' filters the LLVM 9.0 ebuild, which we never want to target, from
|
|
# this list.
|
|
candidates = [
|
|
x for x in os.listdir(ebuild_path)
|
|
if x.endswith('.ebuild') and '_pre' in x
|
|
]
|
|
|
|
if not candidates:
|
|
raise ValueError('No ebuilds found under %r' % ebuild_path)
|
|
|
|
ebuild = os.path.join(ebuild_path, max(candidates))
|
|
with open(ebuild, encoding='utf-8') as f:
|
|
var_name_eq = var_name + '='
|
|
for orig_line in f:
|
|
if not orig_line.startswith(var_name_eq):
|
|
continue
|
|
|
|
# We shouldn't see much variety here, so do the simplest thing possible.
|
|
line = orig_line[len(var_name_eq):]
|
|
# Remove comments
|
|
line = line.split('#')[0]
|
|
# Remove quotes
|
|
line = shlex.split(line)
|
|
if len(line) != 1:
|
|
raise ValueError('Expected exactly one quoted value in %r' % orig_line)
|
|
return line[0].strip()
|
|
|
|
raise ValueError('No %s= line found in %r' % (var_name, ebuild))
|
|
|
|
|
|
# Resolves a git ref (or similar) to a LLVM SHA.
|
|
def resolve_llvm_ref(llvm_dir: str, sha: str) -> str:
|
|
return subprocess.check_output(
|
|
['git', 'rev-parse', sha],
|
|
encoding='utf-8',
|
|
cwd=llvm_dir,
|
|
).strip()
|
|
|
|
|
|
# Get the package name of an LLVM project
|
|
def project_to_package(project: str) -> str:
|
|
if project == 'libunwind':
|
|
return 'llvm-libunwind'
|
|
return project
|
|
|
|
|
|
# Get the LLVM project name of a package
|
|
def package_to_project(package: str) -> str:
|
|
if package == 'llvm-libunwind':
|
|
return 'libunwind'
|
|
return package
|
|
|
|
|
|
# Get the LLVM projects change in the specifed sha
|
|
def get_package_names(sha: str, llvm_dir: str) -> list:
|
|
paths = subprocess.check_output(
|
|
['git', 'show', '--name-only', '--format=', sha],
|
|
cwd=llvm_dir,
|
|
encoding='utf-8').splitlines()
|
|
# Some LLVM projects are built by LLVM ebuild on X86, so always apply the
|
|
# patch to LLVM ebuild
|
|
packages = {'llvm'}
|
|
# Detect if there are more packages to apply the patch to
|
|
for path in paths:
|
|
package = project_to_package(path.split('/')[0])
|
|
if package in ('compiler-rt', 'libcxx', 'libcxxabi', 'llvm-libunwind'):
|
|
packages.add(package)
|
|
packages = list(sorted(packages))
|
|
return packages
|
|
|
|
|
|
def main():
|
|
chroot.VerifyOutsideChroot()
|
|
logging.basicConfig(
|
|
format='%(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: %(message)s',
|
|
level=logging.INFO,
|
|
)
|
|
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
'--chroot_path',
|
|
default=os.path.join(os.path.expanduser('~'), 'chromiumos'),
|
|
help='the path to the chroot (default: %(default)s)')
|
|
parser.add_argument(
|
|
'--start_sha',
|
|
default='llvm-next',
|
|
help='LLVM SHA that the patch should start applying at. You can specify '
|
|
'"llvm" or "llvm-next", as well. Defaults to %(default)s.')
|
|
parser.add_argument(
|
|
'--sha',
|
|
required=True,
|
|
action='append',
|
|
help='The LLVM git SHA to cherry-pick.')
|
|
parser.add_argument(
|
|
'--create_cl',
|
|
default=False,
|
|
action='store_true',
|
|
help='Automatically create a CL if specified')
|
|
args = parser.parse_args()
|
|
|
|
llvm_symlink = chroot.ConvertChrootPathsToAbsolutePaths(
|
|
args.chroot_path,
|
|
chroot.GetChrootEbuildPaths(args.chroot_path, ['sys-devel/llvm']))[0]
|
|
llvm_symlink_dir = os.path.dirname(llvm_symlink)
|
|
|
|
git_status = subprocess.check_output(['git', 'status', '-s'],
|
|
cwd=llvm_symlink_dir,
|
|
encoding='utf-8')
|
|
if git_status:
|
|
raise ValueError('Uncommited changes detected in %s' %
|
|
os.path.dirname(os.path.dirname(llvm_symlink_dir)))
|
|
|
|
start_sha = args.start_sha
|
|
if start_sha == 'llvm':
|
|
start_sha = parse_ebuild_for_assignment(llvm_symlink_dir, 'LLVM_HASH')
|
|
elif start_sha == 'llvm-next':
|
|
start_sha = parse_ebuild_for_assignment(llvm_symlink_dir, 'LLVM_NEXT_HASH')
|
|
logging.info('Base llvm hash == %s', start_sha)
|
|
|
|
llvm_config = git_llvm_rev.LLVMConfig(
|
|
remote='origin', dir=get_llvm_hash.GetAndUpdateLLVMProjectInLLVMTools())
|
|
|
|
start_sha = resolve_llvm_ref(llvm_config.dir, start_sha)
|
|
start_rev = git_llvm_rev.translate_sha_to_rev(llvm_config, start_sha)
|
|
|
|
if args.create_cl:
|
|
branch = 'cherry-pick'
|
|
git.CreateBranch(llvm_symlink_dir, branch)
|
|
symlinks_to_uprev = []
|
|
commit_messages = [
|
|
'llvm: cherry-pick CLs from upstream\n',
|
|
]
|
|
|
|
for sha in args.sha:
|
|
sha = resolve_llvm_ref(llvm_config.dir, sha)
|
|
rev = git_llvm_rev.translate_sha_to_rev(llvm_config, sha)
|
|
# Find out the llvm projects changed in this commit
|
|
packages = get_package_names(sha, llvm_config.dir)
|
|
# Find out the ebuild symlinks of the corresponding ChromeOS packages
|
|
symlinks = chroot.GetChrootEbuildPaths(args.chroot_path, [
|
|
'sys-devel/llvm' if package == 'llvm' else 'sys-libs/' + package
|
|
for package in packages
|
|
])
|
|
symlinks = chroot.ConvertChrootPathsToAbsolutePaths(args.chroot_path,
|
|
symlinks)
|
|
|
|
# Create a patch and add its metadata for each package
|
|
for package, symlink in zip(packages, symlinks):
|
|
symlink_dir = os.path.dirname(symlink)
|
|
patches_json_path = os.path.join(symlink_dir, 'files/PATCHES.json')
|
|
relative_patches_dir = 'cherry' if package == 'llvm' else ''
|
|
patches_dir = os.path.join(symlink_dir, 'files', relative_patches_dir)
|
|
logging.info('Cherrypicking %s (%s) into %s', rev, sha, package)
|
|
|
|
add_cherrypick(patches_json_path, patches_dir, relative_patches_dir,
|
|
start_rev, llvm_config.dir, rev, sha, package)
|
|
if args.create_cl:
|
|
symlinks_to_uprev.extend(symlinks)
|
|
commit_messages.extend([
|
|
'\n\nreviews.llvm.org/rG%s\n' % sha,
|
|
subprocess.check_output(['git', 'log', '-n1', '--oneline', sha],
|
|
cwd=llvm_config.dir,
|
|
encoding='utf-8')
|
|
])
|
|
|
|
logging.info('Complete.')
|
|
|
|
if args.create_cl:
|
|
symlinks_to_uprev = list(sorted(set(symlinks_to_uprev)))
|
|
for symlink in symlinks_to_uprev:
|
|
update_chromeos_llvm_hash.UprevEbuildSymlink(symlink)
|
|
subprocess.check_output(['git', 'add', '--all'], cwd=symlink_dir)
|
|
git.UploadChanges(llvm_symlink_dir, branch, commit_messages)
|
|
git.DeleteBranch(llvm_symlink_dir, branch)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|