xref: /aosp_15_r20/external/crosvm/tools/chromeos/merge_bot (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1*bb4ee6a4SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*bb4ee6a4SAndroid Build Coastguard Worker# Copyright 2022 The ChromiumOS Authors
3*bb4ee6a4SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
4*bb4ee6a4SAndroid Build Coastguard Worker# found in the LICENSE file.
5*bb4ee6a4SAndroid Build Coastguard Worker
6*bb4ee6a4SAndroid Build Coastguard Worker# This script is used by the CI system to regularly update the merge and dry run changes.
7*bb4ee6a4SAndroid Build Coastguard Worker#
8*bb4ee6a4SAndroid Build Coastguard Worker# It can be run locally as well, however some permissions are only given to the bot's service
9*bb4ee6a4SAndroid Build Coastguard Worker# account (and are enabled with --is-bot).
10*bb4ee6a4SAndroid Build Coastguard Worker#
11*bb4ee6a4SAndroid Build Coastguard Worker# See `./tools/chromeos/merge_bot -h` for details.
12*bb4ee6a4SAndroid Build Coastguard Worker#
13*bb4ee6a4SAndroid Build Coastguard Worker# When testing this script locally, use MERGE_BOT_TEST=1 ./tools/chromeos/merge_bot
14*bb4ee6a4SAndroid Build Coastguard Worker# to use different tags and prevent emails from being sent or the CQ from being triggered.
15*bb4ee6a4SAndroid Build Coastguard Worker
16*bb4ee6a4SAndroid Build Coastguard Workerfrom contextlib import contextmanager
17*bb4ee6a4SAndroid Build Coastguard Workerimport os
18*bb4ee6a4SAndroid Build Coastguard Workerfrom pathlib import Path
19*bb4ee6a4SAndroid Build Coastguard Workerimport sys
20*bb4ee6a4SAndroid Build Coastguard Workerfrom datetime import date
21*bb4ee6a4SAndroid Build Coastguard Workerfrom typing import List
22*bb4ee6a4SAndroid Build Coastguard Workerimport random
23*bb4ee6a4SAndroid Build Coastguard Workerimport string
24*bb4ee6a4SAndroid Build Coastguard Worker
25*bb4ee6a4SAndroid Build Coastguard Workersys.path.append(os.path.dirname(sys.path[0]))
26*bb4ee6a4SAndroid Build Coastguard Worker
27*bb4ee6a4SAndroid Build Coastguard Workerimport re
28*bb4ee6a4SAndroid Build Coastguard Worker
29*bb4ee6a4SAndroid Build Coastguard Workerfrom impl.common import CROSVM_ROOT, batched, cmd, quoted, run_commands, GerritChange, GERRIT_URL
30*bb4ee6a4SAndroid Build Coastguard Worker
31*bb4ee6a4SAndroid Build Coastguard Workergit = cmd("git")
32*bb4ee6a4SAndroid Build Coastguard Workergit_log = git("log --decorate=no --color=never")
33*bb4ee6a4SAndroid Build Coastguard Workercurl = cmd("curl --silent --fail")
34*bb4ee6a4SAndroid Build Coastguard Workerchmod = cmd("chmod")
35*bb4ee6a4SAndroid Build Coastguard Workerdev_container = cmd("tools/dev_container")
36*bb4ee6a4SAndroid Build Coastguard Workermkdir = cmd("mkdir -p")
37*bb4ee6a4SAndroid Build Coastguard Worker
38*bb4ee6a4SAndroid Build Coastguard WorkerUPSTREAM_URL = "https://chromium.googlesource.com/crosvm/crosvm"
39*bb4ee6a4SAndroid Build Coastguard WorkerCROS_URL = "https://chromium.googlesource.com/chromiumos/platform/crosvm"
40*bb4ee6a4SAndroid Build Coastguard Worker
41*bb4ee6a4SAndroid Build Coastguard Worker# Gerrit tags used to identify bot changes.
42*bb4ee6a4SAndroid Build Coastguard WorkerTESTING = "MERGE_BOT_TEST" in os.environ
43*bb4ee6a4SAndroid Build Coastguard Workerif TESTING:
44*bb4ee6a4SAndroid Build Coastguard Worker    MERGE_TAG = "testing-crosvm-merge"
45*bb4ee6a4SAndroid Build Coastguard Worker    DRY_RUN_TAG = "testing-crosvm-merge-dry-run"
46*bb4ee6a4SAndroid Build Coastguard Workerelse:
47*bb4ee6a4SAndroid Build Coastguard Worker    MERGE_TAG = "crosvm-merge"  # type: ignore
48*bb4ee6a4SAndroid Build Coastguard Worker    DRY_RUN_TAG = "crosvm-merge-dry-run"  # type: ignore
49*bb4ee6a4SAndroid Build Coastguard Worker
50*bb4ee6a4SAndroid Build Coastguard Worker# This is the email of the account that posts CQ messages.
51*bb4ee6a4SAndroid Build Coastguard WorkerLUCI_EMAIL = "[email protected]"
52*bb4ee6a4SAndroid Build Coastguard Worker
53*bb4ee6a4SAndroid Build Coastguard Worker# Do not create more dry runs than this within a 24h timespan
54*bb4ee6a4SAndroid Build Coastguard WorkerMAX_DRY_RUNS_PER_DAY = 2
55*bb4ee6a4SAndroid Build Coastguard Worker
56*bb4ee6a4SAndroid Build Coastguard Worker
57*bb4ee6a4SAndroid Build Coastguard Workerdef list_active_merges():
58*bb4ee6a4SAndroid Build Coastguard Worker    return GerritChange.query(
59*bb4ee6a4SAndroid Build Coastguard Worker        "project:chromiumos/platform/crosvm",
60*bb4ee6a4SAndroid Build Coastguard Worker        "branch:chromeos",
61*bb4ee6a4SAndroid Build Coastguard Worker        "status:open",
62*bb4ee6a4SAndroid Build Coastguard Worker        f"hashtag:{MERGE_TAG}",
63*bb4ee6a4SAndroid Build Coastguard Worker    )
64*bb4ee6a4SAndroid Build Coastguard Worker
65*bb4ee6a4SAndroid Build Coastguard Worker
66*bb4ee6a4SAndroid Build Coastguard Workerdef list_active_dry_runs():
67*bb4ee6a4SAndroid Build Coastguard Worker    return GerritChange.query(
68*bb4ee6a4SAndroid Build Coastguard Worker        "project:chromiumos/platform/crosvm",
69*bb4ee6a4SAndroid Build Coastguard Worker        "branch:chromeos",
70*bb4ee6a4SAndroid Build Coastguard Worker        "status:open",
71*bb4ee6a4SAndroid Build Coastguard Worker        f"hashtag:{DRY_RUN_TAG}",
72*bb4ee6a4SAndroid Build Coastguard Worker    )
73*bb4ee6a4SAndroid Build Coastguard Worker
74*bb4ee6a4SAndroid Build Coastguard Worker
75*bb4ee6a4SAndroid Build Coastguard Workerdef list_recent_dry_runs(age: str):
76*bb4ee6a4SAndroid Build Coastguard Worker    return GerritChange.query(
77*bb4ee6a4SAndroid Build Coastguard Worker        "project:chromiumos/platform/crosvm",
78*bb4ee6a4SAndroid Build Coastguard Worker        "branch:chromeos",
79*bb4ee6a4SAndroid Build Coastguard Worker        f"-age:{age}",
80*bb4ee6a4SAndroid Build Coastguard Worker        f"hashtag:{DRY_RUN_TAG}",
81*bb4ee6a4SAndroid Build Coastguard Worker    )
82*bb4ee6a4SAndroid Build Coastguard Worker
83*bb4ee6a4SAndroid Build Coastguard Worker
84*bb4ee6a4SAndroid Build Coastguard Workerdef bug_notes(commit_range: str):
85*bb4ee6a4SAndroid Build Coastguard Worker    "Returns a string with all BUG=... lines of the specified commit range."
86*bb4ee6a4SAndroid Build Coastguard Worker    return "\n".join(
87*bb4ee6a4SAndroid Build Coastguard Worker        set(
88*bb4ee6a4SAndroid Build Coastguard Worker            line
89*bb4ee6a4SAndroid Build Coastguard Worker            for line in git_log(commit_range, "--pretty=%b").lines()
90*bb4ee6a4SAndroid Build Coastguard Worker            if re.match(r"^BUG=", line, re.I) and not re.match(r"^BUG=None", line, re.I)
91*bb4ee6a4SAndroid Build Coastguard Worker        )
92*bb4ee6a4SAndroid Build Coastguard Worker    )
93*bb4ee6a4SAndroid Build Coastguard Worker
94*bb4ee6a4SAndroid Build Coastguard Worker
95*bb4ee6a4SAndroid Build Coastguard Workerdef setup_tracking_branch(branch_name: str, tracking: str):
96*bb4ee6a4SAndroid Build Coastguard Worker    "Create and checkout `branch_name` tracking `tracking`. Overwrites existing branch."
97*bb4ee6a4SAndroid Build Coastguard Worker    git("fetch -q cros", tracking).fg()
98*bb4ee6a4SAndroid Build Coastguard Worker    git("checkout", f"cros/{tracking}").fg(quiet=True)
99*bb4ee6a4SAndroid Build Coastguard Worker    git("branch -D", branch_name).fg(quiet=True, check=False)
100*bb4ee6a4SAndroid Build Coastguard Worker    git("checkout -b", branch_name, "--track", f"cros/{tracking}").fg()
101*bb4ee6a4SAndroid Build Coastguard Worker
102*bb4ee6a4SAndroid Build Coastguard Worker
103*bb4ee6a4SAndroid Build Coastguard Worker@contextmanager
104*bb4ee6a4SAndroid Build Coastguard Workerdef tracking_branch_context(branch_name: str, tracking: str):
105*bb4ee6a4SAndroid Build Coastguard Worker    "Switches to a tracking branch and back after the context is exited."
106*bb4ee6a4SAndroid Build Coastguard Worker    # Remember old head. Prefer branch name if available, otherwise revision of detached head.
107*bb4ee6a4SAndroid Build Coastguard Worker    old_head = git("symbolic-ref -q --short HEAD").stdout(check=False)
108*bb4ee6a4SAndroid Build Coastguard Worker    if not old_head:
109*bb4ee6a4SAndroid Build Coastguard Worker        old_head = git("rev-parse HEAD").stdout()
110*bb4ee6a4SAndroid Build Coastguard Worker    setup_tracking_branch(branch_name, tracking)
111*bb4ee6a4SAndroid Build Coastguard Worker    yield
112*bb4ee6a4SAndroid Build Coastguard Worker    git("checkout", old_head).fg()
113*bb4ee6a4SAndroid Build Coastguard Worker
114*bb4ee6a4SAndroid Build Coastguard Worker
115*bb4ee6a4SAndroid Build Coastguard Workerdef gerrit_prerequisites():
116*bb4ee6a4SAndroid Build Coastguard Worker    "Make sure we can upload to gerrit."
117*bb4ee6a4SAndroid Build Coastguard Worker
118*bb4ee6a4SAndroid Build Coastguard Worker    # Setup cros remote which we are merging into
119*bb4ee6a4SAndroid Build Coastguard Worker    if git("remote get-url cros").fg(check=False) != 0:
120*bb4ee6a4SAndroid Build Coastguard Worker        print("Setting up remote: cros")
121*bb4ee6a4SAndroid Build Coastguard Worker        git("remote add cros", CROS_URL).fg()
122*bb4ee6a4SAndroid Build Coastguard Worker    actual_remote = git("remote get-url cros").stdout()
123*bb4ee6a4SAndroid Build Coastguard Worker    if actual_remote != CROS_URL:
124*bb4ee6a4SAndroid Build Coastguard Worker        print(f"WARNING: Your remote 'cros' is {actual_remote} and does not match {CROS_URL}")
125*bb4ee6a4SAndroid Build Coastguard Worker
126*bb4ee6a4SAndroid Build Coastguard Worker    # Install gerrit Change-Id hook
127*bb4ee6a4SAndroid Build Coastguard Worker    hook_path = CROSVM_ROOT / ".git/hooks/commit-msg"
128*bb4ee6a4SAndroid Build Coastguard Worker    if not hook_path.exists():
129*bb4ee6a4SAndroid Build Coastguard Worker        hook_path.parent.mkdir(exist_ok=True)
130*bb4ee6a4SAndroid Build Coastguard Worker        curl(f"{GERRIT_URL}/tools/hooks/commit-msg").write_to(hook_path)
131*bb4ee6a4SAndroid Build Coastguard Worker        chmod("+x", hook_path).fg()
132*bb4ee6a4SAndroid Build Coastguard Worker
133*bb4ee6a4SAndroid Build Coastguard Worker
134*bb4ee6a4SAndroid Build Coastguard Workerdef upload_to_gerrit(target_branch: str, *extra_params: str):
135*bb4ee6a4SAndroid Build Coastguard Worker    if not TESTING:
136*bb4ee6a4SAndroid Build Coastguard Worker        extra_params = ("[email protected]", *extra_params)
137*bb4ee6a4SAndroid Build Coastguard Worker    for i in range(3):
138*bb4ee6a4SAndroid Build Coastguard Worker        try:
139*bb4ee6a4SAndroid Build Coastguard Worker            print(f"Uploading to gerrit (Attempt {i})")
140*bb4ee6a4SAndroid Build Coastguard Worker            git(f"push cros HEAD:refs/for/{target_branch}%{','.join(extra_params)}").fg()
141*bb4ee6a4SAndroid Build Coastguard Worker            return
142*bb4ee6a4SAndroid Build Coastguard Worker        except:
143*bb4ee6a4SAndroid Build Coastguard Worker            continue
144*bb4ee6a4SAndroid Build Coastguard Worker    raise Exception("Could not upload changes to gerrit.")
145*bb4ee6a4SAndroid Build Coastguard Worker
146*bb4ee6a4SAndroid Build Coastguard Worker
147*bb4ee6a4SAndroid Build Coastguard Workerdef rename_files_to_random(dir_path: str):
148*bb4ee6a4SAndroid Build Coastguard Worker    "Rename all files in a folder to random file names with extension kept"
149*bb4ee6a4SAndroid Build Coastguard Worker    print("Renaming all files in " + dir_path)
150*bb4ee6a4SAndroid Build Coastguard Worker    file_names = os.listdir(dir_path)
151*bb4ee6a4SAndroid Build Coastguard Worker    for file_name in filter(os.path.isfile, map(lambda x: os.path.join(dir_path, x), file_names)):
152*bb4ee6a4SAndroid Build Coastguard Worker        file_extension = os.path.splitext(file_name)[1]
153*bb4ee6a4SAndroid Build Coastguard Worker        new_name_stem = "".join(
154*bb4ee6a4SAndroid Build Coastguard Worker            random.choice(string.ascii_lowercase + string.digits) for _ in range(16)
155*bb4ee6a4SAndroid Build Coastguard Worker        )
156*bb4ee6a4SAndroid Build Coastguard Worker        new_path = os.path.join(dir_path, new_name_stem + file_extension)
157*bb4ee6a4SAndroid Build Coastguard Worker        print(f"Renaming {file_name} to {new_path}")
158*bb4ee6a4SAndroid Build Coastguard Worker        os.rename(file_name, new_path)
159*bb4ee6a4SAndroid Build Coastguard Worker
160*bb4ee6a4SAndroid Build Coastguard Worker
161*bb4ee6a4SAndroid Build Coastguard Workerdef create_pgo_profile():
162*bb4ee6a4SAndroid Build Coastguard Worker    "Create PGO profile matching HEAD at merge."
163*bb4ee6a4SAndroid Build Coastguard Worker    has_kvm = os.path.exists("/dev/kvm")
164*bb4ee6a4SAndroid Build Coastguard Worker    if not has_kvm:
165*bb4ee6a4SAndroid Build Coastguard Worker        return
166*bb4ee6a4SAndroid Build Coastguard Worker    os.chdir(CROSVM_ROOT)
167*bb4ee6a4SAndroid Build Coastguard Worker    tmpdirname = "target/pgotmp/" + "".join(
168*bb4ee6a4SAndroid Build Coastguard Worker        random.choice(string.ascii_lowercase + string.digits) for _ in range(16)
169*bb4ee6a4SAndroid Build Coastguard Worker    )
170*bb4ee6a4SAndroid Build Coastguard Worker    mkdir(tmpdirname).fg()
171*bb4ee6a4SAndroid Build Coastguard Worker    benchmark_list = list(
172*bb4ee6a4SAndroid Build Coastguard Worker        map(
173*bb4ee6a4SAndroid Build Coastguard Worker            lambda x: os.path.splitext(x)[0],
174*bb4ee6a4SAndroid Build Coastguard Worker            filter(lambda x: x.endswith(".rs"), os.listdir("e2e_tests/benches")),
175*bb4ee6a4SAndroid Build Coastguard Worker        )
176*bb4ee6a4SAndroid Build Coastguard Worker    )
177*bb4ee6a4SAndroid Build Coastguard Worker    print(f"Building instrumented binary, perf data will be saved to {tmpdirname}")
178*bb4ee6a4SAndroid Build Coastguard Worker    dev_container(
179*bb4ee6a4SAndroid Build Coastguard Worker        "./tools/build_release --build-profile release --profile-generate /workspace/" + tmpdirname
180*bb4ee6a4SAndroid Build Coastguard Worker    ).fg()
181*bb4ee6a4SAndroid Build Coastguard Worker    print()
182*bb4ee6a4SAndroid Build Coastguard Worker    print("List of benchmarks to run:")
183*bb4ee6a4SAndroid Build Coastguard Worker    for bench_name in benchmark_list:
184*bb4ee6a4SAndroid Build Coastguard Worker        print(bench_name)
185*bb4ee6a4SAndroid Build Coastguard Worker    print()
186*bb4ee6a4SAndroid Build Coastguard Worker    dev_container("mkdir -p /var/empty").fg()
187*bb4ee6a4SAndroid Build Coastguard Worker    for bench_name in benchmark_list:
188*bb4ee6a4SAndroid Build Coastguard Worker        print(f"Running bechmark: {bench_name}")
189*bb4ee6a4SAndroid Build Coastguard Worker        dev_container(f"./tools/bench {bench_name}").fg()
190*bb4ee6a4SAndroid Build Coastguard Worker        # Instrumented binary always give same file name to generated .profraw files, rename to avoid
191*bb4ee6a4SAndroid Build Coastguard Worker        # overwriting profile from previous bench suite
192*bb4ee6a4SAndroid Build Coastguard Worker        rename_files_to_random(tmpdirname)
193*bb4ee6a4SAndroid Build Coastguard Worker    mkdir("profiles").fg()
194*bb4ee6a4SAndroid Build Coastguard Worker    dev_container(
195*bb4ee6a4SAndroid Build Coastguard Worker        f"cargo profdata -- merge -o /workspace/profiles/benchmarks.profdata /workspace/{tmpdirname}"
196*bb4ee6a4SAndroid Build Coastguard Worker    ).fg()
197*bb4ee6a4SAndroid Build Coastguard Worker    dev_container("xz -f -9e -T 0 /workspace/profiles/benchmarks.profdata").fg()
198*bb4ee6a4SAndroid Build Coastguard Worker
199*bb4ee6a4SAndroid Build Coastguard Worker
200*bb4ee6a4SAndroid Build Coastguard Worker####################################################################################################
201*bb4ee6a4SAndroid Build Coastguard Worker# The functions below are callable via the command line
202*bb4ee6a4SAndroid Build Coastguard Worker
203*bb4ee6a4SAndroid Build Coastguard Worker
204*bb4ee6a4SAndroid Build Coastguard Workerdef create_merge_commits(
205*bb4ee6a4SAndroid Build Coastguard Worker    revision: str, max_size: int = 0, create_dry_run: bool = False, force_pgo: bool = False
206*bb4ee6a4SAndroid Build Coastguard Worker):
207*bb4ee6a4SAndroid Build Coastguard Worker    "Merges `revision` into HEAD, creating merge commits including at most `max-size` commits."
208*bb4ee6a4SAndroid Build Coastguard Worker    os.chdir(CROSVM_ROOT)
209*bb4ee6a4SAndroid Build Coastguard Worker
210*bb4ee6a4SAndroid Build Coastguard Worker    # Find list of commits to merge, then batch them into smaller merges.
211*bb4ee6a4SAndroid Build Coastguard Worker    commits = git_log(f"HEAD..{revision}", "--pretty=%H").lines()
212*bb4ee6a4SAndroid Build Coastguard Worker    if not commits:
213*bb4ee6a4SAndroid Build Coastguard Worker        print("Nothing to merge.")
214*bb4ee6a4SAndroid Build Coastguard Worker        return (0, False)
215*bb4ee6a4SAndroid Build Coastguard Worker    else:
216*bb4ee6a4SAndroid Build Coastguard Worker        commit_authors = git_log(f"HEAD..{revision}", "--pretty=%an").lines()
217*bb4ee6a4SAndroid Build Coastguard Worker        if all(map(lambda x: x == "recipe-roller", commit_authors)):
218*bb4ee6a4SAndroid Build Coastguard Worker            print("All commits are from recipe roller, don't merge yet")
219*bb4ee6a4SAndroid Build Coastguard Worker            return (0, False)
220*bb4ee6a4SAndroid Build Coastguard Worker
221*bb4ee6a4SAndroid Build Coastguard Worker    # Create a merge commit for each batch
222*bb4ee6a4SAndroid Build Coastguard Worker    batches = list(batched(commits, max_size)) if max_size > 0 else [commits]
223*bb4ee6a4SAndroid Build Coastguard Worker    has_conflicts = False
224*bb4ee6a4SAndroid Build Coastguard Worker    for i, batch in enumerate(reversed(batches)):
225*bb4ee6a4SAndroid Build Coastguard Worker        target = batch[0]
226*bb4ee6a4SAndroid Build Coastguard Worker        previous_rev = git(f"rev-parse {batch[-1]}^").stdout()
227*bb4ee6a4SAndroid Build Coastguard Worker        commit_range = f"{previous_rev}..{batch[0]}"
228*bb4ee6a4SAndroid Build Coastguard Worker
229*bb4ee6a4SAndroid Build Coastguard Worker        # Put together a message containing info about what's in the merge.
230*bb4ee6a4SAndroid Build Coastguard Worker        batch_str = f"{i + 1}/{len(batches)}" if len(batches) > 1 else ""
231*bb4ee6a4SAndroid Build Coastguard Worker        title = "Merge with upstream" if not create_dry_run else f"Merge dry run"
232*bb4ee6a4SAndroid Build Coastguard Worker        message = "\n\n".join(
233*bb4ee6a4SAndroid Build Coastguard Worker            [
234*bb4ee6a4SAndroid Build Coastguard Worker                f"{title} {date.today().isoformat()} {batch_str}",
235*bb4ee6a4SAndroid Build Coastguard Worker                git_log(commit_range, "--oneline").stdout(),
236*bb4ee6a4SAndroid Build Coastguard Worker                f"{UPSTREAM_URL}/+log/{commit_range}",
237*bb4ee6a4SAndroid Build Coastguard Worker                *([bug_notes(commit_range)] if not create_dry_run else []),
238*bb4ee6a4SAndroid Build Coastguard Worker            ]
239*bb4ee6a4SAndroid Build Coastguard Worker        )
240*bb4ee6a4SAndroid Build Coastguard Worker
241*bb4ee6a4SAndroid Build Coastguard Worker        # git 'trailers' go into a separate paragraph to make sure they are properly separated.
242*bb4ee6a4SAndroid Build Coastguard Worker        trailers = "Commit: False" if create_dry_run or TESTING else ""
243*bb4ee6a4SAndroid Build Coastguard Worker
244*bb4ee6a4SAndroid Build Coastguard Worker        # Perfom merge
245*bb4ee6a4SAndroid Build Coastguard Worker        code = git("merge --no-ff", target, "-m", quoted(message), "-m", quoted(trailers)).fg(
246*bb4ee6a4SAndroid Build Coastguard Worker            check=False
247*bb4ee6a4SAndroid Build Coastguard Worker        )
248*bb4ee6a4SAndroid Build Coastguard Worker        if code != 0:
249*bb4ee6a4SAndroid Build Coastguard Worker            if not Path(".git/MERGE_HEAD").exists():
250*bb4ee6a4SAndroid Build Coastguard Worker                raise Exception("git merge failed for a reason other than merge conflicts.")
251*bb4ee6a4SAndroid Build Coastguard Worker            print("Merge has conflicts. Creating commit with conflict markers.")
252*bb4ee6a4SAndroid Build Coastguard Worker            git("add --update .").fg()
253*bb4ee6a4SAndroid Build Coastguard Worker            message = f"(CONFLICT) {message}"
254*bb4ee6a4SAndroid Build Coastguard Worker            git("commit", "-m", quoted(message), "-m", quoted(trailers)).fg()
255*bb4ee6a4SAndroid Build Coastguard Worker            has_conflicts = True
256*bb4ee6a4SAndroid Build Coastguard Worker        # Only uprev PGO profile on Monday to reduce impact on repo size
257*bb4ee6a4SAndroid Build Coastguard Worker        # TODO: b/181105093 - Re-evaluate throttling strategy after sometime
258*bb4ee6a4SAndroid Build Coastguard Worker        if date.today().weekday() == 0 or force_pgo:
259*bb4ee6a4SAndroid Build Coastguard Worker            create_pgo_profile()
260*bb4ee6a4SAndroid Build Coastguard Worker            git("add profiles/benchmarks.profdata.xz").fg()
261*bb4ee6a4SAndroid Build Coastguard Worker            git("commit --amend --no-edit").fg()
262*bb4ee6a4SAndroid Build Coastguard Worker
263*bb4ee6a4SAndroid Build Coastguard Worker    return (len(batches), has_conflicts)
264*bb4ee6a4SAndroid Build Coastguard Worker
265*bb4ee6a4SAndroid Build Coastguard Worker
266*bb4ee6a4SAndroid Build Coastguard Workerdef status():
267*bb4ee6a4SAndroid Build Coastguard Worker    "Shows the current status of pending merge and dry run changes in gerrit."
268*bb4ee6a4SAndroid Build Coastguard Worker    print("Active dry runs:")
269*bb4ee6a4SAndroid Build Coastguard Worker    for dry_run in list_active_dry_runs():
270*bb4ee6a4SAndroid Build Coastguard Worker        print(dry_run.pretty_info())
271*bb4ee6a4SAndroid Build Coastguard Worker    print()
272*bb4ee6a4SAndroid Build Coastguard Worker    print("Active merges:")
273*bb4ee6a4SAndroid Build Coastguard Worker    for merge in list_active_merges():
274*bb4ee6a4SAndroid Build Coastguard Worker        print(merge.pretty_info())
275*bb4ee6a4SAndroid Build Coastguard Worker
276*bb4ee6a4SAndroid Build Coastguard Worker
277*bb4ee6a4SAndroid Build Coastguard Workerdef update_merges(
278*bb4ee6a4SAndroid Build Coastguard Worker    revision: str,
279*bb4ee6a4SAndroid Build Coastguard Worker    target_branch: str = "chromeos",
280*bb4ee6a4SAndroid Build Coastguard Worker    max_size: int = 15,
281*bb4ee6a4SAndroid Build Coastguard Worker    is_bot: bool = False,
282*bb4ee6a4SAndroid Build Coastguard Worker):
283*bb4ee6a4SAndroid Build Coastguard Worker    """Uploads a new set of merge commits if the previous batch has been submitted."""
284*bb4ee6a4SAndroid Build Coastguard Worker    gerrit_prerequisites()
285*bb4ee6a4SAndroid Build Coastguard Worker    parsed_revision = git("rev-parse", revision).stdout()
286*bb4ee6a4SAndroid Build Coastguard Worker
287*bb4ee6a4SAndroid Build Coastguard Worker    active_merges = list_active_merges()
288*bb4ee6a4SAndroid Build Coastguard Worker    if active_merges:
289*bb4ee6a4SAndroid Build Coastguard Worker        print("Nothing to do. Previous merges are still pending:")
290*bb4ee6a4SAndroid Build Coastguard Worker        for merge in active_merges:
291*bb4ee6a4SAndroid Build Coastguard Worker            print(merge.pretty_info())
292*bb4ee6a4SAndroid Build Coastguard Worker        return
293*bb4ee6a4SAndroid Build Coastguard Worker    else:
294*bb4ee6a4SAndroid Build Coastguard Worker        print(f"Creating merge of {parsed_revision} into cros/{target_branch}")
295*bb4ee6a4SAndroid Build Coastguard Worker        with tracking_branch_context("merge-bot-branch", target_branch):
296*bb4ee6a4SAndroid Build Coastguard Worker            count, has_conflicts = create_merge_commits(
297*bb4ee6a4SAndroid Build Coastguard Worker                parsed_revision, max_size, create_dry_run=False
298*bb4ee6a4SAndroid Build Coastguard Worker            )
299*bb4ee6a4SAndroid Build Coastguard Worker            if count > 0:
300*bb4ee6a4SAndroid Build Coastguard Worker                labels: List[str] = []
301*bb4ee6a4SAndroid Build Coastguard Worker                if not has_conflicts:
302*bb4ee6a4SAndroid Build Coastguard Worker                    if not TESTING:
303*bb4ee6a4SAndroid Build Coastguard Worker                        labels.append("l=Commit-Queue+1")
304*bb4ee6a4SAndroid Build Coastguard Worker                    if is_bot:
305*bb4ee6a4SAndroid Build Coastguard Worker                        labels.append("l=Bot-Commit+1")
306*bb4ee6a4SAndroid Build Coastguard Worker                upload_to_gerrit(target_branch, f"hashtag={MERGE_TAG}", *labels)
307*bb4ee6a4SAndroid Build Coastguard Worker
308*bb4ee6a4SAndroid Build Coastguard Worker
309*bb4ee6a4SAndroid Build Coastguard Workerdef update_dry_runs(
310*bb4ee6a4SAndroid Build Coastguard Worker    revision: str,
311*bb4ee6a4SAndroid Build Coastguard Worker    target_branch: str = "chromeos",
312*bb4ee6a4SAndroid Build Coastguard Worker    max_size: int = 0,
313*bb4ee6a4SAndroid Build Coastguard Worker    is_bot: bool = False,
314*bb4ee6a4SAndroid Build Coastguard Worker):
315*bb4ee6a4SAndroid Build Coastguard Worker    """
316*bb4ee6a4SAndroid Build Coastguard Worker    Maintains dry run changes in gerrit, usually run by the crosvm bot, but can be called by
317*bb4ee6a4SAndroid Build Coastguard Worker    developers as well.
318*bb4ee6a4SAndroid Build Coastguard Worker    """
319*bb4ee6a4SAndroid Build Coastguard Worker    gerrit_prerequisites()
320*bb4ee6a4SAndroid Build Coastguard Worker    parsed_revision = git("rev-parse", revision).stdout()
321*bb4ee6a4SAndroid Build Coastguard Worker
322*bb4ee6a4SAndroid Build Coastguard Worker    # Close active dry runs if they are done.
323*bb4ee6a4SAndroid Build Coastguard Worker    print("Checking active dry runs")
324*bb4ee6a4SAndroid Build Coastguard Worker    for dry_run in list_active_dry_runs():
325*bb4ee6a4SAndroid Build Coastguard Worker        cq_votes = dry_run.get_votes("Commit-Queue")
326*bb4ee6a4SAndroid Build Coastguard Worker        if not cq_votes or max(cq_votes) > 0:
327*bb4ee6a4SAndroid Build Coastguard Worker            print(dry_run, "CQ is still running.")
328*bb4ee6a4SAndroid Build Coastguard Worker            continue
329*bb4ee6a4SAndroid Build Coastguard Worker
330*bb4ee6a4SAndroid Build Coastguard Worker        # Check for luci results and add V+-1 votes to make it easier to identify failed dry runs.
331*bb4ee6a4SAndroid Build Coastguard Worker        luci_messages = dry_run.get_messages_by(LUCI_EMAIL)
332*bb4ee6a4SAndroid Build Coastguard Worker        if not luci_messages:
333*bb4ee6a4SAndroid Build Coastguard Worker            print(dry_run, "No luci messages yet.")
334*bb4ee6a4SAndroid Build Coastguard Worker            continue
335*bb4ee6a4SAndroid Build Coastguard Worker
336*bb4ee6a4SAndroid Build Coastguard Worker        last_luci_message = luci_messages[-1]
337*bb4ee6a4SAndroid Build Coastguard Worker        if "This CL passed the CQ dry run" in last_luci_message or (
338*bb4ee6a4SAndroid Build Coastguard Worker            "This CL has passed the run" in last_luci_message
339*bb4ee6a4SAndroid Build Coastguard Worker        ):
340*bb4ee6a4SAndroid Build Coastguard Worker            dry_run.review(
341*bb4ee6a4SAndroid Build Coastguard Worker                "I think this dry run was SUCCESSFUL.",
342*bb4ee6a4SAndroid Build Coastguard Worker                {
343*bb4ee6a4SAndroid Build Coastguard Worker                    "Verified": 1,
344*bb4ee6a4SAndroid Build Coastguard Worker                    "Bot-Commit": 0,
345*bb4ee6a4SAndroid Build Coastguard Worker                },
346*bb4ee6a4SAndroid Build Coastguard Worker            )
347*bb4ee6a4SAndroid Build Coastguard Worker        elif "Failed builds" in last_luci_message or (
348*bb4ee6a4SAndroid Build Coastguard Worker            "This CL has failed the run. Reason:" in last_luci_message
349*bb4ee6a4SAndroid Build Coastguard Worker        ):
350*bb4ee6a4SAndroid Build Coastguard Worker            dry_run.review(
351*bb4ee6a4SAndroid Build Coastguard Worker                "I think this dry run FAILED.",
352*bb4ee6a4SAndroid Build Coastguard Worker                {
353*bb4ee6a4SAndroid Build Coastguard Worker                    "Verified": -1,
354*bb4ee6a4SAndroid Build Coastguard Worker                    "Bot-Commit": 0,
355*bb4ee6a4SAndroid Build Coastguard Worker                },
356*bb4ee6a4SAndroid Build Coastguard Worker            )
357*bb4ee6a4SAndroid Build Coastguard Worker
358*bb4ee6a4SAndroid Build Coastguard Worker        dry_run.abandon("Dry completed.")
359*bb4ee6a4SAndroid Build Coastguard Worker
360*bb4ee6a4SAndroid Build Coastguard Worker    active_dry_runs = list_active_dry_runs()
361*bb4ee6a4SAndroid Build Coastguard Worker    if active_dry_runs:
362*bb4ee6a4SAndroid Build Coastguard Worker        print("There are active dry runs, not creating a new one.")
363*bb4ee6a4SAndroid Build Coastguard Worker        print("Active dry runs:")
364*bb4ee6a4SAndroid Build Coastguard Worker        for dry_run in active_dry_runs:
365*bb4ee6a4SAndroid Build Coastguard Worker            print(dry_run.pretty_info())
366*bb4ee6a4SAndroid Build Coastguard Worker        return
367*bb4ee6a4SAndroid Build Coastguard Worker
368*bb4ee6a4SAndroid Build Coastguard Worker    num_dry_runs = len(list_recent_dry_runs("1d"))
369*bb4ee6a4SAndroid Build Coastguard Worker    if num_dry_runs >= MAX_DRY_RUNS_PER_DAY:
370*bb4ee6a4SAndroid Build Coastguard Worker        print(f"Already created {num_dry_runs} in the past 24h. Not creating another one.")
371*bb4ee6a4SAndroid Build Coastguard Worker        return
372*bb4ee6a4SAndroid Build Coastguard Worker
373*bb4ee6a4SAndroid Build Coastguard Worker    print(f"Creating dry run merge of {parsed_revision} into cros/{target_branch}")
374*bb4ee6a4SAndroid Build Coastguard Worker    with tracking_branch_context("merge-bot-branch", target_branch):
375*bb4ee6a4SAndroid Build Coastguard Worker        count, has_conflicts = create_merge_commits(
376*bb4ee6a4SAndroid Build Coastguard Worker            parsed_revision, max_size, create_dry_run=True, force_pgo=True
377*bb4ee6a4SAndroid Build Coastguard Worker        )
378*bb4ee6a4SAndroid Build Coastguard Worker        if count > 0 and not has_conflicts:
379*bb4ee6a4SAndroid Build Coastguard Worker            upload_to_gerrit(
380*bb4ee6a4SAndroid Build Coastguard Worker                target_branch,
381*bb4ee6a4SAndroid Build Coastguard Worker                f"hashtag={DRY_RUN_TAG}",
382*bb4ee6a4SAndroid Build Coastguard Worker                *(["l=Commit-Queue+1"] if not TESTING else []),
383*bb4ee6a4SAndroid Build Coastguard Worker                *(["l=Bot-Commit+1"] if is_bot else []),
384*bb4ee6a4SAndroid Build Coastguard Worker            )
385*bb4ee6a4SAndroid Build Coastguard Worker        else:
386*bb4ee6a4SAndroid Build Coastguard Worker            if has_conflicts:
387*bb4ee6a4SAndroid Build Coastguard Worker                print("Not uploading dry-run with conflicts.")
388*bb4ee6a4SAndroid Build Coastguard Worker            else:
389*bb4ee6a4SAndroid Build Coastguard Worker                print("Nothing to upload.")
390*bb4ee6a4SAndroid Build Coastguard Worker
391*bb4ee6a4SAndroid Build Coastguard Worker
392*bb4ee6a4SAndroid Build Coastguard Workerrun_commands(
393*bb4ee6a4SAndroid Build Coastguard Worker    create_merge_commits,
394*bb4ee6a4SAndroid Build Coastguard Worker    status,
395*bb4ee6a4SAndroid Build Coastguard Worker    update_merges,
396*bb4ee6a4SAndroid Build Coastguard Worker    update_dry_runs,
397*bb4ee6a4SAndroid Build Coastguard Worker    gerrit_prerequisites,
398*bb4ee6a4SAndroid Build Coastguard Worker)
399