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