Index: git_cl.py |
diff --git a/git_cl.py b/git_cl.py |
index c130f3d770a003dc0bfc3404805a428b86bb2a4c..09132a1b5be5a61cff2c5e03af977066cd640a7d 100755 |
--- a/git_cl.py |
+++ b/git_cl.py |
@@ -340,8 +340,126 @@ def _buildbucket_retry(operation_name, http, *args, **kwargs): |
assert False, 'unreachable' |
+def _get_bucket_map(changelist, options, option_parser): |
+ """Returns a dict mapping bucket names (or master names) to |
+ builders and tests, for triggering try jobs. |
+ """ |
+ if not options.bot: |
+ change = changelist.GetChange( |
+ changelist.GetCommonAncestorWithUpstream(), None) |
+ |
+ # Get try masters from PRESUBMIT.py files. |
+ masters = presubmit_support.DoGetTryMasters( |
+ change=change, |
+ changed_files=change.LocalPaths(), |
+ repository_root=settings.GetRoot(), |
+ default_presubmit=None, |
+ project=None, |
+ verbose=options.verbose, |
+ output_stream=sys.stdout) |
+ |
+ if masters: |
+ return masters |
nodir
2016/11/04 21:49:40
this returns masters, not buckets. Caused https://
nodir
2016/11/04 21:55:42
https://codereview.chromium.org/2482523002
|
+ |
+ # Fall back to deprecated method: get try slaves from PRESUBMIT.py |
+ # files. |
+ options.bot = presubmit_support.DoGetTrySlaves( |
+ change=change, |
+ changed_files=change.LocalPaths(), |
+ repository_root=settings.GetRoot(), |
+ default_presubmit=None, |
+ project=None, |
+ verbose=options.verbose, |
+ output_stream=sys.stdout) |
+ |
+ if not options.bot: |
+ return {} |
+ |
+ if options.bucket: |
+ return {options.bucket: {b: [] for b in options.bot}} |
+ |
+ builders_and_tests = {} |
+ |
+ # TODO(machenbach): The old style command-line options don't support |
+ # multiple try masters yet. |
+ old_style = filter(lambda x: isinstance(x, basestring), options.bot) |
+ new_style = filter(lambda x: isinstance(x, tuple), options.bot) |
+ |
+ for bot in old_style: |
+ if ':' in bot: |
+ option_parser.error('Specifying testfilter is no longer supported') |
+ elif ',' in bot: |
+ option_parser.error('Specify one bot per --bot flag') |
+ else: |
+ builders_and_tests.setdefault(bot, []) |
+ |
+ for bot, tests in new_style: |
+ builders_and_tests.setdefault(bot, []).extend(tests) |
+ |
+ if not options.master: |
+ # TODO(qyearsley): crbug.com/640740 |
+ options.master, error_message = _get_builder_master(options.bot) |
+ if error_message: |
+ option_parser.error( |
+ 'Tryserver master cannot be found because: %s\n' |
+ 'Please manually specify the tryserver master, e.g. ' |
+ '"-m tryserver.chromium.linux".' % error_message) |
+ |
+ # Return a master map with one master to be backwards compatible. The |
+ # master name defaults to an empty string, which will cause the master |
+ # not to be set on rietveld (deprecated). |
+ bucket = '' |
+ if options.master: |
+ # Add the "master." prefix to the master name to obtain the bucket name. |
+ bucket = _prefix_master(options.master) |
+ return {bucket: builders_and_tests} |
+ |
+ |
+def _get_builder_master(bot_list): |
+ """Fetches a master for the given list of builders. |
+ |
+ Returns a pair (master, error_message), where either master or |
+ error_message is None. |
+ """ |
+ map_url = 'https://builders-map.appspot.com/' |
+ try: |
+ master_map = json.load(urllib2.urlopen(map_url)) |
+ except urllib2.URLError as e: |
+ return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' % |
+ (map_url, e)) |
+ except ValueError as e: |
+ return None, ('Invalid json string from %s. Error: %s.' % (map_url, e)) |
+ if not master_map: |
+ return None, 'Failed to build master map.' |
+ |
+ result_master = '' |
+ for bot in bot_list: |
+ builder = bot.split(':', 1)[0] |
+ master_list = master_map.get(builder, []) |
+ if not master_list: |
+ return None, ('No matching master for builder %s.' % builder) |
+ elif len(master_list) > 1: |
+ return None, ('The builder name %s exists in multiple masters %s.' % |
+ (builder, master_list)) |
+ else: |
+ cur_master = master_list[0] |
+ if not result_master: |
+ result_master = cur_master |
+ elif result_master != cur_master: |
+ return None, 'The builders do not belong to the same master.' |
+ return result_master, None |
+ |
+ |
def _trigger_try_jobs(auth_config, changelist, buckets, options, |
category='git_cl_try', patchset=None): |
+ """Sends a request to Buildbucket to trigger try jobs for a changelist. |
+ |
+ Args: |
+ auth_config: AuthConfig for Rietveld. |
+ changelist: Changelist that the try jobs are associated with. |
+ buckets: A nested dict mapping bucket names to builders to tests. |
+ options: Command-line options. |
+ """ |
assert changelist.GetIssue(), 'CL must be uploaded first' |
codereview_url = changelist.GetCodereviewServer() |
assert codereview_url, 'CL must be uploaded first' |
@@ -1582,6 +1700,31 @@ class Changelist(object): |
assert self.GetIssue() |
return self._codereview_impl.SetCQState(new_state) |
+ def TriggerDryRun(self): |
+ """Triggers a dry run and prints a warning on failure.""" |
+ # TODO(qyearsley): Either re-use this method in CMDset_commit |
+ # and CMDupload, or change CMDtry to trigger dry runs with |
+ # just SetCQState, and catch keyboard interrupt and other |
+ # errors in that method. |
+ try: |
+ self.SetCQState(_CQState.DRY_RUN) |
+ print('scheduled CQ Dry Run on %s' % self.GetIssueURL()) |
+ return 0 |
+ except KeyboardInterrupt: |
+ raise |
+ except: |
+ print('WARNING: failed to trigger CQ Dry Run.\n' |
+ 'Either:\n' |
+ ' * your project has no CQ\n' |
+ ' * you don\'t have permission to trigger Dry Run\n' |
+ ' * bug in this code (see stack trace below).\n' |
+ 'Consider specifying which bots to trigger manually ' |
+ 'or asking your project owners for permissions ' |
+ 'or contacting Chrome Infrastructure team at ' |
+ 'https://www.chromium.org/infra\n\n') |
+ # Still raise exception so that stack trace is printed. |
+ raise |
+ |
# Forward methods to codereview specific implementation. |
def CloseIssue(self): |
@@ -4648,37 +4791,6 @@ def GetTreeStatusReason(): |
return status['message'] |
-def GetBuilderMaster(bot_list): |
- """For a given builder, fetch the master from AE if available.""" |
- map_url = 'https://builders-map.appspot.com/' |
- try: |
- master_map = json.load(urllib2.urlopen(map_url)) |
- except urllib2.URLError as e: |
- return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' % |
- (map_url, e)) |
- except ValueError as e: |
- return None, ('Invalid json string from %s. Error: %s.' % (map_url, e)) |
- if not master_map: |
- return None, 'Failed to build master map.' |
- |
- result_master = '' |
- for bot in bot_list: |
- builder = bot.split(':', 1)[0] |
- master_list = master_map.get(builder, []) |
- if not master_list: |
- return None, ('No matching master for builder %s.' % builder) |
- elif len(master_list) > 1: |
- return None, ('The builder name %s exists in multiple masters %s.' % |
- (builder, master_list)) |
- else: |
- cur_master = master_list[0] |
- if not result_master: |
- result_master = cur_master |
- elif result_master != cur_master: |
- return None, 'The builders do not belong to the same master.' |
- return result_master, None |
- |
- |
def CMDtree(parser, args): |
"""Shows the status of the tree.""" |
_, args = parser.parse_args(args) |
@@ -4696,8 +4808,7 @@ def CMDtree(parser, args): |
def CMDtry(parser, args): |
- """Triggers try jobs using CQ dry run or BuildBucket for individual builders. |
- """ |
+ """Triggers try jobs using either BuildBucket or CQ dry run.""" |
group = optparse.OptionGroup(parser, 'Try job options') |
group.add_option( |
'-b', '--bot', action='append', |
@@ -4772,97 +4883,13 @@ def CMDtry(parser, args): |
if options.bucket and options.master: |
parser.error('Only one of --bucket and --master may be used.') |
- if options.bot and not options.master and not options.bucket: |
- options.master, err_msg = GetBuilderMaster(options.bot) |
- if err_msg: |
- parser.error('Tryserver master cannot be found because: %s\n' |
- 'Please manually specify the tryserver master' |
- ', e.g. "-m tryserver.chromium.linux".' % err_msg) |
+ buckets = _get_bucket_map(cl, options, parser) |
- def GetMasterMap(): |
- """Returns {master: {builder_name: [test_names]}}. Not buckets!""" |
- # Process --bot. |
- if not options.bot: |
- change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None) |
- |
- # Get try masters from PRESUBMIT.py files. |
- masters = presubmit_support.DoGetTryMasters( |
- change, |
- change.LocalPaths(), |
- settings.GetRoot(), |
- None, |
- None, |
- options.verbose, |
- sys.stdout) |
- if masters: |
- return masters |
- |
- # Fall back to deprecated method: get try slaves from PRESUBMIT.py files. |
- options.bot = presubmit_support.DoGetTrySlaves( |
- change, |
- change.LocalPaths(), |
- settings.GetRoot(), |
- None, |
- None, |
- options.verbose, |
- sys.stdout) |
- |
- if not options.bot: |
- return {} |
- |
- builders_and_tests = {} |
- # TODO(machenbach): The old style command-line options don't support |
- # multiple try masters yet. |
- old_style = filter(lambda x: isinstance(x, basestring), options.bot) |
- new_style = filter(lambda x: isinstance(x, tuple), options.bot) |
- |
- for bot in old_style: |
- if ':' in bot: |
- parser.error('Specifying testfilter is no longer supported') |
- elif ',' in bot: |
- parser.error('Specify one bot per --bot flag') |
- else: |
- builders_and_tests.setdefault(bot, []) |
- |
- for bot, tests in new_style: |
- builders_and_tests.setdefault(bot, []).extend(tests) |
- |
- # Return a master map with one master to be backwards compatible. The |
- # master name defaults to an empty string, which will cause the master |
- # not to be set on rietveld (deprecated). |
- return {options.master: builders_and_tests} |
- |
- if options.bucket: |
- buckets = {options.bucket: {b: [] for b in options.bot}} |
- else: |
- buckets = {} |
- for master, data in GetMasterMap().iteritems(): |
- # Add the "master." prefix to the master name to obtain the bucket name. |
- bucket = _prefix_master(master) if master else '' |
- buckets[bucket] = data |
- |
- if not buckets: |
- # Default to triggering Dry Run (see http://crbug.com/625697). |
- if options.verbose: |
- print('git cl try with no bots now defaults to CQ Dry Run.') |
- try: |
- cl.SetCQState(_CQState.DRY_RUN) |
- print('scheduled CQ Dry Run on %s' % cl.GetIssueURL()) |
- return 0 |
- except KeyboardInterrupt: |
- raise |
- except: |
- print('WARNING: failed to trigger CQ Dry Run.\n' |
- 'Either:\n' |
- ' * your project has no CQ\n' |
- ' * you don\'t have permission to trigger Dry Run\n' |
- ' * bug in this code (see stack trace below).\n' |
- 'Consider specifying which bots to trigger manually ' |
- 'or asking your project owners for permissions ' |
- 'or contacting Chrome Infrastructure team at ' |
- 'https://www.chromium.org/infra\n\n') |
- # Still raise exception so that stack trace is printed. |
- raise |
+ if not buckets: |
+ # Default to triggering Dry Run (see http://crbug.com/625697). |
+ if options.verbose: |
+ print('git cl try with no bots now defaults to CQ Dry Run.') |
+ return cl.TriggerDryRun() |
for builders in buckets.itervalues(): |
if any('triggered' in b for b in builders): |
@@ -4880,6 +4907,7 @@ def CMDtry(parser, args): |
'By default, git cl try uses the latest patchset from ' |
'codereview, continuing to use patchset %s.\n' % |
(patchset, cl.GetPatchset(), patchset)) |
+ |
try: |
_trigger_try_jobs(auth_config, cl, buckets, options, 'git_cl_try', |
patchset) |
@@ -5001,6 +5029,7 @@ def CMDset_commit(parser, args): |
if options.clear: |
state = _CQState.NONE |
elif options.dry_run: |
+ # TODO(qyearsley): Use cl.TriggerDryRun. |
state = _CQState.DRY_RUN |
else: |
state = _CQState.COMMIT |