277 lines
8.9 KiB
Python
277 lines
8.9 KiB
Python
|
#!/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()
|