• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2019 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Modifies a tryjob based off of arguments."""
8
9from __future__ import print_function
10
11import argparse
12import enum
13import json
14import os
15import sys
16
17import chroot
18import failure_modes
19import get_llvm_hash
20import update_chromeos_llvm_hash
21import update_packages_and_run_tests
22import update_tryjob_status
23
24
25class ModifyTryjob(enum.Enum):
26  """Options to modify a tryjob."""
27
28  REMOVE = 'remove'
29  RELAUNCH = 'relaunch'
30  ADD = 'add'
31
32
33def GetCommandLineArgs():
34  """Parses the command line for the command line arguments."""
35
36  # Default path to the chroot if a path is not specified.
37  cros_root = os.path.expanduser('~')
38  cros_root = os.path.join(cros_root, 'chromiumos')
39
40  # Create parser and add optional command-line arguments.
41  parser = argparse.ArgumentParser(
42      description='Removes, relaunches, or adds a tryjob.')
43
44  # Add argument for the JSON file to use for the update of a tryjob.
45  parser.add_argument(
46      '--status_file',
47      required=True,
48      help='The absolute path to the JSON file that contains the tryjobs used '
49      'for bisecting LLVM.')
50
51  # Add argument that determines what action to take on the revision specified.
52  parser.add_argument(
53      '--modify_tryjob',
54      required=True,
55      choices=[modify_tryjob.value for modify_tryjob in ModifyTryjob],
56      help='What action to perform on the tryjob.')
57
58  # Add argument that determines which revision to search for in the list of
59  # tryjobs.
60  parser.add_argument('--revision',
61                      required=True,
62                      type=int,
63                      help='The revision to either remove or relaunch.')
64
65  # Add argument for other change lists that want to run alongside the tryjob.
66  parser.add_argument(
67      '--extra_change_lists',
68      type=int,
69      nargs='+',
70      help='change lists that would like to be run alongside the change list '
71      'of updating the packages')
72
73  # Add argument for custom options for the tryjob.
74  parser.add_argument('--options',
75                      required=False,
76                      nargs='+',
77                      help='options to use for the tryjob testing')
78
79  # Add argument for the builder to use for the tryjob.
80  parser.add_argument('--builder',
81                      help='builder to use for the tryjob testing')
82
83  # Add argument for a specific chroot path.
84  parser.add_argument('--chroot_path',
85                      default=cros_root,
86                      help='the path to the chroot (default: %(default)s)')
87
88  # Add argument for whether to display command contents to `stdout`.
89  parser.add_argument('--verbose',
90                      action='store_true',
91                      help='display contents of a command to the terminal '
92                      '(default: %(default)s)')
93
94  args_output = parser.parse_args()
95
96  if (not os.path.isfile(args_output.status_file)
97      or not args_output.status_file.endswith('.json')):
98    raise ValueError('File does not exist or does not ending in ".json" '
99                     ': %s' % args_output.status_file)
100
101  if (args_output.modify_tryjob == ModifyTryjob.ADD.value
102      and not args_output.builder):
103    raise ValueError('A builder is required for adding a tryjob.')
104  elif (args_output.modify_tryjob != ModifyTryjob.ADD.value
105        and args_output.builder):
106    raise ValueError('Specifying a builder is only available when adding a '
107                     'tryjob.')
108
109  return args_output
110
111
112def GetCLAfterUpdatingPackages(packages, git_hash, svn_version, chroot_path,
113                               patch_metadata_file, svn_option):
114  """Updates the packages' LLVM_NEXT."""
115
116  change_list = update_chromeos_llvm_hash.UpdatePackages(
117      packages,
118      update_chromeos_llvm_hash.LLVMVariant.next,
119      git_hash,
120      svn_version,
121      chroot_path,
122      patch_metadata_file,
123      failure_modes.FailureModes.DISABLE_PATCHES,
124      svn_option,
125      extra_commit_msg=None)
126
127  print('\nSuccessfully updated packages to %d' % svn_version)
128  print('Gerrit URL: %s' % change_list.url)
129  print('Change list number: %d' % change_list.cl_number)
130
131  return change_list
132
133
134def CreateNewTryjobEntryForBisection(cl, extra_cls, options, builder,
135                                     chroot_path, cl_url, revision):
136  """Submits a tryjob and adds additional information."""
137
138  # Get the tryjob results after submitting the tryjob.
139  # Format of 'tryjob_results':
140  # [
141  #   {
142  #     'link' : [TRYJOB_LINK],
143  #     'buildbucket_id' : [BUILDBUCKET_ID],
144  #     'extra_cls' : [EXTRA_CLS_LIST],
145  #     'options' : [EXTRA_OPTIONS_LIST],
146  #     'builder' : [BUILDER_AS_A_LIST]
147  #   }
148  # ]
149  tryjob_results = update_packages_and_run_tests.RunTryJobs(
150      cl, extra_cls, options, [builder], chroot_path)
151  print('\nTryjob:')
152  print(tryjob_results[0])
153
154  # Add necessary information about the tryjob.
155  tryjob_results[0]['url'] = cl_url
156  tryjob_results[0]['rev'] = revision
157  tryjob_results[0]['status'] = update_tryjob_status.TryjobStatus.PENDING.value
158  tryjob_results[0]['cl'] = cl
159
160  return tryjob_results[0]
161
162
163def AddTryjob(packages, git_hash, revision, chroot_path, patch_metadata_file,
164              extra_cls, options, builder, verbose, svn_option):
165  """Submits a tryjob."""
166
167  update_chromeos_llvm_hash.verbose = verbose
168
169  change_list = GetCLAfterUpdatingPackages(packages, git_hash, revision,
170                                           chroot_path, patch_metadata_file,
171                                           svn_option)
172
173  tryjob_dict = CreateNewTryjobEntryForBisection(change_list.cl_number,
174                                                 extra_cls, options, builder,
175                                                 chroot_path, change_list.url,
176                                                 revision)
177
178  return tryjob_dict
179
180
181def PerformTryjobModification(revision, modify_tryjob, status_file, extra_cls,
182                              options, builder, chroot_path, verbose):
183  """Removes, relaunches, or adds a tryjob.
184
185  Args:
186    revision: The revision associated with the tryjob.
187    modify_tryjob: What action to take on the tryjob.
188      Ex: ModifyTryjob.REMOVE, ModifyTryjob.RELAUNCH, ModifyTryjob.ADD
189    status_file: The .JSON file that contains the tryjobs.
190    extra_cls: Extra change lists to be run alongside tryjob
191    options: Extra options to pass into 'cros tryjob'.
192    builder: The builder to use for 'cros tryjob'.
193    chroot_path: The absolute path to the chroot (used by 'cros tryjob' when
194    relaunching a tryjob).
195    verbose: Determines whether to print the contents of a command to `stdout`.
196  """
197
198  # Format of 'bisect_contents':
199  # {
200  #   'start': [START_REVISION_OF_BISECTION]
201  #   'end': [END_REVISION_OF_BISECTION]
202  #   'jobs' : [
203  #       {[TRYJOB_INFORMATION]},
204  #       {[TRYJOB_INFORMATION]},
205  #       ...,
206  #       {[TRYJOB_INFORMATION]}
207  #   ]
208  # }
209  with open(status_file) as tryjobs:
210    bisect_contents = json.load(tryjobs)
211
212  if not bisect_contents['jobs'] and modify_tryjob != ModifyTryjob.ADD:
213    sys.exit('No tryjobs in %s' % status_file)
214
215  tryjob_index = update_tryjob_status.FindTryjobIndex(revision,
216                                                      bisect_contents['jobs'])
217
218  # 'FindTryjobIndex()' returns None if the tryjob was not found.
219  if tryjob_index is None and modify_tryjob != ModifyTryjob.ADD:
220    raise ValueError('Unable to find tryjob for %d in %s' %
221                     (revision, status_file))
222
223  # Determine the action to take based off of 'modify_tryjob'.
224  if modify_tryjob == ModifyTryjob.REMOVE:
225    del bisect_contents['jobs'][tryjob_index]
226
227    print('Successfully deleted the tryjob of revision %d' % revision)
228  elif modify_tryjob == ModifyTryjob.RELAUNCH:
229    # Need to update the tryjob link and buildbucket ID.
230    tryjob_results = update_packages_and_run_tests.RunTryJobs(
231        bisect_contents['jobs'][tryjob_index]['cl'],
232        bisect_contents['jobs'][tryjob_index]['extra_cls'],
233        bisect_contents['jobs'][tryjob_index]['options'],
234        bisect_contents['jobs'][tryjob_index]['builder'], chroot_path)
235
236    bisect_contents['jobs'][tryjob_index][
237        'status'] = update_tryjob_status.TryjobStatus.PENDING.value
238    bisect_contents['jobs'][tryjob_index]['link'] = tryjob_results[0]['link']
239    bisect_contents['jobs'][tryjob_index]['buildbucket_id'] = tryjob_results[
240        0]['buildbucket_id']
241
242    print('Successfully relaunched the tryjob for revision %d and updated '
243          'the tryjob link to %s' % (revision, tryjob_results[0]['link']))
244  elif modify_tryjob == ModifyTryjob.ADD:
245    # Tryjob exists already.
246    if tryjob_index is not None:
247      raise ValueError('Tryjob already exists (index is %d) in %s.' %
248                       (tryjob_index, status_file))
249
250    # Make sure the revision is within the bounds of the start and end of the
251    # bisection.
252    elif bisect_contents['start'] < revision < bisect_contents['end']:
253
254      patch_metadata_file = 'PATCHES.json'
255
256      git_hash, revision = get_llvm_hash.GetLLVMHashAndVersionFromSVNOption(
257          revision)
258
259      tryjob_dict = AddTryjob(update_chromeos_llvm_hash.DEFAULT_PACKAGES,
260                              git_hash, revision, chroot_path,
261                              patch_metadata_file, extra_cls, options, builder,
262                              verbose, revision)
263
264      bisect_contents['jobs'].append(tryjob_dict)
265
266      print('Successfully added tryjob of revision %d' % revision)
267    else:
268      raise ValueError('Failed to add tryjob to %s' % status_file)
269  else:
270    raise ValueError('Invalid "modify_tryjob" option provided: %s' %
271                     modify_tryjob)
272
273  with open(status_file, 'w') as update_tryjobs:
274    json.dump(bisect_contents,
275              update_tryjobs,
276              indent=4,
277              separators=(',', ': '))
278
279
280def main():
281  """Removes, relaunches, or adds a tryjob."""
282
283  chroot.VerifyOutsideChroot()
284
285  args_output = GetCommandLineArgs()
286
287  PerformTryjobModification(args_output.revision,
288                            ModifyTryjob(args_output.modify_tryjob),
289                            args_output.status_file,
290                            args_output.extra_change_lists,
291                            args_output.options, args_output.builder,
292                            args_output.chroot_path, args_output.verbose)
293
294
295if __name__ == '__main__':
296  main()
297