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 12import argparse 13import json 14import os 15import re 16import subprocess 17import sys 18import tempfile 19import urllib2 20 21 22BUCKET_SKIA_PRIMARY = 'skia/skia.primary' 23BUCKET_SKIA_INTERNAL = 'skia-internal/skia.internal' 24INFRA_BOTS = os.path.join('infra', 'bots') 25TASKS_JSON = os.path.join(INFRA_BOTS, 'tasks.json') 26REPO_INTERNAL = 'https://skia.googlesource.com/internal_test.git' 27TMP_DIR = os.path.join(tempfile.gettempdir(), 'sktry') 28 29SKIA_ROOT = os.path.realpath(os.path.join( 30 os.path.dirname(os.path.abspath(__file__)), os.pardir)) 31SKIA_INFRA_BOTS = os.path.join(SKIA_ROOT, INFRA_BOTS) 32sys.path.insert(0, SKIA_INFRA_BOTS) 33 34import utils 35 36 37def 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!') 46 cwd = next_cwd 47 48 49def 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([ 62 utils.GIT, 'show', 'master:%s' % JOBS_JSON])) 63 return (BUCKET_SKIA_INTERNAL, jobs) 64 65 66def main(): 67 # Parse arguments. 68 d = 'Helper script for triggering try jobs.' 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.') 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.')) 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 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]) 104 # Load and filter the list of jobs. 105 jobs = [] 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)) 118 if args.internal: 119 jobs.append(get_jobs(REPO_INTERNAL)) 120 if args.job: 121 filtered_jobs = [] 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: 125 filtered_jobs.append((bucket, filtered)) 126 jobs = filtered_jobs 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) 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 140 if args.list: 141 return 142 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 161 162 # Trigger the try jobs. 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) 173 174 175if __name__ == '__main__': 176 main() 177