| # Copyright 2016 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """An interface to git-cl. |
| |
| The git-cl tool is responsible for communicating with Rietveld, Gerrit, |
| and Buildbucket to manage changelists and try jobs associated with them. |
| """ |
| |
| import json |
| import logging |
| import re |
| |
| from webkitpy.common.net.buildbot import Build, filter_latest_builds |
| |
| _log = logging.getLogger(__name__) |
| |
| # A refresh token may be needed for some commands, such as git cl try, |
| # in order to authenticate with buildbucket. |
| _COMMANDS_THAT_TAKE_REFRESH_TOKEN = ('try',) |
| |
| |
| class GitCL(object): |
| |
| def __init__(self, host, auth_refresh_token_json=None, cwd=None): |
| self._host = host |
| self._auth_refresh_token_json = auth_refresh_token_json |
| self._cwd = cwd |
| |
| def run(self, args): |
| """Runs git-cl with the given arguments and returns the output.""" |
| command = ['git', 'cl'] + args |
| if self._auth_refresh_token_json and args[0] in _COMMANDS_THAT_TAKE_REFRESH_TOKEN: |
| command += ['--auth-refresh-token-json', self._auth_refresh_token_json] |
| return self._host.executive.run_command(command, cwd=self._cwd) |
| |
| def trigger_try_jobs(self, builders=None): |
| builders = builders or self._host.builders.all_try_builder_names() |
| if 'android_blink_rel' in builders: |
| self.run(['try', '-b', 'android_blink_rel']) |
| builders.remove('android_blink_rel') |
| # TODO(qyearsley): Stop explicitly adding the master name when |
| # git cl try can get the master name; see http://crbug.com/700523. |
| command = ['try', '-m', 'tryserver.blink'] |
| for builder in sorted(builders): |
| command.extend(['-b', builder]) |
| self.run(command) |
| |
| def get_issue_number(self): |
| return self.run(['issue']).split()[2] |
| |
| def wait_for_try_jobs(self, poll_delay_seconds=10 * 60, timeout_seconds=120 * 60): |
| """Waits until all try jobs are finished. |
| |
| Args: |
| poll_delay_seconds: Time to wait between fetching results. |
| timeout_seconds: Time to wait before aborting. |
| |
| Returns: |
| A list of try job result dicts, or None if a timeout occurred. |
| """ |
| start = self._host.time() |
| self._host.print_('Waiting for try jobs (timeout: %d seconds).' % timeout_seconds) |
| while self._host.time() - start < timeout_seconds: |
| self._host.sleep(poll_delay_seconds) |
| try_results = self.fetch_try_results() |
| _log.debug('Fetched try results: %s', try_results) |
| if self.all_jobs_finished(try_results): |
| self._host.print_('All jobs finished.') |
| return try_results |
| self._host.print_('Waiting. %d seconds passed.' % (self._host.time() - start)) |
| self._host.sleep(poll_delay_seconds) |
| self._host.print_('Timed out waiting for try results.') |
| return None |
| |
| def latest_try_jobs(self, builder_names=None): |
| """Returns a list of Builds for the latest jobs for the given builders.""" |
| try_results = self.fetch_try_results() |
| if builder_names: |
| try_results = [r for r in try_results if r['builder_name'] in builder_names] |
| return filter_latest_builds(self._try_result_to_build(r) for r in try_results) |
| |
| def fetch_try_results(self): |
| """Requests results f try jobs for the current CL.""" |
| with self._host.filesystem.mkdtemp() as temp_directory: |
| results_path = self._host.filesystem.join(temp_directory, 'try-results.json') |
| self.run(['try-results', '--json', results_path]) |
| contents = self._host.filesystem.read_text_file(results_path) |
| _log.debug('Fetched try results to file "%s".', results_path) |
| self._host.filesystem.remove(results_path) |
| return json.loads(contents) |
| |
| @staticmethod |
| def _try_result_to_build(try_result): |
| """Converts a parsed try result dict to a Build object.""" |
| builder_name = try_result['builder_name'] |
| url = try_result['url'] |
| if url is None: |
| return Build(builder_name, None) |
| match = re.match(r'.*/builds/(\d+)?$', url) |
| build_number = match.group(1) |
| assert build_number and build_number.isdigit() |
| return Build(builder_name, int(build_number)) |
| |
| @staticmethod |
| def all_jobs_finished(try_results): |
| return all(r.get('status') == 'COMPLETED' for r in try_results) |
| |
| @staticmethod |
| def has_failing_try_results(try_results): |
| return any(r.get('result') == 'FAILURE' for r in try_results) |