aosp12/external/toolchain-utils/llvm_tools/llvm_patch_management.py

277 lines
8.9 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- 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.
#
# pylint: disable=global-statement
"""Creates the arguments for the patch manager for LLVM."""
from __future__ import print_function
import argparse
import os
from failure_modes import FailureModes
import chroot
import get_llvm_hash
import patch_manager
import subprocess_helpers
# If set to `True`, then the contents of `stdout` after executing a command will
# be displayed to the terminal.
verbose = False
def GetCommandLineArgs():
"""Parses the commandline for the optional commandline arguments.
Returns:
An argument parser object that contains all the commandline arguments.
"""
# 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='Patch management for packages.')
# Add argument for a specific chroot path.
parser.add_argument(
'--chroot_path',
type=patch_manager.is_directory,
default=cros_root,
help='the absolute path to the chroot (default: %(default)s)')
# Add argument for which packages to manage their patches.
parser.add_argument(
'--packages',
required=False,
nargs='+',
default=['sys-devel/llvm'],
help='the packages to manage their patches (default: %(default)s)')
# Add argument for whether to display command contents to `stdout`.
parser.add_argument(
'--verbose',
action='store_true',
help='display contents of a command to the terminal '
'(default: %(default)s)')
# Add argument for the LLVM version to use for patch management.
parser.add_argument(
'--llvm_version',
type=int,
help='the LLVM version to use for patch management. Alternatively, you '
'can pass "google3" or "google3-unstable". (Default: "google3")')
# Add argument for the mode of the patch management when handling patches.
parser.add_argument(
'--failure_mode',
default=FailureModes.FAIL.value,
choices=[FailureModes.FAIL.value, FailureModes.CONTINUE.value,
FailureModes.DISABLE_PATCHES.value,
FailureModes.REMOVE_PATCHES.value],
help='the mode of the patch manager when handling failed patches ' \
'(default: %(default)s)')
# Add argument for the patch metadata file in $FILESDIR of LLVM.
parser.add_argument(
'--patch_metadata_file',
default='PATCHES.json',
help='the .json file in $FILESDIR that has all the patches and their '
'metadata if applicable (default: %(default)s)')
# Parse the command line.
args_output = parser.parse_args()
global verbose
verbose = args_output.verbose
unique_packages = list(set(args_output.packages))
# Duplicate packages were passed into the command line
if len(unique_packages) != len(args_output.packages):
raise ValueError('Duplicate packages were passed in: %s' % ' '.join(
args_output.packages))
args_output.packages = unique_packages
return args_output
def GetPathToFilesDirectory(chroot_path, package):
"""Gets the absolute path to $FILESDIR of the package.
Args:
chroot_path: The absolute path to the chroot.
package: The package to find its absolute path to $FILESDIR.
Returns:
The absolute path to $FILESDIR.
Raises:
ValueError: An invalid chroot path has been provided.
"""
if not os.path.isdir(chroot_path):
raise ValueError('Invalid chroot provided: %s' % chroot_path)
# Get the absolute chroot path to the ebuild.
chroot_ebuild_path = subprocess_helpers.ChrootRunCommand(
chroot_path, ['equery', 'w', package], verbose=verbose)
# Get the absolute chroot path to $FILESDIR's parent directory.
filesdir_parent_path = os.path.dirname(chroot_ebuild_path.strip())
# Get the relative path to $FILESDIR's parent directory.
rel_path = _GetRelativePathOfChrootPath(filesdir_parent_path)
# Construct the absolute path to the package's 'files' directory.
return os.path.join(chroot_path, rel_path, 'files/')
def _GetRelativePathOfChrootPath(chroot_path):
"""Gets the relative path of the chroot path passed in.
Args:
chroot_path: The chroot path to get its relative path.
Returns:
The relative path after '/mnt/host/source/'.
Raises:
ValueError: The prefix of 'chroot_path' did not match '/mnt/host/source/'.
"""
chroot_prefix = '/mnt/host/source/'
if not chroot_path.startswith(chroot_prefix):
raise ValueError('Invalid prefix for the chroot path: %s' % chroot_path)
return chroot_path[len(chroot_prefix):]
def _CheckPatchMetadataPath(patch_metadata_path):
"""Checks that the patch metadata path is valid.
Args:
patch_metadata_path: The absolute path to the .json file that has the
patches and their metadata.
Raises:
ValueError: The file does not exist or the file does not end in '.json'.
"""
if not os.path.isfile(patch_metadata_path):
raise ValueError('Invalid file provided: %s' % patch_metadata_path)
if not patch_metadata_path.endswith('.json'):
raise ValueError('File does not end in ".json": %s' % patch_metadata_path)
def _MoveSrcTreeHEADToGitHash(src_path, git_hash):
"""Moves HEAD to 'git_hash'."""
move_head_cmd = ['git', '-C', src_path, 'checkout', git_hash]
subprocess_helpers.ExecCommandAndCaptureOutput(move_head_cmd, verbose=verbose)
def UpdatePackagesPatchMetadataFile(chroot_path, svn_version,
patch_metadata_file, packages, mode):
"""Updates the packages metadata file.
Args:
chroot_path: The absolute path to the chroot.
svn_version: The version to use for patch management.
patch_metadata_file: The patch metadta file where all the patches and
their metadata are.
packages: All the packages to update their patch metadata file.
mode: The mode for the patch manager to use when an applicable patch
fails to apply.
Ex: 'FailureModes.FAIL'
Returns:
A dictionary where the key is the package name and the value is a dictionary
that has information on the patches.
"""
# A dictionary where the key is the package name and the value is a dictionary
# that has information on the patches.
package_info = {}
llvm_hash = get_llvm_hash.LLVMHash()
with llvm_hash.CreateTempDirectory() as temp_dir:
with get_llvm_hash.CreateTempLLVMRepo(temp_dir) as src_path:
# Ensure that 'svn_version' exists in the chromiumum mirror of LLVM by
# finding its corresponding git hash.
git_hash = get_llvm_hash.GetGitHashFrom(src_path, svn_version)
# Git hash of 'svn_version' exists, so move the source tree's HEAD to
# 'git_hash' via `git checkout`.
_MoveSrcTreeHEADToGitHash(src_path, git_hash)
for cur_package in packages:
# Get the absolute path to $FILESDIR of the package.
filesdir_path = GetPathToFilesDirectory(chroot_path, cur_package)
# Construct the absolute path to the patch metadata file where all the
# patches and their metadata are.
patch_metadata_path = os.path.join(filesdir_path, patch_metadata_file)
# Make sure the patch metadata path is valid.
_CheckPatchMetadataPath(patch_metadata_path)
patch_manager.CleanSrcTree(src_path)
# Get the patch results for the current package.
patches_info = patch_manager.HandlePatches(
svn_version, patch_metadata_path, filesdir_path, src_path, mode)
package_info[cur_package] = patches_info._asdict()
return package_info
def main():
"""Updates the patch metadata file of each package if possible.
Raises:
AssertionError: The script was run inside the chroot.
"""
chroot.VerifyOutsideChroot()
args_output = GetCommandLineArgs()
# Get the google3 LLVM version if a LLVM version was not provided.
llvm_version = args_output.llvm_version
if llvm_version in ('', 'google3', 'google3-unstable'):
llvm_version = get_llvm_hash.GetGoogle3LLVMVersion(
stable=llvm_version != 'google3-unstable')
UpdatePackagesPatchMetadataFile(args_output.chroot_path, llvm_version,
args_output.patch_metadata_file,
args_output.packages,
FailureModes(args_output.failure_mode))
# Only 'disable_patches' and 'remove_patches' can potentially modify the patch
# metadata file.
if args_output.failure_mode == FailureModes.DISABLE_PATCHES.value or \
args_output.failure_mode == FailureModes.REMOVE_PATCHES.value:
print('The patch file %s has been modified for the packages:' %
args_output.patch_metadata_file)
print('\n'.join(args_output.packages))
else:
print('Applicable patches in %s applied successfully.' %
args_output.patch_metadata_file)
if __name__ == '__main__':
main()