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