• 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_packages_and_run_tests
21import update_tryjob_status
22import update_chromeos_llvm_hash
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(
61      '--revision',
62      required=True,
63      type=int,
64      help='The revision to either remove or relaunch.')
65
66  # Add argument for other change lists that want to run alongside the tryjob.
67  parser.add_argument(
68      '--extra_change_lists',
69      type=int,
70      nargs='+',
71      help='change lists that would like to be run alongside the change list '
72      'of updating the packages')
73
74  # Add argument for custom options for the tryjob.
75  parser.add_argument(
76      '--options',
77      required=False,
78      nargs='+',
79      help='options to use for the tryjob testing')
80
81  # Add argument for the builder to use for the tryjob.
82  parser.add_argument('--builder', help='builder to use for the tryjob testing')
83
84  # Add argument for a specific chroot path.
85  parser.add_argument(
86      '--chroot_path',
87      default=cros_root,
88      help='the path to the chroot (default: %(default)s)')
89
90  # Add argument for whether to display command contents to `stdout`.
91  parser.add_argument(
92      '--verbose',
93      action='store_true',
94      help='display contents of a command to the terminal '
95      '(default: %(default)s)')
96
97  args_output = parser.parse_args()
98
99  if not os.path.isfile(args_output.status_file) or \
100      not args_output.status_file.endswith('.json'):
101    raise ValueError('File does not exist or does not ending in ".json" '
102                     ': %s' % args_output.status_file)
103
104  if args_output.modify_tryjob == ModifyTryjob.ADD.value and \
105      not args_output.builder:
106    raise ValueError('A builder is required for adding a tryjob.')
107  elif args_output.modify_tryjob != ModifyTryjob.ADD.value and \
108      args_output.builder:
109    raise ValueError('Specifying a builder is only available when adding a '
110                     'tryjob.')
111
112  return args_output
113
114
115def GetCLAfterUpdatingPackages(packages, git_hash, svn_version, chroot_path,
116                               patch_metadata_file, svn_option):
117  """Updates the packages' LLVM_NEXT."""
118
119  change_list = update_chromeos_llvm_hash.UpdatePackages(
120      packages,
121      update_chromeos_llvm_hash.LLVMVariant.next,
122      git_hash,
123      svn_version,
124      chroot_path,
125      patch_metadata_file,
126      failure_modes.FailureModes.DISABLE_PATCHES,
127      svn_option,
128      extra_commit_msg=None)
129
130  print('\nSuccessfully updated packages to %d' % svn_version)
131  print('Gerrit URL: %s' % change_list.url)
132  print('Change list number: %d' % change_list.cl_number)
133
134  return change_list
135
136
137def CreateNewTryjobEntryForBisection(cl, extra_cls, options, builder,
138                                     chroot_path, cl_url, revision):
139  """Submits a tryjob and adds additional information."""
140
141  # Get the tryjob results after submitting the tryjob.
142  # Format of 'tryjob_results':
143  # [
144  #   {
145  #     'link' : [TRYJOB_LINK],
146  #     'buildbucket_id' : [BUILDBUCKET_ID],
147  #     'extra_cls' : [EXTRA_CLS_LIST],
148  #     'options' : [EXTRA_OPTIONS_LIST],
149  #     'builder' : [BUILDER_AS_A_LIST]
150  #   }
151  # ]
152  tryjob_results = update_packages_and_run_tests.RunTryJobs(
153      cl, extra_cls, options, [builder], chroot_path)
154  print('\nTryjob:')
155  print(tryjob_results[0])
156
157  # Add necessary information about the tryjob.
158  tryjob_results[0]['url'] = cl_url
159  tryjob_results[0]['rev'] = revision
160  tryjob_results[0]['status'] = update_tryjob_status.TryjobStatus.PENDING.value
161  tryjob_results[0]['cl'] = cl
162
163  return tryjob_results[0]
164
165
166def AddTryjob(packages, git_hash, revision, chroot_path, patch_metadata_file,
167              extra_cls, options, builder, verbose, svn_option):
168  """Submits a tryjob."""
169
170  update_chromeos_llvm_hash.verbose = verbose
171
172  change_list = GetCLAfterUpdatingPackages(packages, git_hash, revision,
173                                           chroot_path, patch_metadata_file,
174                                           svn_option)
175
176  tryjob_dict = CreateNewTryjobEntryForBisection(change_list.cl_number,
177                                                 extra_cls, options, builder,
178                                                 chroot_path, change_list.url,
179                                                 revision)
180
181  return tryjob_dict
182
183
184def PerformTryjobModification(revision, modify_tryjob, status_file, extra_cls,
185                              options, builder, chroot_path, verbose):
186  """Removes, relaunches, or adds a tryjob.
187
188  Args:
189    revision: The revision associated with the tryjob.
190    modify_tryjob: What action to take on the tryjob.
191      Ex: ModifyTryjob.REMOVE, ModifyTryjob.RELAUNCH, ModifyTryjob.ADD
192    status_file: The .JSON file that contains the tryjobs.
193    extra_cls: Extra change lists to be run alongside tryjob
194    options: Extra options to pass into 'cros tryjob'.
195    builder: The builder to use for 'cros tryjob'.
196    chroot_path: The absolute path to the chroot (used by 'cros tryjob' when
197    relaunching a tryjob).
198    verbose: Determines whether to print the contents of a command to `stdout`.
199  """
200
201  # Format of 'bisect_contents':
202  # {
203  #   'start': [START_REVISION_OF_BISECTION]
204  #   'end': [END_REVISION_OF_BISECTION]
205  #   'jobs' : [
206  #       {[TRYJOB_INFORMATION]},
207  #       {[TRYJOB_INFORMATION]},
208  #       ...,
209  #       {[TRYJOB_INFORMATION]}
210  #   ]
211  # }
212  with open(status_file) as tryjobs:
213    bisect_contents = json.load(tryjobs)
214
215  if not bisect_contents['jobs'] and modify_tryjob != ModifyTryjob.ADD:
216    sys.exit('No tryjobs in %s' % status_file)
217
218  tryjob_index = update_tryjob_status.FindTryjobIndex(revision,
219                                                      bisect_contents['jobs'])
220
221  # 'FindTryjobIndex()' returns None if the tryjob was not found.
222  if tryjob_index is None and modify_tryjob != ModifyTryjob.ADD:
223    raise ValueError('Unable to find tryjob for %d in %s' %
224                     (revision, status_file))
225
226  # Determine the action to take based off of 'modify_tryjob'.
227  if modify_tryjob == ModifyTryjob.REMOVE:
228    del bisect_contents['jobs'][tryjob_index]
229
230    print('Successfully deleted the tryjob of revision %d' % revision)
231  elif modify_tryjob == ModifyTryjob.RELAUNCH:
232    # Need to update the tryjob link and buildbucket ID.
233    tryjob_results = update_packages_and_run_tests.RunTryJobs(
234        bisect_contents['jobs'][tryjob_index]['cl'],
235        bisect_contents['jobs'][tryjob_index]['extra_cls'],
236        bisect_contents['jobs'][tryjob_index]['options'],
237        bisect_contents['jobs'][tryjob_index]['builder'], chroot_path, verbose)
238
239    bisect_contents['jobs'][tryjob_index][
240        'status'] = update_tryjob_status.TryjobStatus.PENDING.value
241    bisect_contents['jobs'][tryjob_index]['link'] = tryjob_results[0]['link']
242    bisect_contents['jobs'][tryjob_index]['buildbucket_id'] = tryjob_results[0][
243        'buildbucket_id']
244
245    print('Successfully relaunched the tryjob for revision %d and updated '
246          'the tryjob link to %s' % (revision, tryjob_results[0]['link']))
247  elif modify_tryjob == ModifyTryjob.ADD:
248    # Tryjob exists already.
249    if tryjob_index is not None:
250      raise ValueError('Tryjob already exists (index is %d) in %s.' %
251                       (tryjob_index, status_file))
252
253    # Make sure the revision is within the bounds of the start and end of the
254    # bisection.
255    elif bisect_contents['start'] < revision < bisect_contents['end']:
256      update_packages = [
257          'sys-devel/llvm', 'sys-libs/compiler-rt', 'sys-libs/libcxx',
258          'sys-libs/libcxxabi', 'sys-libs/llvm-libunwind'
259      ]
260
261      patch_metadata_file = 'PATCHES.json'
262
263      git_hash, revision = get_llvm_hash.GetLLVMHashAndVersionFromSVNOption(
264          revision)
265
266      tryjob_dict = AddTryjob(update_packages, git_hash, revision, chroot_path,
267                              patch_metadata_file, extra_cls, options, builder,
268                              verbose, revision)
269
270      bisect_contents['jobs'].append(tryjob_dict)
271
272      print('Successfully added tryjob of revision %d' % revision)
273    else:
274      raise ValueError('Failed to add tryjob to %s' % status_file)
275  else:
276    raise ValueError('Invalid "modify_tryjob" option provided: %s' %
277                     modify_tryjob)
278
279  with open(status_file, 'w') as update_tryjobs:
280    json.dump(bisect_contents, update_tryjobs, indent=4, separators=(',', ': '))
281
282
283def main():
284  """Removes, relaunches, or adds a tryjob."""
285
286  chroot.VerifyOutsideChroot()
287
288  args_output = GetCommandLineArgs()
289
290  PerformTryjobModification(args_output.revision,
291                            ModifyTryjob(args_output.modify_tryjob),
292                            args_output.status_file,
293                            args_output.extra_change_lists, args_output.options,
294                            args_output.builder, args_output.chroot_path,
295                            args_output.verbose)
296
297
298if __name__ == '__main__':
299  main()
300