1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2019 The Chromium OS Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6# 7# pylint: disable=global-statement 8 9"""Creates the arguments for the patch manager for LLVM.""" 10 11from __future__ import print_function 12 13import argparse 14import os 15 16from failure_modes import FailureModes 17import chroot 18import get_llvm_hash 19import patch_manager 20import subprocess_helpers 21 22# If set to `True`, then the contents of `stdout` after executing a command will 23# be displayed to the terminal. 24verbose = False 25 26 27def GetCommandLineArgs(): 28 """Parses the commandline for the optional commandline arguments. 29 30 Returns: 31 An argument parser object that contains all the commandline arguments. 32 """ 33 34 # Default path to the chroot if a path is not specified. 35 cros_root = os.path.expanduser('~') 36 cros_root = os.path.join(cros_root, 'chromiumos') 37 38 # Create parser and add optional command-line arguments. 39 parser = argparse.ArgumentParser(description='Patch management for packages.') 40 41 # Add argument for a specific chroot path. 42 parser.add_argument( 43 '--chroot_path', 44 type=patch_manager.is_directory, 45 default=cros_root, 46 help='the absolute path to the chroot (default: %(default)s)') 47 48 # Add argument for which packages to manage their patches. 49 parser.add_argument( 50 '--packages', 51 required=False, 52 nargs='+', 53 default=['sys-devel/llvm'], 54 help='the packages to manage their patches (default: %(default)s)') 55 56 # Add argument for whether to display command contents to `stdout`. 57 parser.add_argument( 58 '--verbose', 59 action='store_true', 60 help='display contents of a command to the terminal ' 61 '(default: %(default)s)') 62 63 # Add argument for the LLVM version to use for patch management. 64 parser.add_argument( 65 '--llvm_version', 66 type=int, 67 help='the LLVM version to use for patch management. Alternatively, you ' 68 'can pass "google3" or "google3-unstable". (Default: "google3")') 69 70 # Add argument for the mode of the patch management when handling patches. 71 parser.add_argument( 72 '--failure_mode', 73 default=FailureModes.FAIL.value, 74 choices=[FailureModes.FAIL.value, FailureModes.CONTINUE.value, 75 FailureModes.DISABLE_PATCHES.value, 76 FailureModes.REMOVE_PATCHES.value], 77 help='the mode of the patch manager when handling failed patches ' \ 78 '(default: %(default)s)') 79 80 # Add argument for the patch metadata file in $FILESDIR of LLVM. 81 parser.add_argument( 82 '--patch_metadata_file', 83 default='PATCHES.json', 84 help='the .json file in $FILESDIR that has all the patches and their ' 85 'metadata if applicable (default: %(default)s)') 86 87 # Parse the command line. 88 args_output = parser.parse_args() 89 90 global verbose 91 92 verbose = args_output.verbose 93 94 unique_packages = list(set(args_output.packages)) 95 96 # Duplicate packages were passed into the command line 97 if len(unique_packages) != len(args_output.packages): 98 raise ValueError('Duplicate packages were passed in: %s' % ' '.join( 99 args_output.packages)) 100 101 args_output.packages = unique_packages 102 103 return args_output 104 105 106def GetPathToFilesDirectory(chroot_path, package): 107 """Gets the absolute path to $FILESDIR of the package. 108 109 Args: 110 chroot_path: The absolute path to the chroot. 111 package: The package to find its absolute path to $FILESDIR. 112 113 Returns: 114 The absolute path to $FILESDIR. 115 116 Raises: 117 ValueError: An invalid chroot path has been provided. 118 """ 119 120 if not os.path.isdir(chroot_path): 121 raise ValueError('Invalid chroot provided: %s' % chroot_path) 122 123 # Get the absolute chroot path to the ebuild. 124 chroot_ebuild_path = subprocess_helpers.ChrootRunCommand( 125 chroot_path, ['equery', 'w', package], verbose=verbose) 126 127 # Get the absolute chroot path to $FILESDIR's parent directory. 128 filesdir_parent_path = os.path.dirname(chroot_ebuild_path.strip()) 129 130 # Get the relative path to $FILESDIR's parent directory. 131 rel_path = _GetRelativePathOfChrootPath(filesdir_parent_path) 132 133 # Construct the absolute path to the package's 'files' directory. 134 return os.path.join(chroot_path, rel_path, 'files/') 135 136 137def _GetRelativePathOfChrootPath(chroot_path): 138 """Gets the relative path of the chroot path passed in. 139 140 Args: 141 chroot_path: The chroot path to get its relative path. 142 143 Returns: 144 The relative path after '/mnt/host/source/'. 145 146 Raises: 147 ValueError: The prefix of 'chroot_path' did not match '/mnt/host/source/'. 148 """ 149 150 chroot_prefix = '/mnt/host/source/' 151 152 if not chroot_path.startswith(chroot_prefix): 153 raise ValueError('Invalid prefix for the chroot path: %s' % chroot_path) 154 155 return chroot_path[len(chroot_prefix):] 156 157 158def _CheckPatchMetadataPath(patch_metadata_path): 159 """Checks that the patch metadata path is valid. 160 161 Args: 162 patch_metadata_path: The absolute path to the .json file that has the 163 patches and their metadata. 164 165 Raises: 166 ValueError: The file does not exist or the file does not end in '.json'. 167 """ 168 169 if not os.path.isfile(patch_metadata_path): 170 raise ValueError('Invalid file provided: %s' % patch_metadata_path) 171 172 if not patch_metadata_path.endswith('.json'): 173 raise ValueError('File does not end in ".json": %s' % patch_metadata_path) 174 175 176def _MoveSrcTreeHEADToGitHash(src_path, git_hash): 177 """Moves HEAD to 'git_hash'.""" 178 179 move_head_cmd = ['git', '-C', src_path, 'checkout', git_hash] 180 181 subprocess_helpers.ExecCommandAndCaptureOutput(move_head_cmd, verbose=verbose) 182 183 184def UpdatePackagesPatchMetadataFile(chroot_path, svn_version, 185 patch_metadata_file, packages, mode): 186 """Updates the packages metadata file. 187 188 Args: 189 chroot_path: The absolute path to the chroot. 190 svn_version: The version to use for patch management. 191 patch_metadata_file: The patch metadta file where all the patches and 192 their metadata are. 193 packages: All the packages to update their patch metadata file. 194 mode: The mode for the patch manager to use when an applicable patch 195 fails to apply. 196 Ex: 'FailureModes.FAIL' 197 198 Returns: 199 A dictionary where the key is the package name and the value is a dictionary 200 that has information on the patches. 201 """ 202 203 # A dictionary where the key is the package name and the value is a dictionary 204 # that has information on the patches. 205 package_info = {} 206 207 llvm_hash = get_llvm_hash.LLVMHash() 208 209 with llvm_hash.CreateTempDirectory() as temp_dir: 210 with get_llvm_hash.CreateTempLLVMRepo(temp_dir) as src_path: 211 # Ensure that 'svn_version' exists in the chromiumum mirror of LLVM by 212 # finding its corresponding git hash. 213 git_hash = get_llvm_hash.GetGitHashFrom(src_path, svn_version) 214 215 # Git hash of 'svn_version' exists, so move the source tree's HEAD to 216 # 'git_hash' via `git checkout`. 217 _MoveSrcTreeHEADToGitHash(src_path, git_hash) 218 219 for cur_package in packages: 220 # Get the absolute path to $FILESDIR of the package. 221 filesdir_path = GetPathToFilesDirectory(chroot_path, cur_package) 222 223 # Construct the absolute path to the patch metadata file where all the 224 # patches and their metadata are. 225 patch_metadata_path = os.path.join(filesdir_path, patch_metadata_file) 226 227 # Make sure the patch metadata path is valid. 228 _CheckPatchMetadataPath(patch_metadata_path) 229 230 patch_manager.CleanSrcTree(src_path) 231 232 # Get the patch results for the current package. 233 patches_info = patch_manager.HandlePatches( 234 svn_version, patch_metadata_path, filesdir_path, src_path, mode) 235 236 package_info[cur_package] = patches_info._asdict() 237 238 return package_info 239 240 241def main(): 242 """Updates the patch metadata file of each package if possible. 243 244 Raises: 245 AssertionError: The script was run inside the chroot. 246 """ 247 248 chroot.VerifyOutsideChroot() 249 250 args_output = GetCommandLineArgs() 251 252 # Get the google3 LLVM version if a LLVM version was not provided. 253 llvm_version = args_output.llvm_version 254 if llvm_version in ('', 'google3', 'google3-unstable'): 255 llvm_version = get_llvm_hash.GetGoogle3LLVMVersion( 256 stable=llvm_version != 'google3-unstable') 257 258 UpdatePackagesPatchMetadataFile(args_output.chroot_path, llvm_version, 259 args_output.patch_metadata_file, 260 args_output.packages, 261 FailureModes(args_output.failure_mode)) 262 263 # Only 'disable_patches' and 'remove_patches' can potentially modify the patch 264 # metadata file. 265 if args_output.failure_mode == FailureModes.DISABLE_PATCHES.value or \ 266 args_output.failure_mode == FailureModes.REMOVE_PATCHES.value: 267 print('The patch file %s has been modified for the packages:' % 268 args_output.patch_metadata_file) 269 print('\n'.join(args_output.packages)) 270 else: 271 print('Applicable patches in %s applied successfully.' % 272 args_output.patch_metadata_file) 273 274 275if __name__ == '__main__': 276 main() 277