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