aboutsummaryrefslogtreecommitdiff
path: root/llvm_tools/update_packages_and_run_tests.py
diff options
context:
space:
mode:
Diffstat (limited to 'llvm_tools/update_packages_and_run_tests.py')
-rwxr-xr-xllvm_tools/update_packages_and_run_tests.py394
1 files changed, 394 insertions, 0 deletions
diff --git a/llvm_tools/update_packages_and_run_tests.py b/llvm_tools/update_packages_and_run_tests.py
new file mode 100755
index 00000000..ee57ea09
--- /dev/null
+++ b/llvm_tools/update_packages_and_run_tests.py
@@ -0,0 +1,394 @@
+#!/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.
+
+"""Runs a tryjob/tryjobs after updating the packages."""
+
+from __future__ import print_function
+
+import argparse
+import datetime
+import json
+import os
+
+from assert_not_in_chroot import VerifyOutsideChroot
+from failure_modes import FailureModes
+from get_llvm_hash import GetLLVMHashAndVersionFromSVNOption
+from get_llvm_hash import is_svn_option
+from subprocess_helpers import ChrootRunCommand
+from subprocess_helpers import ExecCommandAndCaptureOutput
+import update_chromeos_llvm_hash
+
+
+def GetCommandLineArgs():
+ """Parses the command line for the command line arguments.
+
+ Returns:
+ The log level to use when retrieving the LLVM hash or google3 LLVM version,
+ the chroot path to use for executing chroot commands,
+ a list of a package or packages to update their LLVM next hash,
+ and the LLVM version to use when retrieving the LLVM hash.
+ """
+
+ # 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='Update an LLVM hash of packages and run tests.')
+
+ # Add argument for other change lists that want to run alongside the tryjob
+ # which has a change list of updating a package's git hash.
+ parser.add_argument(
+ '--extra_change_lists',
+ type=int,
+ nargs='+',
+ default=[],
+ help='change lists that would like to be run alongside the change list '
+ 'of updating the packages')
+
+ # 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 to choose between llvm and llvm-next.
+ parser.add_argument(
+ '--is_llvm_next',
+ action='store_true',
+ help='which llvm hash to update. Update LLVM_NEXT_HASH if specified. '
+ 'Otherwise, update LLVM_HASH')
+
+ # Add argument for the absolute path to the file that contains information on
+ # the previous tested svn version.
+ parser.add_argument(
+ '--last_tested',
+ help='the absolute path to the file that contains the last tested '
+ 'arguments.')
+
+ # Add argument for the LLVM version to use.
+ parser.add_argument(
+ '--llvm_version',
+ type=is_svn_option,
+ required=True,
+ help='which git hash of LLVM to find '
+ '{google3, ToT, <svn_version>} '
+ '(default: finds the git hash of the google3 LLVM '
+ 'version)')
+
+ # Add argument to add reviewers for the created CL.
+ parser.add_argument(
+ '--reviewers',
+ nargs='+',
+ default=[],
+ help='The reviewers for the package update changelist')
+
+ # 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)')
+
+ subparsers = parser.add_subparsers(dest='subparser_name')
+ # Testing with the tryjobs.
+ tryjob_subparser = subparsers.add_parser('tryjobs')
+ tryjob_subparser.add_argument(
+ '--builders',
+ required=True,
+ nargs='+',
+ default=[],
+ help='builders to use for the tryjob testing')
+
+ # Add argument for custom options for the tryjob.
+ tryjob_subparser.add_argument(
+ '--options',
+ required=False,
+ nargs='+',
+ default=[],
+ help='options to use for the tryjob testing')
+
+ # Testing with CQ.
+ subparsers.add_parser('cq')
+
+ args_output = parser.parse_args()
+
+ subparser_names = ['tryjobs', 'cq']
+ if args_output.subparser_name not in subparser_names:
+ parser.error('one of %s must be specified' % subparser_names)
+
+ return args_output
+
+
+def UnchangedSinceLastRun(last_tested_file, arg_dict):
+ """Gets the arguments used for last run
+
+ Args:
+ last_tested_file: The absolute path to the file that contains the
+ arguments for the last run.
+ arg_dict: The arguments used for this run.
+
+ Returns:
+ Return true if the arguments used for last run exist and are the
+ same as the arguments used for this run. Otherwise return false.
+ """
+
+ if not last_tested_file:
+ return False
+
+ # Get the last tested svn version if the file exists.
+ last_arg_dict = None
+ try:
+ with open(last_tested_file) as f:
+ last_arg_dict = json.load(f)
+
+ except (IOError, ValueError):
+ return False
+
+ return arg_dict == last_arg_dict
+
+
+def AddReviewers(cl, reviewers, chroot_path):
+ """Add reviewers for the created CL."""
+
+ gerrit_abs_path = os.path.join(chroot_path, 'chromite/bin/gerrit')
+ for reviewer in reviewers:
+ cmd = [gerrit_abs_path, 'reviewers', str(cl), reviewer]
+
+ ExecCommandAndCaptureOutput(cmd)
+
+
+def AddTryjobLinkToCL(tryjobs, cl, chroot_path):
+ """Adds the tryjob link(s) to the CL as a comment."""
+
+ # NOTE: Invoking `cros_sdk` does not make each tryjob link appear on its own
+ # line, so invoking the `gerrit` command directly instead of using `cros_sdk`
+ # to do it for us.
+ #
+ # FIXME: Need to figure out why `cros_sdk` does not add each tryjob link as a
+ # newline.
+ gerrit_abs_path = os.path.join(chroot_path, 'chromite/bin/gerrit')
+
+ tryjob_links = ['Started the following tryjobs:']
+ tryjob_links.extend(tryjob['link'] for tryjob in tryjobs)
+
+ add_message_cmd = [
+ gerrit_abs_path, 'message',
+ str(cl), '\n'.join(tryjob_links)
+ ]
+
+ ExecCommandAndCaptureOutput(add_message_cmd)
+
+
+# Testing with tryjobs
+def GetCurrentTimeInUTC():
+ """Returns the current time via `datetime.datetime.utcnow()`."""
+ return datetime.datetime.utcnow()
+
+
+def GetTryJobCommand(change_list, extra_change_lists, options, builder):
+ """Constructs the 'tryjob' command.
+
+ Args:
+ change_list: The CL obtained from updating the packages.
+ extra_change_lists: Extra change lists that would like to be run alongside
+ the change list of updating the packages.
+ options: Options to be passed into the tryjob command.
+ builder: The builder to be passed into the tryjob command.
+
+ Returns:
+ The 'tryjob' command with the change list of updating the packages and
+ any extra information that was passed into the command line.
+ """
+
+ tryjob_cmd = ['cros', 'tryjob', '--yes', '--json', '-g', '%d' % change_list]
+
+ if extra_change_lists:
+ for extra_cl in extra_change_lists:
+ tryjob_cmd.extend(['-g', '%d' % extra_cl])
+
+ tryjob_cmd.append(builder)
+
+ if options:
+ tryjob_cmd.extend('--%s' % option for option in options)
+
+ return tryjob_cmd
+
+
+def RunTryJobs(cl_number, extra_change_lists, options, builders, chroot_path,
+ verbose):
+ """Runs a tryjob/tryjobs.
+
+ Args:
+ cl_number: The CL created by updating the packages.
+ extra_change_lists: Any extra change lists that would run alongside the CL
+ that was created by updating the packages ('cl_number').
+ options: Any options to be passed into the 'tryjob' command.
+ builders: All the builders to run the 'tryjob' with.
+ chroot_path: The absolute path to the chroot.
+ verbose: Print command contents to `stdout`.
+
+ Returns:
+ A list that contains stdout contents of each tryjob, where stdout is
+ information (a hashmap) about the tryjob. The hashmap also contains stderr
+ if there was an error when running a tryjob.
+
+ Raises:
+ ValueError: Failed to submit a tryjob.
+ """
+
+ # Contains the results of each tryjob. The results are retrieved from 'out'
+ # which is stdout of the command executer.
+ tryjob_results = []
+
+ # For each builder passed into the command line:
+ #
+ # Run a tryjob with the change list number obtained from updating the
+ # packages and append additional changes lists and options obtained from the
+ # command line.
+ for cur_builder in builders:
+ tryjob_cmd = GetTryJobCommand(cl_number, extra_change_lists, options,
+ cur_builder)
+
+ out = ChrootRunCommand(chroot_path, tryjob_cmd, verbose=verbose)
+
+ tryjob_launch_time = GetCurrentTimeInUTC()
+
+ tryjob_contents = json.loads(out)
+
+ buildbucket_id = int(tryjob_contents[0]['buildbucket_id'])
+
+ new_tryjob = {
+ 'launch_time': str(tryjob_launch_time),
+ 'link': str(tryjob_contents[0]['url']),
+ 'buildbucket_id': buildbucket_id,
+ 'extra_cls': extra_change_lists,
+ 'options': options,
+ 'builder': [cur_builder]
+ }
+
+ tryjob_results.append(new_tryjob)
+
+ AddTryjobLinkToCL(tryjob_results, cl_number, chroot_path)
+
+ return tryjob_results
+
+
+# Testing with CQ
+def GetCQDependString(dependent_cls):
+ """Get CQ dependency string e.g. `Cq-Depend: chromium:MM, chromium:NN`."""
+
+ if not dependent_cls:
+ return None
+
+ # Cq-Depend must start a new paragraph prefixed with "Cq-Depend".
+ return '\nCq-Depend: ' + ', '.join(('chromium:%s' % i) for i in dependent_cls)
+
+
+def StartCQDryRun(cl, dependent_cls, chroot_path):
+ """Start CQ dry run for the changelist and dependencies."""
+
+ gerrit_abs_path = os.path.join(chroot_path, 'chromite/bin/gerrit')
+
+ cl_list = [cl]
+ cl_list.extend(dependent_cls)
+
+ for changes in cl_list:
+ cq_dry_run_cmd = [gerrit_abs_path, 'label-cq', str(changes), '1']
+
+ ExecCommandAndCaptureOutput(cq_dry_run_cmd)
+
+
+def main():
+ """Updates the packages' LLVM hash and run tests.
+
+ Raises:
+ AssertionError: The script was run inside the chroot.
+ """
+
+ VerifyOutsideChroot()
+
+ args_output = GetCommandLineArgs()
+
+ update_packages = [
+ 'sys-devel/llvm', 'sys-libs/compiler-rt', 'sys-libs/libcxx',
+ 'sys-libs/libcxxabi', 'sys-libs/llvm-libunwind'
+ ]
+
+ patch_metadata_file = 'PATCHES.json'
+
+ svn_option = args_output.llvm_version
+
+ git_hash, svn_version = GetLLVMHashAndVersionFromSVNOption(svn_option)
+
+ # There is no need to run tryjobs when all the key parameters remain unchanged
+ # from last time.
+
+ # If --last_tested is specified, check if the current run has the same
+ # arguments last time --last_tested is used.
+ if args_output.last_tested:
+ chroot_file_paths = update_chromeos_llvm_hash.GetChrootBuildPaths(
+ args_output.chroot_path, update_packages)
+ arg_dict = {
+ 'svn_version': svn_version,
+ 'ebuilds': chroot_file_paths,
+ 'extra_cls': args_output.extra_change_lists,
+ }
+ if args_output.subparser_name == 'tryjobs':
+ arg_dict['builders'] = args_output.builders
+ arg_dict['tryjob_options'] = args_output.options
+ if UnchangedSinceLastRun(args_output.last_tested, arg_dict):
+ print('svn version (%d) matches the last tested svn version in %s' %
+ (svn_version, args_output.last_tested))
+ return
+
+ llvm_variant = update_chromeos_llvm_hash.LLVMVariant.current
+ if args_output.is_llvm_next:
+ llvm_variant = update_chromeos_llvm_hash.LLVMVariant.next
+ update_chromeos_llvm_hash.verbose = args_output.verbose
+ extra_commit_msg = None
+ if args_output.subparser_name == 'cq':
+ extra_commit_msg = GetCQDependString(args_output.extra_change_lists)
+
+ change_list = update_chromeos_llvm_hash.UpdatePackages(
+ update_packages,
+ llvm_variant,
+ git_hash,
+ svn_version,
+ args_output.chroot_path,
+ patch_metadata_file,
+ FailureModes.DISABLE_PATCHES,
+ svn_option,
+ extra_commit_msg=extra_commit_msg)
+
+ AddReviewers(change_list.cl_number, args_output.reviewers,
+ args_output.chroot_path)
+
+ print('Successfully updated packages to %d' % svn_version)
+ print('Gerrit URL: %s' % change_list.url)
+ print('Change list number: %d' % change_list.cl_number)
+
+ if args_output.subparser_name == 'tryjobs':
+ tryjob_results = RunTryJobs(change_list.cl_number,
+ args_output.extra_change_lists,
+ args_output.options, args_output.builders,
+ args_output.chroot_path, args_output.verbose)
+ print('Tryjobs:')
+ for tryjob in tryjob_results:
+ print(tryjob)
+ else:
+ StartCQDryRun(change_list.cl_number, args_output.extra_change_lists,
+ args_output.chroot_path)
+
+ # If --last_tested is specified, record the arguments used
+ if args_output.last_tested:
+ with open(args_output.last_tested, 'w') as f:
+ json.dump(arg_dict, f, indent=2)
+
+
+if __name__ == '__main__':
+ main()