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