diff options
author | George Burgess IV <gbiv@google.com> | 2024-04-02 17:35:47 -0600 |
---|---|---|
committer | Chromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2024-04-08 15:01:40 +0000 |
commit | f710649ffb8b4fd514cc990169e875db8424f22c (patch) | |
tree | 9ca5d1494842e659c81dfdb2809b6fea500b6213 | |
parent | 226bf89b8994bcaf17f7dd48b667d3c2e8e054f3 (diff) | |
download | toolchain-utils-f710649ffb8b4fd514cc990169e875db8424f22c.tar.gz |
afdo_tools: move git utilities into cros_utils
These will be useful soon for an llvm patch cleanup script I'm working
on. Factor them out to reduce maintenance.
BUG=b:332589934
TEST=Unittests & new script
Change-Id: I69114cc8df0a94a8edc9525571d1a7d45d04d369
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/5417086
Reviewed-by: Jordan Abrahams-Whitehead <ajordanr@google.com>
Commit-Queue: George Burgess <gbiv@chromium.org>
Tested-by: George Burgess <gbiv@chromium.org>
-rwxr-xr-x | afdo_tools/update_kernel_afdo.py | 79 | ||||
-rwxr-xr-x | afdo_tools/update_kernel_afdo_test.py | 54 | ||||
-rw-r--r-- | cros_utils/git_utils.py | 107 | ||||
-rwxr-xr-x | cros_utils/git_utils_test.py | 73 |
4 files changed, 193 insertions, 120 deletions
diff --git a/afdo_tools/update_kernel_afdo.py b/afdo_tools/update_kernel_afdo.py index fd7ae16c..a7e40763 100755 --- a/afdo_tools/update_kernel_afdo.py +++ b/afdo_tools/update_kernel_afdo.py @@ -24,11 +24,11 @@ import sys import tempfile from typing import Dict, Generator, Iterable, List, Optional, Tuple +from cros_utils import git_utils + # Folks who should be on the R-line of any CLs that get uploaded. -# Note that `c-compiler-chrome@` is managed by gwsq - it'll replace -# `R=c-compiler-chrome` with the current detective. -CL_REVIEWERS = ("c-compiler-chrome@google.com",) +CL_REVIEWERS = (git_utils.REVIEWER_DETECTIVE,) # Folks who should be on the CC-line of any CLs that get uploaded. CL_CC = ( @@ -737,55 +737,24 @@ def commit_new_profiles( ) -def parse_cl_from_upload_output(upload_output: str) -> str: - """Returns the CL number in the given upload output.""" - id_regex = re.compile( - r"^remote:\s+https://chromium-review\S+/\+/(\d+)\s", re.MULTILINE - ) - - results = id_regex.findall(upload_output) - if len(results) != 1: - raise ValueError( - f"Wanted exactly one match for {id_regex} in {upload_output!r}; " - f"found {len(results)}" - ) - return results[0] - - def upload_head_to_gerrit( toolchain_utils: Path, chromeos_tree: Optional[Path], branch: GitBranch, ): """Uploads HEAD to gerrit as a CL, and sets reviewers/CCs.""" - option_list = [f"r={x}" for x in CL_REVIEWERS] - option_list += (f"cc={x}" for x in CL_CC) - options = ",".join(option_list) - run_result = subprocess.run( - [ - "git", - "push", - branch.remote, - # https://gerrit-review.googlesource.com/Documentation/user-upload.html#reviewers - # for more info on the `%` params. - f"HEAD:refs/for/{branch.branch_name}%{options}", - ], - cwd=toolchain_utils, - check=False, - stdin=subprocess.DEVNULL, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - encoding="utf-8", - ) - - logging.info( - "`git push`ing to %s had this output:\n%s", + cl_ids = git_utils.upload_to_gerrit( + toolchain_utils, + branch.remote, branch.branch_name, - run_result.stdout, + CL_REVIEWERS, + CL_CC, ) - run_result.check_returncode() - cl_id = parse_cl_from_upload_output(run_result.stdout) + if len(cl_ids) > 1: + raise ValueError(f"Unexpected: wanted just one CL upload; got {cl_ids}") + + cl_id = cl_ids[0] logging.info("Uploaded CL http://crrev.com/c/%s successfully.", cl_id) if chromeos_tree is None: @@ -795,29 +764,7 @@ def upload_head_to_gerrit( ) return - # To make the life of the reviewers marginally easier, click buttons - # automatically. - gerrit_commands = ( - ["gerrit", "label-as", cl_id, "1"], - ["gerrit", "label-cq", cl_id, "1"], - ["gerrit", "label-v", cl_id, "1"], - ) - for cmd in gerrit_commands: - # Run the gerrit commands inside of toolchain_utils, since `gerrit` - # needs to be run inside of a ChromeOS tree to work. While - # `toolchain-utils` can be checked out on its own, that's not how this - # script is expeted to be used. - return_code = subprocess.run( - cmd, - cwd=chromeos_tree, - check=False, - stdin=subprocess.DEVNULL, - ).returncode - if return_code: - logging.warning( - "Failed to run gerrit command %s. Ignoring.", - shlex.join(cmd), - ) + git_utils.try_set_autosubmit_labels(chromeos_tree, cl_id) def find_chromeos_tree_root(a_dir: Path) -> Optional[Path]: diff --git a/afdo_tools/update_kernel_afdo_test.py b/afdo_tools/update_kernel_afdo_test.py index 79ed9d1c..1f365959 100755 --- a/afdo_tools/update_kernel_afdo_test.py +++ b/afdo_tools/update_kernel_afdo_test.py @@ -17,44 +17,6 @@ from unittest import mock import update_kernel_afdo -GERRIT_OUTPUT_WITH_ONE_CL = """ -Enumerating objects: 4, done. -Counting objects: 100% (4/4), done. -Delta compression using up to 128 threads -Compressing objects: 100% (2/2), done. -Writing objects: 100% (3/3), 320 bytes | 106.00 KiB/s, done. -Total 3 (delta 1), reused 1 (delta 0), pack-reused 0 (from 0) -remote: Resolving deltas: 100% (1/1) -remote: Processing changes: refs: 1, new: 1, done -remote: -remote: SUCCESS -remote: -remote: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/5375204 DO NOT COMMIT [WIP] [NEW] -remote: -To https://chromium.googlesource.com/chromiumos/third_party/toolchain-utils - * [new reference] HEAD -> refs/for/main -""" - -GERRIT_OUTPUT_WITH_TWO_CLS = """ -Enumerating objects: 4, done. -Counting objects: 100% (4/4), done. -Delta compression using up to 128 threads -Compressing objects: 100% (2/2), done. -Writing objects: 100% (3/3), 320 bytes | 106.00 KiB/s, done. -Total 3 (delta 1), reused 1 (delta 0), pack-reused 0 (from 0) -remote: Resolving deltas: 100% (1/1) -remote: Processing changes: refs: 1, new: 1, done -remote: -remote: SUCCESS -remote: -remote: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/5375204 DO NOT COMMIT [WIP] [NEW] -remote: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/5375205 DO NOT COMMIT [WIP] [NEW] -remote: -To https://chromium.googlesource.com/chromiumos/third_party/toolchain-utils - * [new reference] HEAD -> refs/for/main -""" - - class Test(unittest.TestCase): """Tests for update_kernel_afdo.""" @@ -321,22 +283,6 @@ TOTAL: 2 objects, 1234 bytes (1.1KiB) update_kernel_afdo.write_afdo_descriptor_file(file_path, contents) ) - def test_cl_parsing_from_gerrit_output(self): - self.assertEqual( - update_kernel_afdo.parse_cl_from_upload_output( - GERRIT_OUTPUT_WITH_ONE_CL - ), - "5375204", - ) - - with self.assertRaisesRegex(ValueError, ".*; found 0"): - update_kernel_afdo.parse_cl_from_upload_output("") - - with self.assertRaisesRegex(ValueError, ".*; found 2"): - update_kernel_afdo.parse_cl_from_upload_output( - GERRIT_OUTPUT_WITH_TWO_CLS - ) - def test_repo_autodetects_nothing_if_no_repo_dir(self): self.assertIsNone( update_kernel_afdo.find_chromeos_tree_root( diff --git a/cros_utils/git_utils.py b/cros_utils/git_utils.py new file mode 100644 index 00000000..1ae02ae0 --- /dev/null +++ b/cros_utils/git_utils.py @@ -0,0 +1,107 @@ +# Copyright 2024 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Shared utilities for working with git.""" + +import logging +from pathlib import Path +import re +import shlex +import subprocess +from typing import Iterable, List + + +# Email address used to tag the detective as a reviewer. +REVIEWER_DETECTIVE = "c-compiler-chrome@google.com" + + +def _parse_cls_from_upload_output(upload_output: str) -> List[int]: + """Returns the CL number in the given upload output.""" + id_regex = re.compile( + r"^remote:\s+https://chromium-review\S+/\+/(\d+)\s", re.MULTILINE + ) + + results = id_regex.findall(upload_output) + if not results: + raise ValueError( + f"Wanted at least one match for {id_regex} in {upload_output!r}; " + "found 0" + ) + return [int(x) for x in results] + + +def upload_to_gerrit( + git_repo: Path, + remote: str, + branch: str, + reviewers: Iterable[str] = (), + cc: Iterable[str] = (), + ref: str = "HEAD", +) -> List[int]: + """Uploads `ref` to gerrit, optionally adding reviewers/CCs.""" + # https://gerrit-review.googlesource.com/Documentation/user-upload.html#reviewers + # for more info on the `%` params. + option_list = [f"r={x}" for x in reviewers] + option_list += (f"cc={x}" for x in cc) + if option_list: + trailing_options = "%" + ",".join(option_list) + else: + trailing_options = "" + + run_result = subprocess.run( + [ + "git", + "push", + remote, + # https://gerrit-review.googlesource.com/Documentation/user-upload.html#reviewers + # for more info on the `%` params. + f"{ref}:refs/for/{branch}{trailing_options}", + ], + cwd=git_repo, + check=False, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding="utf-8", + ) + + logging.info( + "`git push`ing %s to %s/%s had this output:\n%s", + ref, + remote, + branch, + run_result.stdout, + ) + run_result.check_returncode() + return _parse_cls_from_upload_output(run_result.stdout) + + +def try_set_autosubmit_labels(chromeos_tree: Path, cl_id: int) -> None: + """Sets autosubmit on a CL. Logs - not raises - on failure. + + This sets a series of convenience labels on the given cl_number, so landing + it (e.g., for the detective) is as easy as possible. + """ + gerrit_cl_id = str(cl_id) + gerrit_commands = ( + ["gerrit", "label-as", gerrit_cl_id, "1"], + ["gerrit", "label-cq", gerrit_cl_id, "1"], + ["gerrit", "label-v", gerrit_cl_id, "1"], + ) + for cmd in gerrit_commands: + # Run the gerrit commands inside of toolchain_utils, since `gerrit` + # needs to be run inside of a ChromeOS tree to work. While + # `toolchain-utils` can be checked out on its own, that's not how this + # script is expeted to be used. + return_code = subprocess.run( + cmd, + cwd=chromeos_tree, + check=False, + stdin=subprocess.DEVNULL, + ).returncode + if return_code: + logging.warning( + "Failed to run gerrit command %s. Ignoring.", + shlex.join(cmd), + ) diff --git a/cros_utils/git_utils_test.py b/cros_utils/git_utils_test.py new file mode 100755 index 00000000..2edf1591 --- /dev/null +++ b/cros_utils/git_utils_test.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# Copyright 2024 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Tests for git_utils.""" + +import unittest + +from cros_utils import git_utils + + +# pylint: disable=protected-access + +GERRIT_OUTPUT_WITH_ONE_CL = """ +Enumerating objects: 4, done. +Counting objects: 100% (4/4), done. +Delta compression using up to 128 threads +Compressing objects: 100% (2/2), done. +Writing objects: 100% (3/3), 320 bytes | 106.00 KiB/s, done. +Total 3 (delta 1), reused 1 (delta 0), pack-reused 0 (from 0) +remote: Resolving deltas: 100% (1/1) +remote: Processing changes: refs: 1, new: 1, done +remote: +remote: SUCCESS +remote: +remote: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/5375204 DO NOT COMMIT [WIP] [NEW] +remote: +To https://chromium.googlesource.com/chromiumos/third_party/toolchain-utils + * [new reference] HEAD -> refs/for/main +""" + +GERRIT_OUTPUT_WITH_TWO_CLS = """ +Enumerating objects: 4, done. +Counting objects: 100% (4/4), done. +Delta compression using up to 128 threads +Compressing objects: 100% (2/2), done. +Writing objects: 100% (3/3), 320 bytes | 106.00 KiB/s, done. +Total 3 (delta 1), reused 1 (delta 0), pack-reused 0 (from 0) +remote: Resolving deltas: 100% (1/1) +remote: Processing changes: refs: 1, new: 1, done +remote: +remote: SUCCESS +remote: +remote: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/5375204 DO NOT COMMIT [WIP] [NEW] +remote: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/5375205 DO NOT COMMIT [WIP] [NEW] +remote: +To https://chromium.googlesource.com/chromiumos/third_party/toolchain-utils + * [new reference] HEAD -> refs/for/main +""" + + +class Test(unittest.TestCase): + """Tests for git_utils.""" + + def test_cl_parsing_complains_if_no_output(self): + with self.assertRaisesRegex(ValueError, ".*; found 0"): + git_utils._parse_cls_from_upload_output("") + + def test_cl_parsing_works_with_one_cl(self): + self.assertEqual( + git_utils._parse_cls_from_upload_output(GERRIT_OUTPUT_WITH_ONE_CL), + [5375204], + ) + + def test_cl_parsing_works_with_two_cls(self): + self.assertEqual( + git_utils._parse_cls_from_upload_output(GERRIT_OUTPUT_WITH_TWO_CLS), + [5375204, 5375205], + ) + + +if __name__ == "__main__": + unittest.main() |