diff options
author | Salud Lemus <saludlemus@google.com> | 2019-07-08 15:51:52 -0700 |
---|---|---|
committer | Salud Lemus <saludlemus@google.com> | 2019-07-12 20:36:07 +0000 |
commit | f3bf30332ef8bdb5a10e847dbfb84e61b4c50c62 (patch) | |
tree | 95f9ba17712ea9e0b8bdd9ab8e8c7b1ba593a7b7 /llvm_tools | |
parent | 9780ea97662c429f6dcb53fb2ef90e98fe1a5f1b (diff) | |
download | toolchain-utils-f3bf30332ef8bdb5a10e847dbfb84e61b4c50c62.tar.gz |
LLVM tools: updates the LLVM next hash of a package or packages
BUG=None
TEST=Ran the script and a CL was uploaded for review for 'sys-devel/llvm'
Change-Id: I827843f4841f18b586ae2dce39197291014cb559
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/1691520
Reviewed-by: George Burgess <gbiv@chromium.org>
Tested-by: Salud Lemus <saludlemus@google.com>
Diffstat (limited to 'llvm_tools')
-rwxr-xr-x | llvm_tools/update_chromeos_llvm_next_hash.py | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/llvm_tools/update_chromeos_llvm_next_hash.py b/llvm_tools/update_chromeos_llvm_next_hash.py new file mode 100755 index 00000000..da191c29 --- /dev/null +++ b/llvm_tools/update_chromeos_llvm_next_hash.py @@ -0,0 +1,502 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +# Copyright 2019 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. + +"""Updates LLVM_NEXT_HASH and uprevs the build of a package or packages. + +For each package, a temporary repo is created and the changes are uploaded +for review. +""" + +from __future__ import print_function + +from pipes import quote +import argparse +import os +import re + +from cros_utils import command_executer +from get_google3_llvm_version import LLVMVersion +from get_llvm_hash import LLVMHash + +ce = command_executer.GetCommandExecuter() + + +def GetCommandLineArgs(): + """Parses the command line for the optional command line arguments. + + Returns: + The log level to use when retrieving the LLVM hash or google3 LLVM version, + the chroot path to use for executing chroot commands, + a list of a package or packages to update their LLVM next hash, + and the LLVM version to use when retrieving the LLVM hash. + """ + + # Default path to the chroot if a path is not specified. + cros_root = os.path.expanduser('~') + cros_root = os.path.join(cros_root, 'chromiumos') + + # Create parser and add optional command-line arguments. + parser = argparse.ArgumentParser( + description='Updates the build\'s hash for llvm-next.') + + # Add argument for a specific chroot path. + parser.add_argument( + '--chroot_path', + default=cros_root, + help='the path to the chroot (default: %(default)s)') + + # Add argument for specific builds to uprev and update their llvm-next hash. + parser.add_argument( + '--update_package', + default=['sys-devel/llvm'], + required=False, + nargs='+', + help='the ebuilds to update their hash for llvm-next ' \ + '(default: %(default)s)') + + # Add argument for the log level. + parser.add_argument( + '--log_level', + default='none', + choices=['none', 'quiet', 'average', 'verbose'], + help='the level for the logs (default: %(default)s)') + + # Add argument for the LLVM version to use. + parser.add_argument( + '--llvm_version', + type=int, + help='the LLVM version to use for retrieving the LLVM hash ' \ + '(default: uses the google3 llvm version)') + + # Parse the command line. + args_output = parser.parse_args() + + # Set the log level for the command executer. + ce.SetLogLevel(log_level=args_output.log_level) + + return (args_output.log_level, args_output.chroot_path, + args_output.update_package, args_output.llvm_version) + + +def GetChrootBuildPaths(chroot_path, package_list): + """Gets the chroot path(s) of the package(s). + + Args: + chroot_path: The absolute path to the chroot to + use for executing chroot commands. + package_list: A list of a package/packages to + be used to find their chroot path. + + Returns: + A list of a chroot path/chroot paths of the package's ebuild file. + + Raises: + ValueError: Failed to get the chroot path of a package. + """ + + chroot_paths = [] + + # Find the chroot path for each package's ebuild. + for cur_package in sorted(set(package_list)): + # Cmd to find the chroot path for the package. + equery_cmd = 'equery w %s' % cur_package + + # Find the chroot path for the package. + ret, chroot_path, err = ce.ChrootRunCommandWOutput( + chromeos_root=chroot_path, command=equery_cmd, print_to_console=False) + + if ret: # failed to get the chroot path + raise ValueError('Failed to get chroot path for the package (%s): %s' % + (cur_package, err)) + + chroot_paths.append(chroot_path.strip()) + + return chroot_paths + + +def _ConvertChrootPathsToSymLinkPaths(chromeos_root, chroot_file_paths): + """Converts the chroot path(s) to absolute symlink path(s). + + Args: + chromeos_root: The absolute path to the chroot. + chroot_file_paths: A list of a chroot path/chroot paths to convert to + a absolute symlink path/symlink paths. + + Returns: + A list of absolute path(s) which are symlinks that point to + the ebuild of the package(s). + + Raises: + ValueError: Invalid prefix for the chroot path or + invalid chroot path(s) were provided. + """ + + symlink_file_paths = [] + + chroot_prefix = '/mnt/host/source/' + + # Iterate through the chroot paths. + # + # For each chroot file path, remove '/mnt/host/source/' prefix + # and combine the chroot path with the result and add it to the list. + for cur_chroot_file_path in chroot_file_paths: + if not cur_chroot_file_path.startswith(chroot_prefix): + raise ValueError( + 'Invalid prefix for the chroot path: %s' % cur_chroot_file_path) + + rel_path = cur_chroot_file_path[len(chroot_prefix):] + + # combine the chromeos root path + '/src/...' + absolute_symlink_path = os.path.join(chromeos_root, rel_path) + + symlink_file_paths.append(absolute_symlink_path) + + return symlink_file_paths + + +def GetEbuildPathsFromSymLinkPaths(symlinks): + """Reads the symlink(s) to get the ebuild path(s) to the package(s). + + Args: + symlinks: A list of absolute path symlink/symlinks that point + to the package's ebuild. + + Returns: + A dictionary where the key is the absolute path of the symlink and the value + is the absolute path to the ebuild that was read from the symlink. + + Raises: + ValueError: Invalid symlink(s) were provided. + """ + + # A dictionary that holds: + # key: absolute symlink path + # value: absolute ebuild path + resolved_paths = {} + + # Iterate through each symlink. + # + # For each symlink, check that it is a valid symlink, + # and then construct the ebuild path, and + # then add the ebuild path to the dict. + for cur_symlink in symlinks: + if not os.path.islink(cur_symlink): + raise ValueError('Invalid symlink provided: %s' % cur_symlink) + + # Construct the absolute path to the ebuild. + ebuild_path = os.path.realpath(cur_symlink) + + if cur_symlink not in resolved_paths: + resolved_paths[cur_symlink] = ebuild_path + + return resolved_paths + + +def UpdateBuildLLVMNextHash(ebuild_path, llvm_hash, llvm_version): + """Updates the build's LLVM_NEXT_HASH. + + The build changes are staged for commit in the temporary repo. + + Args: + ebuild_path: The absolute path to the ebuild. + llvm_hash: The new LLVM hash to use for LLVM_NEXT_HASH. + llvm_version: The revision number of 'llvm_hash'. + + Raises: + ValueError: Invalid ebuild path provided or failed to stage the commit + of the changes or failed to update the LLVM hash. + """ + + # Iterate through each ebuild. + # + # For each ebuild, read the file in + # advance and then create a temporary file + # that gets updated with the new LLVM hash + # and revision number and then the ebuild file + # gets updated to the temporary file. + + if not os.path.isfile(ebuild_path): + raise ValueError('Invalid ebuild path provided: %s' % ebuild_path) + + # Create regex that finds 'LLVM_NEXT_HASH'. + llvm_regex = re.compile('^LLVM_NEXT_HASH=\"[a-z0-9]+\"') + + temp_ebuild_file = '%s.temp' % ebuild_path + + # A flag for whether 'LLVM_NEXT_HASH=...' was updated. + is_updated = False + + with open(ebuild_path) as ebuild_file: + # write updates to a temporary file in case of interrupts + with open(temp_ebuild_file, 'w') as temp_file: + for cur_line in ReplaceLLVMNextHash(ebuild_file, is_updated, llvm_regex, + llvm_hash, llvm_version): + temp_file.write(cur_line) + + os.rename(temp_ebuild_file, ebuild_path) + + # Get the path to the parent directory. + parent_dir = os.path.dirname(ebuild_path) + + # Stage the changes. + ret, _, err = ce.RunCommandWOutput( + 'git -C %s add %s' % (parent_dir, ebuild_path), print_to_console=False) + + if ret: # failed to stage the changes + raise ValueError('Failed to stage the ebuild for commit: %s' % err) + + +def ReplaceLLVMNextHash(ebuild_lines, is_updated, llvm_regex, llvm_hash, + llvm_version): + """Iterates through the ebuild file and updates the 'LLVM_NEXT_HASH'. + + Args: + ebuild_lines: The contents of the ebuild file. + is_updated: A flag for whether 'LLVM_NEXT_HASH' was updated. + llvm_regex: The regex object for finding 'LLVM_NEXT_HASH=...' when + iterating through the contents of the file. + llvm_hash: The new LLVM hash to use for LLVM_NEXT_HASH. + llvm_version: The revision number of 'llvm_hash'. + """ + + for cur_line in ebuild_lines: + if not is_updated and llvm_regex.search(cur_line): + # Update the LLVM next hash and revision number. + cur_line = 'LLVM_NEXT_HASH=\"%s\" # EGIT_COMMIT r%d\n' % (llvm_hash, + llvm_version) + + is_updated = True + + yield cur_line + + if not is_updated: # failed to update 'LLVM_NEXT_HASH' + raise ValueError('Failed to update the LLVM hash.') + + +def UprevEbuild(symlink): + """Uprevs the ebuild's revision number. + + Increases the revision number by 1 and stages the change in + the temporary repo. + + Args: + symlink: The absolute path of the symlink that points to + the ebuild of the package. + + Raises: + ValueError: Failed to uprev the symlink or failed to stage the changes. + """ + + if not os.path.islink(symlink): + raise ValueError('Invalid symlink provided: %s' % symlink) + + # Find the revision number and increment it by 1. + new_symlink, is_changed = re.subn( + r'r([0-9]+).ebuild', + lambda match: 'r%s.ebuild' % str(int(match.group(1)) + 1), + symlink, + count=1) + + if not is_changed: # failed to increment the revision number + raise ValueError('Failed to uprev the ebuild.') + + path_to_symlink_dir = os.path.dirname(symlink) + + # Stage the new symlink for commit. + ret, _, err = ce.RunCommandWOutput( + 'git -C %s mv %s %s' % (path_to_symlink_dir, symlink, new_symlink), + print_to_console=False) + + if ret: # failed to stage the symlink for commit + raise ValueError('Failed to stage the symlink for commit: %s' % err) + + +def _CreateRepo(path_to_repo_dir, llvm_hash): + """Creates a temporary repo for the changes. + + Args: + path_to_repo_dir: The absolute path to the repo. + llvm_hash: The LLVM hash to use for the name of the repo. + + Raises: + ValueError: Failed to create a repo in that directory. + """ + + if not os.path.isdir(path_to_repo_dir): + raise ValueError('Invalid directory path provided: %s' % path_to_repo_dir) + + create_repo_cmd = ' && '.join([ + 'cd %s' % path_to_repo_dir, + 'git reset HEAD --hard', + 'repo start llvm-next-update-%s' % llvm_hash, + ]) + + ret, _, err = ce.RunCommandWOutput(create_repo_cmd, print_to_console=False) + + if ret: # failed to create a repo for the changes + raise ValueError('Failed to create the repo (llvm-next-update-%s): %s' % + (llvm_hash, err)) + + +def _DeleteRepo(path_to_repo_dir, llvm_hash): + """Deletes the temporary repo. + + Args: + path_to_repo_dir: The absolute path of the repo. + llvm_hash: The LLVM hash used for the name of the repo. + + Raises: + ValueError: Failed to delete the repo in that directory. + """ + + if not os.path.isdir(path_to_repo_dir): + raise ValueError('Invalid directory path provided: %s' % path_to_repo_dir) + + delete_repo_cmd = ' && '.join([ + 'cd %s' % path_to_repo_dir, 'git checkout cros/master', + 'git reset HEAD --hard', + 'git branch -D llvm-next-update-%s' % llvm_hash + ]) + + ret, _, err = ce.RunCommandWOutput(delete_repo_cmd, print_to_console=False) + + if ret: # failed to delete the repo + raise ValueError('Failed to delete the repo (llvm-next-update-%s): %s' % + (llvm_hash, err)) + + +def UploadChanges(path_to_repo_dir, llvm_hash, commit_messages): + """Uploads the changes (updating LLVM next hash and uprev symlink) for review. + + Args: + path_to_repo_dir: The absolute path to the repo where changes were made. + llvm_hash: The LLVM hash used for the name of the repo. + commit_messages: A string of commit message(s) (i.e. '-m [message]' + of the changes made. + + Raises: + ValueError: Failed to create a commit or failed to upload the + changes for review. + """ + + if not os.path.isdir(path_to_repo_dir): + raise ValueError('Invalid directory path provided: %s' % path_to_repo_dir) + + commit_cmd = 'cd %s && git commit %s' % (path_to_repo_dir, commit_messages) + + ret, _, err = ce.RunCommandWOutput(commit_cmd, print_to_console=False) + + if ret: # failed to commit the changes + raise ValueError('Failed to create a commit for the changes: %s' % err) + + # Upload the changes for review. + upload_change_cmd = 'cd %s && ' \ + 'yes | repo upload --br=llvm-next-update-%s --no-verify' % ( + path_to_repo_dir, llvm_hash) + + ret, _, err = ce.RunCommandWOutput(upload_change_cmd, print_to_console=False) + + if ret: # failed to upload the changes for review + raise ValueError('Failed to upload changes for review: %s' % err) + + +def CreatePathDictionaryFromPackages(chroot_path, update_packages): + """Creates a symlink and ebuild path pair dictionary from the packages. + + Args: + chroot_path: The absolute path to the chroot. + update_packages: The filtered packages to be updated. + + Returns: + A dictionary where the key is the absolute path to the symlink + of the package and the value is the absolute path to the ebuild of + the package. + """ + + # Construct a list containing the chroot file paths of the package(s). + chroot_file_paths = GetChrootBuildPaths(chroot_path, update_packages) + + # Construct a list containing the symlink(s) of the package(s). + symlink_file_paths = _ConvertChrootPathsToSymLinkPaths( + chroot_path, chroot_file_paths) + + # Create a dictionary where the key is the absolute path of the symlink to + # the package and the value is the absolute path to the ebuild of the package. + return GetEbuildPathsFromSymLinkPaths(symlink_file_paths) + + +def UpdatePackages(paths_dict, llvm_hash, llvm_version): + """Updates the package's LLVM_NEXT_HASH and uprevs the ebuild. + + A temporary repo is created for the changes. The changes are + then uploaded for review. + + Args: + paths_dict: A dictionary that has absolute paths where the + key is the absolute path to the symlink of the package and the + value is the absolute path to the ebuild of the package. + llvm_hash: The LLVM hash to use for 'LLVM_NEXT_HASH'. + llvm_version: The LLVM version of the 'llvm_hash'. + """ + + repo_path = os.path.dirname(paths_dict.itervalues().next()) + + _CreateRepo(repo_path, llvm_hash) + + try: + commit_message_header = 'llvm-next: Update packages to r%d' % llvm_version + commit_messages = ['-m %s' % quote(commit_message_header)] + + commit_messages.append( + '-m %s' % quote('Following packages have been updated:')) + + # Iterate through the dictionary. + # + # For each iteration: + # 1) Update the ebuild's LLVM_NEXT_HASH. + # 2) Uprev the ebuild (symlink). + # 3) Add the modified package to the commit message. + for symlink_path, ebuild_path in paths_dict.items(): + path_to_ebuild_dir = os.path.dirname(ebuild_path) + + UpdateBuildLLVMNextHash(ebuild_path, llvm_hash, llvm_version) + + UprevEbuild(symlink_path) + + cur_dir_name = os.path.basename(path_to_ebuild_dir) + parent_dir_name = os.path.basename(os.path.dirname(path_to_ebuild_dir)) + + new_commit_message = '%s/%s' % (parent_dir_name, cur_dir_name) + + commit_messages.append('-m %s' % quote(new_commit_message)) + + UploadChanges(repo_path, llvm_hash, ' '.join(commit_messages)) + + finally: + _DeleteRepo(repo_path, llvm_hash) + + +def main(): + """Updates the LLVM next hash for each package.""" + + log_level, chroot_path, update_packages, llvm_version = GetCommandLineArgs() + + # Construct a dictionary where the key is the absolute path of the symlink to + # the package and the value is the absolute path to the ebuild of the package. + paths_dict = CreatePathDictionaryFromPackages(chroot_path, update_packages) + + # Get the google3 LLVM version if a LLVM version was not provided. + if not llvm_version: + llvm_version = LLVMVersion(log_level=log_level).GetGoogle3LLVMVersion() + + # Get the LLVM hash. + llvm_hash = LLVMHash(log_level=log_level).GetLLVMHash(llvm_version) + + UpdatePackages(paths_dict, llvm_hash, llvm_version) + + +if __name__ == '__main__': + main() |