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