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