| # Copyright 2017 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. |
| |
| """cros tryjob: Schedule a tryjob.""" |
| |
| from __future__ import print_function |
| |
| import os |
| |
| from chromite.lib import constants |
| from chromite.cli import command |
| from chromite.lib import config_lib |
| from chromite.lib import cros_build_lib |
| from chromite.lib import cros_logging as logging |
| from chromite.lib import remote_try |
| |
| from chromite.cbuildbot import trybot_patch_pool |
| |
| |
| def PrintKnownConfigs(site_config, display_all=False): |
| """Print a list of known buildbot configs. |
| |
| Args: |
| site_config: config_lib.SiteConfig containing all config info. |
| display_all: Print all configs. Otherwise, prints only configs with |
| trybot_list=True. |
| """ |
| def _GetSortKey(config_name): |
| config_dict = site_config[config_name] |
| return (not config_dict.trybot_list, config_dict.description, config_name) |
| |
| COLUMN_WIDTH = 45 |
| if not display_all: |
| print('Note: This is the common list; for all configs, use --all.') |
| print('config'.ljust(COLUMN_WIDTH), 'description') |
| print('------'.ljust(COLUMN_WIDTH), '-----------') |
| config_names = site_config.keys() |
| config_names.sort(key=_GetSortKey) |
| for name in config_names: |
| if display_all or site_config[name].trybot_list: |
| desc = site_config[name].description or '' |
| print(name.ljust(COLUMN_WIDTH), desc) |
| |
| |
| def CbuildbotArgs(options): |
| """Function to generate cbuidlbot command line args. |
| |
| This are pre-api version filtering. |
| |
| Args: |
| options: Parsed cros tryjob tryjob arguments. |
| |
| Returns: |
| List of strings in ['arg1', 'arg2'] format. |
| """ |
| args = [] |
| |
| |
| if options.remote: |
| if options.production: |
| args.append('--buildbot') |
| else: |
| args.append('--remote-trybot') |
| else: |
| args.extend(('--buildroot', options.buildroot, '--no-buildbot-tags')) |
| if not options.production: |
| args.append('--debug') |
| |
| if options.branch: |
| args.extend(('-b', options.branch)) |
| |
| for g in options.gerrit_patches: |
| args.extend(('-g', g)) |
| |
| if options.passthrough: |
| args.extend(options.passthrough) |
| |
| if options.passthrough_raw: |
| args.extend(options.passthrough_raw) |
| |
| return args |
| |
| |
| def RunLocal(options): |
| """Run a local tryjob.""" |
| if cros_build_lib.IsInsideChroot(): |
| cros_build_lib.Die('Local tryjobs cannot be started inside the chroot.') |
| |
| # Create the buildroot, if needed. |
| if not os.path.exists(options.buildroot): |
| prompt = 'Create %s as buildroot' % options.buildroot |
| if not cros_build_lib.BooleanPrompt(prompt=prompt, default=False): |
| print('Please specify a different buildroot via the --buildroot option.') |
| return 1 |
| os.makedirs(options.buildroot) |
| |
| # Define the command to run. |
| launcher = os.path.join(constants.CHROMITE_DIR, 'scripts', 'cbuildbot_launch') |
| args = CbuildbotArgs(options) # The requested build arguments. |
| cmd = ([launcher] + |
| args + |
| options.build_configs) |
| |
| # Run the tryjob. |
| result = cros_build_lib.RunCommand(cmd, debug_level=logging.CRITICAL, |
| error_code_ok=True, cwd=options.buildroot) |
| return result.returncode |
| |
| |
| def RunRemote(options, patch_pool): |
| """Schedule remote tryjobs.""" |
| logging.info('Scheduling remote tryjob(s): %s', |
| ', '.join(options.build_configs)) |
| |
| # Figure out the cbuildbot command line to pass in. |
| args = CbuildbotArgs(options) |
| |
| # TODO: Support build config "build_group" here, when it exists, but |
| # continue to use these values for branches. |
| if options.production: |
| build_group = 'production_tryjob' |
| else: |
| build_group = 'Tryjob' |
| |
| # Figure out the tryjob description. |
| description = options.remote_description |
| if description is None: |
| description = remote_try.DefaultDescription( |
| options.branch, |
| options.gerrit_patches+options.local_patches) |
| |
| print('Submitting tryjob...') |
| tryjob = remote_try.RemoteTryJob( |
| build_configs=options.build_configs, |
| build_group=build_group, |
| remote_description=description, |
| branch=options.branch, |
| pass_through_args=args, |
| local_patches=patch_pool.local_patches, |
| committer_email=options.committer_email, |
| swarming=options.swarming, |
| master_buildbucket_id='', # TODO: Add new option to populate. |
| ) |
| |
| tryjob.Submit(dryrun=False) |
| print('Tryjob submitted!') |
| print('To view your tryjobs, visit:') |
| for link in tryjob.GetTrybotWaterfallLinks(): |
| print(' %s' % link) |
| |
| |
| @command.CommandDecorator('tryjob') |
| class TryjobCommand(command.CliCommand): |
| """Schedule a tryjob.""" |
| |
| EPILOG = """ |
| Remote Examples: |
| cros tryjob -g 123 lumpy-compile-only-pre-cq |
| cros tryjob -g 123 -g 456 lumpy-compile-only-pre-cq daisy-pre-cq |
| cros tryjob -g *123 --hwtest daisy-paladin |
| cros tryjob -p chromiumos/chromite lumpy-compile-only-pre-cq |
| cros tryjob -p chromiumos/chromite:foo_branch lumpy-paladin |
| |
| Local Examples: |
| cros tryjob --local -g 123 daisy-paladin |
| cros tryjob --local --buildroot /my/cool/path -g 123 daisy-paladin |
| |
| Production Examples (danger, can break production if misused): |
| cros tryjob --production --branch release-R61-9765.B asuka-release |
| cros tryjob --production --version 9795.0.0 --channel canary lumpy-payloads |
| """ |
| |
| @classmethod |
| def AddParser(cls, parser): |
| """Adds a parser.""" |
| super(cls, TryjobCommand).AddParser(parser) |
| parser.add_argument( |
| 'build_configs', nargs='*', |
| help='One or more configs to build.') |
| parser.add_argument( |
| '-b', '--branch', default='master', |
| help='The manifest branch to test. The branch to ' |
| 'check the buildroot out to.') |
| parser.add_argument( |
| '--yes', action='store_true', default=False, |
| help='Never prompt to confirm.') |
| parser.add_argument( |
| '--production', action='store_true', default=False, |
| help='This is a production build, NOT a test build. ' |
| 'Confirm with Chrome OS deputy before use.') |
| parser.add_argument( |
| '--pass-through', dest='passthrough_raw', action='append', |
| help='Arguments to pass to cbuildbot. To be avoided.' |
| 'Confirm with Chrome OS deputy before use.') |
| |
| # Do we build locally, on on a trybot builder? |
| where_group = parser.add_argument_group( |
| 'Where', |
| description='Where do we run the tryjob?') |
| where_ex = where_group.add_mutually_exclusive_group() |
| where_ex.add_argument( |
| '--local', action='store_false', dest='remote', |
| help='Run the tryjob on your local machine.') |
| where_ex.add_argument( |
| '--remote', action='store_true', default=True, |
| help='Run the tryjob on a remote builder. (default)') |
| where_group.add_argument( |
| '--swarming', action='store_true', default=False, |
| help='Run the tryjob on a swarming builder (experimental)') |
| where_group.add_argument( |
| '-r', '--buildroot', type='path', dest='buildroot', |
| default=os.path.join(os.path.dirname(constants.SOURCE_ROOT), 'tryjob'), |
| help='Root directory to use for the local tryjob. ' |
| 'NOT the current checkout.') |
| |
| # What patches do we include in the build? |
| what_group = parser.add_argument_group( |
| 'Patch', |
| description='Which patches should be included with the tryjob?') |
| what_group.add_argument( |
| '-g', '--gerrit-patches', action='split_extend', default=[], |
| # metavar='Id1 *int_Id2...IdN', |
| help='Space-separated list of short-form Gerrit ' |
| "Change-Id's or change numbers to patch. " |
| "Please prepend '*' to internal Change-Id's") |
| what_group.add_argument( |
| '-p', '--local-patches', action='split_extend', default=[], |
| # metavar="'<project1>[:<branch1>]...<projectN>[:<branchN>]'", |
| help='Space-separated list of project branches with ' |
| 'patches to apply. Projects are specified by name. ' |
| 'If no branch is specified the current branch of the ' |
| 'project will be used.') |
| |
| # Identifing the request. |
| who_group = parser.add_argument_group( |
| 'Requestor', |
| description='Who is submitting the jobs?') |
| who_group.add_argument( |
| '--committer-email', |
| help='Override default git committer email.') |
| who_group.add_argument( |
| '--remote-description', |
| help='Attach an optional description to a --remote run ' |
| 'to make it easier to identify the results when it ' |
| 'finishes') |
| |
| # Modify the build. |
| how_group = parser.add_argument_group( |
| 'Modifiers', |
| description='How do we modify build behavior?') |
| how_group.add_argument( |
| '--latest-toolchain', dest='passthrough', action='append_option', |
| help='Use the latest toolchain.') |
| how_group.add_argument( |
| '--nochromesdk', dest='passthrough', action='append_option', |
| help="Don't run the ChromeSDK stage which builds " |
| 'Chrome outside of the chroot.') |
| how_group.add_argument( |
| '--timeout', dest='passthrough', action='append_option_value', |
| help='Specify the maximum amount of time this job ' |
| 'can run for, at which point the build will be ' |
| 'aborted. If set to zero, then there is no ' |
| 'timeout.') |
| how_group.add_argument( |
| '--sanity-check-build', dest='passthrough', action='append_option', |
| help='Run the build as a sanity check build.') |
| |
| # Overrides for the build configs testing behaviors. |
| test_group = parser.add_argument_group( |
| 'Testing Flags', |
| description='How do we change testing behavior?') |
| test_group.add_argument( |
| '--hwtest', dest='passthrough', action='append_option', |
| help='Enable hwlab testing. Default false.') |
| test_group.add_argument( |
| '--notests', dest='passthrough', action='append_option', |
| help='Override values from buildconfig, run no ' |
| 'tests, and build no autotest artifacts.') |
| test_group.add_argument( |
| '--novmtests', dest='passthrough', action='append_option', |
| help='Override values from buildconfig, run no vmtests.') |
| test_group.add_argument( |
| '--noimagetests', dest='passthrough', action='append_option', |
| help='Override values from buildconfig and run no image tests.') |
| |
| # <board>-payloads tryjob specific options. |
| payloads_group = parser.add_argument_group( |
| 'Payloads', |
| description='Options only used by payloads tryjobs.') |
| payloads_group.add_argument( |
| '--version', dest='passthrough', action='append_option_value', |
| help='Specify the release version for payload regeneration. ' |
| 'Ex: 9799.0.0') |
| payloads_group.add_argument( |
| '--channel', dest='passthrough', action='append_option_value', |
| help='Specify a channel for a payloads trybot. Can ' |
| 'be specified multiple times. No valid for ' |
| 'non-payloads configs.') |
| |
| # branch_util tryjob specific options. |
| branch_util_group = parser.add_argument_group( |
| 'branch_util', |
| description='Options only used by branch-util tryjobs.') |
| |
| branch_util_group.add_argument( |
| '--branch-name', dest='passthrough', action='append_option_value', |
| help='The branch to create or delete.') |
| branch_util_group.add_argument( |
| '--delete-branch', dest='passthrough', action='append_option', |
| help='Delete the branch specified in --branch-name.') |
| branch_util_group.add_argument( |
| '--rename-to', dest='passthrough', action='append_option_value', |
| help='Rename a branch to the specified name.') |
| branch_util_group.add_argument( |
| '--force-create', dest='passthrough', action='append_option', |
| help='Overwrites an existing branch.') |
| branch_util_group.add_argument( |
| '--skip-remote-push', dest='passthrough', action='append_option', |
| help='Do not actually push to remote git repos. ' |
| 'Used for end-to-end testing branching.') |
| |
| configs_group = parser.add_argument_group( |
| 'Configs', |
| description='Options for displaying available build configs.') |
| configs_group.add_argument( |
| '-l', '--list', action='store_true', dest='list', default=False, |
| help='List the suggested trybot configs to use (see --all)') |
| configs_group.add_argument( |
| '-a', '--all', action='store_true', dest='list_all', default=False, |
| help='List all of the buildbot configs available w/--list') |
| |
| def VerifyOptions(self): |
| """Verify that our command line options make sense.""" |
| site_config = config_lib.GetConfig() |
| |
| # Handle --list before checking that everything else is valid. |
| if self.options.list: |
| PrintKnownConfigs(site_config, self.options.list_all) |
| raise cros_build_lib.DieSystemExit(0) # Exit with success code. |
| |
| # Validate specified build_configs. |
| if not self.options.build_configs: |
| cros_build_lib.Die('At least one build_config is required.') |
| |
| if not self.options.remote and self.options.swarming: |
| cros_build_lib.Die('--swarming cannot be used with local tryjobs.') |
| |
| unknown_build_configs = [b for b in self.options.build_configs |
| if b not in site_config] |
| if unknown_build_configs and not self.options.yes: |
| prompt = ('Unknown build configs; are you sure you want to schedule ' |
| 'for %s?' % ', '.join(unknown_build_configs)) |
| if not cros_build_lib.BooleanPrompt(prompt=prompt, default=False): |
| cros_build_lib.Die('No confirmation.') |
| |
| patches_given = self.options.gerrit_patches or self.options.local_patches |
| |
| # Make sure production builds don't have patches. |
| if self.options.production: |
| if patches_given: |
| cros_build_lib.Die('Patches cannot be included in production builds.') |
| else: |
| # Ask for confirmation if there are no patches to test. |
| if not patches_given and not self.options.yes: |
| prompt = ('No patches were provided; are you sure you want to just ' |
| 'run a build of %s?' % ( |
| self.options.branch if self.options.branch else 'ToT')) |
| if not cros_build_lib.BooleanPrompt(prompt=prompt, default=False): |
| cros_build_lib.Die('No confirmation.') |
| return True |
| |
| def Run(self): |
| """Runs `cros chroot`.""" |
| self.options.Freeze() |
| self.VerifyOptions() |
| |
| # Verify gerrit patches are valid. |
| print('Verifying patches...') |
| patch_pool = trybot_patch_pool.TrybotPatchPool.FromOptions( |
| gerrit_patches=self.options.gerrit_patches, |
| local_patches=self.options.local_patches, |
| sourceroot=constants.SOURCE_ROOT, |
| remote_patches=[]) |
| |
| if self.options.remote: |
| return RunRemote(self.options, patch_pool) |
| else: |
| return RunLocal(self.options) |