1#!/usr/bin/env python3 2 3# Future improvements: 4# - link bugs and code references 5# - handle multiple bug references per line (currently only fetches one) 6# - fetch more than the first ~900 entries from Buganizer 7# - check if CLI tool is available + authenticated 8 9import pathlib, re, subprocess 10 11outfile = "closed_bugs.csv" 12 13print("Searching code for bug references") 14androidx_root = pathlib.Path(__file__).parent.parent.resolve() 15grep_cmd = subprocess.run( 16 ["egrep", 17 18 # -I excludes binary files 19 # -i case insensitive search 20 # -n include line numbers 21 # -r recursive 22 "-Iinr", 23 24 # regex for buganizer format ("b/123456789") 25 "b\/[[:digit:]]{8,9}", 26 27 # Files and directories to include and exclude 28 "--exclude-dir=.idea", 29 "--include=*.gradle", 30 "--include=*.java", 31 "--include=*.kt", 32 "--include=*.xml", 33 34 # Search all of the AndroidX repo checkout 35 f"{androidx_root}" 36 ], 37 capture_output=True, 38 text=True 39) 40raw_output_lines = grep_cmd.stdout.split("\n") 41 42print("Cleaning up search results") 43bug_dict = {} # mapping of bug id to list of filename + line number 44for line in raw_output_lines: 45 regex_result = re.search('b\/[0-9]{8,9}', line) 46 if regex_result is not None: 47 bug_id = regex_result.group(0).removeprefix("b/") 48 file = line.split(":")[0].removeprefix(str(androidx_root)) 49 linenum = line.split(":")[1] 50 51 if bug_id in bug_dict: 52 matching_files = bug_dict[bug_id] 53 else: 54 matching_files = set() 55 matching_files.add(f"{file}:{linenum}") 56 bug_dict[bug_id] = matching_files 57print(f"Found {len(bug_dict)} bugs") 58 59# Create bug id query string. 60# The CLI tool fails if there are too many bugs (>900?); only use the first 900. 61bug_ids = list(bug_dict.keys()) 62bug_ids.sort() 63joined_ids = "|".join(bug_ids[0:899]) 64 65# Query buganizer to determine which of the given bugs are closed. 66# Store the issue, reporter, and assignee of the matching [closed] bugs. 67print("Querying Buganizer to find how many of these bugs are resolved") 68bugged_cmd = subprocess.run( 69 ["bugged", "search", f"id:({joined_ids})", "status:closed", "--columns=issue,reporter,assignee"], 70 capture_output=True, 71 text=True # capture output as String instead of byte sequence 72) 73closed_bug_list = bugged_cmd.stdout.split("\n") 74 75# Remove header and trailing rows of Buganizer query result 76closed_bug_list.pop(0) 77closed_bug_list.pop() 78print(f"{len(closed_bug_list)} have been resolved") 79 80# Combine buganizer results with file search results and write to CSV 81csv_str = "bug_id,reporter,assignee,files\n" 82for line in closed_bug_list: 83 elements = re.split(" +", line) 84 bug_id = elements[0] 85 reporter = elements[1] 86 assignee = elements[2] 87 matching_files = bug_dict[bug_id] 88 line_str = f"b/{bug_id},{reporter},{assignee}," 89 90 # The list of matching file(s) are enclosed in double quotes to preserve \n in the csv 91 line_str += ("\"" + "\n".join(matching_files) + "\"") 92 93 csv_str += line_str + "\n" 94 95print(csv_str, file=open(outfile, 'w')) 96print(f"Wrote results to {outfile}")