Eric Boren | 6376517 | 2017-10-16 14:22:47 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | |
| 3 | # Copyright 2017 Google Inc. |
| 4 | # |
| 5 | # Use of this source code is governed by a BSD-style license that can be |
| 6 | # found in the LICENSE file. |
| 7 | |
| 8 | |
| 9 | """Submit one or more try jobs.""" |
| 10 | |
| 11 | |
| 12 | import argparse |
| 13 | import json |
| 14 | import os |
| 15 | import re |
| 16 | import subprocess |
| 17 | import sys |
Eric Boren | d5c128b | 2017-10-17 14:50:26 -0400 | [diff] [blame] | 18 | import tempfile |
Eric Boren | d7e5562 | 2020-06-25 08:24:08 -0400 | [diff] [blame] | 19 | import urllib2 |
Eric Boren | 6376517 | 2017-10-16 14:22:47 -0400 | [diff] [blame] | 20 | |
| 21 | |
Eric Boren | 1c8b267 | 2019-11-15 13:58:17 -0500 | [diff] [blame] | 22 | BUCKET_SKIA_PRIMARY = 'skia/skia.primary' |
| 23 | BUCKET_SKIA_INTERNAL = 'skia-internal/skia.internal' |
Eric Boren | 86e50fe | 2020-01-14 06:23:58 -0500 | [diff] [blame] | 24 | INFRA_BOTS = os.path.join('infra', 'bots') |
| 25 | TASKS_JSON = os.path.join(INFRA_BOTS, 'tasks.json') |
Eric Boren | d5c128b | 2017-10-17 14:50:26 -0400 | [diff] [blame] | 26 | REPO_INTERNAL = 'https://skia.googlesource.com/internal_test.git' |
| 27 | TMP_DIR = os.path.join(tempfile.gettempdir(), 'sktry') |
Eric Boren | cff9f95 | 2017-10-17 09:18:18 -0400 | [diff] [blame] | 28 | |
Eric Boren | 86e50fe | 2020-01-14 06:23:58 -0500 | [diff] [blame] | 29 | SKIA_ROOT = os.path.realpath(os.path.join( |
| 30 | os.path.dirname(os.path.abspath(__file__)), os.pardir)) |
| 31 | SKIA_INFRA_BOTS = os.path.join(SKIA_ROOT, INFRA_BOTS) |
| 32 | sys.path.insert(0, SKIA_INFRA_BOTS) |
Eric Boren | cff9f95 | 2017-10-17 09:18:18 -0400 | [diff] [blame] | 33 | |
Eric Boren | d5c128b | 2017-10-17 14:50:26 -0400 | [diff] [blame] | 34 | import utils |
| 35 | |
| 36 | |
Eric Boren | 86e50fe | 2020-01-14 06:23:58 -0500 | [diff] [blame] | 37 | def find_repo_root(): |
| 38 | """Find the root directory of the current repository.""" |
| 39 | cwd = os.getcwd() |
| 40 | while True: |
| 41 | if os.path.isdir(os.path.join(cwd, '.git')): |
| 42 | return cwd |
| 43 | next_cwd = os.path.dirname(cwd) |
| 44 | if next_cwd == cwd: |
| 45 | raise Exception('Failed to find repo root!') |
Eric Boren | 7ef10b6 | 2020-01-14 07:00:03 -0500 | [diff] [blame] | 46 | cwd = next_cwd |
Eric Boren | 86e50fe | 2020-01-14 06:23:58 -0500 | [diff] [blame] | 47 | |
| 48 | |
Eric Boren | d5c128b | 2017-10-17 14:50:26 -0400 | [diff] [blame] | 49 | def get_jobs(repo): |
| 50 | """Obtain the list of jobs from the given repo.""" |
| 51 | # Maintain a copy of the repo in the temp dir. |
| 52 | if not os.path.isdir(TMP_DIR): |
| 53 | os.mkdir(TMP_DIR) |
| 54 | with utils.chdir(TMP_DIR): |
| 55 | dirname = repo.split('/')[-1] |
| 56 | if not os.path.isdir(dirname): |
| 57 | subprocess.check_call([ |
| 58 | utils.GIT, 'clone', '--mirror', repo, dirname]) |
| 59 | with utils.chdir(dirname): |
| 60 | subprocess.check_call([utils.GIT, 'remote', 'update']) |
| 61 | jobs = json.loads(subprocess.check_output([ |
Eric Boren | 86e50fe | 2020-01-14 06:23:58 -0500 | [diff] [blame] | 62 | utils.GIT, 'show', 'master:%s' % JOBS_JSON])) |
Eric Boren | d5c128b | 2017-10-17 14:50:26 -0400 | [diff] [blame] | 63 | return (BUCKET_SKIA_INTERNAL, jobs) |
Eric Boren | 6376517 | 2017-10-16 14:22:47 -0400 | [diff] [blame] | 64 | |
| 65 | |
| 66 | def main(): |
| 67 | # Parse arguments. |
Eric Boren | 86e50fe | 2020-01-14 06:23:58 -0500 | [diff] [blame] | 68 | d = 'Helper script for triggering try jobs.' |
Eric Boren | 6376517 | 2017-10-16 14:22:47 -0400 | [diff] [blame] | 69 | parser = argparse.ArgumentParser(description=d) |
| 70 | parser.add_argument('--list', action='store_true', default=False, |
| 71 | help='Just list the jobs; do not trigger anything.') |
Eric Boren | d5c128b | 2017-10-17 14:50:26 -0400 | [diff] [blame] | 72 | parser.add_argument('--internal', action='store_true', default=False, |
| 73 | help=('If set, include internal jobs. You must have ' |
| 74 | 'permission to view internal repos.')) |
Eric Boren | 6376517 | 2017-10-16 14:22:47 -0400 | [diff] [blame] | 75 | parser.add_argument('job', nargs='?', default=None, |
| 76 | help='Job name or regular expression to match job names.') |
| 77 | args = parser.parse_args() |
| 78 | |
Eric Boren | d7e5562 | 2020-06-25 08:24:08 -0400 | [diff] [blame] | 79 | # First, find the Gerrit issue number. If the change was uploaded using Depot |
| 80 | # Tools, this configuration will be present in the git config. |
| 81 | branch = subprocess.check_output(['git', 'branch', '--show-current']).rstrip() |
| 82 | if not branch: |
| 83 | print 'Not on any branch; cannot trigger try jobs.' |
| 84 | sys.exit(1) |
| 85 | branch_issue_config = 'branch.%s.gerritissue' % branch |
| 86 | try: |
| 87 | issue = subprocess.check_output([ |
| 88 | 'git', 'config', '--local', branch_issue_config]) |
| 89 | except subprocess.CalledProcessError: |
| 90 | # Not using Depot Tools. Find the Change-Id line in the most recent commit |
| 91 | # and obtain the issue number using that. |
| 92 | print '"git cl issue" not set; searching for Change-Id footer.' |
| 93 | msg = subprocess.check_output(['git', 'log', '-n1', branch]) |
| 94 | m = re.search('Change-Id: (I[a-f0-9]+)', msg) |
| 95 | if not m: |
| 96 | print ('No gerrit issue found in `git config --local %s` and no Change-Id' |
| 97 | ' found in most recent commit message.') |
| 98 | sys.exit(1) |
| 99 | url = 'https://skia-review.googlesource.com/changes/%s' % m.groups()[0] |
| 100 | resp = urllib2.urlopen(url).read() |
| 101 | issue = str(json.loads('\n'.join(resp.splitlines()[1:]))['_number']) |
| 102 | print 'Setting "git cl issue %s"' % issue |
| 103 | subprocess.check_call(['git', 'cl', 'issue', issue]) |
Eric Boren | d5c128b | 2017-10-17 14:50:26 -0400 | [diff] [blame] | 104 | # Load and filter the list of jobs. |
Eric Boren | cff9f95 | 2017-10-17 09:18:18 -0400 | [diff] [blame] | 105 | jobs = [] |
Eric Boren | 86e50fe | 2020-01-14 06:23:58 -0500 | [diff] [blame] | 106 | tasks_json = os.path.join(find_repo_root(), TASKS_JSON) |
| 107 | with open(tasks_json) as f: |
| 108 | tasks_cfg = json.load(f) |
| 109 | skia_primary_jobs = [] |
| 110 | for k, v in tasks_cfg['jobs'].iteritems(): |
| 111 | skia_primary_jobs.append(k) |
| 112 | skia_primary_jobs.sort() |
| 113 | |
| 114 | # TODO(borenet): This assumes that the current repo is associated with the |
| 115 | # skia.primary bucket. This will work for most repos but it would be better to |
| 116 | # look up the correct bucket to use. |
| 117 | jobs.append((BUCKET_SKIA_PRIMARY, skia_primary_jobs)) |
Eric Boren | d5c128b | 2017-10-17 14:50:26 -0400 | [diff] [blame] | 118 | if args.internal: |
| 119 | jobs.append(get_jobs(REPO_INTERNAL)) |
Eric Boren | 6376517 | 2017-10-16 14:22:47 -0400 | [diff] [blame] | 120 | if args.job: |
Eric Boren | c9080c8 | 2017-10-18 12:53:49 -0400 | [diff] [blame] | 121 | filtered_jobs = [] |
Eric Boren | cff9f95 | 2017-10-17 09:18:18 -0400 | [diff] [blame] | 122 | for bucket, job_list in jobs: |
| 123 | filtered = [j for j in job_list if re.search(args.job, j)] |
| 124 | if len(filtered) > 0: |
Eric Boren | c9080c8 | 2017-10-18 12:53:49 -0400 | [diff] [blame] | 125 | filtered_jobs.append((bucket, filtered)) |
| 126 | jobs = filtered_jobs |
Eric Boren | 6376517 | 2017-10-16 14:22:47 -0400 | [diff] [blame] | 127 | |
| 128 | # Display the list of jobs. |
| 129 | if len(jobs) == 0: |
| 130 | print 'Found no jobs matching "%s"' % repr(args.job) |
| 131 | sys.exit(1) |
Eric Boren | cff9f95 | 2017-10-17 09:18:18 -0400 | [diff] [blame] | 132 | count = 0 |
| 133 | for bucket, job_list in jobs: |
| 134 | count += len(job_list) |
| 135 | print 'Found %d jobs:' % count |
| 136 | for bucket, job_list in jobs: |
| 137 | print ' %s:' % bucket |
| 138 | for j in job_list: |
| 139 | print ' %s' % j |
Eric Boren | 6376517 | 2017-10-16 14:22:47 -0400 | [diff] [blame] | 140 | if args.list: |
| 141 | return |
| 142 | |
Eric Boren | cbe99c0 | 2017-12-11 13:18:52 -0500 | [diff] [blame] | 143 | if count > 1: |
| 144 | # Prompt before triggering jobs. |
| 145 | resp = raw_input('\nDo you want to trigger these jobs? (y/n or i for ' |
| 146 | 'interactive): ') |
| 147 | print '' |
| 148 | if resp != 'y' and resp != 'i': |
| 149 | sys.exit(1) |
| 150 | if resp == 'i': |
| 151 | filtered_jobs = [] |
| 152 | for bucket, job_list in jobs: |
| 153 | new_job_list = [] |
| 154 | for j in job_list: |
| 155 | incl = raw_input(('Trigger %s? (y/n): ' % j)) |
| 156 | if incl == 'y': |
| 157 | new_job_list.append(j) |
| 158 | if len(new_job_list) > 0: |
| 159 | filtered_jobs.append((bucket, new_job_list)) |
| 160 | jobs = filtered_jobs |
Eric Boren | 6376517 | 2017-10-16 14:22:47 -0400 | [diff] [blame] | 161 | |
| 162 | # Trigger the try jobs. |
Eric Boren | cff9f95 | 2017-10-17 09:18:18 -0400 | [diff] [blame] | 163 | for bucket, job_list in jobs: |
| 164 | cmd = ['git', 'cl', 'try', '-B', bucket] |
| 165 | for j in job_list: |
| 166 | cmd.extend(['-b', j]) |
| 167 | try: |
| 168 | subprocess.check_call(cmd) |
| 169 | except subprocess.CalledProcessError: |
| 170 | # Output from the command will fall through, so just exit here rather than |
| 171 | # printing a stack trace. |
| 172 | sys.exit(1) |
Eric Boren | 6376517 | 2017-10-16 14:22:47 -0400 | [diff] [blame] | 173 | |
| 174 | |
| 175 | if __name__ == '__main__': |
| 176 | main() |