#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright 2019 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Modifies a tryjob based off of arguments.""" import argparse import enum import json import os import sys import chroot import failure_modes import get_llvm_hash import update_chromeos_llvm_hash import update_packages_and_run_tests import update_tryjob_status class ModifyTryjob(enum.Enum): """Options to modify a tryjob.""" REMOVE = "remove" RELAUNCH = "relaunch" ADD = "add" def GetCommandLineArgs(): """Parses the command line for the command line 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="Removes, relaunches, or adds a tryjob." ) # Add argument for the JSON file to use for the update of a tryjob. parser.add_argument( "--status_file", required=True, help="The absolute path to the JSON file that contains the tryjobs used " "for bisecting LLVM.", ) # Add argument that determines what action to take on the revision specified. parser.add_argument( "--modify_tryjob", required=True, choices=[modify_tryjob.value for modify_tryjob in ModifyTryjob], help="What action to perform on the tryjob.", ) # Add argument that determines which revision to search for in the list of # tryjobs. parser.add_argument( "--revision", required=True, type=int, help="The revision to either remove or relaunch.", ) # Add argument for other change lists that want to run alongside the tryjob. parser.add_argument( "--extra_change_lists", type=int, nargs="+", help="change lists that would like to be run alongside the change list " "of updating the packages", ) # Add argument for custom options for the tryjob. parser.add_argument( "--options", required=False, nargs="+", help="options to use for the tryjob testing", ) # Add argument for the builder to use for the tryjob. parser.add_argument( "--builder", help="builder to use for the tryjob testing" ) # Add argument for a specific chroot path. parser.add_argument( "--chroot_path", default=cros_root, help="the path to the chroot (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)", ) args_output = parser.parse_args() if not os.path.isfile( args_output.status_file ) or not args_output.status_file.endswith(".json"): raise ValueError( 'File does not exist or does not ending in ".json" ' ": %s" % args_output.status_file ) if ( args_output.modify_tryjob == ModifyTryjob.ADD.value and not args_output.builder ): raise ValueError("A builder is required for adding a tryjob.") elif ( args_output.modify_tryjob != ModifyTryjob.ADD.value and args_output.builder ): raise ValueError( "Specifying a builder is only available when adding a " "tryjob." ) return args_output def GetCLAfterUpdatingPackages( packages, git_hash, svn_version, chroot_path, patch_metadata_file, svn_option, ): """Updates the packages' LLVM_NEXT.""" change_list = update_chromeos_llvm_hash.UpdatePackages( packages=packages, manifest_packages=[], llvm_variant=update_chromeos_llvm_hash.LLVMVariant.next, git_hash=git_hash, svn_version=svn_version, chroot_path=chroot_path, mode=failure_modes.FailureModes.DISABLE_PATCHES, git_hash_source=svn_option, extra_commit_msg=None, ) print("\nSuccessfully updated packages to %d" % svn_version) print("Gerrit URL: %s" % change_list.url) print("Change list number: %d" % change_list.cl_number) return change_list def CreateNewTryjobEntryForBisection( cl, extra_cls, options, builder, chroot_path, cl_url, revision ): """Submits a tryjob and adds additional information.""" # Get the tryjob results after submitting the tryjob. # Format of 'tryjob_results': # [ # { # 'link' : [TRYJOB_LINK], # 'buildbucket_id' : [BUILDBUCKET_ID], # 'extra_cls' : [EXTRA_CLS_LIST], # 'options' : [EXTRA_OPTIONS_LIST], # 'builder' : [BUILDER_AS_A_LIST] # } # ] tryjob_results = update_packages_and_run_tests.RunTryJobs( cl, extra_cls, options, [builder], chroot_path ) print("\nTryjob:") print(tryjob_results[0]) # Add necessary information about the tryjob. tryjob_results[0]["url"] = cl_url tryjob_results[0]["rev"] = revision tryjob_results[0][ "status" ] = update_tryjob_status.TryjobStatus.PENDING.value tryjob_results[0]["cl"] = cl return tryjob_results[0] def AddTryjob( packages, git_hash, revision, chroot_path, patch_metadata_file, extra_cls, options, builder, verbose, svn_option, ): """Submits a tryjob.""" update_chromeos_llvm_hash.verbose = verbose change_list = GetCLAfterUpdatingPackages( packages, git_hash, revision, chroot_path, patch_metadata_file, svn_option, ) tryjob_dict = CreateNewTryjobEntryForBisection( change_list.cl_number, extra_cls, options, builder, chroot_path, change_list.url, revision, ) return tryjob_dict def PerformTryjobModification( revision, modify_tryjob, status_file, extra_cls, options, builder, chroot_path, verbose, ): """Removes, relaunches, or adds a tryjob. Args: revision: The revision associated with the tryjob. modify_tryjob: What action to take on the tryjob. Ex: ModifyTryjob.REMOVE, ModifyTryjob.RELAUNCH, ModifyTryjob.ADD status_file: The .JSON file that contains the tryjobs. extra_cls: Extra change lists to be run alongside tryjob options: Extra options to pass into 'cros tryjob'. builder: The builder to use for 'cros tryjob'. chroot_path: The absolute path to the chroot (used by 'cros tryjob' when relaunching a tryjob). verbose: Determines whether to print the contents of a command to `stdout`. """ # Format of 'bisect_contents': # { # 'start': [START_REVISION_OF_BISECTION] # 'end': [END_REVISION_OF_BISECTION] # 'jobs' : [ # {[TRYJOB_INFORMATION]}, # {[TRYJOB_INFORMATION]}, # ..., # {[TRYJOB_INFORMATION]} # ] # } with open(status_file) as tryjobs: bisect_contents = json.load(tryjobs) if not bisect_contents["jobs"] and modify_tryjob != ModifyTryjob.ADD: sys.exit("No tryjobs in %s" % status_file) tryjob_index = update_tryjob_status.FindTryjobIndex( revision, bisect_contents["jobs"] ) # 'FindTryjobIndex()' returns None if the tryjob was not found. if tryjob_index is None and modify_tryjob != ModifyTryjob.ADD: raise ValueError( "Unable to find tryjob for %d in %s" % (revision, status_file) ) # Determine the action to take based off of 'modify_tryjob'. if modify_tryjob == ModifyTryjob.REMOVE: del bisect_contents["jobs"][tryjob_index] print("Successfully deleted the tryjob of revision %d" % revision) elif modify_tryjob == ModifyTryjob.RELAUNCH: # Need to update the tryjob link and buildbucket ID. tryjob_results = update_packages_and_run_tests.RunTryJobs( bisect_contents["jobs"][tryjob_index]["cl"], bisect_contents["jobs"][tryjob_index]["extra_cls"], bisect_contents["jobs"][tryjob_index]["options"], bisect_contents["jobs"][tryjob_index]["builder"], chroot_path, ) bisect_contents["jobs"][tryjob_index][ "status" ] = update_tryjob_status.TryjobStatus.PENDING.value bisect_contents["jobs"][tryjob_index]["link"] = tryjob_results[0][ "link" ] bisect_contents["jobs"][tryjob_index][ "buildbucket_id" ] = tryjob_results[0]["buildbucket_id"] print( "Successfully relaunched the tryjob for revision %d and updated " "the tryjob link to %s" % (revision, tryjob_results[0]["link"]) ) elif modify_tryjob == ModifyTryjob.ADD: # Tryjob exists already. if tryjob_index is not None: raise ValueError( "Tryjob already exists (index is %d) in %s." % (tryjob_index, status_file) ) # Make sure the revision is within the bounds of the start and end of the # bisection. elif bisect_contents["start"] < revision < bisect_contents["end"]: patch_metadata_file = "PATCHES.json" ( git_hash, revision, ) = get_llvm_hash.GetLLVMHashAndVersionFromSVNOption(revision) tryjob_dict = AddTryjob( update_chromeos_llvm_hash.DEFAULT_PACKAGES, git_hash, revision, chroot_path, patch_metadata_file, extra_cls, options, builder, verbose, revision, ) bisect_contents["jobs"].append(tryjob_dict) print("Successfully added tryjob of revision %d" % revision) else: raise ValueError("Failed to add tryjob to %s" % status_file) else: raise ValueError( 'Invalid "modify_tryjob" option provided: %s' % modify_tryjob ) with open(status_file, "w") as update_tryjobs: json.dump( bisect_contents, update_tryjobs, indent=4, separators=(",", ": ") ) def main(): """Removes, relaunches, or adds a tryjob.""" chroot.VerifyOutsideChroot() args_output = GetCommandLineArgs() PerformTryjobModification( args_output.revision, ModifyTryjob(args_output.modify_tryjob), args_output.status_file, args_output.extra_change_lists, args_output.options, args_output.builder, args_output.chroot_path, args_output.verbose, ) if __name__ == "__main__": main()