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"""Modifies a tryjob based off of arguments.""" 8 9from __future__ import print_function 10 11import argparse 12import enum 13import json 14import os 15import sys 16 17import chroot 18import failure_modes 19import get_llvm_hash 20import update_packages_and_run_tests 21import update_tryjob_status 22import update_chromeos_llvm_hash 23 24 25class ModifyTryjob(enum.Enum): 26 """Options to modify a tryjob.""" 27 28 REMOVE = 'remove' 29 RELAUNCH = 'relaunch' 30 ADD = 'add' 31 32 33def GetCommandLineArgs(): 34 """Parses the command line for the command line arguments.""" 35 36 # Default path to the chroot if a path is not specified. 37 cros_root = os.path.expanduser('~') 38 cros_root = os.path.join(cros_root, 'chromiumos') 39 40 # Create parser and add optional command-line arguments. 41 parser = argparse.ArgumentParser( 42 description='Removes, relaunches, or adds a tryjob.') 43 44 # Add argument for the JSON file to use for the update of a tryjob. 45 parser.add_argument( 46 '--status_file', 47 required=True, 48 help='The absolute path to the JSON file that contains the tryjobs used ' 49 'for bisecting LLVM.') 50 51 # Add argument that determines what action to take on the revision specified. 52 parser.add_argument( 53 '--modify_tryjob', 54 required=True, 55 choices=[modify_tryjob.value for modify_tryjob in ModifyTryjob], 56 help='What action to perform on the tryjob.') 57 58 # Add argument that determines which revision to search for in the list of 59 # tryjobs. 60 parser.add_argument( 61 '--revision', 62 required=True, 63 type=int, 64 help='The revision to either remove or relaunch.') 65 66 # Add argument for other change lists that want to run alongside the tryjob. 67 parser.add_argument( 68 '--extra_change_lists', 69 type=int, 70 nargs='+', 71 help='change lists that would like to be run alongside the change list ' 72 'of updating the packages') 73 74 # Add argument for custom options for the tryjob. 75 parser.add_argument( 76 '--options', 77 required=False, 78 nargs='+', 79 help='options to use for the tryjob testing') 80 81 # Add argument for the builder to use for the tryjob. 82 parser.add_argument('--builder', help='builder to use for the tryjob testing') 83 84 # Add argument for a specific chroot path. 85 parser.add_argument( 86 '--chroot_path', 87 default=cros_root, 88 help='the path to the chroot (default: %(default)s)') 89 90 # Add argument for whether to display command contents to `stdout`. 91 parser.add_argument( 92 '--verbose', 93 action='store_true', 94 help='display contents of a command to the terminal ' 95 '(default: %(default)s)') 96 97 args_output = parser.parse_args() 98 99 if not os.path.isfile(args_output.status_file) or \ 100 not args_output.status_file.endswith('.json'): 101 raise ValueError('File does not exist or does not ending in ".json" ' 102 ': %s' % args_output.status_file) 103 104 if args_output.modify_tryjob == ModifyTryjob.ADD.value and \ 105 not args_output.builder: 106 raise ValueError('A builder is required for adding a tryjob.') 107 elif args_output.modify_tryjob != ModifyTryjob.ADD.value and \ 108 args_output.builder: 109 raise ValueError('Specifying a builder is only available when adding a ' 110 'tryjob.') 111 112 return args_output 113 114 115def GetCLAfterUpdatingPackages(packages, git_hash, svn_version, chroot_path, 116 patch_metadata_file, svn_option): 117 """Updates the packages' LLVM_NEXT.""" 118 119 change_list = update_chromeos_llvm_hash.UpdatePackages( 120 packages, 121 update_chromeos_llvm_hash.LLVMVariant.next, 122 git_hash, 123 svn_version, 124 chroot_path, 125 patch_metadata_file, 126 failure_modes.FailureModes.DISABLE_PATCHES, 127 svn_option, 128 extra_commit_msg=None) 129 130 print('\nSuccessfully updated packages to %d' % svn_version) 131 print('Gerrit URL: %s' % change_list.url) 132 print('Change list number: %d' % change_list.cl_number) 133 134 return change_list 135 136 137def CreateNewTryjobEntryForBisection(cl, extra_cls, options, builder, 138 chroot_path, cl_url, revision): 139 """Submits a tryjob and adds additional information.""" 140 141 # Get the tryjob results after submitting the tryjob. 142 # Format of 'tryjob_results': 143 # [ 144 # { 145 # 'link' : [TRYJOB_LINK], 146 # 'buildbucket_id' : [BUILDBUCKET_ID], 147 # 'extra_cls' : [EXTRA_CLS_LIST], 148 # 'options' : [EXTRA_OPTIONS_LIST], 149 # 'builder' : [BUILDER_AS_A_LIST] 150 # } 151 # ] 152 tryjob_results = update_packages_and_run_tests.RunTryJobs( 153 cl, extra_cls, options, [builder], chroot_path) 154 print('\nTryjob:') 155 print(tryjob_results[0]) 156 157 # Add necessary information about the tryjob. 158 tryjob_results[0]['url'] = cl_url 159 tryjob_results[0]['rev'] = revision 160 tryjob_results[0]['status'] = update_tryjob_status.TryjobStatus.PENDING.value 161 tryjob_results[0]['cl'] = cl 162 163 return tryjob_results[0] 164 165 166def AddTryjob(packages, git_hash, revision, chroot_path, patch_metadata_file, 167 extra_cls, options, builder, verbose, svn_option): 168 """Submits a tryjob.""" 169 170 update_chromeos_llvm_hash.verbose = verbose 171 172 change_list = GetCLAfterUpdatingPackages(packages, git_hash, revision, 173 chroot_path, patch_metadata_file, 174 svn_option) 175 176 tryjob_dict = CreateNewTryjobEntryForBisection(change_list.cl_number, 177 extra_cls, options, builder, 178 chroot_path, change_list.url, 179 revision) 180 181 return tryjob_dict 182 183 184def PerformTryjobModification(revision, modify_tryjob, status_file, extra_cls, 185 options, builder, chroot_path, verbose): 186 """Removes, relaunches, or adds a tryjob. 187 188 Args: 189 revision: The revision associated with the tryjob. 190 modify_tryjob: What action to take on the tryjob. 191 Ex: ModifyTryjob.REMOVE, ModifyTryjob.RELAUNCH, ModifyTryjob.ADD 192 status_file: The .JSON file that contains the tryjobs. 193 extra_cls: Extra change lists to be run alongside tryjob 194 options: Extra options to pass into 'cros tryjob'. 195 builder: The builder to use for 'cros tryjob'. 196 chroot_path: The absolute path to the chroot (used by 'cros tryjob' when 197 relaunching a tryjob). 198 verbose: Determines whether to print the contents of a command to `stdout`. 199 """ 200 201 # Format of 'bisect_contents': 202 # { 203 # 'start': [START_REVISION_OF_BISECTION] 204 # 'end': [END_REVISION_OF_BISECTION] 205 # 'jobs' : [ 206 # {[TRYJOB_INFORMATION]}, 207 # {[TRYJOB_INFORMATION]}, 208 # ..., 209 # {[TRYJOB_INFORMATION]} 210 # ] 211 # } 212 with open(status_file) as tryjobs: 213 bisect_contents = json.load(tryjobs) 214 215 if not bisect_contents['jobs'] and modify_tryjob != ModifyTryjob.ADD: 216 sys.exit('No tryjobs in %s' % status_file) 217 218 tryjob_index = update_tryjob_status.FindTryjobIndex(revision, 219 bisect_contents['jobs']) 220 221 # 'FindTryjobIndex()' returns None if the tryjob was not found. 222 if tryjob_index is None and modify_tryjob != ModifyTryjob.ADD: 223 raise ValueError('Unable to find tryjob for %d in %s' % 224 (revision, status_file)) 225 226 # Determine the action to take based off of 'modify_tryjob'. 227 if modify_tryjob == ModifyTryjob.REMOVE: 228 del bisect_contents['jobs'][tryjob_index] 229 230 print('Successfully deleted the tryjob of revision %d' % revision) 231 elif modify_tryjob == ModifyTryjob.RELAUNCH: 232 # Need to update the tryjob link and buildbucket ID. 233 tryjob_results = update_packages_and_run_tests.RunTryJobs( 234 bisect_contents['jobs'][tryjob_index]['cl'], 235 bisect_contents['jobs'][tryjob_index]['extra_cls'], 236 bisect_contents['jobs'][tryjob_index]['options'], 237 bisect_contents['jobs'][tryjob_index]['builder'], chroot_path, verbose) 238 239 bisect_contents['jobs'][tryjob_index][ 240 'status'] = update_tryjob_status.TryjobStatus.PENDING.value 241 bisect_contents['jobs'][tryjob_index]['link'] = tryjob_results[0]['link'] 242 bisect_contents['jobs'][tryjob_index]['buildbucket_id'] = tryjob_results[0][ 243 'buildbucket_id'] 244 245 print('Successfully relaunched the tryjob for revision %d and updated ' 246 'the tryjob link to %s' % (revision, tryjob_results[0]['link'])) 247 elif modify_tryjob == ModifyTryjob.ADD: 248 # Tryjob exists already. 249 if tryjob_index is not None: 250 raise ValueError('Tryjob already exists (index is %d) in %s.' % 251 (tryjob_index, status_file)) 252 253 # Make sure the revision is within the bounds of the start and end of the 254 # bisection. 255 elif bisect_contents['start'] < revision < bisect_contents['end']: 256 update_packages = [ 257 'sys-devel/llvm', 'sys-libs/compiler-rt', 'sys-libs/libcxx', 258 'sys-libs/libcxxabi', 'sys-libs/llvm-libunwind' 259 ] 260 261 patch_metadata_file = 'PATCHES.json' 262 263 git_hash, revision = get_llvm_hash.GetLLVMHashAndVersionFromSVNOption( 264 revision) 265 266 tryjob_dict = AddTryjob(update_packages, git_hash, revision, chroot_path, 267 patch_metadata_file, extra_cls, options, builder, 268 verbose, revision) 269 270 bisect_contents['jobs'].append(tryjob_dict) 271 272 print('Successfully added tryjob of revision %d' % revision) 273 else: 274 raise ValueError('Failed to add tryjob to %s' % status_file) 275 else: 276 raise ValueError('Invalid "modify_tryjob" option provided: %s' % 277 modify_tryjob) 278 279 with open(status_file, 'w') as update_tryjobs: 280 json.dump(bisect_contents, update_tryjobs, indent=4, separators=(',', ': ')) 281 282 283def main(): 284 """Removes, relaunches, or adds a tryjob.""" 285 286 chroot.VerifyOutsideChroot() 287 288 args_output = GetCommandLineArgs() 289 290 PerformTryjobModification(args_output.revision, 291 ModifyTryjob(args_output.modify_tryjob), 292 args_output.status_file, 293 args_output.extra_change_lists, args_output.options, 294 args_output.builder, args_output.chroot_path, 295 args_output.verbose) 296 297 298if __name__ == '__main__': 299 main() 300