xref: /aosp_15_r20/tools/external_updater/reviewers.py (revision 3c875a214f382db1236d28570d1304ce57138f32)
1*3c875a21SAndroid Build Coastguard Worker# Copyright (C) 2020 The Android Open Source Project
2*3c875a21SAndroid Build Coastguard Worker#
3*3c875a21SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the 'License');
4*3c875a21SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*3c875a21SAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*3c875a21SAndroid Build Coastguard Worker#
7*3c875a21SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
8*3c875a21SAndroid Build Coastguard Worker#
9*3c875a21SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*3c875a21SAndroid Build Coastguard Worker# distributed under the License is distributed on an 'AS IS' BASIS,
11*3c875a21SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*3c875a21SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*3c875a21SAndroid Build Coastguard Worker# limitations under the License.
14*3c875a21SAndroid Build Coastguard Worker"""Find main reviewers for git push commands."""
15*3c875a21SAndroid Build Coastguard Worker
16*3c875a21SAndroid Build Coastguard Workerfrom collections.abc import MutableMapping
17*3c875a21SAndroid Build Coastguard Workerimport math
18*3c875a21SAndroid Build Coastguard Workerimport random
19*3c875a21SAndroid Build Coastguard Workerfrom typing import List, Set, Union
20*3c875a21SAndroid Build Coastguard Worker
21*3c875a21SAndroid Build Coastguard Worker# To randomly pick one of multiple reviewers, we put them in a List[str]
22*3c875a21SAndroid Build Coastguard Worker# to work with random.choice efficiently.
23*3c875a21SAndroid Build Coastguard Worker# To pick all of multiple reviewers, we use a Set[str].
24*3c875a21SAndroid Build Coastguard Worker
25*3c875a21SAndroid Build Coastguard Worker# A ProjMapping maps a project path string to
26*3c875a21SAndroid Build Coastguard Worker# (1) a single reviewer email address as a string, or
27*3c875a21SAndroid Build Coastguard Worker# (2) a List of multiple reviewers to be randomly picked, or
28*3c875a21SAndroid Build Coastguard Worker# (3) a Set of multiple reviewers to be all added.
29*3c875a21SAndroid Build Coastguard WorkerProjMapping = MutableMapping[str, Union[str, List[str], Set[str]]]
30*3c875a21SAndroid Build Coastguard Worker
31*3c875a21SAndroid Build Coastguard Worker# Rust crate owners (reviewers).
32*3c875a21SAndroid Build Coastguard WorkerRUST_CRATE_OWNERS: ProjMapping = {
33*3c875a21SAndroid Build Coastguard Worker    'rust/crates/anyhow': '[email protected]',
34*3c875a21SAndroid Build Coastguard Worker    # more rust crate owners could be added later
35*3c875a21SAndroid Build Coastguard Worker    # if so, consider modifying the quotas in RUST_REVIEWERS
36*3c875a21SAndroid Build Coastguard Worker}
37*3c875a21SAndroid Build Coastguard Worker
38*3c875a21SAndroid Build Coastguard WorkerPROJ_REVIEWERS: ProjMapping = {
39*3c875a21SAndroid Build Coastguard Worker    # define non-rust project reviewers here
40*3c875a21SAndroid Build Coastguard Worker}
41*3c875a21SAndroid Build Coastguard Worker
42*3c875a21SAndroid Build Coastguard Worker# Combine all roject reviewers.
43*3c875a21SAndroid Build Coastguard WorkerPROJ_REVIEWERS.update(RUST_CRATE_OWNERS)
44*3c875a21SAndroid Build Coastguard Worker
45*3c875a21SAndroid Build Coastguard Worker# Reviewers for external/rust/crates projects not found in PROJ_REVIEWER.
46*3c875a21SAndroid Build Coastguard Worker# Each person has a quota, the number of projects to review.
47*3c875a21SAndroid Build Coastguard Worker# The sum of these quotas should ideally be at least the number of Rust
48*3c875a21SAndroid Build Coastguard Worker# projects, but this only matters if we have many entries in RUST_CRATE_OWNERS,
49*3c875a21SAndroid Build Coastguard Worker# as we subtract a person's owned crates from their quota.
50*3c875a21SAndroid Build Coastguard WorkerRUST_REVIEWERS: dict[str, float] = {
51*3c875a21SAndroid Build Coastguard Worker    '[email protected]': 20,
52*3c875a21SAndroid Build Coastguard Worker    '[email protected]': 20,
53*3c875a21SAndroid Build Coastguard Worker    '[email protected]': 20,
54*3c875a21SAndroid Build Coastguard Worker    '[email protected]': 20,
55*3c875a21SAndroid Build Coastguard Worker    '[email protected]': 20,
56*3c875a21SAndroid Build Coastguard Worker    # If a Rust reviewer needs to take a vacation, comment out the line,
57*3c875a21SAndroid Build Coastguard Worker    # and distribute the quota to other reviewers.
58*3c875a21SAndroid Build Coastguard Worker}
59*3c875a21SAndroid Build Coastguard Worker
60*3c875a21SAndroid Build Coastguard Worker
61*3c875a21SAndroid Build Coastguard Worker# pylint: disable=invalid-name
62*3c875a21SAndroid Build Coastguard Workerdef add_proj_count(projects: MutableMapping[str, float], reviewer: str, n: float) -> None:
63*3c875a21SAndroid Build Coastguard Worker    """Add n to the number of projects owned by the reviewer."""
64*3c875a21SAndroid Build Coastguard Worker    if reviewer in projects:
65*3c875a21SAndroid Build Coastguard Worker        projects[reviewer] += n
66*3c875a21SAndroid Build Coastguard Worker    else:
67*3c875a21SAndroid Build Coastguard Worker        projects[reviewer] = n
68*3c875a21SAndroid Build Coastguard Worker
69*3c875a21SAndroid Build Coastguard Worker
70*3c875a21SAndroid Build Coastguard Worker# Random Rust reviewers are selected from RUST_REVIEWER_LIST,
71*3c875a21SAndroid Build Coastguard Worker# which is created from RUST_REVIEWERS and PROJ_REVIEWERS.
72*3c875a21SAndroid Build Coastguard Worker# A person P in RUST_REVIEWERS will occur in the RUST_REVIEWER_LIST N times,
73*3c875a21SAndroid Build Coastguard Worker# if N = RUST_REVIEWERS[P] - (number of projects owned by P in PROJ_REVIEWERS)
74*3c875a21SAndroid Build Coastguard Worker# is greater than 0. N is rounded up by math.ceil.
75*3c875a21SAndroid Build Coastguard Workerdef create_rust_reviewer_list() -> List[str]:
76*3c875a21SAndroid Build Coastguard Worker    """Create a list of duplicated reviewers for weighted random selection."""
77*3c875a21SAndroid Build Coastguard Worker    # Count number of projects owned by each reviewer.
78*3c875a21SAndroid Build Coastguard Worker    rust_reviewers = set(RUST_REVIEWERS.keys())
79*3c875a21SAndroid Build Coastguard Worker    projects: dict[str, float] = {}  # map from owner to number of owned projects
80*3c875a21SAndroid Build Coastguard Worker    for value in PROJ_REVIEWERS.values():
81*3c875a21SAndroid Build Coastguard Worker        if isinstance(value, str):  # single reviewer for a project
82*3c875a21SAndroid Build Coastguard Worker            add_proj_count(projects, value, 1)
83*3c875a21SAndroid Build Coastguard Worker            continue
84*3c875a21SAndroid Build Coastguard Worker        # multiple reviewers share one project, count only rust_reviewers
85*3c875a21SAndroid Build Coastguard Worker        reviewers = set(filter(lambda x: x in rust_reviewers, value))
86*3c875a21SAndroid Build Coastguard Worker        if reviewers:
87*3c875a21SAndroid Build Coastguard Worker            count = 1.0 / len(reviewers)  # shared among all reviewers
88*3c875a21SAndroid Build Coastguard Worker            for name in reviewers:
89*3c875a21SAndroid Build Coastguard Worker                add_proj_count(projects, name, count)
90*3c875a21SAndroid Build Coastguard Worker    result = []
91*3c875a21SAndroid Build Coastguard Worker    for (x, n) in RUST_REVIEWERS.items():
92*3c875a21SAndroid Build Coastguard Worker        if x in projects:  # reduce x's quota by the number of assigned ones
93*3c875a21SAndroid Build Coastguard Worker            n = n - projects[x]
94*3c875a21SAndroid Build Coastguard Worker        if n > 0:
95*3c875a21SAndroid Build Coastguard Worker            result.extend([x] * math.ceil(n))
96*3c875a21SAndroid Build Coastguard Worker    if result:
97*3c875a21SAndroid Build Coastguard Worker        return result
98*3c875a21SAndroid Build Coastguard Worker    # Something was wrong or quotas were too small so that nobody
99*3c875a21SAndroid Build Coastguard Worker    # was selected from the RUST_REVIEWERS. Select everyone!!
100*3c875a21SAndroid Build Coastguard Worker    return list(RUST_REVIEWERS.keys())
101*3c875a21SAndroid Build Coastguard Worker
102*3c875a21SAndroid Build Coastguard Worker
103*3c875a21SAndroid Build Coastguard WorkerRUST_REVIEWER_LIST: List[str] = create_rust_reviewer_list()
104*3c875a21SAndroid Build Coastguard Worker
105*3c875a21SAndroid Build Coastguard Worker
106*3c875a21SAndroid Build Coastguard Workerdef find_reviewers(proj_path: str) -> str:
107*3c875a21SAndroid Build Coastguard Worker    """Returns an empty string or a reviewer parameter(s) for git push."""
108*3c875a21SAndroid Build Coastguard Worker    index = proj_path.find('/external/')
109*3c875a21SAndroid Build Coastguard Worker    if index >= 0:  # full path
110*3c875a21SAndroid Build Coastguard Worker        proj_path = proj_path[(index + len('/external/')):]
111*3c875a21SAndroid Build Coastguard Worker    elif proj_path.startswith('external/'):  # relative path
112*3c875a21SAndroid Build Coastguard Worker        proj_path = proj_path[len('external/'):]
113*3c875a21SAndroid Build Coastguard Worker    if proj_path in PROJ_REVIEWERS:
114*3c875a21SAndroid Build Coastguard Worker        reviewers = PROJ_REVIEWERS[proj_path]
115*3c875a21SAndroid Build Coastguard Worker        # pylint: disable=isinstance-second-argument-not-valid-type
116*3c875a21SAndroid Build Coastguard Worker        if isinstance(reviewers, List):  # pick any one reviewer
117*3c875a21SAndroid Build Coastguard Worker            return 'r=' + random.choice(reviewers)
118*3c875a21SAndroid Build Coastguard Worker        if isinstance(reviewers, Set):  # add all reviewers in sorted order
119*3c875a21SAndroid Build Coastguard Worker            return ','.join(map(lambda x: 'r=' + x, sorted(reviewers)))
120*3c875a21SAndroid Build Coastguard Worker        # reviewers must be a string
121*3c875a21SAndroid Build Coastguard Worker        return 'r=' + reviewers
122*3c875a21SAndroid Build Coastguard Worker    if proj_path.startswith('rust/crates/'):
123*3c875a21SAndroid Build Coastguard Worker        return 'r=' + random.choice(RUST_REVIEWER_LIST)
124*3c875a21SAndroid Build Coastguard Worker    return ''
125